<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Interswitch Developer Community</title>
    <description>The latest articles on DEV Community by Interswitch Developer Community (interswitchdevcommunity).</description>
    <link>https://dev.to/interswitchdevcommunity</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F13684%2F9630b289-3590-447c-b685-66f9b0f00fda.png</url>
      <title>DEV Community: Interswitch Developer Community</title>
      <link>https://dev.to/interswitchdevcommunity</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/interswitchdevcommunity"/>
    <language>en</language>
    <item>
      <title>llms.txt Changed How I Integrate APIs: A Reton Case Study With Interswitch VAS</title>
      <dc:creator>Gabriel R Mogaji</dc:creator>
      <pubDate>Wed, 30 Sep 2026 23:20:24 +0000</pubDate>
      <link>https://dev.to/interswitchdevcommunity/llmstxt-changed-how-i-integrate-apis-a-reton-case-study-with-interswitch-vas-1d1b</link>
      <guid>https://dev.to/interswitchdevcommunity/llmstxt-changed-how-i-integrate-apis-a-reton-case-study-with-interswitch-vas-1d1b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbv41vxr5jstq0pckehvm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbv41vxr5jstq0pckehvm.png" alt="Reton Homepage" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I build &lt;a href="https://retonpay.com" rel="noopener noreferrer"&gt;Reton&lt;/a&gt;, a trust-first digital wallet for Africa. Our flagship is Callback Protection: hold a payment, release it when you are ready, or raise a callback, with a timeline a person can actually follow.&lt;/p&gt;

&lt;p&gt;A wallet that only moves money between Reton IDs is incomplete. People still buy airtime, data, electricity, and television. If they have to leave Reton to pay those bills, the balance stops feeling like money they can live on. Value Added Services had to sit on the same ledger we already treat as the source of truth.&lt;/p&gt;

&lt;p&gt;I was not looking for a bank. I was looking for a payments rail. &lt;strong&gt;Interswitch is a payments company. Reton is the wallet.&lt;/strong&gt; That sentence is the architecture. AI coding tools keep trying to smear it.&lt;/p&gt;

&lt;p&gt;This is how Interswitch's &lt;code&gt;llms.txt&lt;/code&gt;, Markdown documentation URLs, and reference pages changed the way I integrate APIs in Cursor, and how Reton still refuses to let a model invent payment behaviour.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Reton already owned customer wallets, Reton IDs, available and held balances as projections of a double-entry ledger, PIN-gated money movement, protected transfers, callbacks, and wrong-transfer recovery.&lt;/p&gt;

&lt;p&gt;What we did not need to rebuild was a nationwide biller directory. Interswitch Quickteller VAS already reaches cable, electricity, telecom airtime and data, and related categories. Using that rail is a product decision. Treating that rail as the wallet is a design error.&lt;/p&gt;

&lt;p&gt;The constraint was integration quality under time pressure, while pairing with an AI coding tool. I needed the correct VAS documents (bills payment is not airtime e-pins, and neither is send-money), those documents as clean text rather than a rendered marketing site, and a money path the model was not allowed to invent: debit the Reton ledger inside a database transaction, talk to Interswitch only through a gateway.&lt;/p&gt;

&lt;p&gt;The old method was five browser tabs, a paste into chat, and hope the model still remembered which product the last URL belonged to. The better method is a machine-readable map of the documentation site, then one Markdown page at a time.&lt;/p&gt;

&lt;p&gt;That map is &lt;code&gt;llms.txt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wallet versus rail
&lt;/h2&gt;

&lt;p&gt;If you skip this distinction, you will ship a demo, not a wallet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reton&lt;/strong&gt; is the digital wallet: balances, Callback Protection, recovery, and the double-entry ledger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interswitch&lt;/strong&gt; is the payments partner: cards, transfers, and bill payments underneath. It is not a bank. It is not Reton's ledger.&lt;/p&gt;

&lt;p&gt;The only call chain I accept for bills:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Next.js /bills
  → POST /api/v1/bills/pay          (BillController: HTTP only)
    → BillPayService
        → PIN, KYC limits, fraud
        → LedgerService.debitAvailable
        → InterswitchGatewayInterface.payBill
        → LedgerService.creditAvailable   (clear rail decline only)
  → { "data": ... } or { "error": { "code", "message" } }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will not take provider HTTP from a controller or from the browser, mutate &lt;code&gt;Wallet&lt;/code&gt; balances outside &lt;code&gt;LedgerService&lt;/code&gt;, or grant success because a query string contains &lt;code&gt;resp=00&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Amounts on the ledger are &lt;strong&gt;kobo integers&lt;/strong&gt;. The web app sends naira. The API converts and rejects bills under ₦100.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;llms.txt&lt;/code&gt; actually is
&lt;/h2&gt;

&lt;p&gt;Interswitch publishes a documentation index at &lt;a href="https://docs.interswitchgroup.com/llms.txt" rel="noopener noreferrer"&gt;docs.interswitchgroup.com/llms.txt&lt;/a&gt;. It is a plain-text catalogue: titles, links, and short descriptions. It is not sitemap XML, not a blog post, and not a substitute for OpenAPI.&lt;/p&gt;

&lt;p&gt;The same file tells you to append &lt;code&gt;.md&lt;/code&gt; to a documentation URL to receive the page as Markdown. Those two conventions are instructions for humans and for models. I treat them as part of the integration.&lt;/p&gt;

&lt;p&gt;I do not paste the whole index into every prompt. I fetch it once, pick a short list of URLs from the catalogue, then fetch those as &lt;code&gt;.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without the index, an agent searches the open web, mixes Quickteller versions, and invents paths with a straight face. With the index, the agent starts from Interswitch's own catalogue.&lt;/p&gt;

&lt;p&gt;HTML documentation pages are built for browsers: navigation, scripts, theme wrappers. Models spend tokens on chrome. Markdown pages are built for reading. I stopped pasting HTML URLs into AI chats for Interswitch work.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I actually pull the docs
&lt;/h2&gt;

&lt;p&gt;I keep a vendor-notes folder, not a second brain for the model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; docs/vendor-notes/interswitch

curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://docs.interswitchgroup.com/llms.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; docs/vendor-notes/interswitch/llms.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the file starts with &lt;code&gt;&amp;lt;!doctype&lt;/code&gt;, I did not get the index. I stop. I do not "remember Interswitch VAS" from training data and keep going.&lt;/p&gt;

&lt;p&gt;Then I search the saved file for VAS language (&lt;code&gt;bill&lt;/code&gt;, &lt;code&gt;airtime&lt;/code&gt;, &lt;code&gt;biller&lt;/code&gt;, &lt;code&gt;voucher&lt;/code&gt;) and write matching Markdown URLs to a short list. For Reton that list is usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/value-added-services-overview.md" rel="noopener noreferrer"&gt;Value Added Services overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/bills-payment-1.md" rel="noopener noreferrer"&gt;Bills payment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/airtime-recharge-virtual-top-up.md" rel="noopener noreferrer"&gt;Airtime recharge, virtual top-up&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.interswitchgroup.com/docs/airtime-recharge-e-pins-1.md" rel="noopener noreferrer"&gt;Airtime e-pins&lt;/a&gt;, only if we are actually issuing pins&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.interswitchgroup.com/reference/get-billers.md" rel="noopener noreferrer"&gt;Get billers&lt;/a&gt;, &lt;a href="https://docs.interswitchgroup.com/reference/get-billers-by-category-2.md" rel="noopener noreferrer"&gt;billers by category&lt;/a&gt;, &lt;a href="https://docs.interswitchgroup.com/reference/get-biller-payment-item.md" rel="noopener noreferrer"&gt;biller payment item&lt;/a&gt;, &lt;a href="https://docs.interswitchgroup.com/reference/get-billers-categories.md" rel="noopener noreferrer"&gt;biller categories&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/reference/customer-validation-1.md" rel="noopener noreferrer"&gt;Customer validation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/reference/create-bill.md" rel="noopener noreferrer"&gt;Create bill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/response-codes.md" rel="noopener noreferrer"&gt;VAS response codes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I copy those URLs from the saved index. Interswitch can rename a slug. The customer validation page in the live catalogue is &lt;code&gt;customer-validation-1.md&lt;/code&gt;, not a guessed &lt;code&gt;customer-validation.md&lt;/code&gt;. There is no &lt;code&gt;query-transaction&lt;/code&gt; page in that index. Status for our funding requery lives under collections (&lt;a href="https://docs.interswitchgroup.com/docs/transaction-requery-1.md" rel="noopener noreferrer"&gt;transaction requery&lt;/a&gt;, &lt;a href="https://docs.interswitchgroup.com/reference/get-transaction-status.md" rel="noopener noreferrer"&gt;get transaction status&lt;/a&gt;), which is a different product surface than Quickteller VAS.&lt;/p&gt;

&lt;p&gt;I also keep send-money, card checkout, and the payments-section &lt;a href="https://docs.interswitchgroup.com/docs/pay-bill.md" rel="noopener noreferrer"&gt;Pay Bill&lt;/a&gt; page out of a bills prompt. Pay Bill in the card/checkout guides is not Create Bill in VAS. The &lt;a href="https://docs.interswitchgroup.com/docs/bank-api.md" rel="noopener noreferrer"&gt;Bank API&lt;/a&gt; bills guide is written for banks. Reton is not a bank. Mixing those in one chat is how the agent wires the wrong endpoint.&lt;/p&gt;

&lt;p&gt;Each fetch looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://docs.interswitchgroup.com/docs/value-added-services-overview.md"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; docs/vendor-notes/interswitch/pages/vas-overview.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first heading should be the document title. If I see a theme shell, I refetch with &lt;code&gt;.md&lt;/code&gt;. I attach one guide and one reference for the current task. Electricity this week means the bills guide plus customer validation plus payment item, not the e-pins guide.&lt;/p&gt;

&lt;p&gt;Before any PHP, I make the agent fill a field sheet and cite the saved filename on every row: payment code, customer id, amount, request reference, terminal id. If it cannot cite a file, the row is not done. Payment codes and terminal ids come from merchant config and secrets, never from the browser, never from git.&lt;/p&gt;

&lt;p&gt;Discovery and implementation are separate turns. A prompt I actually use:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Read the saved &lt;code&gt;llms.txt&lt;/code&gt; and the &lt;code&gt;.md&lt;/code&gt; pages I attached.&lt;br&gt;
Interswitch is a payments company, not a bank, and not Reton's wallet. Reton owns balances and the double-entry ledger.&lt;br&gt;
Do not search the public web for Interswitch VAS. Do not invent paths.&lt;br&gt;
Do not write gateway code in this turn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When implementation starts, I name the files it may touch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;backend/src/Wallet/BillPayService.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backend/src/Controller/Api/V1/BillController.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backend/src/Gateway/Interswitch/InterswitchGatewayInterface.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backend/src/Gateway/Interswitch/HttpInterswitchGateway.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backend/src/Gateway/Interswitch/NullInterswitchGateway.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;backend/src/Ledger/LedgerService.php&lt;/code&gt; (read; do not casually rewrite)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backend/tests/ApiV1Test.php&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I refuse unbounded prompts such as "add Interswitch," "make bills like Opay," or "use whatever Quickteller endpoint you remember."&lt;/p&gt;

&lt;h2&gt;
  
  
  The money path
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;LedgerService&lt;/code&gt; is the only code allowed to change wallet balances. Bills are not a new accounting primitive. They are a debit of customer liability, then a rail result.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BillPayment&lt;/code&gt; stores wallet, biller category, customer reference, amount in kobo, payment code, and the idempotency key. We look that key up &lt;strong&gt;before&lt;/strong&gt; any debit. If a row exists, we return it. Double-clicks are normal. The ledger cannot absorb them.&lt;/p&gt;

&lt;p&gt;After PIN, KYC, and fraud checks, the rest runs inside a Doctrine transaction:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;LedgerService::debitAvailable&lt;/code&gt; with kind &lt;code&gt;bill_reserve&lt;/code&gt; and journal id &lt;code&gt;bill-reserve:{reference}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;InterswitchGatewayInterface::payBill(paymentCode, customerId, amountKobo, reference)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;approved&lt;/code&gt; (&lt;code&gt;ResponseCode&lt;/code&gt; &lt;code&gt;90000&lt;/code&gt; on the HTTP gateway): mark completed, audit, return.&lt;/li&gt;
&lt;li&gt;If the rail returns a clear decline: &lt;code&gt;creditAvailable&lt;/code&gt; with kind &lt;code&gt;bill_reverse&lt;/code&gt;, mark failed, throw &lt;code&gt;provider_failed&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The controller never sees Interswitch JSON. The HTTP gateway maps provider fields into &lt;code&gt;{ approved, reference, raw_code }&lt;/code&gt; and posts to &lt;code&gt;/quickteller/v1/transactions&lt;/code&gt;. &lt;code&gt;NullInterswitchGateway&lt;/code&gt; always returns &lt;code&gt;90000&lt;/code&gt; so tests and local UI can move. Empty live credentials must not be described as a live rail. The container still has to bind &lt;code&gt;InterswitchGatewayInterface&lt;/code&gt; to the HTTP gateway in production. Compiling &lt;code&gt;HttpInterswitchGateway&lt;/code&gt; is not the same as using it.&lt;/p&gt;

&lt;p&gt;One gap I will not paper over: the first bills cut treats any non-approval, including a transport error, as a decline and reverses. That is safe only when Interswitch has actually said no. A timeout after the HTTP call left our process is not a decline. The correct next cut is to leave the bill &lt;code&gt;pending&lt;/code&gt; with the reserve in place, then requery. &lt;code&gt;BillPayment&lt;/code&gt; already has a pending status. &lt;code&gt;pay()&lt;/code&gt; does not leave a row there yet.&lt;/p&gt;

&lt;p&gt;If you later move the HTTP call outside the database transaction, you need that same pending path. Do not "optimise" by deleting the reverse on a documented decline.&lt;/p&gt;

&lt;p&gt;Audit logs run after the journal. Audit is evidence. It is not the balance.&lt;/p&gt;

&lt;p&gt;I want PHPUnit to prove the money path, not only HTTP 201: fund a wallet, &lt;code&gt;POST /api/v1/bills/pay&lt;/code&gt; with &lt;code&gt;Idempotency-Key&lt;/code&gt;, assert the Null happy path completes, replay the same key with no second debit, then force a provider failure and assert available balance is restored. Those bill cases are not in &lt;code&gt;ApiV1Test&lt;/code&gt; yet. A green suite that never touches &lt;code&gt;/bills/pay&lt;/code&gt; does not prove this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTTP API
&lt;/h2&gt;

&lt;p&gt;VAS on Reton sits at &lt;code&gt;/bills&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GET /api/v1/bills&lt;/code&gt; returns our catalogue: airtime, data, electricity, TV, internet. It is not a live proxy of Interswitch's biller directory. The labels exist so the app can render tiles. Payment codes stay on the server.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST /api/v1/bills/pay&lt;/code&gt; is a money POST.&lt;/p&gt;

&lt;p&gt;Gates, in order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authentication.&lt;/strong&gt; No &lt;code&gt;User&lt;/code&gt;, 401 &lt;code&gt;unauthenticated&lt;/code&gt;. There is no public pay endpoint for a nicer demo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency-Key.&lt;/strong&gt; Trim the header. Empty means 422. The Next.js &lt;code&gt;moneyApi()&lt;/code&gt; helper always sends a UUID. The server still rejects a missing header so a future mobile client cannot skip it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Body.&lt;/strong&gt; &lt;code&gt;biller&lt;/code&gt;, &lt;code&gt;customer_reference&lt;/code&gt; (max 40), positive &lt;code&gt;amount&lt;/code&gt; in naira, optional &lt;code&gt;pin&lt;/code&gt;. Convert with &lt;code&gt;(int) round(amount * 100)&lt;/code&gt;. Reject below 10000 kobo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PIN.&lt;/strong&gt; If the user has a transaction PIN, verify it. Failure is 403 &lt;code&gt;invalid_pin&lt;/code&gt;, not 500. If they have not set a PIN yet, &lt;code&gt;PinService&lt;/code&gt; does not invent a requirement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KYC.&lt;/strong&gt; &lt;code&gt;KycService::assertWithinLimits&lt;/code&gt; applies. A bill is a money movement. It is not exempt because it is "just airtime."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fraud.&lt;/strong&gt; &lt;code&gt;FraudService::assess&lt;/code&gt; with &lt;code&gt;bill_payment&lt;/code&gt; and &lt;code&gt;amount_kobo&lt;/code&gt;. A hard block is &lt;code&gt;fraud_blocked&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Everything maps to &lt;code&gt;{ "error": { "code", "message" } }&lt;/code&gt;. The browser never receives a raw Interswitch body.&lt;/p&gt;

&lt;p&gt;On the client, submit through &lt;code&gt;moneyApi&lt;/code&gt;, show structured errors, refresh the wallet on success. Never put the PIN in a query string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifiers the server may trust
&lt;/h2&gt;

&lt;p&gt;This is where models sound most confident and do the most damage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customer reference.&lt;/strong&gt; Airtime and data take an MSISDN. Electricity takes a meter number. TV takes a smartcard / IUC. Internet takes an account id. Trim, reject empty, cap length. Do not send a meter number down an airtime path because the field was named &lt;code&gt;accountNumber&lt;/code&gt;. Electricity and TV should run &lt;a href="https://docs.interswitchgroup.com/reference/customer-validation-1.md" rel="noopener noreferrer"&gt;customer validation&lt;/a&gt; before debit. The first Reton slice still forwards the typed string. That is a known gap, not a finished inquiry flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payment codes.&lt;/strong&gt; Payment items are issued to &lt;strong&gt;your merchant in that environment&lt;/strong&gt;. They are not global constants. Sandbox codes are not production truth.&lt;/p&gt;

&lt;p&gt;Reton currently keeps category placeholders in &lt;code&gt;BillPayService::BILLERS&lt;/code&gt; (&lt;code&gt;90101&lt;/code&gt; through &lt;code&gt;90105&lt;/code&gt;) so local and test have a string to send. Those values are not live Quickteller items. Production codes come from the Quickteller dashboard for that environment, stored in secrets, keyed by catalogue id. If the map is missing in production, fail closed. Do not send a placeholder to the live rail. If Cursor "remembers" a DSTV code from training data, discard it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amounts.&lt;/strong&gt; Many airtime top-ups are customer-chosen: the user types naira, the server converts to kobo, then applies min/max, KYC, and available balance. Many electricity tokens and bouquet renewals are inquiry-defined: validate the customer, take the item or amount Interswitch returned, and debit that figure. If the JSON body disagrees with the inquiry, reject the pay request.&lt;/p&gt;

&lt;p&gt;Reton's first bills slice still accepts body amounts for every category. Electricity and TV should move to inquiry-defined amounts before live volume. I would rather write that gap down than pretend the model closed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request references.&lt;/strong&gt; The saved &lt;a href="https://docs.interswitchgroup.com/docs/bills-payment-1.md" rel="noopener noreferrer"&gt;bills payment guide&lt;/a&gt; is the source for length, charset, and merchant prefix. We generate one value, use it on the ledger (&lt;code&gt;bill-reserve:{reference}&lt;/code&gt;), and send it as &lt;code&gt;requestReference&lt;/code&gt; on &lt;code&gt;payBill&lt;/code&gt;. Unique UUIDs are not the same as accepted rail identifiers. Current Reton references look like &lt;code&gt;BP-&lt;/code&gt; plus hex. That is our generator, not a claim that every Quickteller environment will accept it.&lt;/p&gt;

&lt;p&gt;The implementation prompt gets these refusals:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not invent payment codes.&lt;br&gt;
Do not invent requestReference formats.&lt;br&gt;
Do not debit an amount that did not pass server rules.&lt;br&gt;
If the saved guide is silent, ask. Do not guess.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Requery, pending, and frontend lies
&lt;/h2&gt;

&lt;p&gt;Bills that return a synchronous approve or decline are the easy case. Rails also return pending, drop packets, or send the user through a browser. The rule is the same for VAS, card, and wallet pay:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never grant wallet value because the frontend said so.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;resp=00&lt;/code&gt; in a query string is not a ledger permission.&lt;/p&gt;

&lt;p&gt;Sources of truth, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server &lt;strong&gt;requery&lt;/strong&gt; of Interswitch with the reference you stored.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;signed webhook&lt;/strong&gt;, verified before any handler mutates state.&lt;/li&gt;
&lt;li&gt;Your &lt;code&gt;BillPayment&lt;/code&gt; row plus ledger journals. If those disagree with (1) or (2), the journals win for the customer balance, and ops reconciles the rail.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The browser is not on this list.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rail view&lt;/th&gt;
&lt;th&gt;Bill status&lt;/th&gt;
&lt;th&gt;Ledger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Approved&lt;/td&gt;
&lt;td&gt;completed&lt;/td&gt;
&lt;td&gt;Debit remains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documented decline&lt;/td&gt;
&lt;td&gt;failed&lt;/td&gt;
&lt;td&gt;Reverse posted once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pending or timeout after debit&lt;/td&gt;
&lt;td&gt;pending&lt;/td&gt;
&lt;td&gt;Debit remains; worker requeries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;do not complete&lt;/td&gt;
&lt;td&gt;Do not reverse until requery says decline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;InterswitchGatewayInterface::requery&lt;/code&gt; lives on the gateway, not in the controller. On Reton today that method is wired for collections-style status (&lt;code&gt;/collections/api/v1/gettransaction&lt;/code&gt;) and used when we settle &lt;strong&gt;funding&lt;/strong&gt;. It is not yet a bills worker selecting unfinished &lt;code&gt;BillPayment&lt;/code&gt; rows. I am describing the rule we already enforce for deposits, and the rule bills must inherit before we treat timeouts as declines.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST /api/v1/webhooks/interswitch&lt;/code&gt; is public. We verify &lt;code&gt;X-Interswitch-Signature&lt;/code&gt; on the raw body first (HMAC-SHA512), then decode JSON. Unsigned bodies are allowed only when the webhook secret is empty &lt;strong&gt;and&lt;/strong&gt; the kernel is in debug. Production must not run that way. The handlers behind that route currently settle funding and virtual-account inbound, not bills. A VAS webhook that credits a bill without matching a reserved journal would be a new, explicit path, not a side effect I would let the model "reuse."&lt;/p&gt;

&lt;p&gt;I reject diffs that credit a wallet in Next.js because &lt;code&gt;searchParams.resp === '00'&lt;/code&gt;, trust &lt;code&gt;window.location&lt;/code&gt; for amount, or skip requery because "the SDK said success."&lt;/p&gt;

&lt;p&gt;If a bill is pending, the UI says it is being confirmed. The current bills page says "payment successful" after a 201. That is honest only while &lt;code&gt;pay()&lt;/code&gt; refuses to return pending.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the model got wrong
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; did not cause these mistakes. It made the right documents easy to attach, which made the mistakes easier to catch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It treated Interswitch as the bank.&lt;/strong&gt; Early drafts said Interswitch held customer balances. Wrong. Blur that in copy and you will blur it in the domain model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It invented &lt;code&gt;requestReference&lt;/code&gt; formats.&lt;/strong&gt; The model emitted ULIDs because they look unique. Quickteller bill payments are pickier. Unique is not accepted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It trusted the client for amounts.&lt;/strong&gt; Lookup-based bills need a server-side inquiry. The model's first instinct is still "take &lt;code&gt;amount&lt;/code&gt; from the body."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It assumed payment codes are universal.&lt;/strong&gt; Codes are merchant- and environment-specific. A hard-coded code the model "remembers" is a smell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It mixed product surfaces.&lt;/strong&gt; Create Bill, Pay Bill, Bank API, and collections requery all mention bills or transactions. They are not interchangeable. The index is how I keep them apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I will not merge
&lt;/h2&gt;

&lt;p&gt;I run this as a human. Models will tick boxes they did not earn.&lt;/p&gt;

&lt;p&gt;The saved &lt;code&gt;llms.txt&lt;/code&gt; has to be plain text. In-scope pages have to be the &lt;code&gt;.md&lt;/code&gt; files from that index, not a send-money or card guide dropped into a bills task. Copy never calls Interswitch a bank, and never describes Reton as only a frontend over Interswitch.&lt;/p&gt;

&lt;p&gt;On money: the controller has no provider HTTP, debit happens before &lt;code&gt;payBill&lt;/code&gt;, a documented decline reverses the same kobo amount, idempotent replay does not double-debit, PIN and KYC and fraud still run, timeouts are not stored as completed, and no Next.js route grants balance from query params.&lt;/p&gt;

&lt;p&gt;Going live is a separate question from "it compiles." Secrets stay out of git. Production payment codes replace &lt;code&gt;90101&lt;/code&gt;-&lt;code&gt;90105&lt;/code&gt;. The webhook secret is set and unsigned debug is off. Simulate funding is off. The Null gateway is not the production port.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;backend &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; php vendor/bin/phpunit &lt;span class="nt"&gt;--colors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;always
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command is necessary. For bills, it is not sufficient until the suite actually posts &lt;code&gt;/api/v1/bills/pay&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;I used to think "AI-friendly documentation" meant nicer sentences. Interswitch showed me it is closer to infrastructure: an index the agent can fetch, pages it can read without HTML noise, and contracts it can respect.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; did not replace engineering judgment on Reton. It removed the scavenger hunt, so judgment could stay on the part that matters: real money, moving correctly, for real people.&lt;/p&gt;

&lt;p&gt;Start at &lt;a href="https://docs.interswitchgroup.com/llms.txt" rel="noopener noreferrer"&gt;docs.interswitchgroup.com/llms.txt&lt;/a&gt;. Append &lt;code&gt;.md&lt;/code&gt; to the VAS, bills, or airtime page you need. Attach the reference page for field shape. Keep your own ledger rules stricter than the model's optimism.&lt;/p&gt;

&lt;p&gt;Reton is live at &lt;strong&gt;&lt;a href="https://retonpay.com" rel="noopener noreferrer"&gt;retonpay.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>api</category>
      <category>llm</category>
      <category>softwaredevelopment</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building an E-Commerce Checkout with Interswitch Payment Gateway (Web Checkout) -</title>
      <dc:creator>Chizurum Egwunwankwo</dc:creator>
      <pubDate>Fri, 10 Jul 2026 10:17:21 +0000</pubDate>
      <link>https://dev.to/interswitchdevcommunity/building-an-e-commerce-checkout-with-interswitch-payment-gateway-web-checkout--ocp</link>
      <guid>https://dev.to/interswitchdevcommunity/building-an-e-commerce-checkout-with-interswitch-payment-gateway-web-checkout--ocp</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vmza9yptug5xys33ty7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9vmza9yptug5xys33ty7.png" alt="Accept payments with Interswitch" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Take a customer from "Add to Cart" to a verified payment — including the failure cases the quickstart doesn't walk you through&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You're building a checkout for an online store. A customer fills their cart, clicks "Pay," and from that moment a dozen things can happen, most of them fine, some of them not. They complete payment cleanly. They close the tab mid-OTP. Their card gets declined. Your callback fires but the money isn't actually real. A good checkout handles all of these, not just the success path.&lt;/p&gt;

&lt;p&gt;This guide builds a complete e-commerce checkout on the Interswitch Payment Gateway using Web Checkout, then walks through the failure cases that separate a checkout that works in a demo from one that survives real customers.&lt;/p&gt;

&lt;p&gt;A quick note on terminology before we start: Interswitch's Web Checkout has two integration methods: &lt;strong&gt;&lt;em&gt;Inline Checkout&lt;/em&gt;&lt;/strong&gt; (the payment popup opens over your page, no redirect) and &lt;strong&gt;&lt;em&gt;Web Redirect&lt;/em&gt;&lt;/strong&gt; (the customer is sent to Interswitch's payment page and back). We'll use Inline Checkout, since keeping the customer on your store is the better experience for e-commerce.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;A checkout flow for an online store that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Takes the customer's cart total and triggers an Interswitch payment&lt;/li&gt;
&lt;li&gt;Handles the customer completing payment and the ways they don't&lt;/li&gt;
&lt;li&gt;Verifies the payment server-side before fulfilling the order (the part that matters most)&lt;/li&gt;
&lt;li&gt;Moves cleanly from test to production&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 — Test Credentials
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Merchant Code&lt;/td&gt;
&lt;td&gt;MX6072&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pay Item ID&lt;/td&gt;
&lt;td&gt;9405967&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client ID&lt;/td&gt;
&lt;td&gt;IKIAB23A4E2756605C1ABC33CE3C287E27267F660D61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secret&lt;/td&gt;
&lt;td&gt;secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mode&lt;/td&gt;
&lt;td&gt;TEST&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When you go live, you'll generate your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Trigger Checkout From The Cart
&lt;/h2&gt;

&lt;p&gt;For an e-commerce store, Inline Checkout is the right choice. The payment popup opens over your store, the customer never leaves your page, and the cart context stays intact.&lt;/p&gt;

&lt;p&gt;Add the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://newwebpay-sandbox.interswitchng.com/inline-checkout.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now wire it to your cart. The key move here is translating your cart total into what Interswitch expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkoutCart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// cart.total is in naira (e.g. 4500.00)&lt;/span&gt;
    &lt;span class="c1"&gt;// Interswitch expects kobo, so multiply by 100&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amountInKobo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Generate a unique reference and store it against this order&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txnRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`order_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;webpayCheckout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;merchant_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MX6072&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;pay_item_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;9405967&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;txn_ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;txnRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;amountInKobo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;566&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                          &lt;span class="c1"&gt;// NGN&lt;/span&gt;
        &lt;span class="na"&gt;cust_email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;cust_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;site_redirect_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yourstore.com/payment-complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// optional for inline; used by web redirect&lt;/span&gt;
        &lt;span class="na"&gt;onComplete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;handlePaymentResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;txnRef&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TEST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things here are the source of most real bugs, so take note of them:&lt;br&gt;
Amount is in kobo. A ₦4,500 cart is 450000, not 4500. Convert at the boundary — Math.round(cart.total * 100) and never let naira leak into the API call.&lt;/p&gt;

&lt;p&gt;txn_ref must be unique per attempt. Notice it includes both the order ID and a timestamp. The order ID alone isn't enough, because a customer might try to pay for the same order twice (first attempt failed, they retry). Each attempt needs its own reference, while still being traceable back to the order.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3 — Handle What The Customer Actually Does
&lt;/h2&gt;

&lt;p&gt;The quickstart shows you the success case. With real customers, it gets trickier. Here's the response handler that deals with reality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handlePaymentResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;txnRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// resp === "00" indicates the customer completed the flow in the browser.&lt;/span&gt;
    &lt;span class="c1"&gt;// (Confirm the callback object's exact field names against a live test —&lt;/span&gt;
    &lt;span class="c1"&gt;//  see the note below.)&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Customer completed the flow — but DON'T fulfill yet.&lt;/span&gt;
        &lt;span class="c1"&gt;// Send to your server to verify before giving value.&lt;/span&gt;
        &lt;span class="nf"&gt;verifyAndFulfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;txnRef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Customer did not complete — declined, cancelled, or errored.&lt;/span&gt;
        &lt;span class="c1"&gt;// Do NOT fulfill. Show a retry option.&lt;/span&gt;
        &lt;span class="nf"&gt;showRetryOption&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A note on field names: Interswitch's Web Redirect response is documented to return resp (response code — 00 means approved), desc (description), apprAmt (approved amount), txnref, and retRef. The Inline onComplete callback receives a response object that mirrors these, but the docs don't enumerate its fields explicitly, so console.log(response) once in the sandbox and confirm the exact keys before you rely on them. Whatever the field names, the rule in Step 4 is unchanged: the callback is never your source of truth.&lt;/p&gt;

&lt;p&gt;But there's a case the response handler alone can't catch: the customer who closes the tab mid-payment. If a customer starts the OTP step and then closes the popup, your onComplete callback may never fire. As far as your frontend knows, nothing happened, but the payment might have actually gone through on Interswitch's side. &lt;/p&gt;

&lt;p&gt;This is exactly why you cannot rely on the callback as your source of truth, and why the next step exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — Verify Server-Side (The Rule You Never Break)
&lt;/h2&gt;

&lt;p&gt;This is the most important section in this guide.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never fulfill an order based on the client-side callback alone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The callback firing with a success code means the customer completed the payment flow in their browser. It does not, by itself, prove the money is real and in your account. Two reasons this matters for an e-commerce store specifically-&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;the abandoned-tab problem from above&lt;/strong&gt; - the callback might never fire even when payment succeeded. Your server needs to be able to check independently.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;client responses can be manipulated&lt;/strong&gt;. A malicious user can fake a success response to your frontend. If you ship goods on that alone, you've shipped for free.&lt;/p&gt;

&lt;p&gt;So before you mark the order paid and trigger fulfillment, your server independently verifies the transaction with Interswitch using your merchant code, the transaction reference you stored, and the amount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// On your server — verify before fulfilling&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyAndFulfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;txnRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectedAmountInKobo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;merchantCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MX6072&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`https://sandbox.interswitchng.com/collections/api/v1/gettransaction.json`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="s2"&gt;`?merchantcode=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;merchantCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="s2"&gt;`&amp;amp;transactionreference=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;txnRef&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="s2"&gt;`&amp;amp;amount=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;expectedAmountInKobo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// ResponseCode "00" = approved. Always confirm the returned&lt;/span&gt;
    &lt;span class="c1"&gt;// Amount matches your order total before giving value.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isApproved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amountMatches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;expectedAmountInKobo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isApproved&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;amountMatches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;markOrderPaid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;triggerFulfillment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;flagForReview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseDescription&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to note about this verification call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auth on the requery.&lt;/strong&gt; The simplified call above passes only the query parameters. Some Interswitch setups require an additional Hash header on the gettransaction.json requery (a SHA-512 hash of the requery string plus your secret key). Confirm against your Developer Console whether your integration needs it, and add it before going live if so.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The returned Amount is also in kobo&lt;/strong&gt;. You're comparing your expectedAmountInKobo against Interswitch's Amount, both in the minor unit so don't accidentally compare naira to kobo and trigger a false mismatch.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check the amount, not just the status. Verify not only that a payment happened, but that the amount paid matches the order total. A customer paying ₦100 for a ₦45,000 order is a payment that "succeeded" and one you must never fulfill. Confirm both that the money is real and that it's the right money.&lt;/p&gt;

&lt;p&gt;The flow is always: customer's browser says "I think I paid" → your server confirms "yes, the money is real and it's the correct amount" → only then do you give value. Build this from day one. It's far harder to retrofit after you've been fulfilling on trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Complete a Test Payment
&lt;/h2&gt;

&lt;p&gt;With the flow wired up, use Interswitch's test cards to run a payment in sandbox. Enter a test card in the popup, complete the OTP, and watch your handler fire. Then deliberately test the failure paths: cancel mid-flow, close the tab, use a declining test card. A checkout you've only tested on the success path is a checkout you haven't really tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — Go Live
&lt;/h2&gt;

&lt;p&gt;When test works and the failure cases are handled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate your own credentials and replace the sandbox ones&lt;/li&gt;
&lt;li&gt;Change the sandbox host to the live host (script: newwebpay.interswitchng.com; verification: webpay.interswitchng.com)&lt;/li&gt;
&lt;li&gt;Set mode: 'LIVE'&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code doesn't change. only credentials, URL, and mode. What you tested is what ships.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;A note on base URLs:&lt;/em&gt;&lt;/strong&gt; Interswitch uses a few sandbox hostnames across their docs and plugins (sandbox.interswitchng.com, newwebpay-sandbox.interswitchng.com, and others). The ones used here match the current Web Checkout documentation, but always confirm the exact base URLs against your Developer Console before going live.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Note on Transaction Verification
&lt;/h2&gt;

&lt;p&gt;For the server-side verification step, you use the gettransaction.json endpoint to confirm status with Interswitch. One thing worth knowing as you architect this: the Transaction Search API does not currently cover IPG payments, so your verification uses IPG's own transaction status check (shown above), not Transaction Search. Keep those two systems distinct, they solve different problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up...
&lt;/h2&gt;

&lt;p&gt;A real e-commerce checkout isn't the success path; it's the success path plus every way a customer can deviate from it. We built a flow that converts the cart correctly (kobo, unique references), handles the customer who completes payment and the one who closes the tab, and most importantly, verifies server-side that the money is real and the amount is right before shipping a single item.&lt;/p&gt;

&lt;p&gt;The one rule, again, because it's the one that matters: the browser says "I paid," your server confirms "the money is real and correct," and only then do you fulfill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/quickstart-accept-your-first-payment-in-5-minutes" rel="noopener noreferrer"&gt;QuickStart — Accept Your First Payment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/web-checkout" rel="noopener noreferrer"&gt;Web Checkout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/default-test-credentials" rel="noopener noreferrer"&gt;Default Test Credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/getting-integration-credentials" rel="noopener noreferrer"&gt;Getting Integration Credentials&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Building checkout with Interswitch? Join the Interswitch &lt;a href="https://join.slack.com/t/iswdevelopercommunity/shared_invite/zt-3tef2a52s-0mI470Fh7hJ_R8O9wfHHIw" rel="noopener noreferrer"&gt;Developer Community on Slack&lt;/a&gt;, share what you're building and get help from developers who've shipped it.&lt;/p&gt;

</description>
      <category>interswitchpaymentgateway</category>
      <category>webcheckout</category>
    </item>
    <item>
      <title>Building an Automated POS Settlement Monitor with Interswitch Transaction Search</title>
      <dc:creator>Chizurum Egwunwankwo</dc:creator>
      <pubDate>Tue, 30 Jun 2026 17:04:50 +0000</pubDate>
      <link>https://dev.to/interswitchdevcommunity/building-an-automated-pos-settlement-monitor-with-interswitch-transaction-search-2c4k</link>
      <guid>https://dev.to/interswitchdevcommunity/building-an-automated-pos-settlement-monitor-with-interswitch-transaction-search-2c4k</guid>
      <description>&lt;p&gt;If you run a business with POS terminals - a retail chain, a string of agent locations, a payments business with terminals in the field, you know the evening ritual. Transactions came in all day. Now you need to know which ones actually settled, which are still pending, and whether the money that hit your account matches what your terminals reported. &lt;/p&gt;

&lt;p&gt;This guide does that. We'll build an automated settlement monitor using the &lt;a href="https://docs.interswitchgroup.com/docs/transaction-search" rel="noopener noreferrer"&gt;Interswitch Transaction Search API&lt;/a&gt;; one that takes your terminals, finds the day's transactions, and tells you exactly what settled and what didn't. &lt;/p&gt;

&lt;h2&gt;
  
  
  On Coverage
&lt;/h2&gt;

&lt;p&gt;Before we write a line of code, you need to know what Transaction Search actually covers, because building on the wrong assumption wastes your time.&lt;br&gt;
Per Interswitch's transaction coverage, here's what's fully supported today:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transaction Type&lt;/th&gt;
&lt;th&gt;Transaction&lt;/th&gt;
&lt;th&gt;Settlement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Purchase (POS &amp;amp; Web)&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfer (ATM &amp;amp; POS)&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash Withdrawal&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agency Banking&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quickteller Transfers&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two types we'll build on - POS Purchase and POS Transfer - have full transaction and settlement coverage. That's deliberate. It means every reconciliation lookup in this guide returns complete data.&lt;/p&gt;

&lt;p&gt;One thing to note: &lt;a href="https://interswitchgroup.com/ipg" rel="noopener noreferrer"&gt;Interswitch Payment Gateway (IPG&lt;/a&gt;) purchases are not currently covered by Transaction Search. If you're verifying IPG web checkout payments, that's a different flow. Transaction Search is built for transactions that flow through the switch: POS, ATM, cash withdrawal, transfers. &lt;/p&gt;
&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;A settlement monitor that does three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authenticates&lt;/strong&gt; against Interswitch Passport for an access token&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Searches&lt;/strong&gt; each terminal's transactions for a given day&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pulls&lt;/strong&gt; full settlement details for each transaction and flags anything not yet settled&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end you'll have a function you can drop into a cron job that runs every evening and emails you a settlement report.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1 - Setup Your Project
&lt;/h2&gt;

&lt;p&gt;Get your credentials first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create an account on the &lt;a href="https://developer.interswitchgroup.com/" rel="noopener noreferrer"&gt;Interswitch Developer Console&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set up a Project and select Transaction Search from the available APIs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A test Client ID and Secret Key are generated for you&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 2 - Authentication
&lt;/h2&gt;

&lt;p&gt;Every Transaction Search call needs a Bearer token, obtained by exchanging your Client ID and Secret against the Interswitch Passport endpoint. Tokens are short-lived - cache and refresh rather than requesting one per call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;
&lt;span class="c1"&gt;// auth.js&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://passport-sandbox.interswitchng.com/passport/oauth/token &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Basic &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;grant_type=client_credentials&amp;amp;scope=profile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 - Find A Terminal's Transaction
&lt;/h2&gt;

&lt;p&gt;This is where the POS case study comes alive. Quick Search lets you find transactions by terminal_id, exactly the field you have for every one of your POS devices. Combine it with a date and you get every transaction that terminal processed that day.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;terminal_id&lt;/td&gt;
&lt;td&gt;Identifies a terminal belonging to a merchant&lt;/td&gt;
&lt;td&gt;8 chars — you have this for every device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;merchant_code&lt;/td&gt;
&lt;td&gt;Uniquely identifies a registered merchant&lt;/td&gt;
&lt;td&gt;12 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stan&lt;/td&gt;
&lt;td&gt;Transaction number from the terminal&lt;/td&gt;
&lt;td&gt;6 digits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rrn&lt;/td&gt;
&lt;td&gt;Retrieval Reference Number&lt;/td&gt;
&lt;td&gt;12 digits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;masked_pan&lt;/td&gt;
&lt;td&gt;Card number (first 6 + last 4 visible)&lt;/td&gt;
&lt;td&gt;16–19 digits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;transaction date&lt;/td&gt;
&lt;td&gt;The date the transaction occured (YYYY-MM-DD)&lt;/td&gt;
&lt;td&gt;used in quick start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;transaction_amount&lt;/td&gt;
&lt;td&gt;Amount in lower denomination (₦20 → 2000)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two rules that catch everyone: use transaction_date (YYYY-MM-DD), and transaction_amount is in the lower denomination — ₦200 is 20000, not 200.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// findTerminalTransactions.js&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;searchByTerminal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;terminalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://switch-online-gateway-service.k9.isw.la/switch-online-gateway-service/api/v1/gateway/quick-search?page_size=20&amp;amp;page_number=1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ClientId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;terminal_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;terminalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;transaction_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;          &lt;span class="c1"&gt;// per docs (YYYY-MM-DD)&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling a full day of transactions
&lt;/h2&gt;

&lt;p&gt;A busy terminal does more than 20 transactions a day, and search caps at 20 per page (page_size can't exceed 20). The response tells you totalPages — loop through page_number until you've covered them all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getAllTerminalTransactions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;terminalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;pageNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;`https://switch-online-gateway-service.k9.isw.la/switch-online-gateway-service/api/v1/gateway/quick-search?page_size=20&amp;amp;page_number=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pageNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ClientId&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;terminal_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;terminalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;transaction_date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;totalPages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totalPages&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;pageNumber&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pageNumber&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4 - Pull Settlement Details
&lt;/h2&gt;

&lt;p&gt;Search tells you a transaction happened. To know whether the money settled, take the transaction_id and call Get Transaction Details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;
&lt;span class="c1"&gt;// getDetails.js&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTransactionDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`https://switch-online-gateway-service.k9.isw.la/switch-online-gateway-service/api/v1/gateway/transaction?transaction_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response breaks into four blocks. For settlement reconciliation, the first two are what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"globalOutputData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PURCHASE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"210000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"transactionDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-10-09T17:47:35.240"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"transactionStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"COMPLETED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settlementDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2023-10-10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settlementStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SETTLED"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"transactionData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"terminalId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20573ZLY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"stan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"373758"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"channel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"rrn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"696843517287"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"responseCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"responseMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Approved or completed successfully"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"merchantCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2057LA200002957"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"settlementData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settlementBreakdownList"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fee&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;breakdown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;party&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"settlementDataAvailable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The globalOutputData block answers the two questions your reconciliation is really asking: transaction status (did the payment complete?) and settlement status (did the money move?).&lt;/p&gt;




&lt;h2&gt;
  
  
  The Insight That Saves You: Transaction Status and Settlement Status Are Different
&lt;/h2&gt;

&lt;p&gt;This is the single most important thing in this guide.&lt;/p&gt;

&lt;p&gt;A POS transaction can be COMPLETED but PENDING SETTLEMENT. The customer's card was charged, the purchase succeeded, your terminal printed "approved" but the money has not yet landed in your settlement account. &lt;/p&gt;

&lt;p&gt;These are two independent facts.&lt;/p&gt;

&lt;p&gt;The most common reconciliation mistake is treating "transaction succeeded" as "money received." They are not the same, and the gap between them is exactly what you're monitoring for. A transaction stuck in PENDING SETTLEMENT for longer than expected is the thing you want flagged.&lt;/p&gt;

&lt;p&gt;The statuses you'll see:&lt;em&gt;PENDING, COMPLETED, REVERSED Settlement status: SETTLED, PENDING SETTLEMENT, NO SETTLEMENT, UNSUPPORTED&lt;/em&gt;&lt;br&gt;
And within the transaction data, the response code maps cleanly: 00 is approved, 09 (or none) is pending, anything else (91, 06, 51...) is a failure.&lt;/p&gt;


&lt;h2&gt;
  
  
  Putting It Together: The Settlement Monitor
&lt;/h2&gt;

&lt;p&gt;Here's the whole thing: give it your terminals and a date, get back a clean settlement report flagging anything that hasn't settled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runSettlementMonitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;terminalIds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;settled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;terminalId&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;terminalIds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getAllTerminalTransactions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;terminalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notFound&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;terminalId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txn&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getTransactionDetails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;txn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;globalOutputData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;terminalId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;transactionStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;settlementStatus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settlementStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;settlementDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settlementDate&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settlementStatus&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SETTLED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionStatus&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// completed but not yet settled — watch these&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Run it every evening for all your terminals&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runSettlementMonitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20573ZLY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20577C9O&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2TEP5C7W&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2023-10-09&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Settled: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;settled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Pending settlement (watch these): &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop that into a cron job, pipe the output into an email or a Slack webhook, and you've automated reconciliation. The pending bucket is one to watch, those are completed transactions whose money hasn't landed yet.&lt;/p&gt;




&lt;h2&gt;
  
  
  When A Transaction Isn't Found
&lt;/h2&gt;

&lt;p&gt;Interswitch is explicit that Transaction Search hasn't reached 100% coverage. If a POS transaction you know exists doesn't come back, treat the 404 as "not found in search" rather than "doesn't exist," and reconfirm through the &lt;a href="https://help.interswitchng.com" rel="noopener noreferrer"&gt;Interswitch Help Desk&lt;/a&gt;. Build that into your notFound handling rather than letting it break the run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;We took three API calls: &lt;strong&gt;&lt;em&gt;authenticate, search by terminal, get settlement details&lt;/em&gt;&lt;/strong&gt;, and turned them into an automated settlement monitor for a POS business. &lt;/p&gt;

&lt;p&gt;The key isn't any single call. It's the insight that transaction status and settlement status are different questions, and that the gap between them is exactly what reconciliation is about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/transaction-search-getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/transaction-search-overview" rel="noopener noreferrer"&gt;Transaction Search Overview
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/transaction-set-coverage" rel="noopener noreferrer"&gt;Transaction Set Coverage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/quick-search" rel="noopener noreferrer"&gt;Quick Search Reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/get-transaction-details" rel="noopener noreferrer"&gt;Get Transaction Details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.interswitchgroup.com/docs/swol-response-codes" rel="noopener noreferrer"&gt;Response Codes &amp;amp; Statuses&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Running POS terminals and building reconciliation tooling?&lt;a href="https://join.slack.com/t/iswdevelopercommunity/shared_invite/zt-3tef2a52s-0mI470Fh7hJ_R8O9wfHHIw" rel="noopener noreferrer"&gt; Join our community&lt;/a&gt; on Slack and share what you're building.&lt;/p&gt;

</description>
      <category>transactionsearchapi</category>
      <category>interswitchapi</category>
      <category>payment</category>
      <category>api</category>
    </item>
  </channel>
</rss>
