<?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: Afriex</title>
    <description>The latest articles on DEV Community by Afriex (afriex).</description>
    <link>https://dev.to/afriex</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%2F12790%2Fb22225a9-dfbf-4c5a-a15a-2d25dba8e294.jpeg</url>
      <title>DEV Community: Afriex</title>
      <link>https://dev.to/afriex</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/afriex"/>
    <language>en</language>
    <item>
      <title>Your Neobank Doesn't Need a Crypto Feature. It Needs Better Rails.</title>
      <dc:creator>0xSonOfUri</dc:creator>
      <pubDate>Fri, 25 Sep 2026 10:35:13 +0000</pubDate>
      <link>https://dev.to/afriex/your-neobank-doesnt-need-a-crypto-feature-it-needs-better-rails-31e</link>
      <guid>https://dev.to/afriex/your-neobank-doesnt-need-a-crypto-feature-it-needs-better-rails-31e</guid>
      <description>&lt;p&gt;&lt;em&gt;How stablecoins are becoming part of the infrastructure behind the next generation of global neobanks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A neobank can make banking feel incredibly simple.&lt;/p&gt;

&lt;p&gt;Open an account.&lt;/p&gt;

&lt;p&gt;Receive money.&lt;/p&gt;

&lt;p&gt;Convert currencies.&lt;/p&gt;

&lt;p&gt;Send money.&lt;/p&gt;

&lt;p&gt;Spend.&lt;/p&gt;

&lt;p&gt;The interface can be reduced to a few screens and a handful of buttons.&lt;/p&gt;

&lt;p&gt;But behind that simplicity is a much harder problem.&lt;/p&gt;

&lt;p&gt;Money still has to move.&lt;/p&gt;

&lt;p&gt;It has to cross borders, settle between institutions, pass through liquidity providers, convert between currencies, reach local payment rails, and eventually arrive in the customer's account.&lt;/p&gt;

&lt;p&gt;For a neobank operating across multiple markets, the complexity grows quickly.&lt;/p&gt;

&lt;p&gt;This is where stablecoins become interesting.&lt;/p&gt;

&lt;p&gt;Not because every neobank needs to become a crypto company.&lt;/p&gt;

&lt;p&gt;Not because customers necessarily want to interact with blockchains.&lt;/p&gt;

&lt;p&gt;But because stablecoins introduce another way to move and settle value across the financial system.&lt;/p&gt;

&lt;p&gt;The question for neobanks is therefore not simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Should we add crypto?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Can stablecoins become one of the rails underneath the financial experience we are already building?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Neobank UX Is Simple. The Money Movement Isn't
&lt;/h2&gt;

&lt;p&gt;When a customer sends $1,000 internationally, they don't think about the infrastructure behind the transaction.&lt;/p&gt;

&lt;p&gt;They think:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send $1,000 → recipient receives money.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The neobank, however, may have to deal with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Funding&lt;/li&gt;
&lt;li&gt;FX conversion&lt;/li&gt;
&lt;li&gt;Liquidity&lt;/li&gt;
&lt;li&gt;Payment routing&lt;/li&gt;
&lt;li&gt;Banking partners&lt;/li&gt;
&lt;li&gt;Correspondent relationships&lt;/li&gt;
&lt;li&gt;Local payment rails&lt;/li&gt;
&lt;li&gt;Settlement&lt;/li&gt;
&lt;li&gt;Reconciliation&lt;/li&gt;
&lt;li&gt;Compliance&lt;/li&gt;
&lt;li&gt;Transaction monitoring&lt;/li&gt;
&lt;li&gt;Failed payments and retries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The customer sees a transfer.&lt;/p&gt;

&lt;p&gt;The neobank operates a financial orchestration layer.&lt;/p&gt;

&lt;p&gt;That difference is important.&lt;/p&gt;

&lt;p&gt;The value of a neobank is not simply putting a nicer interface on top of banking.&lt;/p&gt;

&lt;p&gt;It is abstracting a complicated financial system into a product that feels simple.&lt;/p&gt;

&lt;p&gt;And as neobanks become more global, that abstraction becomes harder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Neobanks Were Built to Hide Banking Infrastructure
&lt;/h2&gt;

&lt;p&gt;Traditional banking infrastructure was not designed around the experience of a modern software product.&lt;/p&gt;

&lt;p&gt;Moving money internationally can involve multiple institutions, currencies, settlement systems, and intermediaries.&lt;/p&gt;

&lt;p&gt;A modern neobank wants something different.&lt;/p&gt;

&lt;p&gt;It wants to give a developer or product team an abstraction like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send money
Receive money
Convert money
Hold money
Pay someone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The infrastructure underneath should handle the complexity.&lt;/p&gt;

&lt;p&gt;This is the same reason modern software companies rely on APIs and cloud infrastructure rather than building every underlying system themselves.&lt;/p&gt;

&lt;p&gt;The neobank becomes the experience layer.&lt;/p&gt;

&lt;p&gt;The infrastructure provider becomes the execution layer.&lt;/p&gt;

&lt;p&gt;And increasingly, stablecoins can become part of that execution layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stablecoins Change the Settlement Conversation
&lt;/h2&gt;

&lt;p&gt;Stablecoins are often introduced as a crypto product.&lt;/p&gt;

&lt;p&gt;For financial infrastructure, their more interesting property is simpler:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They are digital representations of value that can move on blockchain networks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That creates a potential settlement mechanism that operates differently from traditional banking rails.&lt;/p&gt;

&lt;p&gt;A simplified cross-border flow could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USD
 │
 ▼
Stablecoin
 │
 ▼
Blockchain Settlement
 │
 ▼
Stablecoin Liquidity
 │
 ▼
Local Currency
 │
 ▼
Local Payment Rail
 │
 ▼
Customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The customer does not necessarily need to see any of this.&lt;/p&gt;

&lt;p&gt;They may simply see:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$1,000 sent → local currency received.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The blockchain is infrastructure.&lt;/p&gt;

&lt;p&gt;The neobank remains the product.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stablecoins Don't Replace the Neobank
&lt;/h2&gt;

&lt;p&gt;A common mistake is to think that adopting stablecoins means rebuilding the entire financial product around crypto.&lt;/p&gt;

&lt;p&gt;It doesn't have to.&lt;/p&gt;

&lt;p&gt;A neobank can continue to own the parts customers actually interact with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accounts&lt;/li&gt;
&lt;li&gt;Balances&lt;/li&gt;
&lt;li&gt;Cards&lt;/li&gt;
&lt;li&gt;Transfers&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;Beneficiaries&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Customer support&lt;/li&gt;
&lt;li&gt;Compliance and identity&lt;/li&gt;
&lt;li&gt;Financial controls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stablecoins can sit underneath some of these experiences.&lt;/p&gt;

&lt;p&gt;For example, a neobank could potentially use stablecoin infrastructure for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cross-border settlement&lt;/li&gt;
&lt;li&gt;Treasury transfers&lt;/li&gt;
&lt;li&gt;Liquidity movement&lt;/li&gt;
&lt;li&gt;International payouts&lt;/li&gt;
&lt;li&gt;Internal value movement between markets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The customer doesn't necessarily need a wallet.&lt;/p&gt;

&lt;p&gt;They don't necessarily need to know what chain was used.&lt;/p&gt;

&lt;p&gt;They may not even know that a stablecoin was involved.&lt;/p&gt;

&lt;p&gt;This is arguably where stablecoins become most interesting for mainstream financial products:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;when the blockchain disappears behind the interface.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Places Stablecoins Matter to Neobanks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Cross-Border Settlement
&lt;/h3&gt;

&lt;p&gt;Global neobanks eventually run into the same problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we move money efficiently between countries?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional international payment infrastructure can involve multiple intermediaries and settlement processes.&lt;/p&gt;

&lt;p&gt;Stablecoins introduce another path for transferring value between systems.&lt;/p&gt;

&lt;p&gt;The architecture could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market A
USD
 │
 ▼
Stablecoin
 │
 ▼
Global Settlement
 │
 ▼
Stablecoin
 │
 ▼
Market B
Local Currency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local customer experience can remain entirely fiat-based.&lt;/p&gt;

&lt;p&gt;This creates an important architectural possibility:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global settlement does not have to look like local banking.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A neobank can potentially use different rails for different parts of the transaction.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Treasury and Liquidity
&lt;/h3&gt;

&lt;p&gt;A global neobank has another problem that customers rarely see:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where should liquidity sit?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a company operates across multiple markets, it has to manage money across different currencies and financial systems.&lt;/p&gt;

&lt;p&gt;Treasury teams have to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Currency exposure&lt;/li&gt;
&lt;li&gt;Liquidity requirements&lt;/li&gt;
&lt;li&gt;Settlement timing&lt;/li&gt;
&lt;li&gt;Funding accounts&lt;/li&gt;
&lt;li&gt;FX conversion&lt;/li&gt;
&lt;li&gt;Capital movement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stablecoins can become another instrument for moving liquidity between markets.&lt;/p&gt;

&lt;p&gt;Instead of thinking only in terms of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bank A → Bank B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the infrastructure can potentially support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bank A
   ↓
Stablecoin
   ↓
Liquidity Layer
   ↓
Bank B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to make treasury "crypto."&lt;/p&gt;

&lt;p&gt;The goal is to make treasury more programmable and flexible.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Global Payouts
&lt;/h3&gt;

&lt;p&gt;Consider a neobank paying thousands of users, merchants, contractors, or suppliers across different countries.&lt;/p&gt;

&lt;p&gt;The payout experience should be simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Recipient
Amount
Currency
Send
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the infrastructure needs to determine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which rail should be used?&lt;/li&gt;
&lt;li&gt;Where should the funds come from?&lt;/li&gt;
&lt;li&gt;Which currency should be converted?&lt;/li&gt;
&lt;li&gt;What liquidity is available?&lt;/li&gt;
&lt;li&gt;How should the transaction settle?&lt;/li&gt;
&lt;li&gt;How should it be reconciled?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stablecoins can become one component in that routing and settlement system.&lt;/p&gt;

&lt;p&gt;The important concept is &lt;strong&gt;rail abstraction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The customer asks for an outcome.&lt;/p&gt;

&lt;p&gt;The infrastructure chooses the mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Neobank of the Future May Be Rail-Agnostic
&lt;/h2&gt;

&lt;p&gt;A neobank shouldn't necessarily be defined by the payment rail it uses.&lt;/p&gt;

&lt;p&gt;It should be defined by the financial experience it provides.&lt;/p&gt;

&lt;p&gt;Underneath the experience, there may be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ACH&lt;/li&gt;
&lt;li&gt;SEPA&lt;/li&gt;
&lt;li&gt;SWIFT&lt;/li&gt;
&lt;li&gt;Local bank transfers&lt;/li&gt;
&lt;li&gt;Mobile money&lt;/li&gt;
&lt;li&gt;Card networks&lt;/li&gt;
&lt;li&gt;Stablecoins&lt;/li&gt;
&lt;li&gt;Other emerging settlement networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A customer should not have to care.&lt;/p&gt;

&lt;p&gt;They should be able to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Send $500 to this person."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The infrastructure determines how to execute it.&lt;/p&gt;

&lt;p&gt;That means the modern payment stack starts to look less like a collection of individual payment products and more like an orchestration layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     NEOBANK APP
                         │
          ┌──────────────┼──────────────┐
          │              │              │
       Accounts        Cards         Payments
          │              │              │
          └──────────────┼──────────────┘
                         │
                 PAYMENT ORCHESTRATION
                         │
          ┌──────────────┼──────────────┐
          │              │              │
      Bank Rails      Stablecoins      FX
          │              │              │
          └──────────────┼──────────────┘
                         │
                 LOCAL PAYMENT RAILS
                         │
                      CUSTOMER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stablecoin is not necessarily the product.&lt;/p&gt;

&lt;p&gt;It is a rail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Neobank Stack
&lt;/h2&gt;

&lt;p&gt;This leads to a broader way of thinking about financial infrastructure.&lt;/p&gt;

&lt;p&gt;A modern neobank may have several layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────┐
│        CUSTOMER EXPERIENCE       │
├──────────────────────────────────┤
│          NEOBANK APP             │
├──────────────────────────────────┤
│     FINANCIAL ORCHESTRATION      │
├──────────────────────────────────┤
│ Banking │ Stablecoins │ FX │ API │
├──────────────────────────────────┤
│      GLOBAL PAYMENT RAILS        │
├──────────────────────────────────┤
│ Accounts │ Liquidity │ Settlement│
└──────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application layer is what customers see.&lt;/p&gt;

&lt;p&gt;The infrastructure layer is what makes the experience possible.&lt;/p&gt;

&lt;p&gt;Stablecoins increasingly belong in the conversation about that infrastructure layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Neobanks Should Actually Ask
&lt;/h2&gt;

&lt;p&gt;The strategic question shouldn't be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Should we add a crypto feature?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That frames stablecoins as a consumer product decision.&lt;/p&gt;

&lt;p&gt;A more useful set of questions is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can we settle cross-border transactions more efficiently?&lt;/li&gt;
&lt;li&gt;Can we reduce dependency on unnecessary intermediaries?&lt;/li&gt;
&lt;li&gt;Can we move liquidity between markets more easily?&lt;/li&gt;
&lt;li&gt;Can we support more currencies and markets?&lt;/li&gt;
&lt;li&gt;Can we make treasury operations more programmable?&lt;/li&gt;
&lt;li&gt;Can we reconcile fiat and digital settlement flows?&lt;/li&gt;
&lt;li&gt;Can we route transactions across multiple rails?&lt;/li&gt;
&lt;li&gt;Can we hide infrastructure complexity from customers?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions are much closer to the real opportunity.&lt;/p&gt;

&lt;p&gt;The objective isn't to make customers use crypto.&lt;/p&gt;

&lt;p&gt;The objective is to make money movement better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hard Part Isn't Stablecoins
&lt;/h2&gt;

&lt;p&gt;There is a temptation to think that once a company has access to stablecoins, the infrastructure problem is solved.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;Stablecoin movement is only one component of a much larger financial system.&lt;/p&gt;

&lt;p&gt;A global neobank still needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stablecoin Liquidity
        │
        ▼
      FX
        │
        ▼
Local Accounts
        │
        ▼
Local Payment Rails
        │
        ▼
Settlement
        │
        ▼
Reconciliation
        │
        ▼
Customer Balance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every layer matters.&lt;/p&gt;

&lt;p&gt;A fast settlement mechanism is not enough if the money cannot reach the customer's local bank account.&lt;/p&gt;

&lt;p&gt;A great FX rate is not enough if liquidity is unavailable.&lt;/p&gt;

&lt;p&gt;A local account is not enough if the business cannot reconcile thousands of transactions.&lt;/p&gt;

&lt;p&gt;The infrastructure has to work as a system.&lt;/p&gt;

&lt;p&gt;This is where payment infrastructure providers become important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure Is Becoming the Product
&lt;/h2&gt;

&lt;p&gt;The next generation of financial companies may not build every component themselves.&lt;/p&gt;

&lt;p&gt;They will compose infrastructure.&lt;/p&gt;

&lt;p&gt;A neobank can focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer experience&lt;/li&gt;
&lt;li&gt;Product design&lt;/li&gt;
&lt;li&gt;Distribution&lt;/li&gt;
&lt;li&gt;Brand&lt;/li&gt;
&lt;li&gt;Financial products&lt;/li&gt;
&lt;li&gt;Customer relationships&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While infrastructure providers handle increasingly complex capabilities such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Global collections&lt;/li&gt;
&lt;li&gt;Local accounts&lt;/li&gt;
&lt;li&gt;Payouts&lt;/li&gt;
&lt;li&gt;FX&lt;/li&gt;
&lt;li&gt;Treasury&lt;/li&gt;
&lt;li&gt;Liquidity&lt;/li&gt;
&lt;li&gt;Settlement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Afriex's AFX platform is positioned around this broader infrastructure problem, providing businesses with capabilities for global collections, programmable payouts, local bank accounts, and FX/treasury management through its platform and APIs.&lt;/p&gt;

&lt;p&gt;For a neobank, the value of this model is not simply "having an API."&lt;/p&gt;

&lt;p&gt;It is being able to connect the pieces required to operate a global money product without building every financial rail from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stablecoins Are Part of a Larger Convergence
&lt;/h2&gt;

&lt;p&gt;The interesting future is not necessarily:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Banks vs. crypto.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It may be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Banking infrastructure + stablecoins + APIs + local payment rails.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The boundaries between these systems are becoming less important.&lt;/p&gt;

&lt;p&gt;A customer may hold a dollar balance.&lt;/p&gt;

&lt;p&gt;A business may fund that balance through a bank transfer.&lt;/p&gt;

&lt;p&gt;The neobank may move value through a stablecoin.&lt;/p&gt;

&lt;p&gt;A local partner may settle the final amount into a bank account.&lt;/p&gt;

&lt;p&gt;The user simply sees:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Money arrived.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the abstraction financial infrastructure should provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Global Neobank Needs Better Rails
&lt;/h2&gt;

&lt;p&gt;Neobanks started by making banking software-first.&lt;/p&gt;

&lt;p&gt;The next phase is making global money movement software-first.&lt;/p&gt;

&lt;p&gt;That requires infrastructure that can operate across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Currencies&lt;/li&gt;
&lt;li&gt;Countries&lt;/li&gt;
&lt;li&gt;Banking systems&lt;/li&gt;
&lt;li&gt;Payment rails&lt;/li&gt;
&lt;li&gt;Liquidity pools&lt;/li&gt;
&lt;li&gt;Settlement networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stablecoins are not the answer to every financial infrastructure problem.&lt;/p&gt;

&lt;p&gt;But they introduce a new primitive for moving and settling value.&lt;/p&gt;

&lt;p&gt;And that primitive is increasingly relevant to companies building financial products for a world where money needs to move globally, instantly, and programmatically.&lt;/p&gt;

&lt;p&gt;The neobank of the future doesn't necessarily need to look like a crypto company.&lt;/p&gt;

&lt;p&gt;It may simply use crypto infrastructure where it makes the underlying financial system work better.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your neobank doesn't need a crypto feature. It needs better rails.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And stablecoins may become one of those rails.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>crypto</category>
      <category>fintech</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>Afriex Integrations: Sandbox, Idempotency, and Webhook Simulation</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Mon, 21 Sep 2026 19:50:13 +0000</pubDate>
      <link>https://dev.to/afriex/afriex-integrations-sandbox-idempotency-and-webhook-simulation-4k0j</link>
      <guid>https://dev.to/afriex/afriex-integrations-sandbox-idempotency-and-webhook-simulation-4k0j</guid>
      <description>&lt;p&gt;Most payment integration bugs never show up in a manual test. You click through the happy path once, it works, you ship it. Then in production a network blip causes a retry, two webhook deliveries arrive out of order, or a duplicate request slips through — and now you're debugging a double payout at the worst possible time.&lt;/p&gt;

&lt;p&gt;63% of async API failures stem from race conditions invisible to synchronous testing tools. Payment APIs are almost entirely asynchronous — a transaction returns &lt;code&gt;PENDING&lt;/code&gt;, then changes state later via webhook — which means the bugs that matter most are exactly the ones a quick manual test will never catch.&lt;/p&gt;

&lt;p&gt;This guide covers how to actually test an Afriex integration: sandbox setup, idempotency verification, and simulating webhook events without waiting for a real transaction to change state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Set up your sandbox environment
&lt;/h2&gt;

&lt;p&gt;Everything starts in the &lt;code&gt;staging&lt;/code&gt; environment. No approval process, no waiting — sign up at &lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;business.afriex.com&lt;/a&gt;, grab your sandbox API key, and you have a fully working test environment immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AfriexSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;webhookPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&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;Keep a completely separate API key and webhook public key for staging versus production. Never test against production, even by accident — a hardcoded &lt;code&gt;environment: "production"&lt;/code&gt; in a test file is a classic way to make a real transaction during a test run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing idempotency: verify the effect, not just the response
&lt;/h2&gt;

&lt;p&gt;A repeated 200 or 201 response is not enough evidence that idempotency works. An API can return a reassuring response while creating a duplicate record underneath. The test needs to check the actual downstream effect — was exactly one transaction created — not just what the response looked like.&lt;/p&gt;

&lt;p&gt;Here's the pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;testIdempotency&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;reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`test-idem-&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`idem-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;reference&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WITHDRAW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-customer-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-payment-method-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Fire the same request twice with the same idempotency key&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;first&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&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;second&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// The real assertion: both calls return the SAME transaction ID&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;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;second&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency failed — two different transaction IDs were created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Confirm only one transaction actually exists for this reference&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allTransactions&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WITHDRAW&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;matching&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allTransactions&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="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;reference&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;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;matching&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="s2"&gt;`Expected exactly 1 transaction, found &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;matching&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test proves the actual invariant that matters: repeated delivery of one logical write produces no more than one committed effect. Checking &lt;code&gt;transactionId&lt;/code&gt; equality across both calls, then confirming only one record exists in the list, is what separates a real idempotency test from one that just checks for a 200.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also test the negative case&lt;/strong&gt; — the same key with a genuinely different payload should be rejected, not silently accepted as a new request or silently merged with the original:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;testIdempotencyKeyReuse&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;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`idem-reuse-test-&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WITHDRAW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-customer-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-payment-method-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ref-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Same key, different amount — this should be rejected, not silently accepted&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WITHDRAW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-customer-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;destinationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-payment-method-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sourceAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;50&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// different amount&lt;/span&gt;
      &lt;span class="na"&gt;destinationAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;50&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ref-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// same key&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Expected an error for key reuse with different payload&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This is the expected path&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;Run this in your CI pipeline against sandbox, not just once manually. A regression here is exactly the kind of bug that passes code review and fails in production three months later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Simulating concurrent retries
&lt;/h2&gt;

&lt;p&gt;The scenario that actually breaks systems in production: your disbursement worker calls &lt;code&gt;transactions.create&lt;/code&gt;, the network times out before your code sees the response, your retry logic fires a second call with the same idempotency key — but what if both requests hit Afriex at nearly the same moment, not sequentially?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;testConcurrentIdempotentRequests&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;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`idem-concurrent-&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WITHDRAW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-customer-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test-payment-method-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;20&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`concurrent-&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="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Fire both requests at the same time, not sequentially&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;resultA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resultB&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&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;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;resultA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactionId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;resultB&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Concurrent requests with the same idempotency key produced different transactions&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;This is the test most teams skip because it requires deliberately racing two requests against each other, rather than just calling an endpoint twice in sequence. It is also the test that catches the bug a sequential test cannot — a race condition only shows up in reality when requests actually overlap.&lt;/p&gt;




&lt;h2&gt;
  
  
  Simulating webhooks without waiting for real transactions
&lt;/h2&gt;

&lt;p&gt;Waiting for a real transaction to move through &lt;code&gt;PENDING&lt;/code&gt; → &lt;code&gt;PROCESSING&lt;/code&gt; → &lt;code&gt;COMPLETED&lt;/code&gt; in sandbox to test your webhook handler is slow and unreliable. Afriex's SDK gives you a way to fire a real, signed webhook on demand against an existing sandbox entity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Trigger a real signed webhook event for an existing sandbox transaction&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;triggerTestWebhook&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRANSACTION.UPDATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;entityId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;existing-sandbox-transaction-id&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;Because this fires a genuinely signed payload to your configured webhook URL, your signature verification code runs exactly as it would with a real event — this is not a mocked payload, it is the same delivery mechanism Afriex uses in production. To receive it locally, expose your dev server with a tunnel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ngrok http 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the ngrok HTTPS URL as your webhook URL in the sandbox dashboard, then trigger events for every status your handler needs to process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statusesToTest&lt;/span&gt; &lt;span class="o"&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;TRANSACTION.CREATED&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;TRANSACTION.UPDATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// fire this with different underlying statuses&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;event&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;statusesToTest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;triggerTestWebhook&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;entityId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;existing-sandbox-transaction-id&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the handler against &lt;code&gt;COMPLETED&lt;/code&gt;, &lt;code&gt;FAILED&lt;/code&gt;, &lt;code&gt;IN_REVIEW&lt;/code&gt;, and &lt;code&gt;RETRY&lt;/code&gt; specifically. These four are the ones most often mishandled — &lt;code&gt;IN_REVIEW&lt;/code&gt; and &lt;code&gt;RETRY&lt;/code&gt; are not failures and should never trigger a failure notification, but a handler that has only been tested against the happy path will often get this wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing webhook idempotency (not just transaction idempotency)
&lt;/h2&gt;

&lt;p&gt;Afriex retries webhook delivery up to 12 times with exponential backoff. Your handler will receive the same event more than once by design. Test this explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;testWebhookIdempotency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebhookPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Process the same webhook payload twice&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processWebhookEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;processWebhookEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// simulate a redelivery&lt;/span&gt;

  &lt;span class="c1"&gt;// Assert the side effect only happened once&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notificationCount&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;countNotificationsSent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&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;transactionId&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;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;notificationCount&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="s2"&gt;`Expected 1 notification, sent &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notificationCount&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your webhook handler sends an email or updates a balance on every delivery without checking whether it already processed that event, a retried webhook will double the side effect even though the underlying transaction only happened once.&lt;/p&gt;




&lt;h2&gt;
  
  
  What a full test suite for an Afriex integration should cover
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;What it proves&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Same idempotency key, same payload, called twice&lt;/td&gt;
&lt;td&gt;Returns the same transaction, no duplicate created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same idempotency key, different payload&lt;/td&gt;
&lt;td&gt;Rejected, not silently accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same idempotency key, concurrent calls&lt;/td&gt;
&lt;td&gt;Race condition does not produce two transactions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhook signature verification&lt;/td&gt;
&lt;td&gt;Invalid signature is rejected before any processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhook redelivery&lt;/td&gt;
&lt;td&gt;Side effects (emails, balance updates) happen exactly once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Each terminal status (&lt;code&gt;COMPLETED&lt;/code&gt;, &lt;code&gt;FAILED&lt;/code&gt;, &lt;code&gt;REJECTED&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Handler takes the correct action for each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-terminal statuses (&lt;code&gt;IN_REVIEW&lt;/code&gt;, &lt;code&gt;RETRY&lt;/code&gt;, &lt;code&gt;PROCESSING&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Handler does not treat these as failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate fetch before transaction creation&lt;/td&gt;
&lt;td&gt;Displayed rate matches what the transaction actually uses&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Build this as an actual test file that runs in CI against sandbox, not a manual checklist you run once before a release. The bugs this catches are exactly the ones that are invisible until a specific, rare sequence of events happens in production — which is precisely when you don't want to be debugging them for the first time.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>webhook</category>
      <category>sandbox</category>
      <category>payment</category>
    </item>
    <item>
      <title>When Money Becomes Infrastructure</title>
      <dc:creator>0xSonOfUri</dc:creator>
      <pubDate>Sat, 12 Sep 2026 06:45:51 +0000</pubDate>
      <link>https://dev.to/afriex/when-money-becomes-infrastructure-5774</link>
      <guid>https://dev.to/afriex/when-money-becomes-infrastructure-5774</guid>
      <description>&lt;p&gt;&lt;em&gt;Part II of a two-part series on the infrastructure behind global money movement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In Part I, we explored a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why is sending money across borders still so much harder than sending a message?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer was that money does not simply move.&lt;/p&gt;

&lt;p&gt;Behind every international transfer are institutions, networks, currencies, compliance systems, liquidity providers, settlement processes, and technical infrastructure.&lt;/p&gt;

&lt;p&gt;But something important is changing.&lt;/p&gt;

&lt;p&gt;For decades, accessing this infrastructure required becoming part of the financial system itself. A company that wanted to move money needed relationships with banks, payment processors, settlement partners, and regulatory institutions.&lt;/p&gt;

&lt;p&gt;Today, a growing number of those capabilities are becoming accessible as infrastructure.&lt;/p&gt;

&lt;p&gt;Through APIs.&lt;/p&gt;

&lt;p&gt;Through programmable accounts.&lt;/p&gt;

&lt;p&gt;Through wallet systems.&lt;/p&gt;

&lt;p&gt;Through stablecoin rails.&lt;/p&gt;

&lt;p&gt;Through embedded financial services.&lt;/p&gt;

&lt;p&gt;This changes the question for builders.&lt;/p&gt;

&lt;p&gt;The question is no longer only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How does money move?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Increasingly, the question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What can we build when money movement becomes programmable infrastructure?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That shift has implications far beyond payments.&lt;/p&gt;

&lt;p&gt;It changes how companies think about financial products.&lt;/p&gt;

&lt;p&gt;It changes what developers can build.&lt;/p&gt;

&lt;p&gt;And it may fundamentally change who gets to participate in the global financial system.&lt;/p&gt;

&lt;p&gt;This is where the story continues.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Financial Institutions to Financial Infrastructure
&lt;/h2&gt;

&lt;p&gt;Historically, financial capabilities were tightly coupled with financial institutions.&lt;/p&gt;

&lt;p&gt;If you wanted to provide banking services, you needed to be a bank.&lt;/p&gt;

&lt;p&gt;If you wanted to issue payment cards, you needed deep relationships with card networks and issuing banks.&lt;/p&gt;

&lt;p&gt;If you wanted to move money internationally, you needed access to correspondent banking networks and settlement relationships.&lt;/p&gt;

&lt;p&gt;The infrastructure was largely inaccessible to ordinary builders.&lt;/p&gt;

&lt;p&gt;That model made sense.&lt;/p&gt;

&lt;p&gt;Financial systems are complicated.&lt;/p&gt;

&lt;p&gt;They involve trust, regulation, risk management, liquidity, identity verification, fraud prevention, and settlement.&lt;/p&gt;

&lt;p&gt;But the internet has repeatedly shown what happens when complex infrastructure becomes accessible through abstractions.&lt;/p&gt;

&lt;p&gt;Consider computing.&lt;/p&gt;

&lt;p&gt;A company once needed to purchase physical servers, build data centers, maintain hardware, and employ teams responsible for infrastructure.&lt;/p&gt;

&lt;p&gt;Today, a developer can deploy an application in minutes.&lt;/p&gt;

&lt;p&gt;They do not need to own a data center.&lt;/p&gt;

&lt;p&gt;They use infrastructure.&lt;/p&gt;

&lt;p&gt;Cloud computing separated:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the people building products&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;from&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the people operating the underlying infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A similar transformation is happening in financial technology.&lt;/p&gt;

&lt;p&gt;Companies increasingly do not need to build every financial capability themselves.&lt;/p&gt;

&lt;p&gt;They can integrate infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  The API Changed What Software Could Become
&lt;/h2&gt;

&lt;p&gt;An API is, at its simplest, a way for one software system to communicate with another.&lt;/p&gt;

&lt;p&gt;But APIs have done something much bigger than enable communication.&lt;/p&gt;

&lt;p&gt;They have turned infrastructure into building blocks.&lt;/p&gt;

&lt;p&gt;A developer does not need to understand how an entire cloud provider operates to deploy an application.&lt;/p&gt;

&lt;p&gt;They use an API.&lt;/p&gt;

&lt;p&gt;A developer does not need to build a mapping network to display a map.&lt;/p&gt;

&lt;p&gt;They use an API.&lt;/p&gt;

&lt;p&gt;A developer does not need to build a messaging network to send a notification.&lt;/p&gt;

&lt;p&gt;They use an API.&lt;/p&gt;

&lt;p&gt;The same abstraction is increasingly appearing in finance.&lt;/p&gt;

&lt;p&gt;Instead of building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;account provisioning systems&lt;/li&gt;
&lt;li&gt;payment routing infrastructure&lt;/li&gt;
&lt;li&gt;wallet technology&lt;/li&gt;
&lt;li&gt;currency conversion systems&lt;/li&gt;
&lt;li&gt;payout networks&lt;/li&gt;
&lt;li&gt;transaction monitoring tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;companies can integrate specialized infrastructure.&lt;/p&gt;

&lt;p&gt;This does not mean the complexity disappears.&lt;/p&gt;

&lt;p&gt;It means the complexity can be handled at a different layer.&lt;/p&gt;

&lt;p&gt;And that distinction matters.&lt;/p&gt;

&lt;p&gt;The company building the customer experience can focus on the product.&lt;/p&gt;

&lt;p&gt;The infrastructure provider can focus on the rails.&lt;/p&gt;




&lt;h2&gt;
  
  
  Financial Services Are Becoming Composable
&lt;/h2&gt;

&lt;p&gt;One of the most important ideas in modern software is composability.&lt;/p&gt;

&lt;p&gt;Instead of building everything from scratch, developers combine specialized services.&lt;/p&gt;

&lt;p&gt;A modern software product might use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cloud infrastructure from one provider&lt;/li&gt;
&lt;li&gt;authentication from another&lt;/li&gt;
&lt;li&gt;messaging from another&lt;/li&gt;
&lt;li&gt;analytics from another&lt;/li&gt;
&lt;li&gt;payment processing from another&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final product feels like one application.&lt;/p&gt;

&lt;p&gt;But behind the scenes, it is composed from multiple infrastructure layers.&lt;/p&gt;

&lt;p&gt;Finance is increasingly moving in the same direction.&lt;/p&gt;

&lt;p&gt;A financial product can combine capabilities such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer onboarding&lt;/li&gt;
&lt;li&gt;identity verification&lt;/li&gt;
&lt;li&gt;virtual accounts&lt;/li&gt;
&lt;li&gt;wallet infrastructure&lt;/li&gt;
&lt;li&gt;foreign exchange&lt;/li&gt;
&lt;li&gt;stablecoin settlement&lt;/li&gt;
&lt;li&gt;collections&lt;/li&gt;
&lt;li&gt;payouts&lt;/li&gt;
&lt;li&gt;transaction monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each capability can be provided by specialized infrastructure.&lt;/p&gt;

&lt;p&gt;The result is something powerful.&lt;/p&gt;

&lt;p&gt;Financial products no longer need to be built as entirely self-contained financial institutions.&lt;/p&gt;

&lt;p&gt;They can be assembled.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Difference Between Building Rails and Building Products
&lt;/h2&gt;

&lt;p&gt;This distinction is easy to miss.&lt;/p&gt;

&lt;p&gt;There is a major difference between building financial infrastructure and building a financial product.&lt;/p&gt;

&lt;p&gt;Financial infrastructure might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;settlement networks&lt;/li&gt;
&lt;li&gt;banking connections&lt;/li&gt;
&lt;li&gt;custody systems&lt;/li&gt;
&lt;li&gt;liquidity infrastructure&lt;/li&gt;
&lt;li&gt;payment rails&lt;/li&gt;
&lt;li&gt;ledger systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A financial product might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a freelancer payment platform&lt;/li&gt;
&lt;li&gt;a global payroll tool&lt;/li&gt;
&lt;li&gt;a marketplace&lt;/li&gt;
&lt;li&gt;a remittance application&lt;/li&gt;
&lt;li&gt;a creator economy product&lt;/li&gt;
&lt;li&gt;a treasury management dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The product does not necessarily need to own the rails.&lt;/p&gt;

&lt;p&gt;It needs to solve a customer problem.&lt;/p&gt;

&lt;p&gt;That creates an important shift in how founders and developers think.&lt;/p&gt;

&lt;p&gt;Imagine a company building a marketplace for international businesses.&lt;/p&gt;

&lt;p&gt;Its customers need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;receive payments&lt;/li&gt;
&lt;li&gt;hold balances&lt;/li&gt;
&lt;li&gt;convert currencies&lt;/li&gt;
&lt;li&gt;pay suppliers&lt;/li&gt;
&lt;li&gt;track transactions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The company could attempt to build all the underlying infrastructure itself.&lt;/p&gt;

&lt;p&gt;Or it could focus on the experience that connects those capabilities.&lt;/p&gt;

&lt;p&gt;The second approach allows the company to spend more time solving its actual problem.&lt;/p&gt;

&lt;p&gt;This is one of the biggest opportunities created by financial infrastructure.&lt;/p&gt;

&lt;p&gt;Builders can focus on:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What should happen for the customer?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;rather than starting with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do we rebuild the entire financial system?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Embedded Finance Changes the Product Surface
&lt;/h2&gt;

&lt;p&gt;Financial services are no longer limited to traditional financial applications.&lt;/p&gt;

&lt;p&gt;A company does not have to call itself a bank to include financial capabilities in its product.&lt;/p&gt;

&lt;p&gt;A marketplace can offer payouts.&lt;/p&gt;

&lt;p&gt;A logistics platform can offer payment collection.&lt;/p&gt;

&lt;p&gt;A freelancer platform can help users receive international payments.&lt;/p&gt;

&lt;p&gt;A software company can provide wallets.&lt;/p&gt;

&lt;p&gt;A business platform can provide expense management.&lt;/p&gt;

&lt;p&gt;A commerce platform can facilitate settlement.&lt;/p&gt;

&lt;p&gt;This is commonly described as embedded finance.&lt;/p&gt;

&lt;p&gt;The financial capability is embedded inside another product.&lt;/p&gt;

&lt;p&gt;The user may not even think of the product as a financial application.&lt;/p&gt;

&lt;p&gt;They simply see a feature that helps them complete a task.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;A creator uses a platform.&lt;/p&gt;

&lt;p&gt;The platform helps them receive money.&lt;/p&gt;

&lt;p&gt;The creator does not necessarily care which settlement partner is involved.&lt;/p&gt;

&lt;p&gt;They care that the money arrives.&lt;/p&gt;

&lt;p&gt;A business owner uses a dashboard.&lt;/p&gt;

&lt;p&gt;The dashboard helps them pay a supplier.&lt;/p&gt;

&lt;p&gt;The business owner does not necessarily want to understand the infrastructure behind the transaction.&lt;/p&gt;

&lt;p&gt;They want the payment to work.&lt;/p&gt;

&lt;p&gt;This is an important principle.&lt;/p&gt;

&lt;p&gt;The best infrastructure often becomes invisible to the end user.&lt;/p&gt;




&lt;h2&gt;
  
  
  Money Is Becoming Programmable
&lt;/h2&gt;

&lt;p&gt;The next major shift is programmability.&lt;/p&gt;

&lt;p&gt;Traditional money movement often follows predefined processes.&lt;/p&gt;

&lt;p&gt;A person initiates a payment.&lt;/p&gt;

&lt;p&gt;The payment moves through a network.&lt;/p&gt;

&lt;p&gt;The receiving party receives the funds.&lt;/p&gt;

&lt;p&gt;But programmable financial infrastructure makes it possible for software to participate more deeply in financial workflows.&lt;/p&gt;

&lt;p&gt;Software can potentially determine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when a payment should happen&lt;/li&gt;
&lt;li&gt;where funds should be routed&lt;/li&gt;
&lt;li&gt;which currency should be used&lt;/li&gt;
&lt;li&gt;which account should receive funds&lt;/li&gt;
&lt;li&gt;how transactions should be categorized&lt;/li&gt;
&lt;li&gt;when balances should trigger an action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns money movement into something applications can orchestrate.&lt;/p&gt;

&lt;p&gt;Consider payroll.&lt;/p&gt;

&lt;p&gt;A company might traditionally perform payroll through a series of manual processes.&lt;/p&gt;

&lt;p&gt;With programmable infrastructure, a system could potentially:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;calculate payments&lt;/li&gt;
&lt;li&gt;initiate transfers&lt;/li&gt;
&lt;li&gt;route funds&lt;/li&gt;
&lt;li&gt;notify recipients&lt;/li&gt;
&lt;li&gt;reconcile transactions&lt;/li&gt;
&lt;li&gt;generate reports&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The financial transaction becomes part of a larger software workflow.&lt;/p&gt;

&lt;p&gt;This is the important shift.&lt;/p&gt;

&lt;p&gt;The payment is no longer an isolated event.&lt;/p&gt;

&lt;p&gt;It becomes a component.&lt;/p&gt;




&lt;h2&gt;
  
  
  APIs Turn Financial Actions Into Software Primitives
&lt;/h2&gt;

&lt;p&gt;Developers think in primitives.&lt;/p&gt;

&lt;p&gt;A database provides a way to store information.&lt;/p&gt;

&lt;p&gt;An authentication service provides a way to identify users.&lt;/p&gt;

&lt;p&gt;A cloud provider provides a way to deploy applications.&lt;/p&gt;

&lt;p&gt;Financial infrastructure is increasingly exposing similar primitives.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create an account.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a wallet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Receive funds.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Convert currency.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send a payout.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check a balance.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retrieve a transaction.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These actions can become building blocks inside applications.&lt;/p&gt;

&lt;p&gt;Once that happens, financial capabilities can be integrated into workflows that were previously impossible or extremely expensive to build.&lt;/p&gt;

&lt;p&gt;Imagine a business management platform.&lt;/p&gt;

&lt;p&gt;The product already knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who the customer is&lt;/li&gt;
&lt;li&gt;what they purchased&lt;/li&gt;
&lt;li&gt;how much they owe&lt;/li&gt;
&lt;li&gt;when payment is due&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding financial infrastructure allows the same platform to participate in the movement of money associated with those activities.&lt;/p&gt;

&lt;p&gt;The software does not just record the business.&lt;/p&gt;

&lt;p&gt;It can potentially help execute the business.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Rise of Financial Operating Systems
&lt;/h2&gt;

&lt;p&gt;As more financial capabilities become programmable, companies are beginning to build something larger than payment products.&lt;/p&gt;

&lt;p&gt;They are building financial operating systems.&lt;/p&gt;

&lt;p&gt;These products bring multiple activities into one environment.&lt;/p&gt;

&lt;p&gt;A business might be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;collect payments&lt;/li&gt;
&lt;li&gt;manage balances&lt;/li&gt;
&lt;li&gt;make payouts&lt;/li&gt;
&lt;li&gt;monitor transactions&lt;/li&gt;
&lt;li&gt;manage currencies&lt;/li&gt;
&lt;li&gt;automate workflows&lt;/li&gt;
&lt;li&gt;reconcile financial activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value is not necessarily any individual feature.&lt;/p&gt;

&lt;p&gt;The value comes from coordination.&lt;/p&gt;

&lt;p&gt;Financial activity is often fragmented.&lt;/p&gt;

&lt;p&gt;A business may use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one platform for receiving money&lt;/li&gt;
&lt;li&gt;another for sending payments&lt;/li&gt;
&lt;li&gt;another for accounting&lt;/li&gt;
&lt;li&gt;another for foreign exchange&lt;/li&gt;
&lt;li&gt;another for treasury&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every additional system creates friction.&lt;/p&gt;

&lt;p&gt;More logins.&lt;/p&gt;

&lt;p&gt;More reconciliation.&lt;/p&gt;

&lt;p&gt;More operational processes.&lt;/p&gt;

&lt;p&gt;More data moving between platforms.&lt;/p&gt;

&lt;p&gt;Infrastructure can make it possible to bring some of these capabilities closer together.&lt;/p&gt;

&lt;p&gt;That does not mean one company will replace the entire financial ecosystem.&lt;/p&gt;

&lt;p&gt;But it does mean products can create more unified experiences.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stablecoins Introduce a New Infrastructure Layer
&lt;/h2&gt;

&lt;p&gt;Stablecoins are becoming an important part of this conversation.&lt;/p&gt;

&lt;p&gt;Not because every user wants to interact with crypto.&lt;/p&gt;

&lt;p&gt;Most users do not want to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blockchains&lt;/li&gt;
&lt;li&gt;gas fees&lt;/li&gt;
&lt;li&gt;private keys&lt;/li&gt;
&lt;li&gt;smart contracts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They want to move money.&lt;/p&gt;

&lt;p&gt;The significance of stablecoins is therefore often not the user interface.&lt;/p&gt;

&lt;p&gt;It is the infrastructure layer underneath.&lt;/p&gt;

&lt;p&gt;A stablecoin can potentially provide a digital representation of value that moves across internet-native networks.&lt;/p&gt;

&lt;p&gt;This creates new possibilities for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;treasury movement&lt;/li&gt;
&lt;li&gt;settlement&lt;/li&gt;
&lt;li&gt;liquidity&lt;/li&gt;
&lt;li&gt;cross-border transfers&lt;/li&gt;
&lt;li&gt;programmable financial workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A customer might interact with a completely familiar experience.&lt;/p&gt;

&lt;p&gt;They might simply see:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Send $500.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Behind the scenes, however, the infrastructure may involve multiple layers.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer Initiates Payment
          ↓
Collection Infrastructure
          ↓
Currency Conversion
          ↓
Settlement Layer
          ↓
Liquidity Movement
          ↓
Local Payout Network
          ↓
Recipient Receives Funds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In some systems, stablecoin infrastructure may play a role somewhere in that process.&lt;/p&gt;

&lt;p&gt;The user does not necessarily need to see it.&lt;/p&gt;

&lt;p&gt;And that may be one of the most important lessons.&lt;/p&gt;

&lt;p&gt;Infrastructure does not need to become the product.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Customer Should Not Have to Care About the Rails
&lt;/h2&gt;

&lt;p&gt;There is a tendency in technology to make the underlying technology the main story.&lt;/p&gt;

&lt;p&gt;But users rarely care about infrastructure for its own sake.&lt;/p&gt;

&lt;p&gt;Nobody opens a ride-sharing application because they are excited about GPS APIs.&lt;/p&gt;

&lt;p&gt;Nobody sends an email because they are fascinated by SMTP.&lt;/p&gt;

&lt;p&gt;Nobody watches a movie because they want to experience a content delivery network.&lt;/p&gt;

&lt;p&gt;They care about the outcome.&lt;/p&gt;

&lt;p&gt;Financial products should be thought about in the same way.&lt;/p&gt;

&lt;p&gt;The customer wants to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;get paid&lt;/li&gt;
&lt;li&gt;send money&lt;/li&gt;
&lt;li&gt;receive money&lt;/li&gt;
&lt;li&gt;pay someone&lt;/li&gt;
&lt;li&gt;manage their business&lt;/li&gt;
&lt;li&gt;access their funds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The underlying infrastructure should support those outcomes.&lt;/p&gt;

&lt;p&gt;The better the infrastructure works, the less the customer may notice it.&lt;/p&gt;

&lt;p&gt;That does not reduce the importance of infrastructure.&lt;/p&gt;

&lt;p&gt;It increases it.&lt;/p&gt;

&lt;p&gt;Because invisible infrastructure still has to be reliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Global Money Requires Local Reality
&lt;/h2&gt;

&lt;p&gt;One of the hardest problems in global payments is that the world is simultaneously connected and fragmented.&lt;/p&gt;

&lt;p&gt;The internet is global.&lt;/p&gt;

&lt;p&gt;Financial systems are often local.&lt;/p&gt;

&lt;p&gt;Different countries have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different currencies&lt;/li&gt;
&lt;li&gt;different regulations&lt;/li&gt;
&lt;li&gt;different banking systems&lt;/li&gt;
&lt;li&gt;different payment preferences&lt;/li&gt;
&lt;li&gt;different identity requirements&lt;/li&gt;
&lt;li&gt;different settlement networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A global product cannot simply treat every market as identical.&lt;/p&gt;

&lt;p&gt;A user in Nigeria may have different payment expectations from a user in the United States.&lt;/p&gt;

&lt;p&gt;A business in Kenya may operate differently from a business in the United Kingdom.&lt;/p&gt;

&lt;p&gt;A customer in Ghana may prefer a different financial workflow from a customer in Europe.&lt;/p&gt;

&lt;p&gt;This means global infrastructure needs to understand local systems.&lt;/p&gt;

&lt;p&gt;The challenge is not simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Connect every country.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Connect different financial realities without forcing every user to understand the complexity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much harder problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Infrastructure Layer Has to Translate Complexity
&lt;/h2&gt;

&lt;p&gt;Imagine two businesses in different countries.&lt;/p&gt;

&lt;p&gt;They may use different currencies.&lt;/p&gt;

&lt;p&gt;They may have accounts with different institutions.&lt;/p&gt;

&lt;p&gt;They may use different local payment networks.&lt;/p&gt;

&lt;p&gt;They may operate under different regulatory environments.&lt;/p&gt;

&lt;p&gt;Yet the product experience should ideally feel simple.&lt;/p&gt;

&lt;p&gt;The infrastructure layer becomes a translator.&lt;/p&gt;

&lt;p&gt;It translates:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;one system into another.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It translates:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;one currency into another.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It translates:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;one network into another.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It translates:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;complex processes into simple actions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why financial infrastructure is difficult to build.&lt;/p&gt;

&lt;p&gt;The complexity has not disappeared.&lt;/p&gt;

&lt;p&gt;Someone still has to manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;liquidity&lt;/li&gt;
&lt;li&gt;settlement&lt;/li&gt;
&lt;li&gt;compliance&lt;/li&gt;
&lt;li&gt;risk&lt;/li&gt;
&lt;li&gt;network relationships&lt;/li&gt;
&lt;li&gt;reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference is where that complexity lives.&lt;/p&gt;

&lt;p&gt;Good infrastructure allows product builders to access complicated capabilities without recreating the entire underlying system.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Developers
&lt;/h2&gt;

&lt;p&gt;For developers, the opportunity is enormous.&lt;/p&gt;

&lt;p&gt;A generation ago, building a global financial product required a massive organization.&lt;/p&gt;

&lt;p&gt;Today, a small engineering team can begin with infrastructure.&lt;/p&gt;

&lt;p&gt;That does not mean building financial products is easy.&lt;/p&gt;

&lt;p&gt;Far from it.&lt;/p&gt;

&lt;p&gt;Financial applications still require careful thinking about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;security&lt;/li&gt;
&lt;li&gt;authentication&lt;/li&gt;
&lt;li&gt;compliance&lt;/li&gt;
&lt;li&gt;user experience&lt;/li&gt;
&lt;li&gt;transaction states&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;li&gt;reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the starting point has changed.&lt;/p&gt;

&lt;p&gt;Developers can spend less time asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I build a payment network?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and more time asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What financial problem is currently poorly solved?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where innovation happens.&lt;/p&gt;

&lt;p&gt;The infrastructure enables the product.&lt;/p&gt;

&lt;p&gt;But the product creates the value.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Best Opportunities Are Often Workflow Problems
&lt;/h2&gt;

&lt;p&gt;Payments alone are rarely the complete problem.&lt;/p&gt;

&lt;p&gt;The real opportunity is often the workflow surrounding the payment.&lt;/p&gt;

&lt;p&gt;Consider an international freelancer.&lt;/p&gt;

&lt;p&gt;Their problem may not simply be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I need to receive money.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Their actual workflow might involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;finding clients&lt;/li&gt;
&lt;li&gt;invoicing&lt;/li&gt;
&lt;li&gt;receiving payments&lt;/li&gt;
&lt;li&gt;converting currencies&lt;/li&gt;
&lt;li&gt;managing balances&lt;/li&gt;
&lt;li&gt;tracking income&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A payment feature solves one part of the workflow.&lt;/p&gt;

&lt;p&gt;A well-designed product can solve the larger problem.&lt;/p&gt;

&lt;p&gt;The same is true for businesses.&lt;/p&gt;

&lt;p&gt;A business does not merely need to send a payment.&lt;/p&gt;

&lt;p&gt;It may need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approve the payment&lt;/li&gt;
&lt;li&gt;schedule it&lt;/li&gt;
&lt;li&gt;assign it to a department&lt;/li&gt;
&lt;li&gt;track it&lt;/li&gt;
&lt;li&gt;reconcile it&lt;/li&gt;
&lt;li&gt;report it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why infrastructure creates opportunities for vertical products.&lt;/p&gt;

&lt;p&gt;The financial action becomes part of a specialized workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Programmable Money Creates Automation Opportunities
&lt;/h2&gt;

&lt;p&gt;Once financial actions become accessible through software, automation becomes increasingly important.&lt;/p&gt;

&lt;p&gt;Imagine a company that regularly pays contractors.&lt;/p&gt;

&lt;p&gt;Instead of manually initiating every payment, a system could potentially automate parts of the workflow.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Work Completed
      ↓
Manager Approval
      ↓
Invoice Verified
      ↓
Payment Scheduled
      ↓
Funds Sent
      ↓
Transaction Recorded
      ↓
Recipient Notified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payment itself is only one step.&lt;/p&gt;

&lt;p&gt;The larger workflow is where automation creates value.&lt;/p&gt;

&lt;p&gt;The same concept can apply to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payroll&lt;/li&gt;
&lt;li&gt;vendor payments&lt;/li&gt;
&lt;li&gt;subscriptions&lt;/li&gt;
&lt;li&gt;marketplace settlements&lt;/li&gt;
&lt;li&gt;recurring collections&lt;/li&gt;
&lt;li&gt;treasury operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Financial infrastructure allows money movement to become connected to business logic.&lt;/p&gt;

&lt;p&gt;That is a major change.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Future May Belong to Orchestration
&lt;/h2&gt;

&lt;p&gt;As infrastructure providers become more specialized, another category becomes increasingly important.&lt;/p&gt;

&lt;p&gt;The orchestrator.&lt;/p&gt;

&lt;p&gt;An orchestrator does not necessarily own every rail.&lt;/p&gt;

&lt;p&gt;It coordinates them.&lt;/p&gt;

&lt;p&gt;Imagine a product with access to multiple capabilities.&lt;/p&gt;

&lt;p&gt;It may have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;different payment methods&lt;/li&gt;
&lt;li&gt;multiple currencies&lt;/li&gt;
&lt;li&gt;several payout networks&lt;/li&gt;
&lt;li&gt;multiple liquidity sources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The challenge becomes choosing how to coordinate them.&lt;/p&gt;

&lt;p&gt;Which route should be used?&lt;/p&gt;

&lt;p&gt;Which currency should be selected?&lt;/p&gt;

&lt;p&gt;Which provider is available?&lt;/p&gt;

&lt;p&gt;How should failures be handled?&lt;/p&gt;

&lt;p&gt;How should the transaction be tracked?&lt;/p&gt;

&lt;p&gt;The orchestration layer creates intelligence around infrastructure.&lt;/p&gt;

&lt;p&gt;This may become one of the most important areas of financial technology.&lt;/p&gt;

&lt;p&gt;The future is not necessarily one network replacing every other network.&lt;/p&gt;

&lt;p&gt;It may be better coordination between networks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Reliability Matters More Than Novelty
&lt;/h2&gt;

&lt;p&gt;Moving money is different from many other software problems.&lt;/p&gt;

&lt;p&gt;A social application can survive a minor interface bug.&lt;/p&gt;

&lt;p&gt;A payment application can lose trust.&lt;/p&gt;

&lt;p&gt;If a user sends money, they expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the transaction to be processed correctly&lt;/li&gt;
&lt;li&gt;the amount to be accurate&lt;/li&gt;
&lt;li&gt;the recipient to receive the funds&lt;/li&gt;
&lt;li&gt;the status to be clear&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Financial infrastructure therefore needs a different level of discipline.&lt;/p&gt;

&lt;p&gt;The user experience must account for uncertainty.&lt;/p&gt;

&lt;p&gt;What happens when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a network is unavailable?&lt;/li&gt;
&lt;li&gt;a transaction is pending?&lt;/li&gt;
&lt;li&gt;a payout fails?&lt;/li&gt;
&lt;li&gt;a recipient cannot receive funds?&lt;/li&gt;
&lt;li&gt;a conversion rate changes?&lt;/li&gt;
&lt;li&gt;a transfer requires additional review?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These questions are not edge cases.&lt;/p&gt;

&lt;p&gt;They are part of the product.&lt;/p&gt;

&lt;p&gt;The best financial products do not pretend complexity does not exist.&lt;/p&gt;

&lt;p&gt;They manage complexity without exposing unnecessary complexity to the customer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Trust Is Infrastructure Too
&lt;/h2&gt;

&lt;p&gt;When we talk about infrastructure, we often think about technology.&lt;/p&gt;

&lt;p&gt;Servers.&lt;/p&gt;

&lt;p&gt;Networks.&lt;/p&gt;

&lt;p&gt;APIs.&lt;/p&gt;

&lt;p&gt;Databases.&lt;/p&gt;

&lt;p&gt;But financial infrastructure also depends on trust.&lt;/p&gt;

&lt;p&gt;Users need to trust that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;their money is safe&lt;/li&gt;
&lt;li&gt;transactions are accurately recorded&lt;/li&gt;
&lt;li&gt;balances are correct&lt;/li&gt;
&lt;li&gt;sensitive information is protected&lt;/li&gt;
&lt;li&gt;problems can be resolved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trust is built through technology.&lt;/p&gt;

&lt;p&gt;But it is also built through operations.&lt;/p&gt;

&lt;p&gt;Through transparency.&lt;/p&gt;

&lt;p&gt;Through reliability.&lt;/p&gt;

&lt;p&gt;Through customer support.&lt;/p&gt;

&lt;p&gt;Through clear communication.&lt;/p&gt;

&lt;p&gt;This means a company cannot simply integrate an API and declare that it has solved finance.&lt;/p&gt;

&lt;p&gt;Infrastructure creates capabilities.&lt;/p&gt;

&lt;p&gt;Trust creates adoption.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Infrastructure Opportunity Is Bigger Than Payments
&lt;/h2&gt;

&lt;p&gt;Payments are one of the most visible applications of financial infrastructure.&lt;/p&gt;

&lt;p&gt;But the opportunity extends much further.&lt;/p&gt;

&lt;p&gt;Programmable financial infrastructure can support:&lt;/p&gt;

&lt;h3&gt;
  
  
  Global Payroll
&lt;/h3&gt;

&lt;p&gt;Companies increasingly work with people across borders.&lt;/p&gt;

&lt;p&gt;Infrastructure can help support systems that manage international compensation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Marketplaces
&lt;/h3&gt;

&lt;p&gt;Marketplaces need to collect money from one side of the platform and distribute it to another.&lt;/p&gt;

&lt;p&gt;Financial infrastructure can simplify those flows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creator Economies
&lt;/h3&gt;

&lt;p&gt;Creators increasingly operate internationally.&lt;/p&gt;

&lt;p&gt;Products can help them receive, manage, and move money.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business Operations
&lt;/h3&gt;

&lt;p&gt;Businesses need tools for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;collections&lt;/li&gt;
&lt;li&gt;payouts&lt;/li&gt;
&lt;li&gt;expenses&lt;/li&gt;
&lt;li&gt;reconciliation&lt;/li&gt;
&lt;li&gt;reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Treasury Management
&lt;/h3&gt;

&lt;p&gt;Companies operating internationally may need to manage multiple currencies and liquidity positions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedded Financial Products
&lt;/h3&gt;

&lt;p&gt;Software companies can integrate financial capabilities directly into their existing products.&lt;/p&gt;

&lt;p&gt;The common pattern is simple.&lt;/p&gt;

&lt;p&gt;Everywhere there is a financial workflow, there may be an infrastructure opportunity.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Access to Innovation
&lt;/h2&gt;

&lt;p&gt;The biggest consequence of infrastructure is not that it makes existing products easier to build.&lt;/p&gt;

&lt;p&gt;It makes new products possible.&lt;/p&gt;

&lt;p&gt;When a capability becomes easier to access, more people experiment with it.&lt;/p&gt;

&lt;p&gt;Cloud infrastructure created millions of software companies that would never have built their own data centers.&lt;/p&gt;

&lt;p&gt;Mobile platforms created businesses that would never have existed without smartphones.&lt;/p&gt;

&lt;p&gt;Financial infrastructure may create a similar wave.&lt;/p&gt;

&lt;p&gt;The important thing is access.&lt;/p&gt;

&lt;p&gt;When a small team can access capabilities that previously required massive organizations, the range of possible products expands.&lt;/p&gt;

&lt;p&gt;A founder does not need to begin by asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do we have the resources to become a financial institution?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They can begin with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What experience should exist that does not exist today?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much more interesting question.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Afriex Fits Into This Story
&lt;/h2&gt;

&lt;p&gt;The story of modern financial infrastructure is ultimately about reducing the distance between an idea and a working financial product.&lt;/p&gt;

&lt;p&gt;That is where platforms such as Afriex become relevant.&lt;/p&gt;

&lt;p&gt;Builders and businesses do not always want to spend their time navigating the underlying complexity of money movement.&lt;/p&gt;

&lt;p&gt;They want to build.&lt;/p&gt;

&lt;p&gt;They want to create products.&lt;/p&gt;

&lt;p&gt;They want to serve customers.&lt;/p&gt;

&lt;p&gt;They want to operate across borders.&lt;/p&gt;

&lt;p&gt;Infrastructure can provide the foundation that makes those ambitions more practical.&lt;/p&gt;

&lt;p&gt;The goal is not necessarily to make every company become a financial institution.&lt;/p&gt;

&lt;p&gt;The goal is to give companies access to financial capabilities so they can focus on what they do best.&lt;/p&gt;

&lt;p&gt;This is the broader idea behind financial infrastructure.&lt;/p&gt;

&lt;p&gt;The rails exist so that builders can build on top of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Next Generation of Financial Products May Not Look Like Fintech
&lt;/h2&gt;

&lt;p&gt;This may be the most interesting consequence of all.&lt;/p&gt;

&lt;p&gt;The next generation of financial products may not describe themselves as fintech.&lt;/p&gt;

&lt;p&gt;They may be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;commerce companies&lt;/li&gt;
&lt;li&gt;creator platforms&lt;/li&gt;
&lt;li&gt;business software products&lt;/li&gt;
&lt;li&gt;marketplaces&lt;/li&gt;
&lt;li&gt;logistics platforms&lt;/li&gt;
&lt;li&gt;workforce platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Financial capabilities will simply be part of what they do.&lt;/p&gt;

&lt;p&gt;A company might say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We help businesses manage suppliers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And somewhere inside the product is payment infrastructure.&lt;/p&gt;

&lt;p&gt;Another company might say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We help creators run their businesses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And somewhere inside the product is a financial layer.&lt;/p&gt;

&lt;p&gt;The financial capability becomes embedded inside the customer experience.&lt;/p&gt;

&lt;p&gt;This is how infrastructure changes industries.&lt;/p&gt;

&lt;p&gt;It stops being a separate category.&lt;/p&gt;

&lt;p&gt;It becomes a capability.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Shift Is Happening Beneath the Surface
&lt;/h2&gt;

&lt;p&gt;Consumers may not notice the transformation immediately.&lt;/p&gt;

&lt;p&gt;They will simply notice that products become more capable.&lt;/p&gt;

&lt;p&gt;Payments may become faster.&lt;/p&gt;

&lt;p&gt;Global products may become easier to access.&lt;/p&gt;

&lt;p&gt;Businesses may automate more of their financial operations.&lt;/p&gt;

&lt;p&gt;Developers may build things that previously required large financial organizations.&lt;/p&gt;

&lt;p&gt;The transformation is happening beneath the surface.&lt;/p&gt;

&lt;p&gt;APIs.&lt;/p&gt;

&lt;p&gt;Networks.&lt;/p&gt;

&lt;p&gt;Wallets.&lt;/p&gt;

&lt;p&gt;Settlement systems.&lt;/p&gt;

&lt;p&gt;Liquidity.&lt;/p&gt;

&lt;p&gt;Stablecoins.&lt;/p&gt;

&lt;p&gt;Compliance technology.&lt;/p&gt;

&lt;p&gt;All of these layers are contributing to a new financial stack.&lt;/p&gt;

&lt;p&gt;The customer sees an application.&lt;/p&gt;

&lt;p&gt;The builder sees an API.&lt;/p&gt;

&lt;p&gt;The infrastructure provider sees a network.&lt;/p&gt;

&lt;p&gt;Together, they create a financial experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;In Part I, we explored why moving money across borders is complicated.&lt;/p&gt;

&lt;p&gt;The answer was simple.&lt;/p&gt;

&lt;p&gt;The world is financially fragmented.&lt;/p&gt;

&lt;p&gt;Different currencies.&lt;/p&gt;

&lt;p&gt;Different networks.&lt;/p&gt;

&lt;p&gt;Different regulations.&lt;/p&gt;

&lt;p&gt;Different institutions.&lt;/p&gt;

&lt;p&gt;Moving money requires coordination between all of them.&lt;/p&gt;

&lt;p&gt;In Part II, we explored what happens when that coordination becomes accessible as infrastructure.&lt;/p&gt;

&lt;p&gt;The answer is equally significant.&lt;/p&gt;

&lt;p&gt;Builders get more leverage.&lt;/p&gt;

&lt;p&gt;Companies can focus more on products.&lt;/p&gt;

&lt;p&gt;Financial capabilities become programmable.&lt;/p&gt;

&lt;p&gt;Money movement becomes part of software workflows.&lt;/p&gt;

&lt;p&gt;And new categories of businesses become possible.&lt;/p&gt;

&lt;p&gt;The future of finance may not be about everyone becoming a bank.&lt;/p&gt;

&lt;p&gt;It may be about more builders gaining access to the capabilities that banks and financial institutions have historically controlled.&lt;/p&gt;

&lt;p&gt;That is what infrastructure changes.&lt;/p&gt;

&lt;p&gt;It takes something complicated.&lt;/p&gt;

&lt;p&gt;It creates an abstraction.&lt;/p&gt;

&lt;p&gt;And then it gives more people the ability to build.&lt;/p&gt;

&lt;p&gt;The rails may remain invisible.&lt;/p&gt;

&lt;p&gt;But what gets built on top of them could change everything.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Part I:&lt;/strong&gt; &lt;em&gt;The Hidden Complexity of Moving Money Across Borders&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part II:&lt;/strong&gt; &lt;em&gt;When Money Becomes Infrastructure&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>fintech</category>
      <category>infrastructure</category>
    </item>
    <item>
      <title>The Hidden Complexity of Moving Money Across Borders</title>
      <dc:creator>0xSonOfUri</dc:creator>
      <pubDate>Sat, 12 Sep 2026 06:31:12 +0000</pubDate>
      <link>https://dev.to/afriex/the-hidden-complexity-of-moving-money-across-borders-512</link>
      <guid>https://dev.to/afriex/the-hidden-complexity-of-moving-money-across-borders-512</guid>
      <description>&lt;h2&gt;
  
  
  Why sending money from one country to another is still harder than sending a message and the infrastructure working behind the scenes
&lt;/h2&gt;

&lt;p&gt;Sending money should be simple.&lt;/p&gt;

&lt;p&gt;Open an app. Enter a recipient. Choose an amount. Press &lt;strong&gt;Send&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A few seconds later, someone on the other side of the world receives a notification.&lt;/p&gt;

&lt;p&gt;From the user's perspective, that is the entire experience.&lt;/p&gt;

&lt;p&gt;But behind that single button is one of the most complex systems humanity has ever built.&lt;/p&gt;

&lt;p&gt;Moving money across borders can involve multiple financial institutions, currencies, payment networks, compliance systems, liquidity providers, settlement arrangements, foreign exchange markets, treasury operations, and local payout infrastructure.&lt;/p&gt;

&lt;p&gt;The experience may take seconds.&lt;/p&gt;

&lt;p&gt;The infrastructure behind it has been developing for decades.&lt;/p&gt;

&lt;p&gt;And that creates an interesting contradiction.&lt;/p&gt;

&lt;p&gt;We live in a world where information can travel globally almost instantly. A message sent from Lagos can reach London, Nairobi, New York, or Accra in milliseconds. Software can be deployed globally. A video can be streamed across continents. A company can serve millions of customers in dozens of countries from a single application.&lt;/p&gt;

&lt;p&gt;But moving value is different.&lt;/p&gt;

&lt;p&gt;Sending money from one country to another is still far more complicated than sending information.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because the internet standardized the movement of information.&lt;/p&gt;

&lt;p&gt;The global financial system did not develop as one unified network.&lt;/p&gt;

&lt;p&gt;It developed country by country.&lt;/p&gt;

&lt;p&gt;Bank by bank.&lt;/p&gt;

&lt;p&gt;Currency by currency.&lt;/p&gt;

&lt;p&gt;Regulation by regulation.&lt;/p&gt;

&lt;p&gt;That history still shapes how money moves today.&lt;/p&gt;

&lt;p&gt;In this article, we will look beneath the simple experience of a cross-border payment and explore the hidden infrastructure involved in moving money around the world.&lt;/p&gt;

&lt;p&gt;This is not a guide to sending money.&lt;/p&gt;

&lt;p&gt;It is a look at the systems working behind the &lt;strong&gt;Send&lt;/strong&gt; button.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Money Doesn't Move the Way Most People Think
&lt;/h2&gt;

&lt;p&gt;When most people imagine sending money internationally, they imagine something physical moving from one place to another.&lt;/p&gt;

&lt;p&gt;Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You
  ↓
Your Money
  ↓
Recipient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In reality, financial systems are usually more complicated.&lt;/p&gt;

&lt;p&gt;A cross-border payment can involve the communication of payment instructions, changes to balances, settlement between institutions, foreign exchange conversions, and the coordination of liquidity.&lt;/p&gt;

&lt;p&gt;The important point is this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A payment is not always as simple as taking money from one location and physically moving it to another.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Financial institutions maintain records of who owns what. They maintain balances. They maintain obligations to one another. They communicate instructions. They settle transactions. They manage liquidity.&lt;/p&gt;

&lt;p&gt;A customer sees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send $100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The infrastructure may see something closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment Instruction
        ↓
Authorization
        ↓
Compliance Checks
        ↓
Currency Conversion
        ↓
Liquidity Allocation
        ↓
Settlement
        ↓
Local Payout
        ↓
Recipient Balance Updated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact process depends on the countries, institutions, currencies, and payment methods involved.&lt;/p&gt;

&lt;p&gt;But the larger lesson is important.&lt;/p&gt;

&lt;p&gt;The simplicity of a modern payment interface does not mean the underlying financial system is simple.&lt;/p&gt;

&lt;p&gt;In fact, one of the primary jobs of modern financial infrastructure is to hide that complexity from the user.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. A Global Economy Built on Local Financial Systems
&lt;/h2&gt;

&lt;p&gt;The world feels increasingly global.&lt;/p&gt;

&lt;p&gt;Businesses sell internationally. Teams work remotely. Families live across multiple countries. Freelancers work with clients around the world. Digital products can acquire customers almost anywhere.&lt;/p&gt;

&lt;p&gt;But the financial systems supporting this global activity are still heavily localized.&lt;/p&gt;

&lt;p&gt;Every country has its own financial environment.&lt;/p&gt;

&lt;p&gt;That environment can include different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;currencies&lt;/li&gt;
&lt;li&gt;banks&lt;/li&gt;
&lt;li&gt;payment networks&lt;/li&gt;
&lt;li&gt;regulations&lt;/li&gt;
&lt;li&gt;licensing requirements&lt;/li&gt;
&lt;li&gt;settlement systems&lt;/li&gt;
&lt;li&gt;consumer payment habits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A person in one country may primarily use bank transfers. Someone in another market may rely heavily on mobile money. A business elsewhere may use card payments, domestic clearing systems, or other local rails.&lt;/p&gt;

&lt;p&gt;There is no single global payment rail that every person, bank, business, and financial institution uses in exactly the same way.&lt;/p&gt;

&lt;p&gt;Instead, cross-border money movement often requires infrastructure capable of connecting different systems.&lt;/p&gt;

&lt;p&gt;Think about the problem.&lt;/p&gt;

&lt;p&gt;A customer may want to pay in one currency. A business may operate in another. The recipient may want to receive a completely different currency.&lt;/p&gt;

&lt;p&gt;The sender's financial institution and the recipient's financial institution may not even operate on the same domestic payment network.&lt;/p&gt;

&lt;p&gt;So the challenge is not simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Move money from Country A to Country B.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The real challenge may be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Connect multiple financial systems, currencies, institutions, regulatory environments, and liquidity sources in a way that produces a simple experience for the customer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a very different problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Correspondent Banking World
&lt;/h2&gt;

&lt;p&gt;One of the most important concepts in international banking is correspondent banking.&lt;/p&gt;

&lt;p&gt;Banks do not necessarily maintain direct relationships with every other bank in the world. Instead, financial institutions can rely on relationships with other institutions to facilitate international transactions.&lt;/p&gt;

&lt;p&gt;A simplified representation might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sender's Bank
      ↓
Correspondent Institution
      ↓
Another Financial Institution
      ↓
Recipient's Bank
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In more complex cases, there can be additional intermediaries. The exact route depends on the institutions and currencies involved.&lt;/p&gt;

&lt;p&gt;This creates a fundamental challenge.&lt;/p&gt;

&lt;p&gt;The more systems and institutions involved in a transaction, the more coordination may be required.&lt;/p&gt;

&lt;p&gt;Potential issues can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;additional processing time&lt;/li&gt;
&lt;li&gt;multiple fees&lt;/li&gt;
&lt;li&gt;operational complexity&lt;/li&gt;
&lt;li&gt;reconciliation requirements&lt;/li&gt;
&lt;li&gt;limited visibility into transaction status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the customer's perspective, the experience may simply be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why hasn't the money arrived yet?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But underneath that question may be multiple institutions processing instructions and settling obligations.&lt;/p&gt;

&lt;p&gt;This is one reason cross-border payments historically developed a reputation for being slow, expensive, and difficult to track.&lt;/p&gt;

&lt;p&gt;The problem was not that financial institutions did not know how to move money. The problem was that global financial infrastructure developed through a network of institutions that were not originally designed to function like a single, instant, consumer-facing application.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Sending an Instruction Is Not the Same as Settling Value
&lt;/h2&gt;

&lt;p&gt;This distinction is useful when thinking about financial infrastructure.&lt;/p&gt;

&lt;p&gt;Imagine sending a message. The message can travel from one device to another. The internet has protocols for routing information.&lt;/p&gt;

&lt;p&gt;Money is different.&lt;/p&gt;

&lt;p&gt;Financial systems must answer questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who owns the value?&lt;/li&gt;
&lt;li&gt;Which institution is responsible for the balance?&lt;/li&gt;
&lt;li&gt;Has the transaction been authorized?&lt;/li&gt;
&lt;li&gt;Has the transaction been settled?&lt;/li&gt;
&lt;li&gt;Is the receiving institution able to make the funds available?&lt;/li&gt;
&lt;li&gt;Is there sufficient liquidity in the required currency?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A payment instruction can be communicated quickly.&lt;/p&gt;

&lt;p&gt;That does not automatically mean final settlement happens at the same speed.&lt;/p&gt;

&lt;p&gt;This distinction between &lt;strong&gt;communication&lt;/strong&gt; and &lt;strong&gt;settlement&lt;/strong&gt; is one of the reasons financial infrastructure is more complicated than a simple messaging system.&lt;/p&gt;

&lt;p&gt;A modern application may show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment sent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But several processes may still need to occur behind the scenes depending on the payment method and infrastructure being used.&lt;/p&gt;

&lt;p&gt;That is why the architecture behind a payment matters.&lt;/p&gt;

&lt;p&gt;Speed is not simply a matter of making the user interface faster. The infrastructure responsible for authorization, settlement, liquidity, compliance, and payout also matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Every Currency Introduces Another Layer
&lt;/h2&gt;

&lt;p&gt;Now add currencies to the equation.&lt;/p&gt;

&lt;p&gt;Suppose someone wants to send money internationally.&lt;/p&gt;

&lt;p&gt;The sender has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Currency A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recipient wants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Currency B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The payment now involves more than money movement.&lt;/p&gt;

&lt;p&gt;It may also involve foreign exchange.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Currency A
     ↓
Foreign Exchange
     ↓
Currency B
     ↓
Recipient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That simple diagram hides an enormous amount of infrastructure.&lt;/p&gt;

&lt;p&gt;Foreign exchange can involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exchange rates&lt;/li&gt;
&lt;li&gt;spreads&lt;/li&gt;
&lt;li&gt;liquidity&lt;/li&gt;
&lt;li&gt;market availability&lt;/li&gt;
&lt;li&gt;conversion timing&lt;/li&gt;
&lt;li&gt;treasury management&lt;/li&gt;
&lt;li&gt;risk management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a business that receives revenue in one currency but needs to make payments in several others.&lt;/p&gt;

&lt;p&gt;That business needs to think about more than individual transactions.&lt;/p&gt;

&lt;p&gt;It needs to think about where its liquidity exists.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USD Balance
    ↓
Need to pay suppliers in another market
    ↓
FX Conversion Required
    ↓
Liquidity Required
    ↓
Local Payout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At a small scale, this may seem straightforward.&lt;/p&gt;

&lt;p&gt;At a global scale, it becomes an operational challenge.&lt;/p&gt;

&lt;p&gt;This is why foreign exchange infrastructure is an essential part of cross-border payments.&lt;/p&gt;

&lt;p&gt;Moving value between countries often means moving between currencies.&lt;/p&gt;

&lt;p&gt;And moving between currencies requires someone, somewhere, to provide liquidity.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Liquidity: The Invisible Requirement Behind Every Payment
&lt;/h2&gt;

&lt;p&gt;Liquidity is one of those financial concepts that customers rarely think about.&lt;/p&gt;

&lt;p&gt;But infrastructure providers think about it constantly.&lt;/p&gt;

&lt;p&gt;Imagine a company that needs to make payments in several markets. Customers may be paying the company in dollars, euros, pounds, and local currencies. Meanwhile, the company may need to make payouts in completely different currencies.&lt;/p&gt;

&lt;p&gt;The company needs to ensure that value is available where it is needed.&lt;/p&gt;

&lt;p&gt;That is a liquidity problem.&lt;/p&gt;

&lt;p&gt;A simplified example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customers
    ↓
USD Collected
    ↓
Treasury
    ↓
Liquidity Management
    ↓
Currency Conversion
    ↓
Local Funds Available
    ↓
Payout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recipient of the payment does not care about this process.&lt;/p&gt;

&lt;p&gt;They care about one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did I receive my money?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is exactly why infrastructure is valuable.&lt;/p&gt;

&lt;p&gt;Good infrastructure hides complexity.&lt;/p&gt;

&lt;p&gt;The user does not need to understand how liquidity was coordinated. The business does not necessarily want to manually manage every underlying financial relationship.&lt;/p&gt;

&lt;p&gt;The infrastructure handles as much of that complexity as possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. The Local Payout Problem
&lt;/h2&gt;

&lt;p&gt;Sending money across a border is only half the problem.&lt;/p&gt;

&lt;p&gt;The recipient must still receive it.&lt;/p&gt;

&lt;p&gt;This sounds obvious.&lt;/p&gt;

&lt;p&gt;But it introduces another layer of complexity.&lt;/p&gt;

&lt;p&gt;How should the recipient receive funds?&lt;/p&gt;

&lt;p&gt;Depending on the market, the answer could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a bank account&lt;/li&gt;
&lt;li&gt;a mobile wallet&lt;/li&gt;
&lt;li&gt;a digital wallet&lt;/li&gt;
&lt;li&gt;a virtual account&lt;/li&gt;
&lt;li&gt;another local payment method&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means that a global payment experience often requires local delivery infrastructure.&lt;/p&gt;

&lt;p&gt;Imagine building a product that serves users across multiple countries. Your customers may expect completely different experiences.&lt;/p&gt;

&lt;p&gt;One market might expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bank Transfer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another might expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mobile Money
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another may prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The product cannot simply think globally.&lt;/p&gt;

&lt;p&gt;It must also understand how value is delivered locally.&lt;/p&gt;

&lt;p&gt;This is one of the central challenges of building cross-border financial products.&lt;/p&gt;

&lt;p&gt;Global movement requires local access.&lt;/p&gt;

&lt;p&gt;A system can be technically capable of moving value internationally, but if the recipient cannot conveniently receive that value through familiar local infrastructure, the experience is incomplete.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Compliance Travels With the Money
&lt;/h2&gt;

&lt;p&gt;There is another reason moving money is different from sending information.&lt;/p&gt;

&lt;p&gt;Money operates inside regulated systems.&lt;/p&gt;

&lt;p&gt;Financial institutions have responsibilities.&lt;/p&gt;

&lt;p&gt;Those responsibilities can include processes related to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identity verification&lt;/li&gt;
&lt;li&gt;anti-money laundering requirements&lt;/li&gt;
&lt;li&gt;transaction monitoring&lt;/li&gt;
&lt;li&gt;sanctions screening&lt;/li&gt;
&lt;li&gt;fraud prevention&lt;/li&gt;
&lt;li&gt;regulatory reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact requirements vary depending on the product, institution, jurisdiction, and transaction.&lt;/p&gt;

&lt;p&gt;But the broader point is simple.&lt;/p&gt;

&lt;p&gt;Moving money is not merely a technical problem.&lt;/p&gt;

&lt;p&gt;It is also a regulatory and operational problem.&lt;/p&gt;

&lt;p&gt;A developer can build a beautiful interface. A company can create an excellent user experience. But the underlying system still needs to operate responsibly within the financial environments in which it functions.&lt;/p&gt;

&lt;p&gt;That creates a major difference between many traditional software products and financial products.&lt;/p&gt;

&lt;p&gt;A social application can send a message.&lt;/p&gt;

&lt;p&gt;A financial application may need to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who is sending the money?&lt;/li&gt;
&lt;li&gt;Who is receiving it?&lt;/li&gt;
&lt;li&gt;What is the nature of the transaction?&lt;/li&gt;
&lt;li&gt;Does the transaction trigger additional review?&lt;/li&gt;
&lt;li&gt;Are there restrictions that apply?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This complexity is often invisible to the user.&lt;/p&gt;

&lt;p&gt;And ideally, much of it should be.&lt;/p&gt;

&lt;p&gt;But it still exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Treasury Is the Operating System Behind Global Money
&lt;/h2&gt;

&lt;p&gt;When people think about financial products, they often think about payments.&lt;/p&gt;

&lt;p&gt;They think about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send
Receive
Pay
Withdraw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But businesses operating at scale need to think about something broader.&lt;/p&gt;

&lt;p&gt;Treasury.&lt;/p&gt;

&lt;p&gt;Treasury is concerned with how an organization manages its financial resources.&lt;/p&gt;

&lt;p&gt;For a company operating across borders, that can involve questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where is our money?&lt;/li&gt;
&lt;li&gt;In which currencies do we hold value?&lt;/li&gt;
&lt;li&gt;Where do we need liquidity?&lt;/li&gt;
&lt;li&gt;How do we fund payouts?&lt;/li&gt;
&lt;li&gt;When should we convert currencies?&lt;/li&gt;
&lt;li&gt;How do we reconcile incoming and outgoing transactions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified global money flow might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer Payment
        ↓
Collection
        ↓
Company Balance
        ↓
Treasury Management
        ↓
Liquidity Allocation
        ↓
Conversion
        ↓
Payout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At scale, these processes become significantly more complicated.&lt;/p&gt;

&lt;p&gt;A company may receive money in one region while needing to make payments somewhere else. It may need to manage balances across multiple currencies. It may need visibility into incoming and outgoing transactions. It may need to reconcile activity across several financial partners.&lt;/p&gt;

&lt;p&gt;This is why the phrase &lt;strong&gt;moving money&lt;/strong&gt; can be misleading.&lt;/p&gt;

&lt;p&gt;The real challenge is often managing the entire lifecycle of value.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Why Cross-Border Payments Can Be Expensive
&lt;/h2&gt;

&lt;p&gt;A customer may see a single fee.&lt;/p&gt;

&lt;p&gt;Behind that fee can be an entire financial system.&lt;/p&gt;

&lt;p&gt;Potential costs can arise from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payment processing&lt;/li&gt;
&lt;li&gt;foreign exchange&lt;/li&gt;
&lt;li&gt;banking relationships&lt;/li&gt;
&lt;li&gt;intermediary institutions&lt;/li&gt;
&lt;li&gt;local payout infrastructure&lt;/li&gt;
&lt;li&gt;liquidity provision&lt;/li&gt;
&lt;li&gt;compliance operations&lt;/li&gt;
&lt;li&gt;fraud prevention&lt;/li&gt;
&lt;li&gt;operational systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not mean every transaction involves every one of these costs in the same way. Payment infrastructure varies.&lt;/p&gt;

&lt;p&gt;But the important point is that a cross-border payment is not one single action.&lt;/p&gt;

&lt;p&gt;It can be a chain of financial operations.&lt;/p&gt;

&lt;p&gt;Historically, each additional layer created opportunities for additional cost.&lt;/p&gt;

&lt;p&gt;That is why infrastructure innovation matters.&lt;/p&gt;

&lt;p&gt;The opportunity is not simply to make a prettier payment application.&lt;/p&gt;

&lt;p&gt;It is to improve the systems behind the experience.&lt;/p&gt;

&lt;p&gt;Reduce unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Improve connectivity.&lt;/p&gt;

&lt;p&gt;Increase transparency.&lt;/p&gt;

&lt;p&gt;Make financial capabilities easier to access.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. The Internet Solved Information. Money Is Still Catching Up.
&lt;/h2&gt;

&lt;p&gt;This is perhaps one of the most interesting ways to think about the modern financial system.&lt;/p&gt;

&lt;p&gt;Information became increasingly global.&lt;/p&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send an email anywhere&lt;/li&gt;
&lt;li&gt;make a video call across continents&lt;/li&gt;
&lt;li&gt;deploy software to users worldwide&lt;/li&gt;
&lt;li&gt;publish information instantly&lt;/li&gt;
&lt;li&gt;collaborate with people thousands of kilometres away&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The infrastructure of the internet makes this possible.&lt;/p&gt;

&lt;p&gt;Developers do not need to negotiate with every telecommunications company before sending an API request.&lt;/p&gt;

&lt;p&gt;They use common infrastructure.&lt;/p&gt;

&lt;p&gt;They use protocols.&lt;/p&gt;

&lt;p&gt;They use networks.&lt;/p&gt;

&lt;p&gt;They build applications on top.&lt;/p&gt;

&lt;p&gt;Money is different.&lt;/p&gt;

&lt;p&gt;The global financial system is made up of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;institutions&lt;/li&gt;
&lt;li&gt;currencies&lt;/li&gt;
&lt;li&gt;regulations&lt;/li&gt;
&lt;li&gt;payment networks&lt;/li&gt;
&lt;li&gt;local systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not one single network.&lt;/p&gt;

&lt;p&gt;This fragmentation is one of the reasons financial infrastructure is becoming such an important area of innovation.&lt;/p&gt;

&lt;p&gt;The challenge is not necessarily to replace every existing financial system.&lt;/p&gt;

&lt;p&gt;The challenge is to make the systems easier to connect.&lt;/p&gt;

&lt;p&gt;Easier to program.&lt;/p&gt;

&lt;p&gt;Easier to build on.&lt;/p&gt;

&lt;p&gt;Easier to access.&lt;/p&gt;

&lt;p&gt;The future may not require one universal financial network.&lt;/p&gt;

&lt;p&gt;But it increasingly requires better infrastructure for connecting existing networks.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. From Financial Institutions to Financial Infrastructure
&lt;/h2&gt;

&lt;p&gt;For a long time, building a financial product required significant direct interaction with the financial system.&lt;/p&gt;

&lt;p&gt;A company might need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;establish banking relationships&lt;/li&gt;
&lt;li&gt;negotiate partnerships&lt;/li&gt;
&lt;li&gt;build operational processes&lt;/li&gt;
&lt;li&gt;maintain settlement accounts&lt;/li&gt;
&lt;li&gt;create compliance operations&lt;/li&gt;
&lt;li&gt;integrate multiple payment providers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many startups, this was a major barrier.&lt;/p&gt;

&lt;p&gt;The company was not just building a product.&lt;/p&gt;

&lt;p&gt;It was building the infrastructure required to support the product.&lt;/p&gt;

&lt;p&gt;That model is changing.&lt;/p&gt;

&lt;p&gt;Increasingly, specialized infrastructure providers expose financial capabilities through software interfaces.&lt;/p&gt;

&lt;p&gt;Instead of building every component, businesses can access certain capabilities as infrastructure.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before

Financial Product Company
        ↓
Build Everything
        ↓
Financial Operations
        ↓
Product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Infrastructure Provider
        ↓
Financial Capabilities
        ↓
APIs and Integrations
        ↓
Financial Product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not mean financial complexity disappears.&lt;/p&gt;

&lt;p&gt;It means complexity can be packaged.&lt;/p&gt;

&lt;p&gt;A company can focus on its product while infrastructure providers handle specialized parts of the underlying system.&lt;/p&gt;

&lt;p&gt;That is a profound shift.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. APIs Don't Remove Complexity. They Package It.
&lt;/h2&gt;

&lt;p&gt;It is easy to look at an API and think:&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;await&lt;/span&gt; &lt;span class="nf"&gt;sendPayment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple.&lt;/p&gt;

&lt;p&gt;But the simplicity of an API call does not mean the underlying process is simple.&lt;/p&gt;

&lt;p&gt;An API may represent years of infrastructure development.&lt;/p&gt;

&lt;p&gt;Behind one endpoint could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;banking relationships&lt;/li&gt;
&lt;li&gt;compliance systems&lt;/li&gt;
&lt;li&gt;payment routing&lt;/li&gt;
&lt;li&gt;settlement processes&lt;/li&gt;
&lt;li&gt;monitoring&lt;/li&gt;
&lt;li&gt;security&lt;/li&gt;
&lt;li&gt;reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API does not magically eliminate those things.&lt;/p&gt;

&lt;p&gt;It creates an interface through which developers can access capabilities without rebuilding everything themselves.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;APIs don't remove financial complexity.&lt;/p&gt;

&lt;p&gt;They package complexity into reusable infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is similar to cloud computing.&lt;/p&gt;

&lt;p&gt;A developer can create a server without physically constructing a data centre.&lt;/p&gt;

&lt;p&gt;That does not mean the data centre disappeared.&lt;/p&gt;

&lt;p&gt;Someone still built and operates it.&lt;/p&gt;

&lt;p&gt;The infrastructure simply became accessible through an abstraction.&lt;/p&gt;

&lt;p&gt;Financial infrastructure is increasingly moving in a similar direction.&lt;/p&gt;

&lt;p&gt;Developers can focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;user experiences&lt;/li&gt;
&lt;li&gt;workflows&lt;/li&gt;
&lt;li&gt;automation&lt;/li&gt;
&lt;li&gt;business logic&lt;/li&gt;
&lt;li&gt;distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while specialized providers focus on underlying financial capabilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. The New Financial Stack
&lt;/h2&gt;

&lt;p&gt;We can think about a modern financial product as a stack.&lt;/p&gt;

&lt;p&gt;At the top is the experience the user sees.&lt;/p&gt;

&lt;p&gt;Below that is the application logic.&lt;/p&gt;

&lt;p&gt;Below that is the infrastructure enabling financial actions.&lt;/p&gt;

&lt;p&gt;And beneath that are the networks and institutions that make the infrastructure possible.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────┐
│        APPLICATION           │
│                              │
│  UX • Product • Experience   │
└──────────────▲───────────────┘
               │
┌──────────────┴───────────────┐
│       BUSINESS LOGIC         │
│                              │
│  Rules • Workflows • Automation
└──────────────▲───────────────┘
               │
┌──────────────┴───────────────┐
│   FINANCIAL INFRASTRUCTURE   │
│                              │
│  APIs • Accounts • Payments  │
│  Wallets • FX • Payouts      │
└──────────────▲───────────────┘
               │
┌──────────────┴───────────────┐
│     FINANCIAL NETWORKS       │
│                              │
│  Banks • Payment Rails       │
│  Liquidity • Settlement      │
└──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application does not necessarily need to own every layer.&lt;/p&gt;

&lt;p&gt;That creates new possibilities.&lt;/p&gt;

&lt;p&gt;A startup can focus on solving a specific customer problem.&lt;/p&gt;

&lt;p&gt;A marketplace can focus on buyers and sellers.&lt;/p&gt;

&lt;p&gt;A payroll product can focus on employers and workers.&lt;/p&gt;

&lt;p&gt;A creator platform can focus on creators.&lt;/p&gt;

&lt;p&gt;The infrastructure beneath the product can provide access to financial capabilities.&lt;/p&gt;

&lt;p&gt;This is one of the most important changes happening in fintech.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Stablecoins Add a New Infrastructure Layer
&lt;/h2&gt;

&lt;p&gt;Stablecoins have added another dimension to conversations about global money movement.&lt;/p&gt;

&lt;p&gt;The most interesting use case is not always visible to consumers.&lt;/p&gt;

&lt;p&gt;A customer may not necessarily care what infrastructure is used behind a transaction.&lt;/p&gt;

&lt;p&gt;They care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;speed&lt;/li&gt;
&lt;li&gt;reliability&lt;/li&gt;
&lt;li&gt;cost&lt;/li&gt;
&lt;li&gt;accessibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But stablecoins can potentially provide new infrastructure capabilities for certain financial workflows.&lt;/p&gt;

&lt;p&gt;Depending on the system and use case, they can be used in areas such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;value transfer&lt;/li&gt;
&lt;li&gt;settlement&lt;/li&gt;
&lt;li&gt;liquidity movement&lt;/li&gt;
&lt;li&gt;treasury operations&lt;/li&gt;
&lt;li&gt;programmable financial workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why stablecoins are increasingly discussed as infrastructure rather than simply as consumer-facing assets.&lt;/p&gt;

&lt;p&gt;The customer might see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send Money
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The underlying architecture could involve a more complex flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Collection
    ↓
Settlement
    ↓
Treasury Movement
    ↓
Liquidity
    ↓
Payout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important idea is not that every payment product should use stablecoins.&lt;/p&gt;

&lt;p&gt;The important idea is that new forms of programmable value can create new options for infrastructure design.&lt;/p&gt;

&lt;p&gt;Financial products can increasingly separate the customer experience from the complexity of the underlying settlement architecture.&lt;/p&gt;

&lt;p&gt;That separation is powerful.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Invisible Infrastructure Is Often the Best Infrastructure
&lt;/h2&gt;

&lt;p&gt;The best infrastructure is often invisible.&lt;/p&gt;

&lt;p&gt;Think about the technologies you use every day.&lt;/p&gt;

&lt;p&gt;When you open a website, you probably do not think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNS&lt;/li&gt;
&lt;li&gt;TCP/IP&lt;/li&gt;
&lt;li&gt;data centres&lt;/li&gt;
&lt;li&gt;load balancers&lt;/li&gt;
&lt;li&gt;databases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You simply expect the page to load.&lt;/p&gt;

&lt;p&gt;When you send a message, you do not think about the network infrastructure behind it.&lt;/p&gt;

&lt;p&gt;You simply expect the message to arrive.&lt;/p&gt;

&lt;p&gt;Financial infrastructure should increasingly work the same way.&lt;/p&gt;

&lt;p&gt;The average customer should not need to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;correspondent banking&lt;/li&gt;
&lt;li&gt;liquidity management&lt;/li&gt;
&lt;li&gt;settlement architecture&lt;/li&gt;
&lt;li&gt;payment routing&lt;/li&gt;
&lt;li&gt;treasury operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They should simply understand the outcome.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did the money arrive?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the ultimate test of infrastructure.&lt;/p&gt;

&lt;p&gt;Not whether the architecture is impressive.&lt;/p&gt;

&lt;p&gt;Not whether the system uses fashionable technology.&lt;/p&gt;

&lt;p&gt;The question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the infrastructure make the experience better?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Better infrastructure should make financial products:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;easier to build&lt;/li&gt;
&lt;li&gt;easier to operate&lt;/li&gt;
&lt;li&gt;easier to scale&lt;/li&gt;
&lt;li&gt;easier to access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And ideally, easier to use.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. The Opportunity Is Not Just Moving Money Faster
&lt;/h2&gt;

&lt;p&gt;When people talk about fintech innovation, the conversation often focuses on speed.&lt;/p&gt;

&lt;p&gt;Faster payments.&lt;/p&gt;

&lt;p&gt;Instant settlement.&lt;/p&gt;

&lt;p&gt;Real-time transfers.&lt;/p&gt;

&lt;p&gt;Those things matter.&lt;/p&gt;

&lt;p&gt;But the opportunity is bigger.&lt;/p&gt;

&lt;p&gt;The real transformation may come from making financial capabilities more programmable.&lt;/p&gt;

&lt;p&gt;Consider what happens when software can interact directly with financial infrastructure.&lt;/p&gt;

&lt;p&gt;A product can potentially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trigger a payment&lt;/li&gt;
&lt;li&gt;create an account&lt;/li&gt;
&lt;li&gt;initiate a payout&lt;/li&gt;
&lt;li&gt;retrieve transaction information&lt;/li&gt;
&lt;li&gt;automate reconciliation&lt;/li&gt;
&lt;li&gt;manage workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This changes what developers can build.&lt;/p&gt;

&lt;p&gt;Financial operations become part of the software stack.&lt;/p&gt;

&lt;p&gt;Instead of manually coordinating every financial action, software can increasingly participate in the workflow.&lt;/p&gt;

&lt;p&gt;That does not remove the need for responsible financial operations.&lt;/p&gt;

&lt;p&gt;But it changes the interface through which businesses access those capabilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. The Most Important Shift: From Building Rails to Building Products
&lt;/h2&gt;

&lt;p&gt;This may be the defining shift.&lt;/p&gt;

&lt;p&gt;Historically, a company entering financial services often faced a difficult question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do we build or connect to the financial infrastructure we need?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today, the question is increasingly becoming:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What can we build on top of financial infrastructure?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That difference matters.&lt;/p&gt;

&lt;p&gt;It changes where startups spend their time.&lt;/p&gt;

&lt;p&gt;Instead of spending years attempting to recreate foundational capabilities, they can increasingly focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product design&lt;/li&gt;
&lt;li&gt;customer experience&lt;/li&gt;
&lt;li&gt;automation&lt;/li&gt;
&lt;li&gt;vertical-specific workflows&lt;/li&gt;
&lt;li&gt;distribution&lt;/li&gt;
&lt;li&gt;business models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a freelancer platform.&lt;/p&gt;

&lt;p&gt;The company does not necessarily want to become a bank.&lt;/p&gt;

&lt;p&gt;Its core problem may be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Help businesses pay freelancers efficiently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The platform needs financial capabilities.&lt;/p&gt;

&lt;p&gt;But the financial infrastructure is not necessarily the product itself.&lt;/p&gt;

&lt;p&gt;The product is the workflow.&lt;/p&gt;

&lt;p&gt;Similarly, a marketplace may not want to build a global banking network.&lt;/p&gt;

&lt;p&gt;It wants to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;collect payments&lt;/li&gt;
&lt;li&gt;manage transactions&lt;/li&gt;
&lt;li&gt;pay sellers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Infrastructure can make those capabilities accessible.&lt;/p&gt;

&lt;p&gt;The rails exist.&lt;/p&gt;

&lt;p&gt;The opportunity is to build better products on top of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. What Happens Next?
&lt;/h2&gt;

&lt;p&gt;We are moving toward a world where financial infrastructure becomes increasingly modular.&lt;/p&gt;

&lt;p&gt;Businesses can combine different capabilities.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accounts
+
Payments
+
Payouts
+
FX
+
Wallet Infrastructure
+
Automation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result is not necessarily a bank.&lt;/p&gt;

&lt;p&gt;It could be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a marketplace&lt;/li&gt;
&lt;li&gt;a payroll platform&lt;/li&gt;
&lt;li&gt;a creator economy product&lt;/li&gt;
&lt;li&gt;a global commerce application&lt;/li&gt;
&lt;li&gt;a treasury tool&lt;/li&gt;
&lt;li&gt;a business operations platform&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The innovation happens at the combination layer.&lt;/p&gt;

&lt;p&gt;The infrastructure provides primitives.&lt;/p&gt;

&lt;p&gt;Builders decide what to create with them.&lt;/p&gt;

&lt;p&gt;This is similar to what happened in software.&lt;/p&gt;

&lt;p&gt;Cloud computing did not eliminate software development.&lt;/p&gt;

&lt;p&gt;It expanded what software developers could build.&lt;/p&gt;

&lt;p&gt;APIs did not eliminate complexity from the internet.&lt;/p&gt;

&lt;p&gt;They made capabilities more accessible.&lt;/p&gt;

&lt;p&gt;Financial infrastructure may follow a similar path.&lt;/p&gt;

&lt;p&gt;The most important companies may not be the ones that build every underlying rail.&lt;/p&gt;

&lt;p&gt;They may be the ones that understand customer problems well enough to use existing infrastructure in new ways.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: The World Is Becoming More Connected. Money Is Learning to Catch Up.
&lt;/h2&gt;

&lt;p&gt;The global financial system was not designed as one unified application.&lt;/p&gt;

&lt;p&gt;It evolved over decades.&lt;/p&gt;

&lt;p&gt;Country by country.&lt;/p&gt;

&lt;p&gt;Institution by institution.&lt;/p&gt;

&lt;p&gt;Currency by currency.&lt;/p&gt;

&lt;p&gt;That history created an incredibly powerful financial system.&lt;/p&gt;

&lt;p&gt;But it also created fragmentation.&lt;/p&gt;

&lt;p&gt;Moving money across borders can involve a combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;financial institutions&lt;/li&gt;
&lt;li&gt;payment networks&lt;/li&gt;
&lt;li&gt;foreign exchange&lt;/li&gt;
&lt;li&gt;liquidity&lt;/li&gt;
&lt;li&gt;compliance&lt;/li&gt;
&lt;li&gt;settlement&lt;/li&gt;
&lt;li&gt;local payout infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user may only see one button.&lt;/p&gt;

&lt;p&gt;Behind that button is an entire world.&lt;/p&gt;

&lt;p&gt;But something is changing.&lt;/p&gt;

&lt;p&gt;Financial infrastructure is becoming more accessible.&lt;/p&gt;

&lt;p&gt;More programmable.&lt;/p&gt;

&lt;p&gt;More modular.&lt;/p&gt;

&lt;p&gt;More connected.&lt;/p&gt;

&lt;p&gt;Businesses increasingly do not need to build every underlying financial capability themselves.&lt;/p&gt;

&lt;p&gt;They can build on infrastructure.&lt;/p&gt;

&lt;p&gt;And that changes the question.&lt;/p&gt;

&lt;p&gt;The question is no longer only:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do we connect to the financial system?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Increasingly, the question becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What can we build on top of it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The internet created a world where information became programmable.&lt;/p&gt;

&lt;p&gt;Financial infrastructure is moving toward a future where value can increasingly become part of software workflows too.&lt;/p&gt;

&lt;p&gt;The rails are becoming more accessible.&lt;/p&gt;

&lt;p&gt;The interfaces are becoming more developer-friendly.&lt;/p&gt;

&lt;p&gt;The infrastructure is becoming easier to build on.&lt;/p&gt;

&lt;p&gt;And that creates an enormous opportunity.&lt;/p&gt;

&lt;p&gt;Because once the complexity of financial infrastructure is abstracted away, builders can focus on something far more important.&lt;/p&gt;

&lt;p&gt;The problem.&lt;/p&gt;

&lt;p&gt;The customer.&lt;/p&gt;

&lt;p&gt;The experience.&lt;/p&gt;

&lt;p&gt;The product.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part II: When Money Becomes Infrastructure
&lt;/h2&gt;

&lt;p&gt;In the second part of this series, we will explore what happens when financial capabilities become accessible as programmable infrastructure.&lt;/p&gt;

&lt;p&gt;We will look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the rise of financial APIs&lt;/li&gt;
&lt;li&gt;the modern financial product stack&lt;/li&gt;
&lt;li&gt;programmable money&lt;/li&gt;
&lt;li&gt;stablecoins as infrastructure&lt;/li&gt;
&lt;li&gt;embedded finance&lt;/li&gt;
&lt;li&gt;the new generation of global financial products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And ultimately, one important question:&lt;/p&gt;

&lt;h2&gt;
  
  
  What will builders create when they no longer have to build every rail themselves?
&lt;/h2&gt;




&lt;p&gt;&lt;em&gt;This is Part I of a two-part series exploring the infrastructure behind global money and the technologies reshaping how financial products are built.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>fintech</category>
      <category>infrastructure</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>How to Collect Mobile Money Payments for Your SaaS Through Afriex</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Fri, 28 Aug 2026 18:23:48 +0000</pubDate>
      <link>https://dev.to/afriex/how-to-collect-mobile-money-payments-for-your-saas-through-afriex-2kgo</link>
      <guid>https://dev.to/afriex/how-to-collect-mobile-money-payments-for-your-saas-through-afriex-2kgo</guid>
      <description>&lt;p&gt;Building a product is hard enough. Getting paid for it shouldn't be. In this post I'll show you how to accept mobile money payments from Kenya, Uganda and Tanzania in 5 steps.&lt;/p&gt;

&lt;p&gt;Cards barely work for recurring billing in these markets. Mobile money — a wallet held against a phone number — is how people actually pay.&lt;/p&gt;

&lt;p&gt;Mobile money &lt;strong&gt;collection&lt;/strong&gt; is live in Kenya (KES), Uganda (UGX), Tanzania (TZS), Côte d'Ivoire and Benin (XOF), Cameroon (XAF) and Ethiopia (ETB). Ghana, Rwanda and a few others are payout-only for now, so check the &lt;a href="https://docs.afriex.com/guides/supported-currencies" rel="noopener noreferrer"&gt;supported rails table&lt;/a&gt; before you support a market.&lt;/p&gt;

&lt;p&gt;The flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;list providers -&amp;gt; verify the number -&amp;gt; save it -&amp;gt; charge it -&amp;gt; track it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @afriex/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/afriex/client.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AfriexSDK&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@afriex/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AfriexSDK&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&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;production&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;staging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;webhookPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retryConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;retryDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&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;Retries are off by default — turn them on. Grab both keys from &lt;strong&gt;Developer -&amp;gt; API keys&lt;/strong&gt; and &lt;strong&gt;Developer -&amp;gt; Webhooks&lt;/strong&gt; at business.afriex.com. Staging and production webhook keys differ.&lt;/p&gt;

&lt;p&gt;Your customer must exist in Afriex before they can have a payment method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/afriex/customers.ts&lt;/span&gt;
&lt;span class="k"&gt;export&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;createAfriexCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;customer&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// E.164, must match countryCode&lt;/span&gt;
    &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// "KE" | "UG" | "TZ"&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;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// store this on your user row&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the email or phone already exists you get a &lt;code&gt;400&lt;/code&gt; back, but &lt;code&gt;details.data.customerId&lt;/code&gt; carries the existing ID — adopt it instead of doing a lookup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — List the providers
&lt;/h2&gt;

&lt;p&gt;Don't hardcode providers. Fetch them per country.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/afriex/institutions.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MobileMoneyProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;institutionId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&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;listMobileMoneyProviders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MobileMoneyProvider&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstitutions&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MOBILE_MONEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;countryCode&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;Live responses:&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;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;KE&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;"institutionCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SAFARICOM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"institutionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SAFARICOM"&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="nl"&gt;"institutionCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AIRTEL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"institutionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AIRTEL"&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;UG&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;"institutionCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MTN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"institutionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MTN"&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="nl"&gt;"institutionCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AIRTEL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"institutionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AIRTEL"&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;Send &lt;code&gt;institutionCode&lt;/code&gt; to the API. Don't show &lt;code&gt;institutionName&lt;/code&gt; to users — &lt;code&gt;SAFARICOM&lt;/code&gt; means nothing to someone who calls it M-Pesa. Map it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/afriex/provider-labels.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROVIDER_LABELS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;SAFARICOM&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;M-Pesa&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;MTN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MTN MoMo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;AIRTEL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Airtel Money&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;h2&gt;
  
  
  Step 2 — Verify the number
&lt;/h2&gt;

&lt;p&gt;Check who owns a number before you save it. This is the difference between a typo caught in the UI and a failed charge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/afriex/resolve-account.ts&lt;/span&gt;
&lt;span class="k"&gt;export&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;resolveMobileMoneyAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recipientName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;institutionCode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MOBILE_MONEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;countryCode&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;Returns &lt;code&gt;{ recipientName, institutionName, institutionCode }&lt;/code&gt;. Show &lt;code&gt;recipientName&lt;/code&gt; and make the user confirm before continuing.&lt;/p&gt;

&lt;p&gt;Staging returns canned values here (you'll see &lt;code&gt;John Doe&lt;/code&gt;), so build the confirmation UI but test it against production before trusting the name.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — Save the payment method
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/afriex/payment-methods.ts&lt;/span&gt;
&lt;span class="k"&gt;export&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;saveMobileMoneyMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&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;paymentMethod&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MOBILE_MONEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;phoneNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;institution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;institutionCode&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPOSIT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// required — see below&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;paymentMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethodId&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;&lt;strong&gt;Don't skip &lt;code&gt;type&lt;/code&gt;.&lt;/strong&gt; It sets the method's capability, and it defaults to &lt;code&gt;WITHDRAW&lt;/code&gt; — the payout direction. Omit it and you get a method you can send money &lt;em&gt;to&lt;/em&gt;, not collect &lt;em&gt;from&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// with type: "DEPOSIT"     -&amp;gt; "capabilities": ["DEPOSIT"]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;// with type omitted        -&amp;gt; "capabilities": ["WITHDRAW"]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your charge in step 4 will fail against a &lt;code&gt;WITHDRAW&lt;/code&gt;-only method, and the error won't obviously point back here.&lt;/p&gt;

&lt;p&gt;Also worth knowing: &lt;code&gt;currency&lt;/code&gt; is derived from country and channel — don't send it. And the API stores &lt;code&gt;accountNumber&lt;/code&gt; with a &lt;code&gt;+&lt;/code&gt; prefix even though it accepts it without one, so normalise phone numbers on the way in rather than round-tripping the stored value.&lt;/p&gt;

&lt;p&gt;Store &lt;code&gt;paymentMethodId&lt;/code&gt; against your subscription. You'll pass it as &lt;code&gt;sourceId&lt;/code&gt; on every charge.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Charge it
&lt;/h2&gt;

&lt;p&gt;Collecting is a &lt;code&gt;DEPOSIT&lt;/code&gt; with the customer's wallet as &lt;code&gt;sourceId&lt;/code&gt;. For a deposit, &lt;strong&gt;&lt;code&gt;destinationAmount&lt;/code&gt; is required&lt;/strong&gt; and &lt;code&gt;sourceAmount&lt;/code&gt; is optional — the reverse of the withdraw rules.&lt;/p&gt;

&lt;p&gt;The simplest correct setup collects in the customer's own currency, so no conversion happens at charge time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/afriex/collect.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ulid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ulid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;chargeSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;subscriptionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;localAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// e.g. "3750.00"&lt;/span&gt;
  &lt;span class="nl"&gt;localCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "KES" | "UGX" | "TZS"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subscriptionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localCurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&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;reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`sub_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;subscriptionId&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPOSIT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localCurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localCurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// same currency — no FX on collection&lt;/span&gt;
    &lt;span class="na"&gt;destinationAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;localAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// required for DEPOSIT&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ulid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;narration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Monthly subscription&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reference&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;Persist &lt;code&gt;reference&lt;/code&gt; on the subscription row &lt;em&gt;before&lt;/em&gt; you await. If the call times out you still know what to look for — and &lt;code&gt;reference&lt;/code&gt; is what you'll match the webhook on.&lt;/p&gt;

&lt;p&gt;Amounts accept a number or a numeric string, and come back as strings. Keep money as &lt;code&gt;bigint&lt;/code&gt; minor units internally and format only at this boundary — the balance endpoint hands back values like &lt;code&gt;51291590.19999999&lt;/code&gt;, and floats will burn you.&lt;/p&gt;

&lt;p&gt;The response also carries &lt;code&gt;rate&lt;/code&gt; and &lt;code&gt;fee&lt;/code&gt;, both worth logging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collect in local currency, convert on your own schedule
&lt;/h3&gt;

&lt;p&gt;Notice that &lt;code&gt;destinationCurrency&lt;/code&gt; above is KES, not USD. That's deliberate.&lt;/p&gt;

&lt;p&gt;You publish "KES 3,750/month" and the customer agrees to that number. If you set &lt;code&gt;destinationCurrency: "USD"&lt;/code&gt;, you're doing an FX conversion inside every single charge, and one of the two amounts has to absorb the rate movement. On mobile money the charged amount appears in the OTP prompt on the customer's phone, so a figure that drifts away from your pricing page is an abandonment risk, not just an accounting quirk.&lt;/p&gt;

&lt;p&gt;Collecting KES into your KES wallet means no conversion at charge time. The customer pays exactly what you quoted, every cycle. You then convert when you choose, in whatever size you choose, with a &lt;code&gt;SWAP&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/jobs/settle-to-usd.ts&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SWAP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;KES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sourceAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accumulatedKes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// exactly one of source/destination&lt;/span&gt;
  &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ulid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`settlement-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;settlementRunId&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;SWAP&lt;/code&gt; takes &lt;strong&gt;exactly one&lt;/strong&gt; of &lt;code&gt;sourceAmount&lt;/code&gt; or &lt;code&gt;destinationAmount&lt;/code&gt; — the API computes the other side at the live rate. Sending both is rejected with &lt;code&gt;Only one of source amount or destination amount can be provided&lt;/code&gt;. &lt;code&gt;customerId&lt;/code&gt; is omitted, so it runs against your business wallet.&lt;/p&gt;

&lt;p&gt;This also means fewer, larger conversions instead of one per subscriber, which is usually the cheaper side of the spread.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you do convert at charge time
&lt;/h3&gt;

&lt;p&gt;If you'd rather land USD directly, set &lt;code&gt;destinationCurrency: "USD"&lt;/code&gt; and send &lt;code&gt;destinationAmount&lt;/code&gt; as the USD figure — it's required for a deposit either way. &lt;code&gt;sourceAmount&lt;/code&gt; is optional, and if you send both, &lt;code&gt;destinationAmount&lt;/code&gt; wins by default. &lt;code&gt;shouldPreferSourceAmount: true&lt;/code&gt; flips that so the source side drives, but the docs frame that flag around payouts, so test it on a deposit before relying on it. Either way, read the realized &lt;code&gt;rate&lt;/code&gt; and the actual amounts off the response rather than assuming you got what you asked for.&lt;/p&gt;

&lt;h3&gt;
  
  
  The OTP step
&lt;/h3&gt;

&lt;p&gt;Mobile money deposits often need a one-time password. If the response comes back &lt;code&gt;CUSTOMER_ACTION_REQUIRED&lt;/code&gt; with &lt;code&gt;meta.otpRequired: true&lt;/code&gt;, collect the OTP from the customer and submit it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/afriex/authorize-deposit.ts&lt;/span&gt;
&lt;span class="k"&gt;export&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;authorizeDeposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;otp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="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="nx"&gt;otp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OTP&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;otp&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;Skip this and the deposit sits unfinished forever. Handle it in your UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CUSTOMER_ACTION_REQUIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;otpRequired&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;needsOtp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;transaction&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5 — Track the payment
&lt;/h2&gt;

&lt;p&gt;The webhook is the source of truth. Signatures are &lt;code&gt;RSA-SHA256&lt;/code&gt;, base64, computed over the &lt;strong&gt;raw&lt;/strong&gt; body — so you need the raw bytes, not the parsed object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/plugins/raw-body.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fastifyPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fastify-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;fastifyPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addContentTypeParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="na"&gt;parseAs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&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;request&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;verifyAndParse&lt;/code&gt; is synchronous and takes positional arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/routes/webhooks/afriex.ts&lt;/span&gt;
&lt;span class="k"&gt;export&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;afriexWebhookRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FastifyInstance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/webhooks/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-webhook-signature&lt;/span&gt;&lt;span class="dl"&gt;"&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&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;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;missing_signature&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="k"&gt;try&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;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyAndParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRANSACTION.UPDATED&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;applyTransactionEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&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;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;invalid_signature&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return &lt;code&gt;200&lt;/code&gt; fast. Afriex retries failed deliveries up to 12 times with exponential backoff starting at 30 seconds, so a slow handler becomes a duplicate event.&lt;/p&gt;

&lt;p&gt;There's no event ID in the payload, so key idempotency on the transaction and its status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/subscriptions/apply-transaction-event.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TERMINAL&lt;/span&gt; &lt;span class="o"&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;SUCCESS&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;FAILED&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;CANCELLED&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;REJECTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&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;applyTransactionEvent&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;AfriexTransactionEventData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;reference&lt;/span&gt; &lt;span class="o"&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;merchantReference&lt;/span&gt; &lt;span class="o"&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;meta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;reference&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;reference&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPOSIT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;inserted&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;trx&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processedEvents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;transactionId&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;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&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;status&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onConflictDoNothing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;processedEvents&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inserted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// replay&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&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;findByReference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reference&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;subscription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCESS&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;activateSubscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;TERMINAL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&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;status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markPaymentFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;meta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;failureReason&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The composite unique index on &lt;code&gt;(transactionId, status)&lt;/code&gt; is what makes this idempotent — not an &lt;code&gt;if&lt;/code&gt;, which two workers will race through together.&lt;/p&gt;

&lt;p&gt;On failure, &lt;code&gt;meta.failureReason&lt;/code&gt; carries a stable &lt;code&gt;AFX_*&lt;/code&gt; code, a customer-safe &lt;code&gt;message&lt;/code&gt;, and a &lt;code&gt;retryable&lt;/code&gt; boolean. Branch on &lt;code&gt;code&lt;/code&gt;, never the message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;failureReason&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;retryable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;scheduleRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;Codes include &lt;code&gt;AFX_VELOCITY_LIMIT_EXCEEDED&lt;/code&gt;, &lt;code&gt;AFX_AMOUNT_LIMIT_EXCEEDED&lt;/code&gt;, &lt;code&gt;AFX_COMPLIANCE_REJECTED&lt;/code&gt; and &lt;code&gt;AFX_INVALID_RECIPIENT&lt;/code&gt;. The set grows, but existing values don't change meaning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing it
&lt;/h2&gt;

&lt;p&gt;Sandbox settles transactions automatically in a minute or two, and you steer the outcome through &lt;code&gt;meta.reference&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Put this in &lt;code&gt;meta.reference&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;anything containing &lt;code&gt;fail&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;settles &lt;code&gt;FAILED&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SIMULATE_OTP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;returns &lt;code&gt;CUSTOMER_ACTION_REQUIRED&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SIMULATE_NO_OTP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;completes with no OTP step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;anything else&lt;/td&gt;
&lt;td&gt;settles &lt;code&gt;SUCCESS&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sandbox OTP is &lt;code&gt;123456&lt;/code&gt;. Any other value is rejected, so you can test the wrong-OTP path too.&lt;/p&gt;

&lt;p&gt;You can also fire a real signed webhook at your endpoint without generating the activity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;triggerTestWebhook&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRANSACTION.UPDATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;entityId&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Staging and production webhook public keys are different.&lt;/li&gt;
&lt;li&gt;Pagination is &lt;strong&gt;zero-indexed&lt;/strong&gt; — the first page is &lt;code&gt;page: 0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;merchantReference&lt;/code&gt; on the webhook mirrors &lt;code&gt;meta.reference&lt;/code&gt; from create.&lt;/li&gt;
&lt;li&gt;Customer &lt;code&gt;phone&lt;/code&gt; must match &lt;code&gt;countryCode&lt;/code&gt;, or you get &lt;code&gt;PHONE_COUNTRY_MISMATCH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Surface &lt;code&gt;details.friendlyMessage&lt;/code&gt; to users; branch on &lt;code&gt;code&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  That's it
&lt;/h2&gt;

&lt;p&gt;Five calls: list providers, resolve the number, save the method, create a &lt;code&gt;DEPOSIT&lt;/code&gt;, handle the webhook. Plus the OTP step, which is the one most integrations forget.&lt;/p&gt;

</description>
      <category>crossborder</category>
      <category>saas</category>
      <category>afriex</category>
      <category>mobilemoney</category>
    </item>
    <item>
      <title>The New Fintech Stack: APIs, Stablecoins, and the Infrastructure Behind Global Money</title>
      <dc:creator>0xSonOfUri</dc:creator>
      <pubDate>Fri, 21 Aug 2026 09:45:17 +0000</pubDate>
      <link>https://dev.to/afriex/the-new-fintech-stack-apis-stablecoins-and-the-infrastructure-behind-global-money-52kh</link>
      <guid>https://dev.to/afriex/the-new-fintech-stack-apis-stablecoins-and-the-infrastructure-behind-global-money-52kh</guid>
      <description>&lt;p&gt;For decades, building a financial product meant building a significant part of the financial system around it.&lt;/p&gt;

&lt;p&gt;If you wanted to create a payments company, you needed banking relationships.&lt;/p&gt;

&lt;p&gt;If you wanted customers to receive money, you needed accounts.&lt;/p&gt;

&lt;p&gt;If you wanted to move money across borders, you needed access to payment networks, foreign exchange, settlement partners, and liquidity.&lt;/p&gt;

&lt;p&gt;If you wanted to build a global product, all of that complexity had to be solved before you could focus on the product itself.&lt;/p&gt;

&lt;p&gt;That model is changing.&lt;/p&gt;

&lt;p&gt;Today, financial infrastructure is becoming increasingly &lt;strong&gt;programmable, composable, and accessible through APIs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Developers can build on top of infrastructure that handles many of the complicated layers underneath a financial product.&lt;/p&gt;

&lt;p&gt;At the same time, stablecoins are introducing another programmable layer for moving value globally.&lt;/p&gt;

&lt;p&gt;Together, these changes are creating something that looks increasingly like a &lt;strong&gt;new fintech stack&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the most interesting part isn't simply that the infrastructure is getting better.&lt;/p&gt;

&lt;p&gt;It's that &lt;strong&gt;more businesses can now build financial products without owning every layer of the financial infrastructure themselves.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Old Fintech Stack
&lt;/h2&gt;

&lt;p&gt;Historically, financial products were deeply tied to financial institutions.&lt;/p&gt;

&lt;p&gt;If you wanted to build a product that moved money, you typically needed to establish relationships with banks and payment providers.&lt;/p&gt;

&lt;p&gt;The architecture might have looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BANKING SYSTEM
     │
┌────┼────────────┐
│    │            │
Accounts       Payments       FX
│    │            │
└────┼────────────┘
     │
 Settlement
     │
Your Product
     │
 Customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The product was sitting very close to the underlying financial infrastructure.&lt;/p&gt;

&lt;p&gt;That created enormous barriers to entry.&lt;/p&gt;

&lt;p&gt;A startup could have an excellent product idea and still spend months or years solving infrastructure problems before reaching meaningful scale.&lt;/p&gt;

&lt;p&gt;The challenge wasn't necessarily building the user interface.&lt;/p&gt;

&lt;p&gt;The challenge was gaining access to everything behind it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Financial Infrastructure Was Historically a Moat
&lt;/h2&gt;

&lt;p&gt;This complexity wasn't accidental.&lt;/p&gt;

&lt;p&gt;Moving money is difficult.&lt;/p&gt;

&lt;p&gt;A financial transaction can involve multiple systems, institutions, currencies, compliance requirements, ledgers, settlement processes, and counterparties.&lt;/p&gt;

&lt;p&gt;Even a seemingly simple action like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Pay someone $1,000"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;can involve significantly more infrastructure underneath.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
   │
   ▼
Payment Initiation
   │
   ▼
Payment Provider
   │
   ▼
Bank / Financial Institution
   │
   ▼
Clearing
   │
   ▼
Settlement
   │
   ▼
Recipient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every layer introduces its own requirements.&lt;/p&gt;

&lt;p&gt;This meant financial infrastructure itself became a competitive moat.&lt;/p&gt;

&lt;p&gt;Companies that already had access to banking relationships and payment networks had a significant advantage over startups trying to build from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then APIs Changed Software
&lt;/h2&gt;

&lt;p&gt;We've already seen this transformation happen in another industry.&lt;/p&gt;

&lt;p&gt;Cloud computing.&lt;/p&gt;

&lt;p&gt;A company building software in the early days of the internet had to think about physical servers, networking, storage, data centers, hardware maintenance, and infrastructure capacity.&lt;/p&gt;

&lt;p&gt;Then cloud platforms abstracted much of that complexity.&lt;/p&gt;

&lt;p&gt;Developers could request infrastructure through software.&lt;/p&gt;

&lt;p&gt;Instead of thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do we build and maintain a data center?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They could think:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What application do we want to build?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The infrastructure still existed.&lt;/p&gt;

&lt;p&gt;It simply became &lt;strong&gt;programmable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Financial infrastructure is going through a similar transformation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Financial Infrastructure Is Becoming Programmable
&lt;/h2&gt;

&lt;p&gt;Modern financial infrastructure increasingly exposes financial capabilities through APIs.&lt;/p&gt;

&lt;p&gt;Instead of manually coordinating with multiple institutions for every capability, developers can interact with financial infrastructure programmatically.&lt;/p&gt;

&lt;p&gt;The conceptual shift looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEFORE

Business
   │
   ├── Bank relationship
   ├── Payment provider
   ├── FX provider
   ├── Settlement partner
   └── Treasury infrastructure
            │
            ▼
         Product


AFTER

                    ┌──────────────┐
                    │Infrastructure│
                    │     APIs     │
                    └──────┬───────┘
                           │
                           ▼
                        Product
                           │
                           ▼
                        Customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The financial infrastructure hasn't disappeared.&lt;/p&gt;

&lt;p&gt;It has become a layer that developers can integrate with.&lt;/p&gt;




&lt;h2&gt;
  
  
  The New Fintech Stack
&lt;/h2&gt;

&lt;p&gt;This leads us to the idea of a new fintech stack.&lt;/p&gt;

&lt;p&gt;Instead of thinking about fintech as one enormous system that a company has to build itself, we can think about it as a collection of composable layers.&lt;/p&gt;

&lt;p&gt;A simplified version might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│           Customer Experience       │
├─────────────────────────────────────┤
│             Your Product            │
├─────────────────────────────────────┤
│       Financial Application Layer   │
├─────────────────────────────────────┤
│             APIs &amp;amp; SDKs              │
├─────────────────────────────────────┤
│     Payments / Accounts / FX        │
├─────────────────────────────────────┤
│      Settlement &amp;amp; Liquidity         │
├─────────────────────────────────────┤
│       Banking / Financial Rails     │
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The startup doesn't necessarily need to own every layer.&lt;/p&gt;

&lt;p&gt;It can choose which layers to build and which layers to consume.&lt;/p&gt;

&lt;p&gt;That distinction is incredibly important.&lt;/p&gt;




&lt;h2&gt;
  
  
  Build vs. Compose
&lt;/h2&gt;

&lt;p&gt;One of the biggest decisions a fintech founder makes is deciding what to build internally.&lt;/p&gt;

&lt;p&gt;You could build everything.&lt;/p&gt;

&lt;p&gt;But should you?&lt;/p&gt;

&lt;p&gt;A modern software company doesn't build its own database engine, cloud provider, email infrastructure, authentication system, analytics platform, and networking stack before launching its product.&lt;/p&gt;

&lt;p&gt;It composes infrastructure.&lt;/p&gt;

&lt;p&gt;The same thinking is increasingly applicable to financial products.&lt;/p&gt;

&lt;p&gt;Consider a company building a global marketplace.&lt;/p&gt;

&lt;p&gt;It needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer onboarding&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;Accounts&lt;/li&gt;
&lt;li&gt;Payouts&lt;/li&gt;
&lt;li&gt;Currency conversion&lt;/li&gt;
&lt;li&gt;Transaction tracking&lt;/li&gt;
&lt;li&gt;Settlement&lt;/li&gt;
&lt;li&gt;Treasury&lt;/li&gt;
&lt;li&gt;Reporting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The company doesn't necessarily need to build a bank.&lt;/p&gt;

&lt;p&gt;It needs to build a &lt;strong&gt;marketplace&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The financial infrastructure can become a layer beneath that marketplace.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Financial Primitives
&lt;/h2&gt;

&lt;p&gt;This is where the concept of &lt;strong&gt;financial primitives&lt;/strong&gt; becomes useful.&lt;/p&gt;

&lt;p&gt;A primitive is a fundamental capability that can be combined with other capabilities to create something larger.&lt;/p&gt;

&lt;p&gt;For financial products, primitives might include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Accounts
Payments
Payouts
Foreign Exchange
Balances
Transactions
Settlement
Treasury
Stablecoin Transfers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These primitives can then be composed into products.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             MARKETPLACE
                  │
        ┌─────────┴─────────┐
        │                   │
     Payments            Payouts
        │                   │
        └─────────┬─────────┘
                  │
             Accounts
                  │
             Settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The marketplace itself is the product.&lt;/p&gt;

&lt;p&gt;The financial primitives are the infrastructure underneath.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Afriex Fits
&lt;/h2&gt;

&lt;p&gt;This is where platforms such as &lt;strong&gt;Afriex&lt;/strong&gt; become particularly interesting.&lt;/p&gt;

&lt;p&gt;The purpose of financial infrastructure isn't simply to expose APIs.&lt;/p&gt;

&lt;p&gt;The larger opportunity is to give businesses access to capabilities they can incorporate into their own products, operations, and workflows.&lt;/p&gt;

&lt;p&gt;Instead of every company independently figuring out how to connect financial systems across markets, infrastructure providers can provide a layer through which those capabilities become accessible programmatically.&lt;/p&gt;

&lt;p&gt;That changes the question for builders.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I build the entire financial infrastructure?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;the question becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What financial capabilities does my product need?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is a much more interesting problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Infrastructure to Product
&lt;/h2&gt;

&lt;p&gt;Imagine you're building a marketplace.&lt;/p&gt;

&lt;p&gt;Your customers don't care which payment infrastructure powers the transaction.&lt;/p&gt;

&lt;p&gt;They care that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They can pay.&lt;/li&gt;
&lt;li&gt;Their payment works.&lt;/li&gt;
&lt;li&gt;Their money arrives.&lt;/li&gt;
&lt;li&gt;They can receive funds.&lt;/li&gt;
&lt;li&gt;They can understand their balance.&lt;/li&gt;
&lt;li&gt;The experience is fast and reliable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The customer sees the product.&lt;/p&gt;

&lt;p&gt;The infrastructure stays underneath.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 CUSTOMER
                    │
                    ▼
             Your Marketplace
                    │
                    ▼
          Financial Infrastructure
                    │
       ┌────────────┼────────────┐
       ▼            ▼            ▼
    Payments      Accounts       FX
       │            │            │
       └────────────┼────────────┘
                    ▼
                Settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This abstraction is powerful.&lt;/p&gt;

&lt;p&gt;The customer doesn't need to understand the infrastructure.&lt;/p&gt;

&lt;p&gt;They simply need the experience to work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Rise of Stablecoins
&lt;/h2&gt;

&lt;p&gt;Now introduce another major development.&lt;/p&gt;

&lt;p&gt;Stablecoins.&lt;/p&gt;

&lt;p&gt;Stablecoins are particularly interesting because they combine characteristics of digital assets with relatively stable units of account.&lt;/p&gt;

&lt;p&gt;More importantly for developers, they can function as &lt;strong&gt;programmable representations of value&lt;/strong&gt; that can move across blockchain networks.&lt;/p&gt;

&lt;p&gt;That creates another potential layer in the global financial stack.&lt;/p&gt;

&lt;p&gt;Instead of every movement of value needing to happen through traditional financial rails, stablecoins can sometimes provide another mechanism for moving value between participants and systems.&lt;/p&gt;

&lt;p&gt;The architecture can become something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Currency
      │
      ▼
Payment Infrastructure
      │
      ▼
   Settlement
      │
      ▼
Stablecoin Treasury
      │
      ▼
   Settlement
      │
      ▼
Payment Infrastructure
      │
      ▼
Local Currency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The customer may never see any of this.&lt;/p&gt;

&lt;p&gt;From their perspective:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SEND $1,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, the infrastructure may involve multiple layers.&lt;/p&gt;

&lt;p&gt;That is the point of abstraction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stablecoins Don't Replace Everything
&lt;/h2&gt;

&lt;p&gt;It is important not to frame stablecoins as a replacement for the entire financial system.&lt;/p&gt;

&lt;p&gt;They are another component of the stack.&lt;/p&gt;

&lt;p&gt;A global financial product may still need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local payment rails&lt;/li&gt;
&lt;li&gt;Banking relationships&lt;/li&gt;
&lt;li&gt;Currency conversion&lt;/li&gt;
&lt;li&gt;Compliance systems&lt;/li&gt;
&lt;li&gt;Settlement&lt;/li&gt;
&lt;li&gt;Liquidity&lt;/li&gt;
&lt;li&gt;Treasury management&lt;/li&gt;
&lt;li&gt;Customer accounts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stablecoins can fit into that architecture rather than necessarily replacing all of it.&lt;/p&gt;

&lt;p&gt;The interesting opportunity is &lt;strong&gt;composition&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Traditional Rails
       +
APIs
       +
Stablecoins
       +
Liquidity
       +
Financial Infrastructure
       =
New Financial Products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Customer Experience Becomes the Abstraction
&lt;/h2&gt;

&lt;p&gt;This is one of the most important changes.&lt;/p&gt;

&lt;p&gt;Customers don't want to think about financial infrastructure.&lt;/p&gt;

&lt;p&gt;They want to complete an action.&lt;/p&gt;

&lt;p&gt;They want to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pay&lt;/p&gt;

&lt;p&gt;Receive&lt;/p&gt;

&lt;p&gt;Send&lt;/p&gt;

&lt;p&gt;Withdraw&lt;/p&gt;

&lt;p&gt;Convert&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The complexity underneath should ideally be invisible.&lt;/p&gt;

&lt;p&gt;Consider what a customer might see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────┐
│                              │
│        Send Money            │
│                              │
│        $1,000                │
│                              │
│        [ Send ]              │
│                              │
└──────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What they don't see could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication
      ↓
Payment Processing
      ↓
Currency Conversion
      ↓
Liquidity
      ↓
Settlement
      ↓
Treasury
      ↓
Recipient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best infrastructure makes complicated systems feel simple.&lt;/p&gt;




&lt;h2&gt;
  
  
  This Is Similar to Cloud Computing
&lt;/h2&gt;

&lt;p&gt;There is a useful analogy here.&lt;/p&gt;

&lt;p&gt;Cloud computing didn't eliminate servers.&lt;/p&gt;

&lt;p&gt;It abstracted them.&lt;/p&gt;

&lt;p&gt;Payment infrastructure doesn't eliminate banks, payment networks, liquidity providers, or settlement systems.&lt;/p&gt;

&lt;p&gt;It can abstract access to them.&lt;/p&gt;

&lt;p&gt;The developer doesn't necessarily need to know every implementation detail.&lt;/p&gt;

&lt;p&gt;They need a reliable interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLOUD

Application
    ↓
Cloud API
    ↓
Servers
    ↓
Networking
    ↓
Data Centers


FINTECH

Financial Product
    ↓
Financial API
    ↓
Payments
    ↓
Settlement
    ↓
Financial Rails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The abstraction layer is what makes the system accessible to a much larger number of builders.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Developer Becomes a Financial Product Builder
&lt;/h2&gt;

&lt;p&gt;This is perhaps the biggest consequence.&lt;/p&gt;

&lt;p&gt;Historically, financial products were primarily built by financial institutions.&lt;/p&gt;

&lt;p&gt;Increasingly, software companies can build financial experiences too.&lt;/p&gt;

&lt;p&gt;A developer who understands APIs, infrastructure, and product design can potentially build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment platforms&lt;/li&gt;
&lt;li&gt;Global marketplaces&lt;/li&gt;
&lt;li&gt;Payroll products&lt;/li&gt;
&lt;li&gt;Remittance applications&lt;/li&gt;
&lt;li&gt;Treasury tools&lt;/li&gt;
&lt;li&gt;Creator platforms&lt;/li&gt;
&lt;li&gt;B2B payment workflows&lt;/li&gt;
&lt;li&gt;Embedded financial experiences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The developer doesn't need to become a bank to understand the product problem.&lt;/p&gt;

&lt;p&gt;They need to understand the infrastructure available to them.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Marketplace Example
&lt;/h2&gt;

&lt;p&gt;Imagine a marketplace connecting businesses in multiple countries.&lt;/p&gt;

&lt;p&gt;A traditional approach might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Buyer
  ↓
Marketplace
  ↓
Bank
  ↓
Payment Provider
  ↓
Settlement
  ↓
Seller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine composing financial infrastructure through APIs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Buyer
  ↓
Marketplace
  ↓
Financial APIs
  ├── Payment
  ├── Account
  ├── FX
  └── Settlement
        ↓
      Seller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The marketplace can focus on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matching buyers and sellers&lt;/li&gt;
&lt;li&gt;Product discovery&lt;/li&gt;
&lt;li&gt;Pricing&lt;/li&gt;
&lt;li&gt;Customer support&lt;/li&gt;
&lt;li&gt;Trust&lt;/li&gt;
&lt;li&gt;Logistics&lt;/li&gt;
&lt;li&gt;User experience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Financial infrastructure becomes one of the services supporting the marketplace.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Global Payroll Example
&lt;/h2&gt;

&lt;p&gt;Consider another use case.&lt;/p&gt;

&lt;p&gt;A company has employees or contractors across several markets.&lt;/p&gt;

&lt;p&gt;The product doesn't need to become a bank.&lt;/p&gt;

&lt;p&gt;It needs to solve a business problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pay people reliably.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The product could be responsible for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Employee Management
       ↓
Payroll Calculation
       ↓
Payment Instructions
       ↓
Financial Infrastructure
       ↓
Payout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The company's competitive advantage might be payroll automation, reporting, compliance workflows, or employee experience.&lt;/p&gt;

&lt;p&gt;Not the underlying payment rails.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Remittance Example
&lt;/h2&gt;

&lt;p&gt;Remittance products show why this infrastructure layer matters.&lt;/p&gt;

&lt;p&gt;A customer might simply want:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Send money home."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the infrastructure underneath may involve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sender
  ↓
Collection
  ↓
Currency Conversion
  ↓
Liquidity
  ↓
Settlement
  ↓
Local Payout
  ↓
Recipient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The customer should not have to understand any of those layers.&lt;/p&gt;

&lt;p&gt;They should have a simple experience.&lt;/p&gt;

&lt;p&gt;That is the promise of abstraction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Global Businesses Need Global Infrastructure
&lt;/h2&gt;

&lt;p&gt;As businesses become more global, financial infrastructure becomes increasingly important.&lt;/p&gt;

&lt;p&gt;A company may begin in one country.&lt;/p&gt;

&lt;p&gt;Then it acquires customers elsewhere.&lt;/p&gt;

&lt;p&gt;Then it hires internationally.&lt;/p&gt;

&lt;p&gt;Then it pays suppliers across borders.&lt;/p&gt;

&lt;p&gt;Then it expands into new markets.&lt;/p&gt;

&lt;p&gt;Every new market introduces another financial problem.&lt;/p&gt;

&lt;p&gt;The old approach was to build another set of integrations.&lt;/p&gt;

&lt;p&gt;The newer approach is to use infrastructure that is designed to support multiple financial workflows and markets.&lt;/p&gt;

&lt;p&gt;This is where infrastructure becomes a growth enabler rather than simply a technical dependency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Infrastructure Can Accelerate Distribution
&lt;/h2&gt;

&lt;p&gt;There is another important consequence.&lt;/p&gt;

&lt;p&gt;Infrastructure affects how quickly a company can expand.&lt;/p&gt;

&lt;p&gt;Suppose a company has discovered strong product-market fit in one market.&lt;/p&gt;

&lt;p&gt;Its next challenge is expansion.&lt;/p&gt;

&lt;p&gt;If every new market requires rebuilding its financial stack, expansion becomes slow.&lt;/p&gt;

&lt;p&gt;But if financial infrastructure is accessible through reusable APIs and standardized workflows, the company can potentially move faster.&lt;/p&gt;

&lt;p&gt;The equation changes from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Market
    ↓
New Infrastructure
    ↓
New Integrations
    ↓
New Product Work
    ↓
Launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;toward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Market
    ↓
Configure Financial Infrastructure
    ↓
Adapt Product
    ↓
Launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference can be significant.&lt;/p&gt;




&lt;h2&gt;
  
  
  The New Competitive Advantage
&lt;/h2&gt;

&lt;p&gt;This brings us to a larger question.&lt;/p&gt;

&lt;p&gt;If infrastructure becomes increasingly accessible, where does the competitive advantage move?&lt;/p&gt;

&lt;p&gt;It moves upward.&lt;/p&gt;

&lt;p&gt;The infrastructure becomes increasingly standardized.&lt;/p&gt;

&lt;p&gt;The product becomes increasingly differentiated.&lt;/p&gt;

&lt;p&gt;Consider two companies with access to similar financial primitives.&lt;/p&gt;

&lt;p&gt;They could still create completely different products.&lt;/p&gt;

&lt;p&gt;One might build:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A global payroll platform.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A marketplace.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A remittance product.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Another:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A treasury platform.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The underlying capabilities may overlap.&lt;/p&gt;

&lt;p&gt;The customer experience doesn't.&lt;/p&gt;

&lt;p&gt;That is where innovation happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack Becomes More Modular
&lt;/h2&gt;

&lt;p&gt;The fintech stack increasingly starts to resemble modern software architecture.&lt;/p&gt;

&lt;p&gt;Instead of one monolithic system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────┐
│                             │
│      EVERYTHING IN ONE      │
│          COMPANY            │
│                             │
└─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we get specialized layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────┐
│        Your Product         │
├─────────────────────────────┤
│     Financial Workflows     │
├─────────────────────────────┤
│      Infrastructure APIs    │
├─────────────────────────────┤
│ Payments │ FX │ Accounts    │
├─────────────────────────────┤
│ Settlement │ Liquidity      │
├─────────────────────────────┤
│ Banking / Financial Rails   │
└─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer can specialize.&lt;/p&gt;

&lt;p&gt;The product builder doesn't need to own everything.&lt;/p&gt;

&lt;p&gt;They need to orchestrate the right pieces.&lt;/p&gt;




&lt;h2&gt;
  
  
  But Abstraction Doesn't Mean Simplicity
&lt;/h2&gt;

&lt;p&gt;There is an important caveat.&lt;/p&gt;

&lt;p&gt;Abstracting infrastructure does not eliminate complexity.&lt;/p&gt;

&lt;p&gt;It relocates it.&lt;/p&gt;

&lt;p&gt;A company building on financial APIs still needs to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Transaction states&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;Reconciliation&lt;/li&gt;
&lt;li&gt;Compliance requirements&lt;/li&gt;
&lt;li&gt;Webhooks&lt;/li&gt;
&lt;li&gt;Operational monitoring&lt;/li&gt;
&lt;li&gt;Customer support&lt;/li&gt;
&lt;li&gt;Financial risk&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;APIs make infrastructure more accessible.&lt;/p&gt;

&lt;p&gt;They don't make financial engineering trivial.&lt;/p&gt;

&lt;p&gt;That's an important distinction.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Responsibility Moves Up the Stack
&lt;/h2&gt;

&lt;p&gt;When infrastructure is abstracted, the responsibility of the product builder changes.&lt;/p&gt;

&lt;p&gt;You don't necessarily need to understand every underlying payment rail.&lt;/p&gt;

&lt;p&gt;But you absolutely need to understand what your application promises to the customer.&lt;/p&gt;

&lt;p&gt;If your product says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your payment has been received."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You need to be certain that it actually has.&lt;/p&gt;

&lt;p&gt;If your product says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your money is available."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your system needs reliable confirmation.&lt;/p&gt;

&lt;p&gt;If your product says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your payout has been completed."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You need the underlying financial state to support that claim.&lt;/p&gt;

&lt;p&gt;The abstraction layer makes development easier.&lt;/p&gt;

&lt;p&gt;It also makes good engineering practices more important.&lt;/p&gt;




&lt;h2&gt;
  
  
  APIs Are Becoming Financial Building Blocks
&lt;/h2&gt;

&lt;p&gt;The most important shift isn't that APIs exist.&lt;/p&gt;

&lt;p&gt;We've had APIs for years.&lt;/p&gt;

&lt;p&gt;The shift is that &lt;strong&gt;financial capabilities themselves are increasingly exposed as programmable building blocks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers can work with concepts such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
Account
Balance
Payment
Transaction
Payout
Exchange Rate
Webhook
Settlement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These aren't just backend resources.&lt;/p&gt;

&lt;p&gt;They are building blocks for financial products.&lt;/p&gt;

&lt;p&gt;A developer can combine them with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authentication
Databases
AI
Analytics
Notifications
Automation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and create an entirely new product.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI Adds Another Layer
&lt;/h2&gt;

&lt;p&gt;There is another development worth watching.&lt;/p&gt;

&lt;p&gt;AI is increasingly becoming an interface for software.&lt;/p&gt;

&lt;p&gt;Traditionally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Human
 ↓
Code
 ↓
API
 ↓
Infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're beginning to see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Human
 ↓
AI Agent
 ↓
Tools / APIs
 ↓
Financial Infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates interesting possibilities.&lt;/p&gt;

&lt;p&gt;An AI system could potentially help with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment operations&lt;/li&gt;
&lt;li&gt;Treasury monitoring&lt;/li&gt;
&lt;li&gt;Transaction analysis&lt;/li&gt;
&lt;li&gt;Reconciliation workflows&lt;/li&gt;
&lt;li&gt;Financial reporting&lt;/li&gt;
&lt;li&gt;Operational alerts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The infrastructure still matters.&lt;/p&gt;

&lt;p&gt;But the interface for interacting with it is changing.&lt;/p&gt;

&lt;p&gt;This is one reason programmable financial infrastructure becomes even more interesting in an AI-native world.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Financial Stack Is Becoming More Composable
&lt;/h2&gt;

&lt;p&gt;Put all of this together and the picture becomes clearer.&lt;/p&gt;

&lt;p&gt;We have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 AI / AUTOMATION
                       │
                       ▼
                 YOUR PRODUCT
                       │
                       ▼
              FINANCIAL WORKFLOWS
                       │
         ┌─────────────┼─────────────┐
         ▼             ▼             ▼
      Payments       Accounts        FX
         │             │             │
         └─────────────┼─────────────┘
                       ▼
                  Settlement
                       │
              ┌────────┴────────┐
              ▼                 ▼
        Traditional Rails   Stablecoins
              │                 │
              └────────┬────────┘
                       ▼
                    MONEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what makes the new fintech stack so interesting.&lt;/p&gt;

&lt;p&gt;It's no longer one monolithic system.&lt;/p&gt;

&lt;p&gt;It's a collection of layers that can be composed.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Startups
&lt;/h2&gt;

&lt;p&gt;For founders, this changes what is possible.&lt;/p&gt;

&lt;p&gt;You can start with a customer problem.&lt;/p&gt;

&lt;p&gt;Then identify the financial capabilities required to solve it.&lt;/p&gt;

&lt;p&gt;Then assemble those capabilities.&lt;/p&gt;

&lt;p&gt;Instead of beginning with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do we become a financial institution?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can begin with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What financial experience does our customer need?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a dramatically better starting point.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Developers
&lt;/h2&gt;

&lt;p&gt;For developers, the opportunity is equally significant.&lt;/p&gt;

&lt;p&gt;The skill set is shifting.&lt;/p&gt;

&lt;p&gt;You still need to know how to build software.&lt;/p&gt;

&lt;p&gt;But increasingly, you also need to understand how to compose infrastructure.&lt;/p&gt;

&lt;p&gt;The developer of the future may not just ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I write this API call?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They'll ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I compose these financial primitives into a reliable product?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a higher-level engineering problem.&lt;/p&gt;

&lt;p&gt;And potentially a much more valuable one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Financial Infrastructure Providers
&lt;/h2&gt;

&lt;p&gt;For infrastructure providers, the opportunity is to make complexity disappear.&lt;/p&gt;

&lt;p&gt;The best infrastructure isn't necessarily the infrastructure with the most features.&lt;/p&gt;

&lt;p&gt;It's the infrastructure that lets developers accomplish difficult things without needing to understand every underlying system.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good APIs&lt;/li&gt;
&lt;li&gt;Clear documentation&lt;/li&gt;
&lt;li&gt;Reliable infrastructure&lt;/li&gt;
&lt;li&gt;Strong developer tooling&lt;/li&gt;
&lt;li&gt;Good observability&lt;/li&gt;
&lt;li&gt;Predictable behavior&lt;/li&gt;
&lt;li&gt;Easy onboarding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Developer experience becomes part of financial infrastructure itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Future May Be Invisible
&lt;/h2&gt;

&lt;p&gt;The most interesting financial infrastructure may eventually become almost invisible.&lt;/p&gt;

&lt;p&gt;A customer won't care whether a payment passed through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bank A
→ Payment Provider B
→ Stablecoin C
→ Liquidity Provider D
→ Bank E
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They'll simply see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payment successful.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The infrastructure becomes invisible.&lt;/p&gt;

&lt;p&gt;The product becomes visible.&lt;/p&gt;

&lt;p&gt;And that is exactly what good infrastructure is supposed to accomplish.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Afriex Fits Into This Future
&lt;/h2&gt;

&lt;p&gt;Platforms such as Afriex are part of this broader transition.&lt;/p&gt;

&lt;p&gt;The value isn't simply in providing another financial API.&lt;/p&gt;

&lt;p&gt;It's in helping businesses access financial infrastructure that can become part of their own products and operations.&lt;/p&gt;

&lt;p&gt;For a builder, that means the financial layer can become something they &lt;strong&gt;compose into the product&lt;/strong&gt;, rather than something they have to build entirely from scratch.&lt;/p&gt;

&lt;p&gt;The result is a different kind of fintech architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    YOUR BUSINESS
                         │
                         ▼
                  YOUR EXPERIENCE
                         │
                         ▼
                 YOUR APPLICATION
                         │
                         ▼
                  AFRIEX INFRASTRUCTURE
                         │
            ┌────────────┼────────────┐
            ▼            ▼            ▼
         Payments      Accounts       FX
            │            │            │
            └────────────┼────────────┘
                         ▼
                    Settlement
                         │
                         ▼
                  Financial Rails
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The business owns the customer relationship and product experience.&lt;/p&gt;

&lt;p&gt;The infrastructure handles the financial complexity beneath it.&lt;/p&gt;

&lt;p&gt;That's the fundamental idea.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Biggest Shift Isn't Technology
&lt;/h2&gt;

&lt;p&gt;It is tempting to look at all of this as a technology story.&lt;/p&gt;

&lt;p&gt;APIs.&lt;/p&gt;

&lt;p&gt;Cloud infrastructure.&lt;/p&gt;

&lt;p&gt;Stablecoins.&lt;/p&gt;

&lt;p&gt;AI.&lt;/p&gt;

&lt;p&gt;Automation.&lt;/p&gt;

&lt;p&gt;But underneath all of those technologies is a much bigger shift:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who gets to build financial products?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If financial infrastructure becomes increasingly accessible through APIs, then the answer is no longer simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Banks and large financial institutions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Software companies, startups, developers, and businesses that know how to compose the infrastructure.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a profound change.&lt;/p&gt;




&lt;h2&gt;
  
  
  From Owning Infrastructure to Composing Infrastructure
&lt;/h2&gt;

&lt;p&gt;The previous generation of fintech often competed on infrastructure ownership.&lt;/p&gt;

&lt;p&gt;The next generation may increasingly compete on infrastructure composition.&lt;/p&gt;

&lt;p&gt;The question becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What can you combine?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What can you build entirely yourself?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;Because building everything is expensive.&lt;/p&gt;

&lt;p&gt;Composing the right infrastructure can be much faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  The New Fintech Stack
&lt;/h2&gt;

&lt;p&gt;So if we zoom out, the new fintech stack looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────┐
│              EXPERIENCE              │
│          What customers see          │
├──────────────────────────────────────┤
│               PRODUCT                │
│       Business logic &amp;amp; workflows     │
├──────────────────────────────────────┤
│          FINANCIAL APIS              │
│ Accounts • Payments • FX • Payouts  │
├──────────────────────────────────────┤
│         FINANCIAL INFRASTRUCTURE     │
│      Settlement • Liquidity • Rails  │
├──────────────────────────────────────┤
│          PROGRAMMABLE VALUE          │
│              Stablecoins              │
├──────────────────────────────────────┤
│          TRADITIONAL SYSTEMS         │
│        Banks • Networks • Rails      │
└──────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And increasingly, AI and automation can sit across the entire stack.&lt;/p&gt;

&lt;p&gt;The result is a financial ecosystem that is more modular than the one that came before it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Opportunity for Builders
&lt;/h2&gt;

&lt;p&gt;This is what makes the current moment particularly exciting.&lt;/p&gt;

&lt;p&gt;A developer with a good idea doesn't necessarily need to start by building a bank.&lt;/p&gt;

&lt;p&gt;A founder doesn't necessarily need to own every payment rail.&lt;/p&gt;

&lt;p&gt;A marketplace doesn't necessarily need to create its own settlement network.&lt;/p&gt;

&lt;p&gt;A payroll company doesn't necessarily need to build banking infrastructure.&lt;/p&gt;

&lt;p&gt;A remittance startup doesn't necessarily need to recreate the entire global financial system.&lt;/p&gt;

&lt;p&gt;They need to identify the problem.&lt;/p&gt;

&lt;p&gt;Then compose the infrastructure required to solve it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The fintech industry is moving toward a model that looks increasingly familiar to anyone who has built modern software.&lt;/p&gt;

&lt;p&gt;Infrastructure is becoming modular.&lt;/p&gt;

&lt;p&gt;Capabilities are becoming programmable.&lt;/p&gt;

&lt;p&gt;APIs are becoming the interface between businesses and financial systems.&lt;/p&gt;

&lt;p&gt;Stablecoins are introducing new ways to move programmable value.&lt;/p&gt;

&lt;p&gt;AI is creating new ways to interact with software.&lt;/p&gt;

&lt;p&gt;And infrastructure providers are abstracting more of the complexity that once required direct ownership and deep institutional relationships.&lt;/p&gt;

&lt;p&gt;The result is a new fintech stack.&lt;/p&gt;

&lt;p&gt;One where the underlying financial infrastructure can increasingly become a building block.&lt;/p&gt;

&lt;p&gt;And that changes the opportunity for builders.&lt;/p&gt;

&lt;p&gt;The question is no longer simply:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"How do we build the financial infrastructure?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's becoming:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What can we build because the infrastructure is already there?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is where things get interesting.&lt;/p&gt;

&lt;p&gt;Because the next generation of fintech products may not be built by companies that own every financial rail.&lt;/p&gt;

&lt;p&gt;They may be built by companies that know how to &lt;strong&gt;compose the rails into something customers actually want.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The infrastructure is becoming programmable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The money is becoming programmable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now the opportunity is to build what comes next.&lt;/strong&gt;&lt;/p&gt;




</description>
    </item>
    <item>
      <title>Build a Digital Marketplace with Afriex, Fastify and Postgres, Part 2: Checkout and Payouts</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Sun, 16 Aug 2026 06:30:00 +0000</pubDate>
      <link>https://dev.to/afriex/build-a-digital-marketplace-with-afriex-fastify-and-postgres-part-2-checkout-and-payouts-3g7m</link>
      <guid>https://dev.to/afriex/build-a-digital-marketplace-with-afriex-fastify-and-postgres-part-2-checkout-and-payouts-3g7m</guid>
      <description>&lt;p&gt;At the end of &lt;a href="https://dev.to/afriex/build-a-digital-marketplace-with-afriex-fastify-and-postgres-part-1-data-model-and-the-storefront-mej"&gt;Part 1&lt;/a&gt;, Tunde was stuck. He clicked Buy on Amara's ₦12,000 preset pack, an order was created but no money moved, because nothing in Part 1 knows how to move money.&lt;/p&gt;

&lt;p&gt;This part fixes that, in both directions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checkout:&lt;/strong&gt; collect Tunde's ₦12,000 through Afriex: by card, bank transfer, or mobile money, whichever suits him.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payouts:&lt;/strong&gt; track what the platform owes Amara, and send it to her bank account when she asks.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The full source is on &lt;a href="https://github.com/codewithveek/afriex-creator-payout" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; if you want to follow along.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you'll have at the end
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Tunde can pay for an order through the Afriex checkout page.&lt;/li&gt;
&lt;li&gt;A confirmed payment completes the order &lt;strong&gt;and&lt;/strong&gt; credits Amara's balance, minus the platform fee.&lt;/li&gt;
&lt;li&gt;Amara can register her bank account, and the platform verifies it actually belongs to her before any money moves.&lt;/li&gt;
&lt;li&gt;Amara can withdraw her balance on demand, or get paid out on a schedule.&lt;/li&gt;
&lt;li&gt;A failed or ambiguous payout never silently loses money: it either refunds her balance or waits for review.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;p&gt;You need Part 1's code running, plus an &lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;Afriex business account&lt;/a&gt; with API keys in your &lt;code&gt;.env&lt;/code&gt; (&lt;code&gt;AFRIEX_API_KEY&lt;/code&gt; and &lt;code&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&lt;/code&gt;). The withdrawal queue in this part runs on Redis; if you started the repo's &lt;code&gt;docker compose up -d&lt;/code&gt; in Part 1, Redis is already running. The disbursement worker is its own process, started with &lt;code&gt;pnpm worker:dev&lt;/code&gt; alongside &lt;code&gt;pnpm dev&lt;/code&gt;. Everything below assumes the schema and module layout from Part 1.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's look at the whole flow first
&lt;/h2&gt;

&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%2Fxr0fdbas96hx1h0djbg9.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%2Fxr0fdbas96hx1h0djbg9.png" alt="A diagram showing how the payment flows" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Read it top to bottom and it's Tunde's money coming in, then Amara's money going out. Two structural choices in this diagram shape everything that follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;One payment confirmation triggers two separate writes.&lt;/strong&gt; When Tunde's payment clears, the order gets completed (so he gets his download) and a sale gets recorded (so Amara gets credited).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A sale credits a balance; it never sends money.&lt;/strong&gt; Recording that the platform owes Amara ₦10,800 and actually transferring ₦10,800 to her bank are separate acts, possibly days apart. Because they're separate, you can offer instant payouts, weekly sweeps, or minimum-balance thresholds.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Let's create the checkout session
&lt;/h2&gt;

&lt;p&gt;Call the Afriex create session endpoint to create a checkout session, and create an order row pointing at it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/orders/orders.service.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createCheckoutSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;customerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;customerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;successUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cancelUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;sessionUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;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;product&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;productsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPublishedById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productId&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;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getPaymentProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;resolveCheckoutProvider&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;creator&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;creatorsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&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;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCheckoutSession&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;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&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="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&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;customerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&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;successUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;successUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;cancelUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cancelUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;creatorUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;??&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;await&lt;/span&gt; &lt;span class="nx"&gt;ordersRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&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;customerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&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;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&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="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paymentSessionId&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;sessionId&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sessionId&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;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sessionUrl&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;sessionUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;resolveCheckoutProvider&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;The API responds with &lt;code&gt;sessionUrl&lt;/code&gt;, the frontend redirects Tunde there, and he pays on Afriex's page. Here's the provider side of that call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// infra/payment/providers/afriex-checkout.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;createCheckoutSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreateCheckoutSessionParams&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CheckoutSessionResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;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;merchantReference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`co-&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="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&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;hex&lt;/span&gt;&lt;span class="dl"&gt;'&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createSession&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="nf"&gt;toMinorUnits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&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="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&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="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;merchantReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;redirectUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;successUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&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;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&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;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerPhone&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerPhone&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+2340000000000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryCode&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NG&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;channels&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;CARD&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;VIRTUAL_BANK_ACCOUNT&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;MOBILE_MONEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;merchantReference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sessionUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checkoutUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;Three details to slow down on:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;toMinorUnits&lt;/code&gt; converts the amount before it leaves your server.&lt;/strong&gt; Like most payment APIs, Afriex takes amounts in the currency's smallest unit (kobo for naira, cents for dollars). So Amara's ₦12,000.00 product is sent as &lt;code&gt;1200000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;channels&lt;/code&gt; decides what Tunde sees.&lt;/strong&gt; The same call serves a card buyer in the US and a mobile-money buyer in Kenya. You list which channels the platform accepts; Afriex shows Tunde whichever of them work in his country.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;merchantReference&lt;/code&gt;.&lt;/strong&gt; When Afriex later reports "this session was paid," this reference is how you find which order it means.&lt;/p&gt;

&lt;h2&gt;
  
  
  One webhook, two writes
&lt;/h2&gt;

&lt;p&gt;The only way to know when Tunde pays is when Afriex sends you a webhook event, &lt;code&gt;CHECKOUT_SESSION.CREATED&lt;/code&gt;. Your server verifies the request signature to prove it came from Afriex, then acts on it.&lt;/p&gt;

&lt;p&gt;One handler acts on the payment confirmation, and it makes both writes from the diagram:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/sales/sales.controller.ts&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;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isCheckoutCompletedEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;sessionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTransactionId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ordersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;completeOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sessionId&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;creator&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;creatorsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&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;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;currency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;creator&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;salesService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recordConfirmedPayment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;paymentIntentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;creatorUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;grossAmount&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;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GHS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;KES&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;completeOrder&lt;/code&gt; updates the &lt;code&gt;orders.status&lt;/code&gt; to &lt;code&gt;COMPLETED&lt;/code&gt; and issues the download token. &lt;code&gt;recordConfirmedPayment&lt;/code&gt; is where this article's tables begin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/sales/sales.service.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;recordConfirmedPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfirmedPaymentEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;&amp;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;existing&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;salesRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByPaymentIntentId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentIntentId&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;existing&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;existing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// webhook replay — already processed&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;creator&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;creatorsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorUserId&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;sale&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;salesRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paymentIntentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentIntentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;grossAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grossAmount&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PAID&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;earningsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processSale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sale&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;sale&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;That &lt;code&gt;existing&lt;/code&gt; check exists because webhooks get delivered more than once. If your endpoint is slow to respond, or the connection drops after you processed the event but before Afriex has received your &lt;code&gt;200 OK&lt;/code&gt; response, it retries. Normal behavior, not a bug. Handle the same event twice naively and Amara gets credited ₦10,800 twice for one sale.&lt;/p&gt;

&lt;p&gt;The check at the top is the fast path, but it's not the real protection. Two replays arriving at the same moment can both pass it before either inserts. The real protection is &lt;code&gt;paymentIntentId&lt;/code&gt; which carries a &lt;code&gt;UNIQUE&lt;/code&gt; constraint, so the second insert fails at the database no matter how the requests race.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sale → earning → balance
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;processSale&lt;/code&gt; splits the money and credits Amara. Tunde paid ₦12,000, the platform fee is 10% (which is ₦1,200), then Amara gets ₦10,800.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/earnings/earnings.service.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;processSale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sale&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Earning&lt;/span&gt;&lt;span class="o"&gt;&amp;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;fee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeFee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grossAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PLATFORM_FEE_PERCENT&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;earning&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;earningsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;saleId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;grossAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grossAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;platformFeeAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platformFeeAmount&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;fee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;netAmount&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="nx"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CONFIRMED&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;// Credit the creator's balance in the sale currency — not the payout currency.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;creatorsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incrementBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;netAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Gross settles into that currency's pool account; the fee stays platform revenue.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;poolAccount&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;poolAccountsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByCurrencyOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;poolAccountsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;settleSaleIntoPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poolAccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grossAmount&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;earning&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;Notice there is no Afriex call in this function. It's pure bookkeeping, and it writes to two different kinds of account:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amara's balance&lt;/strong&gt; goes up by ₦10,800. This is a liability, money the platform owes her.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The NGN pool account&lt;/strong&gt; goes up by ₦12,000. A pool account tracks the money the platform is actually holding in a given currency. The ₦1,200 gap between what the pool holds and what creators are owed is the platform's revenue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Balances and pools are both tracked &lt;strong&gt;per currency&lt;/strong&gt;. An NGN sale credits an NGN balance and settles into the NGN pool; a USD sale uses its own pool. Keeping the currencies in separate books means a report can always answer "how much NGN do we hold vs. owe?" without any exchange-rate assumptions. It's also why the withdrawal code below refuses to pay an NGN balance out to a USD bank account.&lt;/p&gt;




&lt;h2&gt;
  
  
  Onboarding Amara for payout
&lt;/h2&gt;

&lt;p&gt;Before Amara can withdraw anything, she registers her bank account. This is the one step where the platform asks Afriex to confirm something is &lt;em&gt;real&lt;/em&gt; rather than just storing what was typed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// infra/afriex/afriex-client.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;registerRecipient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RegisterRecipientParams&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;RegisterRecipientResult&lt;/span&gt;&lt;span class="o"&gt;&amp;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;customer&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// A payment method is only VERIFIED when Afriex's own account-resolution&lt;/span&gt;
  &lt;span class="c1"&gt;// endpoint returns an account-holder name matching the creator's name on&lt;/span&gt;
  &lt;span class="c1"&gt;// file — creating the payment method alone proves nothing about whether&lt;/span&gt;
  &lt;span class="c1"&gt;// the account belongs to this creator.&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;verified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;resolvedAccountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&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;resolved&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BANK_ACCOUNT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bankCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;resolvedAccountName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resolved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;recipientName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;verified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resolvedAccountName&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;namesMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolvedAccountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Resolution failed — leave the method PENDING rather than guessing.&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;paymentMethod&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BANK_ACCOUNT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resolvedAccountName&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;institution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bankCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bankName&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="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;afriexCustomerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;afriexPaymentMethodId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;paymentMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;verified&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;&lt;strong&gt;Account resolution&lt;/strong&gt; is the step worth naming: when Amara provides her account number and bank code (gotten from the Afriex institutions list), Afriex verifies the account and returns the registered holder's name. Let's say "AMARA JOHNSON" is the name returned; &lt;code&gt;namesMatch&lt;/code&gt; compares that against the name on her profile, and only a match marks the method &lt;code&gt;VERIFIED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The failure case is the reason this exists. If Amara fat-fingers one digit, the resolved name comes back as some stranger's, the match fails, and the method stays &lt;code&gt;PENDING&lt;/code&gt;, instead of her first ₦10,800 payout landing in that stranger's account, unrecoverable. Only &lt;code&gt;VERIFIED&lt;/code&gt; methods make a creator &lt;code&gt;payoutEligible&lt;/code&gt;, and only eligible creators enter the scheduled sweep.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requesting and disbursing a withdrawal
&lt;/h2&gt;

&lt;p&gt;Amara can withdraw on demand, or a scheduled sweep can pay out every eligible creator's balance. Both paths converge on the same queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/withdrawals/withdrawals.service.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;requestOnDemandWithdrawal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;CurrencyCode&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;creator&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;creatorsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertCooldownElapsed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;creator&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;withdrawCurrency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payoutCurrency&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;payoutMethod&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;payoutMethodsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getVerifiedMethodOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;creatorId&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;payoutMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;withdrawCurrency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Your verified payout method is in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;payoutMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;poolAccount&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;poolAccountsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByCurrencyOrThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withdrawCurrency&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createAndQueue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payoutMethodId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payoutMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;poolAccountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;poolAccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;amount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;available&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="nx"&gt;withdrawCurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ON_DEMAND&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;Queueing instead of calling Afriex inline matters because the transfer call can be slow, can fail, and needs retries, none of which should hold Amara's payout request open. The queue here is BullMQ, a Redis-backed job queue; a worker picks the job up and makes the actual call.&lt;/p&gt;

&lt;h3&gt;
  
  
  The worker, and the one distinction that protects the money
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// infra/queue/worker.ts&lt;/span&gt;
&lt;span class="k"&gt;try&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;transfer&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;afriexClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTransfer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payoutMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;afriexCustomerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payoutMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;afriexPaymentMethodId&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;withdrawal&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;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;withdrawalsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markProcessing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;afriexTransactionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;poolAccountsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrementBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;poolAccount&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withdrawal&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;withdrawalsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markPaid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;isFinalAttempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attemptsMade&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attempts&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;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;isFinalAttempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// let BullMQ retry&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isDefiniteRejection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Afriex explicitly rejected the request (4xx, not a rate limit) —&lt;/span&gt;
    &lt;span class="c1"&gt;// safe to fail the withdrawal and credit the balance back.&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;failWithdrawal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;// Timeout, 5xx, or network error: Afriex may have already processed&lt;/span&gt;
    &lt;span class="c1"&gt;// the transfer server-side. Crediting the balance back here could&lt;/span&gt;
    &lt;span class="c1"&gt;// double-pay the creator if it did go through, so this is parked as&lt;/span&gt;
    &lt;span class="c1"&gt;// UNKNOWN for manual reconciliation instead of guessed at.&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;withdrawalsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markUnknown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&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;When the transfer call fails on its final retry, &lt;em&gt;do we know for certain the money didn't move?&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;What the worker does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4xx rejection (bad account, insufficient pool)&lt;/td&gt;
&lt;td&gt;Afriex definitely did not send money&lt;/td&gt;
&lt;td&gt;Mark &lt;code&gt;FAILED&lt;/code&gt;, credit Amara's balance back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeout, 5xx, network error&lt;/td&gt;
&lt;td&gt;Afriex &lt;em&gt;might&lt;/em&gt; have sent it before things broke&lt;/td&gt;
&lt;td&gt;Mark &lt;code&gt;UNKNOWN&lt;/code&gt;, touch nothing, flag for a human&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;UNKNOWN&lt;/code&gt; state is the careful choice. Suppose the request timed out but Afriex actually completed the transfer. If the worker "helpfully" credited ₦10,800 back to Amara's balance, she'd have the money in her bank &lt;em&gt;and&lt;/em&gt; the balance to withdraw it again: the platform pays twice. Parking the withdrawal for &lt;strong&gt;reconciliation&lt;/strong&gt; by a human (or a later job) checking Afriex's transaction record against yours costs a support ticket. Guessing costs real money.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing the loop
&lt;/h3&gt;

&lt;p&gt;Bank transfers aren't always instant, so a transfer that left in &lt;code&gt;PROCESSING&lt;/code&gt; state gets its final answer by webhook, the same pattern as checkout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// infra/afriex/afriex-webhook.router.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&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="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&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="c1"&gt;// The idempotency key exists in our DB before the transfer call was even&lt;/span&gt;
&lt;span class="c1"&gt;// made, so looking withdrawals up by it — instead of by afriexTransactionId,&lt;/span&gt;
&lt;span class="c1"&gt;// which is only written after the call returns — means the webhook can&lt;/span&gt;
&lt;span class="c1"&gt;// never arrive "too early" to find its withdrawal.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&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;withdrawal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isUuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;withdrawalsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;withdrawalsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByAfriexTransactionId&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMPLETED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SUCCESS&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;withdrawalsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markPaid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CANCELLED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REJECTED&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;withdrawalsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`Afriex reported &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;status&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;creatorsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incrementBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withdrawal&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="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;poolAccountsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incrementBalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;poolAccountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withdrawal&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;idempotency key&lt;/strong&gt; sent to Afriex is the withdrawal's own database ID, chosen &lt;em&gt;before&lt;/em&gt; the transfer call, not after. Because the ID existed in Postgres first, the confirmation webhook can always find its withdrawal by that key, even if it somehow arrives before the transfer call's response was recorded. Sending a key also means a retried &lt;code&gt;createTransfer&lt;/code&gt; with the same key can't produce a second transfer on Afriex's side, the same double-send protection Part 1's unique constraint gave orders, now on the provider's side of the wire.&lt;/p&gt;

&lt;p&gt;Same handler shape as checkout: verify the signature, find the row this event is about, move it to a terminal state, and, on failure, put the money back where it came from, in both books.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where we are
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;✅ Tunde pays through a hosted checkout: card, bank transfer, or mobile money&lt;/li&gt;
&lt;li&gt;✅ One webhook completes his order and records Amara's sale&lt;/li&gt;
&lt;li&gt;✅ A 10% fee split leaves ₦10,800 on Amara's NGN balance and ₦12,000 in the NGN pool&lt;/li&gt;
&lt;li&gt;✅ Amara's bank account is verified by name before it can receive money&lt;/li&gt;
&lt;li&gt;✅ Withdrawals retry, refund on definite failure, and park as &lt;code&gt;UNKNOWN&lt;/code&gt; when the outcome is uncertain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Afriex Business API's multi-currency payout support gives you the opportunity to send and receive payments globally, whether the goal is personal or business, small team or large enterprise. The &lt;a href="https://docs.afriex.com" rel="noopener noreferrer"&gt;docs&lt;/a&gt; cover everything this series didn't touch.&lt;/p&gt;

&lt;p&gt;The full source for this project, including the withdrawal cooldown, minimum-amount logic, and the scheduled sweep cron, is on &lt;a href="https://github.com/codewithveek/afriex-creator-payout" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Questions or feedback? Drop them in the comments or reach out on X &lt;a href="https://x.com/codewithveek" rel="noopener noreferrer"&gt;@codewithveek&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>payments</category>
      <category>marketplace</category>
      <category>crossborder</category>
    </item>
    <item>
      <title>Build a Digital Marketplace with Afriex, Fastify and Postgres, Part 1: Data Model and the Storefront</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Sat, 15 Aug 2026 18:25:07 +0000</pubDate>
      <link>https://dev.to/afriex/build-a-digital-marketplace-with-afriex-fastify-and-postgres-part-1-data-model-and-the-storefront-mej</link>
      <guid>https://dev.to/afriex/build-a-digital-marketplace-with-afriex-fastify-and-postgres-part-1-data-model-and-the-storefront-mej</guid>
      <description>&lt;p&gt;Amara designs Lightroom presets in Lagos. She wants to sell a pack for ₦12,000 to buyers in Nigeria, Ghana, Kenya, and the US.&lt;/p&gt;

&lt;p&gt;Tunde wants to buy that pack. He wants to pay with his card, get the download link in his inbox, and never create an account.&lt;/p&gt;

&lt;p&gt;Amara wants the ₦12,000 to land in her bank account.&lt;/p&gt;

&lt;p&gt;That is the whole product. You've probably used it before: it's Gumroad, or Selar if you're in Nigeria. This series builds a small version of the same thing, and like those platforms, it splits cleanly in two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything up to &lt;em&gt;"a paid order exists"&lt;/em&gt; is a &lt;strong&gt;storefront&lt;/strong&gt;. Products, listings, carts, orders. Mostly reads and writes to your own database.&lt;/li&gt;
&lt;li&gt;Everything after that is &lt;strong&gt;payments&lt;/strong&gt;. Charging Tunde, tracking what you owe Amara, paying her out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This series keeps the halves apart on purpose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1 (this article):&lt;/strong&gt; the four tables and the Fastify API that serve the storefront. No payment provider is involved anywhere in it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2:&lt;/strong&gt; wiring Afriex into what Part 1 built: Checkout to collect Tunde's money, Payouts to send Amara hers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full source is on &lt;a href="https://github.com/codewithveek/afriex-creator-payout" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;: a Fastify + Postgres backend with a Next.js storefront on top. This article covers the backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll have at the end
&lt;/h2&gt;

&lt;p&gt;A running API where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A creator can sign in, create a product, and publish it.&lt;/li&gt;
&lt;li&gt;Anyone can list published products and view one, without an account.&lt;/li&gt;
&lt;li&gt;An order can be created for a product and later marked paid.&lt;/li&gt;
&lt;li&gt;A paid order hands back a download link that expires.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No money moves. That's Part 2's job, and it will not require changing a single table you build here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;p&gt;You need Node 20+, Docker, and a terminal. You should have built a REST API before, in any framework. You do &lt;strong&gt;not&lt;/strong&gt; need to have used Fastify or Drizzle; I will explain everything step by step.&lt;/p&gt;

&lt;p&gt;The repo ships a &lt;code&gt;docker-compose.yml&lt;/code&gt; that starts Postgres (and Redis, which Part 2's payout queue will need), so there's nothing to install by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/codewithveek/afriex-creator-payout
&lt;span class="nb"&gt;cd &lt;/span&gt;afriex-creator-payout
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;        &lt;span class="c"&gt;# starts Postgres and Redis&lt;/span&gt;

&lt;span class="nb"&gt;cd &lt;/span&gt;server
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;pnpm db:migrate
pnpm dev                    &lt;span class="c"&gt;# API on http://localhost:4000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;.env&lt;/code&gt;, set &lt;code&gt;DATABASE_URL&lt;/code&gt; to match the compose credentials:&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="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgresql://afriex:afriex_dev_password@localhost:5432/afriex_creator_payout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd rather run your own Postgres 16+, skip the compose step and point &lt;code&gt;DATABASE_URL&lt;/code&gt; at it instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's start from what the app has to do
&lt;/h2&gt;

&lt;p&gt;Before writing any schema, let's write down the actions as a list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A creator lists a product for a price in a currency.&lt;/li&gt;
&lt;li&gt;A creator keeps a product hidden while they're still editing it.&lt;/li&gt;
&lt;li&gt;Anyone can browse published products.&lt;/li&gt;
&lt;li&gt;A buyer buys one product, with or without an account.&lt;/li&gt;
&lt;li&gt;A buyer gets a download link that only works for them.&lt;/li&gt;
&lt;li&gt;Later, the platform pays the creator.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those six list items are the schema. Item 1 needs &lt;code&gt;creators&lt;/code&gt; and &lt;code&gt;products&lt;/code&gt;. Item 4 needs &lt;code&gt;orders&lt;/code&gt; and an optional &lt;code&gt;customers&lt;/code&gt;. Item 6 is Part 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four tables
&lt;/h2&gt;

&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%2Fo0zrrar6vdkw03ua0u5u.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%2Fo0zrrar6vdkw03ua0u5u.png" alt="A diagram showing the four tables" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;creators&lt;/code&gt;: people who sell. This table holds the payout details Part 2 will read.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;products&lt;/code&gt;: what they sell.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;customers&lt;/code&gt;: people who bought and chose to create an account.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;orders&lt;/code&gt;: one row per purchase attempt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four design decisions in there are worth walking through slowly, because each one prevents a specific bug.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decision 1: buyers and sellers live in separate tables
&lt;/h2&gt;

&lt;p&gt;Amara is a creator. Tunde is a customer. Amara might also buy someone else's presets, so she can be both, but the platform never forces it.&lt;/p&gt;

&lt;p&gt;More importantly, Tunde can buy &lt;strong&gt;without an account at all&lt;/strong&gt;. He types his email at checkout and leaves. That's a &lt;em&gt;guest checkout&lt;/em&gt;, and it's why &lt;code&gt;orders.customer_id&lt;/code&gt; is nullable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set null&lt;/span&gt;&lt;span class="dl"&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 order still stores his email and name directly, so you can email him the download link whether or not a &lt;code&gt;customers&lt;/code&gt; row exists.&lt;/p&gt;

&lt;p&gt;If you'd instead forced every buyer through a shared &lt;code&gt;users&lt;/code&gt; table, you'd have to create an account for Tunde before he can pay. That's a signup form standing between a willing buyer and a checkout button. Notice that Gumroad and Selar both let you buy with nothing but an email for exactly this reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 2: money is &lt;code&gt;numeric(14, 2)&lt;/code&gt;, never a float
&lt;/h2&gt;

&lt;p&gt;Postgres has a &lt;code&gt;real&lt;/code&gt; and a &lt;code&gt;double precision&lt;/code&gt; type. Don't reach for either. Here's why, in a Node REPL:&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;16.08&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;           &lt;span class="c1"&gt;// three copies of a $16.08 product&lt;/span&gt;
&lt;span class="mf"&gt;48.239999999999995&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;4.35&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;          &lt;span class="c1"&gt;// converting $4.35 to cents&lt;/span&gt;
&lt;span class="mf"&gt;434.99999999999994&lt;/span&gt;

&lt;span class="o"&gt;&amp;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="mf"&gt;1.005&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="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;   &lt;span class="c1"&gt;// rounding $1.005 to the nearest cent&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;                     &lt;span class="c1"&gt;// not 1.01&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Floating-point numbers store binary fractions. Decimal amounts like 16.08 have no exact binary form, so every arithmetic operation drifts by a fraction of a cent. Do that across a few thousand orders and a payout report stops matching the ledger.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;numeric&lt;/code&gt; stores digits exactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price&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;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;precision: 14&lt;/code&gt; means up to 14 total digits. &lt;code&gt;scale: 2&lt;/code&gt; means two of them sit after the decimal point. That's up to $999,999,999,999.99 per product, more headroom than any preset pack needs, and cheap to keep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One thing that catches people:&lt;/strong&gt; the Postgres driver returns &lt;code&gt;numeric&lt;/code&gt; columns to JavaScript as &lt;strong&gt;strings&lt;/strong&gt;, not numbers, precisely so it doesn't lose the precision you just paid for. So &lt;code&gt;product.price&lt;/code&gt; is &lt;code&gt;"16.08"&lt;/code&gt;. If you do &lt;code&gt;product.price * 3&lt;/code&gt; you're back in float land. Do money arithmetic either in SQL or with a decimal library like &lt;code&gt;decimal.js&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 3: currency is an enum, not a &lt;code&gt;varchar&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// infra/database/schema/enums.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currencyEnum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pgEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&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;NGN&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;GHS&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;KES&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orderStatusEnum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pgEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;order_status&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PENDING&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;COMPLETED&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;REFUNDED&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;FAILED&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;A Postgres enum is a custom type with a fixed list of allowed values. Insert anything outside the list and the database rejects the row.&lt;/p&gt;

&lt;p&gt;With a &lt;code&gt;varchar&lt;/code&gt; currency column, nothing stops &lt;code&gt;'ngn'&lt;/code&gt;, &lt;code&gt;'NGN '&lt;/code&gt;, &lt;code&gt;'Naira'&lt;/code&gt;, and &lt;code&gt;'NGN'&lt;/code&gt; from all ending up in the same column, and then a payout query that filters &lt;code&gt;WHERE currency = 'NGN'&lt;/code&gt; silently misses orders. The enum makes that impossible at the storage layer instead of hoping every code path remembers to normalize.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;products.currency&lt;/code&gt;, &lt;code&gt;orders.currency&lt;/code&gt;, and Part 2's payout tables all reference this same type. Adding a fifth currency means writing a migration (&lt;code&gt;ALTER TYPE currency ADD VALUE 'ZAR'&lt;/code&gt;), which is exactly the amount of friction supporting a new corridor deserves.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In Postgres 12 and later you can add an enum value inside a transaction, but you can't &lt;em&gt;use&lt;/em&gt; the new value until that transaction commits. Keep the &lt;code&gt;ADD VALUE&lt;/code&gt; migration separate from any migration that writes rows with it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Decision 4: how to search an encrypted email column
&lt;/h2&gt;

&lt;p&gt;Emails are encrypted at rest, so &lt;code&gt;customers.email&lt;/code&gt; holds ciphertext rather than &lt;code&gt;tunde@example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That breaks a query you will absolutely need. Tunde checks out as a guest today. Next month he creates an account with the same address, and you want to attach his old orders to it. The natural query is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'tunde@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns nothing. Proper encryption is &lt;em&gt;randomized&lt;/em&gt;: encrypting the same email twice produces two different ciphertexts, on purpose, so an attacker with the database can't tell which two customers share an address. Randomized ciphertext means equality comparison is off the table.&lt;/p&gt;

&lt;p&gt;The fix is a &lt;strong&gt;blind index&lt;/strong&gt;: a second column holding a keyed hash of the value, used only for exact-match lookups.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// infra/crypto/blind-index.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHmac&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;emailBlindIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&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;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BLIND_INDEX_KEY&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;customerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer_email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;                                    &lt;span class="c1"&gt;// ciphertext&lt;/span&gt;
&lt;span class="nx"&gt;customerEmailHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer_email_hash&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;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// blind index&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lookups then run against the hash, never the plaintext:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;emailBlindIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&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;previousOrders&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerEmailHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hash&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;Three details that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HMAC, not plain SHA-256.&lt;/strong&gt; There are only so many plausible email addresses. Someone holding a table of plain hashes can grind through a wordlist and recover addresses in minutes. HMAC mixes in a secret key that lives outside the database, so a stolen dump is useless without it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalize before hashing.&lt;/strong&gt; &lt;code&gt;Tunde@Example.com&lt;/code&gt; and &lt;code&gt;tunde@example.com&lt;/code&gt; must produce the same hash, or the lookup misses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact match only.&lt;/strong&gt; You can't do &lt;code&gt;LIKE '%@gmail.com'&lt;/code&gt; or sort alphabetically on a blind index. If you need those, you need a different design, usually a searchable subset stored separately, with its own risk tradeoff.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The products table
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// infra/database/schema/products.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pgTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;products&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;defaultRandom&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;creator_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;creators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cascade&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;

    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&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;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;price&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;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&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="nf"&gt;currencyEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;fileUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file_url&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;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file_name&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;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;fileSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file_size&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;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scale&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="na"&gt;published&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created_at&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;withTimezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;defaultNow&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated_at&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;withTimezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;defaultNow&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="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idx_products_creator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idx_products_published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idx_products_published_created&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idx_products_creator_published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&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;&lt;strong&gt;&lt;code&gt;published&lt;/code&gt; replaces a drafts table.&lt;/strong&gt; A product Amara is still writing copy for has &lt;code&gt;published = false&lt;/code&gt; and never appears in public queries. One boolean, one table, nothing to keep in sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The indexes exist because of specific queries.&lt;/strong&gt; Don't add indexes by vibes: every index slows down writes and costs disk. Each one here has a query behind it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;th&gt;The query it serves&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(published, created_at)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The public storefront feed: newest published products first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(creator_id, published)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Amara's dashboard, filtered to her live products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;(creator_id)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Everything else scoped to one creator&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The order of columns in a composite index matters. Postgres can use &lt;code&gt;(published, created_at)&lt;/code&gt; for a query that filters on &lt;code&gt;published&lt;/code&gt; alone, but not for one that filters on &lt;code&gt;created_at&lt;/code&gt; alone, because an index is usable from its leftmost column rightward. Put the column you filter for equality first, the one you sort or range-scan second.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a request travels through the app
&lt;/h2&gt;

&lt;p&gt;Every domain (products, orders, customers, and creators) has the same number of files. &lt;code&gt;app.ts&lt;/code&gt; only bolts modules together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.ts&lt;/span&gt;
&lt;span class="k"&gt;export&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;buildApp&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Fastify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productsRoutes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ordersRoutes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customersRoutes&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;app&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;Here's what each layer is allowed to do:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Its one job&lt;/th&gt;
&lt;th&gt;May import&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Router&lt;/td&gt;
&lt;td&gt;Declare the URL and which guards run before it&lt;/td&gt;
&lt;td&gt;Controller, middleware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controller&lt;/td&gt;
&lt;td&gt;Read the HTTP request, shape the HTTP response&lt;/td&gt;
&lt;td&gt;Service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service&lt;/td&gt;
&lt;td&gt;Enforce the business rules&lt;/td&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repository&lt;/td&gt;
&lt;td&gt;Talk to Postgres&lt;/td&gt;
&lt;td&gt;Drizzle, schema&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A controller never writes SQL. A repository never knows an HTTP request exists. That's the whole convention, and it's what lets Part 2 add a payments module without editing any of these files.&lt;/p&gt;

&lt;h3&gt;
  
  
  The router: the access model in one screen
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/products/products.router.ts&lt;/span&gt;
&lt;span class="k"&gt;export&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;productsRoutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FastifyInstance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/products&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;preHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CREATOR&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;validateBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CreateProductSchema&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productsController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/products/mine&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;preHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CREATOR&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productsController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listMyProducts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// No preHandler. This is the public storefront feed.&lt;/span&gt;
  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/products&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;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productsController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listPublished&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;fastify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/products/:id&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;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;productsController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getById&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;&lt;code&gt;preHandler&lt;/code&gt; is Fastify's term for functions that run before the route handler and can reject the request early. Reading this one file tells you exactly who can access what: creating and listing your own products needs a creator session; the public feed and the product page need nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The controller, and a leak it prevents
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/products/products.controller.ts&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sanitizeForPublic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fileUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;product&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;rest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;productsController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;listPublished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FastifyRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FastifyReply&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;pag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parsePagination&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="p"&gt;}&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;productsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPublished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;page&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="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;pag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;pag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pageSize&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;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sanitizeForPublic&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;buildPaginationMeta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sanitizeForPublic&lt;/code&gt; strips the file fields from a product before it goes out in a public response.&lt;/p&gt;

&lt;p&gt;The same &lt;code&gt;products&lt;/code&gt; row backs two very different responses: Amara's dashboard, where she needs to see her uploaded file, and the public feed, where &lt;code&gt;fileUrl&lt;/code&gt; is the paid download. Forget to strip it and &lt;code&gt;GET /api/products&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// leaking&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9d1b…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lagos Golden Hour Presets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12000.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;"fileUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.example.com/files/lagos-presets.zip"&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// 👈 free for everyone&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;Instead of what it should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9d1b…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lagos Golden Hour Presets"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"12000.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;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NGN"&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;You could solve it with a second table or a database view. Stripping the fields once, at the HTTP boundary, is less machinery, as long as it happens in &lt;em&gt;one&lt;/em&gt; function that every public response goes through, rather than being re-remembered at each endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  The service: this is where the business rules live
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/products/products.service.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;&amp;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;product&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;productsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&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;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product not found&lt;/span&gt;&lt;span class="dl"&gt;'&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;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;creatorId&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;NotFoundError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;productsRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;))&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that both failures throw the same &lt;code&gt;NotFoundError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's deliberate. If "you don't own this" returned a 403 and "no such product" returned a 404, then a creator could loop through product IDs and use the status codes to map out the catalogue: 403 means "real product, someone else's", 404 means "nothing here". Returning 404 for both tells the prober nothing. The cost is that a creator debugging their own integration sees a slightly less helpful error, a fair trade, and one worth writing in a comment so nobody "fixes" it later.&lt;/p&gt;

&lt;h3&gt;
  
  
  The repository: the only file that interacts with Drizzle
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// modules/products/products.repository.ts&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findPublished&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;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;rows&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;limit&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;total&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;published&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&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;
  
  
  The orders table, where the storefront stops
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// infra/database/schema/orders.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pgTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;defaultRandom&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;productId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;restrict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;creatorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;creator_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;creators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;restrict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set null&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;

    &lt;span class="na"&gt;customerEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer_email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;customerEmailHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer_email_hash&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;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;customerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;customer_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&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="nf"&gt;numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;amount&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;precision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&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="nf"&gt;currencyEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;currency&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;orderStatusEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="na"&gt;paymentSessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payment_session_id&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;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

    &lt;span class="na"&gt;downloadTokenEncrypted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;download_token_encrypted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;downloadTokenHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;download_token_hash&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;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;downloadTokenExpiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;download_token_expires_at&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;withTimezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;

    &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created_at&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;withTimezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;defaultNow&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated_at&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;withTimezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;defaultNow&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;h3&gt;
  
  
  The order row exists before any money moves
&lt;/h3&gt;

&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%2Fioaxxkt5vk8a3c3dybww.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%2Fioaxxkt5vk8a3c3dybww.png" alt="An image showing the order flow" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When Tunde clicks Buy, the API writes a &lt;code&gt;PENDING&lt;/code&gt; order first, with a &lt;code&gt;paymentSessionId&lt;/code&gt;, and only then calls the payment provider. Later, a confirmation flips the row to &lt;code&gt;COMPLETED&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That ordering does real work. &lt;code&gt;paymentSessionId&lt;/code&gt; is &lt;code&gt;unique&lt;/code&gt;, which makes order creation idempotent: if Tunde double-clicks Buy or his phone retries the request on a flaky connection, the second insert hits a unique-constraint violation instead of creating a second order and a second charge. Running it twice with the same session has the same effect as running it once.&lt;/p&gt;

&lt;p&gt;Creating an order 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;-X&lt;/span&gt; POST http://localhost:3000/api/orders &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "productId": "9d1b…",
    "customerEmail": "tunde@example.com",
    "customerName": "Tunde A."
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4f22…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PENDING"&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;"12000.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;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NGN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"paymentSessionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sess_01HZ…"&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;At this point in Part 1, &lt;code&gt;paymentSessionId&lt;/code&gt; is generated locally. In Part 2 it becomes the ID Afriex hands back when you open a checkout session, and nothing else on this page changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Download access is a token, not a boolean
&lt;/h3&gt;

&lt;p&gt;A paid order gets a signed download token. The raw token goes out by email or redirect; the database only ever stores &lt;code&gt;downloadTokenHash&lt;/code&gt;. Every download request hashes the incoming token and compares.&lt;/p&gt;

&lt;p&gt;The reason is a bad day. If someone reads your database, hashes give them nothing usable: they can't reverse a hash into a working token. Storing raw tokens would hand them working download links for every order you've ever fulfilled.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;downloadTokenExpiresAt&lt;/code&gt; limits the other bad day: a link forwarded into a WhatsApp group stops working in 24 hours instead of forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  The delete rules encode what each row means
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;productId&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="nl"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;restrict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nl"&gt;creatorId&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="nl"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;restrict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nl"&gt;customerId&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="nl"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set null&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;restrict&lt;/code&gt; means Postgres refuses to delete a product or creator that has orders. An order is a financial record. If Amara deletes a product, the row proving Tunde paid ₦12,000 for it must survive; you'll need it for refunds, support, and Part 2's payouts. Deactivate products; don't delete them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;set null&lt;/code&gt; on the customer is the opposite case. If Tunde deletes his account, the order stays (you still owe Amara for it) and simply stops pointing at a customer.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this table deliberately doesn't know
&lt;/h3&gt;

&lt;p&gt;Nowhere in &lt;code&gt;orders&lt;/code&gt; is there a column naming a payment provider. Stripe, Paystack, Afriex Checkout: the schema can't tell which one confirmed the payment.&lt;/p&gt;

&lt;p&gt;That's what makes Part 2 an additive change. Whatever collects the money has exactly one job against this table: flip &lt;code&gt;PENDING&lt;/code&gt; to &lt;code&gt;COMPLETED&lt;/code&gt;. Everything downstream (the download token, Amara's dashboard, Tunde's order history) already works off &lt;code&gt;status&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where we are
&lt;/h2&gt;

&lt;p&gt;You now have a storefront that runs on its own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Amara can create a product and publish it&lt;/li&gt;
&lt;li&gt;✅ Anyone can browse published products, without leaking the file URL&lt;/li&gt;
&lt;li&gt;✅ An order can be created for a guest buyer and marked paid&lt;/li&gt;
&lt;li&gt;✅ A paid order yields an expiring download link&lt;/li&gt;
&lt;li&gt;❌ No money has actually changed hands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part 2 adds exactly two things, and neither one touches the tables above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A checkout module&lt;/strong&gt; that opens an Afriex checkout session and stores its ID in &lt;code&gt;orders.paymentSessionId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A webhook handler&lt;/strong&gt; that verifies Afriex's callback and flips &lt;code&gt;orders.status&lt;/code&gt; to &lt;code&gt;COMPLETED&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then Amara's payouts. This is also where the platform comparison shows the true capabilities of Afriex: Selar pays creators out locally, but Afriex disburses across 30+ countries (bank transfer in Nigeria, mobile money in Kenya or Ghana, SWIFT to the US or UK), so the same payout module works whether Amara banks in Lagos or her co-creator banks in Nairobi.&lt;/p&gt;

&lt;p&gt;In the next part of this series, you will learn how to integrate the Afriex Business API into everything built here: creating hosted checkout sessions so buyers can pay by card, bank transfer, or mobile money, handling the webhook that confirms a payment and completes the order, splitting each sale into the platform fee and the creator's earnings, verifying a creator's bank account before any money is sent to it, and disbursing payouts on demand or on a schedule. See you in Part 2.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>marketplace</category>
      <category>payments</category>
      <category>crossborder</category>
    </item>
    <item>
      <title>How to Accept International Payments as an African Developer or Business</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Thu, 30 Jul 2026 18:44:40 +0000</pubDate>
      <link>https://dev.to/afriex/how-to-accept-international-payments-as-an-african-developer-or-business-33eh</link>
      <guid>https://dev.to/afriex/how-to-accept-international-payments-as-an-african-developer-or-business-33eh</guid>
      <description>&lt;p&gt;If you're an African developer, SaaS founder, freelancer, or online business, one of the biggest challenges isn't finding customers. It's getting paid by them.&lt;/p&gt;

&lt;p&gt;Most articles about African payment APIs focus on moving money out of Africa. But what if your customers are the ones sending money to you?&lt;/p&gt;

&lt;p&gt;Whether you're billing international clients, collecting subscription payments, or accepting payments from marketplace users, you need a collection method that's easy for customers and simple to reconcile on your end.&lt;/p&gt;

&lt;p&gt;The Afriex Business API offers three different ways to collect payments, each designed for a different use case. Depending on who your customers are and how they prefer to pay, you can collect funds through dedicated virtual accounts, shared pool accounts, or stablecoin wallets.&lt;/p&gt;

&lt;p&gt;In this guide, you'll learn how each collection method works, when to use it, and how to integrate it into your application. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Collection Methods
&lt;/h2&gt;

&lt;p&gt;Although all three collection methods ultimately deposit funds into your Afriex Business wallet, they differ in how customers send money and how you identify who made each payment.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;How the payer sends&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dedicated virtual account&lt;/td&gt;
&lt;td&gt;Known customers, repeat payments&lt;/td&gt;
&lt;td&gt;Bank transfer to a unique account number&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pool account&lt;/td&gt;
&lt;td&gt;Quick collection, one-off payments&lt;/td&gt;
&lt;td&gt;Bank transfer with a reference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crypto wallet&lt;/td&gt;
&lt;td&gt;Customers holding USDT or USDC&lt;/td&gt;
&lt;td&gt;Crypto transfer to a wallet address&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Choosing the right method depends on your product and your payment flow. If you already know your customers and expect them to pay repeatedly, dedicated virtual accounts provide the smoothest experience. If you want to launch quickly without creating individual accounts for every customer, pool accounts are a great fit. And if your users prefer paying with stablecoins, crypto wallets make that process straightforward. &lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Dedicated Virtual Accounts
&lt;/h2&gt;

&lt;p&gt;Dedicated virtual accounts are the easiest way to reconcile bank transfers from repeat customers.&lt;/p&gt;

&lt;p&gt;Instead of asking every customer to send money to the same account and include a payment reference, each customer receives their own unique bank account number. Any payment that arrives in that account automatically belongs to that customer, so there's no manual matching or reconciliation required.&lt;/p&gt;

&lt;p&gt;This approach works especially well for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SaaS products with recurring subscriptions&lt;/li&gt;
&lt;li&gt;Marketplaces that maintain seller balances&lt;/li&gt;
&lt;li&gt;Businesses collecting repeat payments from customers&lt;/li&gt;
&lt;li&gt;Platforms serving African businesses that pay through bank transfers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Afriex supports two types of dedicated virtual accounts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static virtual accounts&lt;/strong&gt; remain permanently assigned to a customer, making them ideal for ongoing billing relationships.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic virtual accounts&lt;/strong&gt; are temporary. They're tied to a specific payment amount and expire after a short period, making them a good choice for invoices and one-time payments. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating a Static or Dynamic Virtual Account
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Static virtual account — permanent, for repeat payments&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;staticAccount&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createVirtualAccount&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-customer-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;COLLECTIONS&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;// Dynamic virtual account — expires after a short window, for a single invoice&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamicAccount&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createVirtualAccount&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-customer-id&lt;/span&gt;&lt;span class="dl"&gt;"&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="mi"&gt;150000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// exact amount expected&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Both return a real account number and bank name to share with the customer&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="nx"&gt;staticAccount&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;accountNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// e.g. "3820404958"&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="nx"&gt;staticAccount&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;institution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// e.g. "FIDELITY BANK"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've created the account, share the returned &lt;strong&gt;account number&lt;/strong&gt; and &lt;strong&gt;bank name&lt;/strong&gt; with your customer. Behind the scenes, Afriex automatically associates every payment with the customer that owns that virtual account, eliminating the need for payment references or manual reconciliation. &lt;/p&gt;

&lt;h2&gt;
  
  
  Reusing Existing Accounts
&lt;/h2&gt;

&lt;p&gt;Creating static virtual accounts is idempotent. If you request another static account for the same customer and currency, Afriex returns the existing account instead of generating a new one.&lt;/p&gt;

&lt;p&gt;A good practice is to check whether an account already exists before creating one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listVirtualAccounts&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-customer-id&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&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;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create one&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createVirtualAccount&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-customer-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;COLLECTIONS&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach keeps your integration predictable while ensuring each customer maintains a single permanent account for recurring payments. &lt;/p&gt;

&lt;h3&gt;
  
  
  NGN Requirements
&lt;/h3&gt;

&lt;p&gt;If you're creating &lt;strong&gt;static NGN virtual accounts&lt;/strong&gt;, the customer must have a &lt;strong&gt;Bank Verification Number (BVN)&lt;/strong&gt; on file. This is a regulatory requirement set by the Central Bank of Nigeria.&lt;/p&gt;

&lt;p&gt;You can provide the BVN when creating the customer or update it later through the customer KYC endpoint.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dynamic virtual accounts don't have this requirement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the time of writing, dedicated virtual accounts has support for &lt;strong&gt;NGN&lt;/strong&gt;, &lt;strong&gt;USD&lt;/strong&gt;, &lt;strong&gt;GBP&lt;/strong&gt;, and &lt;strong&gt;EUR&lt;/strong&gt; accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Pool Accounts
&lt;/h2&gt;

&lt;p&gt;Pool accounts are the fastest way to start accepting bank transfers when you don't need a dedicated account number for every customer.&lt;/p&gt;

&lt;p&gt;Instead of creating individual virtual accounts, Afriex provides a shared account for a specific country. Every customer pays into the same account, while a unique payment reference identifies who the payment belongs to.&lt;/p&gt;

&lt;p&gt;This approach significantly reduces the setup required, making it a great choice for products that need to start collecting payments quickly.&lt;/p&gt;

&lt;p&gt;The trade-off is that your application becomes responsible for keeping track of the payment reference associated with each customer. Though as long as those references are stored correctly, reconciling incoming deposits remains straightforward. &lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieving a Pool Account
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&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;poolAccount&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listPoolAccounts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-customer-id&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;// Share both pieces with the customer&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="nx"&gt;poolAccount&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;accountNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// the shared account number&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="nx"&gt;poolAccount&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;reference&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;// unique to this customer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike dedicated virtual accounts, there isn't a separate account number for each customer. Everyone transfers money to the same bank account, but each customer receives their own unique &lt;strong&gt;reference&lt;/strong&gt;, which must be included when making the payment.&lt;/p&gt;

&lt;p&gt;When the transfer arrives, Afriex uses that reference to identify which customer made the payment and credits it accordingly. &lt;/p&gt;

&lt;h3&gt;
  
  
  Store the Reference Before Sharing It
&lt;/h3&gt;

&lt;p&gt;The payment reference is the key to matching an incoming transfer to the correct customer. Because of that, you should save it immediately after retrieving the pool account details.&lt;/p&gt;

&lt;p&gt;A good pattern is to persist the payment details first, then send the payment instructions to the customer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Store reference before sharing account details with customer&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pendingPayments&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-customer-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;afriexReference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;poolAccount&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;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;expectedAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50000&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Then share account details with your customer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;paymentInstructions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;bank&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;poolAccount&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;institution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;poolAccount&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;accountNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;poolAccount&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;accountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;poolAccount&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;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// customer MUST include this&lt;/span&gt;
  &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50000&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;Persisting the reference helps ensure every transfer can be matched correctly, even if the customer completes the payment hours or days later. &lt;/p&gt;

&lt;h3&gt;
  
  
  Country-Based Accounts
&lt;/h3&gt;

&lt;p&gt;Pool accounts are provisioned per country. You simply provide the appropriate country code, and Afriex returns the shared account for that market.&lt;/p&gt;

&lt;p&gt;There's no need to specify the currency separately because it's derived automatically from the country. For example, passing &lt;code&gt;NG&lt;/code&gt; returns an account that accepts &lt;strong&gt;NGN&lt;/strong&gt; payments. &lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: Crypto Wallets (USDT / USDC)
&lt;/h2&gt;

&lt;p&gt;Not every customer prefers paying through a bank transfer. Some already hold stablecoins and would rather pay directly from their crypto wallet.&lt;/p&gt;

&lt;p&gt;For these customers, Afriex lets you generate dedicated wallet addresses for supported stablecoins such as &lt;strong&gt;USDT&lt;/strong&gt; and &lt;strong&gt;USDC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When funds arrive, Afriex automatically converts the deposit to its USD equivalent and records it as a &lt;strong&gt;DEPOSIT&lt;/strong&gt; transaction in your business wallet. That means you don't need to perform a separate conversion step before reconciling payments. &lt;/p&gt;

&lt;h3&gt;
  
  
  Creating or Retrieving a Customer Wallet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Get or create a USDT wallet for a specific customer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCryptoWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USDT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;your-customer-id&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;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="nx"&gt;wallet&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;addresses&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Returns addresses on multiple networks, e.g:&lt;/span&gt;
&lt;span class="c1"&gt;// [&lt;/span&gt;
&lt;span class="c1"&gt;//   { address: "0x1234...5678", network: "ETHEREUM_MAINNET" },&lt;/span&gt;
&lt;span class="c1"&gt;//   { address: "TYASr5...HLS",  network: "TRON_MAINNET" }&lt;/span&gt;
&lt;span class="c1"&gt;// ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response contains wallet addresses for multiple blockchain networks. Share the address that matches the network your customer intends to use.&lt;/p&gt;

&lt;p&gt;For many customers, &lt;strong&gt;Tron&lt;/strong&gt; is the preferred option because transaction fees are generally lower than on Ethereum.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The wallet creation endpoint is idempotent, so requesting a wallet for the same customer and asset returns the same address instead of creating a new one each time. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Detecting Crypto Deposits
&lt;/h3&gt;

&lt;p&gt;Crypto deposits work a little differently from bank transfers.&lt;/p&gt;

&lt;p&gt;Instead of sending a webhook when funds arrive, you'll need to periodically check your transaction history for new &lt;strong&gt;DEPOSIT&lt;/strong&gt; records.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recentDeposits&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPOSIT&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;Your application can poll this endpoint at a suitable interval and process any newly discovered deposits.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep in mind that crypto wallets are available only in production and require the customer to complete verification before a wallet can be created. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Knowing When a Deposit Has Landed
&lt;/h2&gt;

&lt;p&gt;Collecting payments is only one part of the integration. Your application also needs a reliable way to know when funds have actually arrived so you can update balances, fulfill orders, or unlock access for customers.&lt;/p&gt;

&lt;p&gt;For payments made through &lt;strong&gt;dedicated virtual accounts&lt;/strong&gt; and &lt;strong&gt;pool accounts&lt;/strong&gt;, Afriex sends a webhook whenever a deposit is received. Instead of repeatedly checking for new transactions, your application can simply listen for these events and react as they happen. &lt;/p&gt;

&lt;h3&gt;
  
  
  Handling Deposit Webhooks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&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;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-webhook-signature&lt;/span&gt;&lt;span class="dl"&gt;"&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;signature&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;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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing signature&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Read raw body before any parsing — signature is verified against these exact bytes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawBody&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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;event&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyAndParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&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;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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid signature&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRANSACTION.CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&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="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPOSIT&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="kd"&gt;const&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="nx"&gt;destinationAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;destinationCurrency&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&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="c1"&gt;// Attribute the deposit to the right customer and update your records&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;recordDeposit&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="na"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;destinationAmount&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="nx"&gt;destinationCurrency&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;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="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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 few implementation details are worth paying attention to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always verify the webhook signature before processing the request.&lt;/li&gt;
&lt;li&gt;Read the raw request body before parsing it. Signature verification depends on the original payload.&lt;/li&gt;
&lt;li&gt;Respond quickly after processing the event. Long-running work should happen asynchronously whenever possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These practices help keep your webhook endpoint secure and reliable in production. &lt;/p&gt;

&lt;h3&gt;
  
  
  Registering Your Webhook
&lt;/h3&gt;

&lt;p&gt;Once your endpoint is ready, register it in the Afriex dashboard under &lt;strong&gt;Developers &amp;gt; Webhooks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll also need to configure your application with the &lt;code&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&lt;/code&gt;, which is used to verify incoming webhook signatures.&lt;/p&gt;

&lt;p&gt;Your webhook should return an HTTP &lt;strong&gt;200&lt;/strong&gt; response within five seconds. If it doesn't, Afriex automatically retries delivery using exponential backoff, giving your application multiple chances to process the event successfully. &lt;/p&gt;

&lt;p&gt;For a complete walkthrough of every available webhook event and recommended handling patterns, see the &lt;strong&gt;&lt;a href="https://dev.to/afriex/afriex-webhook-integration-guide-signature-verification-event-handling-and-production-best-3c9o"&gt;Afriex webhook integration guide&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Collection Method
&lt;/h2&gt;

&lt;p&gt;Each payment method solves a different problem, so the right choice depends on how your customers pay and how much reconciliation you want your application to handle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Dedicated Virtual Accounts if...
&lt;/h3&gt;

&lt;p&gt;Choose dedicated virtual accounts when your customers are known ahead of time and are likely to make repeat payments.&lt;/p&gt;

&lt;p&gt;Since every customer receives a unique bank account number, incoming transfers are automatically attributed to the correct customer. This eliminates manual reconciliation and provides the smoothest experience for subscription platforms, marketplaces, and businesses with recurring billing. &lt;/p&gt;

&lt;h3&gt;
  
  
  Use Pool Accounts if...
&lt;/h3&gt;

&lt;p&gt;Pool accounts are the better option when you want to start accepting payments quickly without creating virtual accounts for every customer.&lt;/p&gt;

&lt;p&gt;Although you'll need to store and match payment references yourself, the integration is lighter and requires less operational overhead, making it an excellent choice for products that prioritize speed of implementation. &lt;/p&gt;

&lt;h3&gt;
  
  
  Use Crypto Wallets if...
&lt;/h3&gt;

&lt;p&gt;Crypto wallets are ideal when your customers already hold &lt;strong&gt;USDT&lt;/strong&gt; or &lt;strong&gt;USDC&lt;/strong&gt; and prefer paying with stablecoins instead of making bank transfers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;p&gt;If you like to explore each payment method in more detail, these guides provide a deeper look at their individual integration flows:&lt;/p&gt;

&lt;p&gt;*&lt;br&gt;
  &lt;a href="https://dev.to/afriex/how-to-accept-usdt-and-usdc-payments-and-settle-automatically-in-usd-with-afriex-2m79"&gt;How to accept USDT and USDC&lt;/a&gt;&lt;br&gt;
  &lt;a href="https://dev.to/afriex/afriex-virtual-accounts-a-developers-guide-to-dedicated-and-pool-accounts-260b"&gt;Virtual accounts and pool accounts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also explore the Afriex Business platform and API documentation here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;https://business.afriex.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.afriex.com" rel="noopener noreferrer"&gt;https://docs.afriex.com&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;There's no single payment collection method that's right for every product.&lt;/p&gt;

&lt;p&gt;If your business relies on repeat customers, dedicated virtual accounts provide the simplest reconciliation experience. If speed matters more than individual account management, pool accounts let you start collecting payments with minimal setup. And if your customers already use stablecoins, crypto wallets offer a familiar way to pay while Afriex handles the conversion behind the scenes.&lt;/p&gt;

&lt;p&gt;By offering multiple collection methods through a single API, you can support different customer preferences without maintaining separate payment integrations.&lt;/p&gt;

&lt;p&gt;Whether you're building a SaaS platform, marketplace, fintech product, or any business that serves customers across borders, the Afriex Business API gives you the flexibility to accept payments in the way that best fits your users.&lt;/p&gt;

</description>
      <category>crossborder</category>
      <category>afriex</category>
      <category>payments</category>
      <category>api</category>
    </item>
    <item>
      <title>Build a Remittance App with the Afriex Cross-border API</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Tue, 30 Jun 2026 22:54:02 +0000</pubDate>
      <link>https://dev.to/afriex/build-a-remittance-app-with-the-afriex-business-api-1adg</link>
      <guid>https://dev.to/afriex/build-a-remittance-app-with-the-afriex-business-api-1adg</guid>
      <description>&lt;p&gt;Sending money across borders involves more moving parts than most developers expect. You need to know the live exchange rate before the user confirms. You need to register the recipient correctly. You need to attach their bank account or mobile wallet. You need to trigger the transfer and then track it through to settlement. And you need to handle failures gracefully when something goes wrong in the corridor.&lt;/p&gt;

&lt;p&gt;Most of that complexity is what the Afriex Cross-border API is built to handle. This article walks through the full remittance flow — from registering a sender to confirming delivery, using the Afriex SDK from start to finish.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get your API key
&lt;/h2&gt;

&lt;p&gt;Everything starts at &lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;business.afriex.com&lt;/a&gt;. Create a Business account if you do not have one. Once you are in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Developer&lt;/strong&gt; then &lt;strong&gt;API Keys&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create new keys&lt;/strong&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%2Fvvnd8m3nazk5vsnevlh6.png" alt="Afriex business dashboard developer tab" width="800" height="323"&gt;
&lt;/li&gt;
&lt;li&gt;Give it a name (e.g. "Remittance App")&lt;/li&gt;
&lt;li&gt;Set the permissions your use case needs — for a remittance app, you need at minimum: &lt;strong&gt;View wallet balances&lt;/strong&gt;, &lt;strong&gt;Initiate withdrawals&lt;/strong&gt;, &lt;strong&gt;View payment methods&lt;/strong&gt;, and &lt;strong&gt;Add or manage payment methods&lt;/strong&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%2F26e6bba5sakudwvedub8.png" alt="Afriex business dashboard API permissions sheet" width="800" height="367"&gt;
&lt;/li&gt;
&lt;li&gt;Copy the key immediately. You cannot view it again after leaving the page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Store it as an environment variable:&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="nv"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-api-key-here
&lt;span class="nv"&gt;AFRIEX_ENVIRONMENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;staging  &lt;span class="c"&gt;# switch to production when you go live&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start in the &lt;code&gt;staging&lt;/code&gt; environment so nothing real moves while you build.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install and initialize the SDK
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @afriex/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize once and reuse the instance across your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/lib/afriex.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@afriex/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Afriex&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_ENVIRONMENT&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;staging&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;retryConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;retryDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;retryableStatusCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;408&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;webhookPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&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 &lt;code&gt;retryConfig&lt;/code&gt; handles transient failures automatically. If the Afriex API returns a 429 or a 503, the SDK retries up to three times with a delay between attempts, so you do not have to write that logic yourself. The &lt;code&gt;webhookPublicKey&lt;/code&gt; is used later for signature verification, you can get it from the &lt;strong&gt;Developers &amp;gt; Webhooks&lt;/strong&gt; section of your dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Show the live exchange rate
&lt;/h2&gt;

&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%2F95ivqiv4kwaet3ltrwgb.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%2F95ivqiv4kwaet3ltrwgb.png" alt="showing Dollar to Naira exchange rate" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before anything else, show the sender what rate they are getting. A remittance user deciding between providers makes that decision based on the rate they see. Never show a hardcoded or cached rate — always fetch live.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;getRate&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRates&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fromSymbols&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="na"&gt;toSymbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;to&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;rate&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;rates&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="nx"&gt;to&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;rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Rate not available for &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;to&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage: sender sends USD, recipient gets NGN&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rate&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;getRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&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;NGN&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;`1 USD = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; NGN`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This call returns the live mid-market rate for the currency pair. Show it to the user with a clear note about when it was fetched, since rates can shift between the moment they see it and the moment they confirm. The rate the user sees is informational — the actual conversion happens on Afriex's end when the transaction is created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create the recipient as a customer
&lt;/h2&gt;

&lt;p&gt;The recipient needs to exist in the Afriex system before you can attach a payment method or send them money. This is a one-time step per recipient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;registerRecipient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;details&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;customer&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;fullName&lt;/span&gt;&lt;span class="p"&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;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&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;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&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;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// E.164 format, e.g. +2348012345678&lt;/span&gt;
    &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&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;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// ISO 3166-1 alpha-2, e.g. NG, KE, GH&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;customer&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;recipient&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;registerRecipient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Victory Lucky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;victory@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+2348012345678&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&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;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="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// store this — you need it in every subsequent call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the &lt;code&gt;customerId&lt;/code&gt; that comes back. Every subsequent API call references this ID rather than the recipient's personal details.&lt;/p&gt;

&lt;p&gt;If you are building a product where senders send to the same recipient repeatedly (for example, a diaspora parent sending to family), store the &lt;code&gt;customerId&lt;/code&gt; against the recipient record in your own database on first creation and skip this step for future transfers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Attach the recipient's payment method
&lt;/h2&gt;

&lt;p&gt;Now tell Afriex where to send the money — the recipient's bank account or mobile wallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bank account
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&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;attachBankAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// bank code, e.g. "058" for GTBank Nigeria&lt;/span&gt;
  &lt;span class="nl"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;paymentMethod&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BANK_ACCOUNT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;institution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;institutionName&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;paymentMethod&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;bankMethod&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;attachBankAccount&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Victory Lucky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0123456789&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;058&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GTBank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&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;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="nx"&gt;bankMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// store this too&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mobile money
&lt;/h3&gt;

&lt;p&gt;For recipients receiving on MTN, M-Pesa, Airtel, or similar networks, the channel changes but the pattern is the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mobileMethod&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MOBILE_MONEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Victory Lucky&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;accountNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+2348012345678&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// recipient's mobile number&lt;/span&gt;
  &lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NG&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;institution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;institutionCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MTN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;institutionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MTN Mobile Money&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not sure which institution code to use? Call &lt;code&gt;afriex.paymentMethods.listInstitutions({ countryCode: "NG", channel: "BANK_ACCOUNT" })&lt;/code&gt; to get the full list for any country and channel combination.&lt;/p&gt;

&lt;p&gt;Like the &lt;code&gt;customerId&lt;/code&gt;, save the &lt;code&gt;paymentMethodId&lt;/code&gt; that comes back. This is what you pass when you create the transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Send the transfer
&lt;/h2&gt;

&lt;p&gt;This is the call that actually moves money. Everything before this was set up, this is the execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/afriex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&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;sendRemittance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// currency the sender is sending&lt;/span&gt;
  &lt;span class="nl"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// currency the recipient receives&lt;/span&gt;
  &lt;span class="nl"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// your internal reference for this transfer&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;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`idem-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transaction&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WITHDRAW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceAmount&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;params&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationAmount&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;params&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;narration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Remittance — &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&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="na"&gt;merchantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference&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;transaction&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;transfer&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;sendRemittance&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;paymentMethodId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bankMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethodId&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;sourceCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;destinationCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NGN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;txn-478-001&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;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="nx"&gt;transfer&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="c1"&gt;// the Afriex transaction ID — store this&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="nx"&gt;transfer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// starts as PENDING&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things worth understanding about this call.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;idempotencyKey&lt;/code&gt; is derived from your own reference. If this call fails due to a network error and your code retries it, the same key means Afriex will not create a duplicate transaction. It recognizes the key and returns the existing transaction instead of creating a new one.&lt;/p&gt;

&lt;p&gt;The transaction comes back with a status of &lt;code&gt;PENDING&lt;/code&gt;. That is not a failure — it is the expected starting state. The actual settlement happens asynchronously, and Afriex tells you what happened next through webhooks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Track the transfer through webhooks
&lt;/h2&gt;

&lt;p&gt;You will not know the outcome of a transfer by polling the response from the &lt;code&gt;create&lt;/code&gt; call. Afriex sends a signed HTTP POST to your webhook URL every time the transaction status changes. This is how you know when money has actually landed.&lt;/p&gt;

&lt;p&gt;First, register your webhook URL in the Afriex dashboard under &lt;strong&gt;Developers &amp;gt; Webhooks&lt;/strong&gt;. Copy the webhook public key from that page and add it to your environment:&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="nv"&gt;AFRIEX_WEBHOOK_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-webhook-public-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use the built-in handler from the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Signature verification&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhooks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyAndParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signature&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;event&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Webhook handler (Next.js route example)&lt;/span&gt;
&lt;span class="k"&gt;export&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;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-webhook-signature&lt;/span&gt;&lt;span class="dl"&gt;"&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;signature&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;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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Missing signature&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Read raw body before any parsing — the signature was computed against these exact bytes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawBody&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&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;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rawBody&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;payload&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;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="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid signature&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRANSACTION.UPDATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRANSACTION.CREATED&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="kd"&gt;const&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="nx"&gt;status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&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="c1"&gt;// Update your database with the new status&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateTransferStatus&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="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// Money has landed — notify the sender&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifySender&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your transfer was delivered.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IN_REVIEW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// Compliance hold — not a failure, just needs time&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifySender&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your transfer is under review.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RETRY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// The network is retrying automatically — no action needed&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REJECTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;// Terminal failure — let the sender know and offer a retry&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;notifySender&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your transfer could not be completed.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&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;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="na"&gt;received&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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 few important things about how this handler is written.&lt;/p&gt;

&lt;p&gt;It reads the raw body with &lt;code&gt;req.text()&lt;/code&gt; before parsing anything. The SDK handles the RSA-SHA256 signature verification which was computed against those exact &lt;code&gt;rawBody&lt;/code&gt; bytes. If you parse to JSON first and re-serialize, the verification will fail even if the payload is genuine.&lt;/p&gt;

&lt;p&gt;It returns &lt;code&gt;200&lt;/code&gt; immediately. Afriex expects a response within about five seconds. If your handler does slow work (database writes, email sending) before returning, put that work in a queue and return immediately. Afriex will retry delivery up to twelve times with exponential backoff if it does not get a success response.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IN_REVIEW&lt;/code&gt; and &lt;code&gt;RETRY&lt;/code&gt; are not failures. &lt;code&gt;IN_REVIEW&lt;/code&gt; means a compliance hold that will resolve into &lt;code&gt;SUCCESS&lt;/code&gt; or &lt;code&gt;REJECTED&lt;/code&gt;. &lt;code&gt;RETRY&lt;/code&gt; means the payment network is handling it automatically. Treating either as a failure will cause you to alert users unnecessarily.&lt;/p&gt;




&lt;h2&gt;
  
  
  The full picture
&lt;/h2&gt;

&lt;p&gt;Here is the complete flow in sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Fetch live rate          →  afriex.rates.getRates()
2. Register recipient       →  afriex.customers.create()
3. Attach payment method    →  afriex.paymentMethods.create()
4. Send the transfer        →  afriex.transactions.create()
5. Receive status updates   →  afriex.webhooks.verifyAndParse()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Steps 2 and 3 are one-time per recipient. For a sender who sends to the same person repeatedly, you store the &lt;code&gt;customerId&lt;/code&gt; and &lt;code&gt;paymentMethodId&lt;/code&gt; and jump straight to step 4 on subsequent transfers.&lt;/p&gt;

&lt;p&gt;Afriex handles the currency conversion, the corridor routing, and the settlement. Your application handles the user flow, the data storage, and the notification layer. The two concerns stay clean and separate.&lt;/p&gt;

&lt;p&gt;The full API reference is at &lt;a href="https://docs.afriex.com" rel="noopener noreferrer"&gt;docs.afriex.com&lt;/a&gt; and the Business dashboard is at &lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;business.afriex.com&lt;/a&gt;. If you have questions, drop them in the comments or reach out on X &lt;a href="https://x.com/codewithveek" rel="noopener noreferrer"&gt;@codewithveek&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>fintech</category>
      <category>crossborder</category>
      <category>api</category>
    </item>
    <item>
      <title>How to Accept USDT and USDC Payments and Settle Automatically in USD with Afriex</title>
      <dc:creator>Victory Lucky</dc:creator>
      <pubDate>Mon, 22 Jun 2026 11:16:12 +0000</pubDate>
      <link>https://dev.to/afriex/how-to-accept-usdt-and-usdc-payments-and-settle-automatically-in-usd-with-afriex-2m79</link>
      <guid>https://dev.to/afriex/how-to-accept-usdt-and-usdc-payments-and-settle-automatically-in-usd-with-afriex-2m79</guid>
      <description>&lt;p&gt;Stablecoin payments are becoming a mainstream way for businesses across Africa and global markets to collect funds. USDT and USDC now account for a significant share of cryptocurrency transaction volume because they offer fast settlement, lower transaction costs, and predictable value compared to traditional cross-border payment methods.&lt;/p&gt;

&lt;p&gt;For many businesses, however, accepting stablecoins introduces a new challenge: managing wallets, handling conversions, and moving funds into a usable currency.&lt;/p&gt;

&lt;p&gt;The Afriex crypto wallet endpoint simplifies this process. You generate a wallet address for USDT or USDC, your customer sends funds to that address, and Afriex automatically converts the deposit into its USD equivalent in your business balance.&lt;/p&gt;

&lt;p&gt;There is no manual conversion step, no separate swap operation, and no need to hold cryptocurrency after the payment is received.&lt;/p&gt;




&lt;h2&gt;
  
  
  Generate a USDT or USDC wallet address
&lt;/h2&gt;

&lt;p&gt;Getting started requires a single API call and one required parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCryptoWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USDT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// or "USDC"&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optional-customer-id&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;If you omit &lt;code&gt;customerId&lt;/code&gt;, the wallet belongs directly to your business account. This is useful when you want a single shared collection address for all incoming crypto payments.&lt;/p&gt;

&lt;p&gt;If you provide a &lt;code&gt;customerId&lt;/code&gt;, the wallet is associated with that specific customer. This approach is ideal when you need automatic payment attribution and want to know exactly which customer made a deposit without requiring payment references or manual reconciliation.&lt;/p&gt;

&lt;p&gt;The endpoint is idempotent. Requesting a wallet for the same asset and customer multiple times returns the same wallet address each time.&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="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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"695271a3ba52c13b669fad2b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"addresses"&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;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x1234567890123456789012345678901234567890"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ETHEREUM_MAINNET"&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="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TYASr5UV6HEcXatwdFQfmLVUqQQQMUxHLS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TRON_MAINNET"&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;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;For USDT wallets, Afriex returns addresses on both Ethereum and Tron networks. You can share whichever network your customer prefers. In most cases, Tron offers lower transaction fees and is often the more economical option for senders.&lt;/p&gt;




&lt;h2&gt;
  
  
  How crypto deposits are converted to USD
&lt;/h2&gt;

&lt;p&gt;Once USDT or USDC arrives at the generated wallet address, Afriex automatically converts the funds into their USD equivalent and records the transaction as a &lt;code&gt;DEPOSIT&lt;/code&gt; in your account.&lt;/p&gt;

&lt;p&gt;No additional API calls are required.&lt;/p&gt;

&lt;p&gt;The balance that appears in your business wallet is already denominated in USD, making it easier to manage accounting, payouts, and reporting without handling cryptocurrency directly.&lt;/p&gt;

&lt;p&gt;This workflow is especially useful for businesses that want to accept stablecoin payments while operating entirely in USD.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detecting successful crypto payments
&lt;/h2&gt;

&lt;p&gt;One important operational detail is that there is currently no dedicated webhook event for crypto-to-USD conversion deposits.&lt;/p&gt;

&lt;p&gt;If your application relies on real-time payment notifications, you should monitor incoming deposits by polling the transactions endpoint and looking for new &lt;code&gt;DEPOSIT&lt;/code&gt; records associated with the wallet's payment method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPOSIT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// filter or sort by createdAt to catch new ones&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A common production approach is to poll periodically, track the latest processed transaction, and reconcile new deposits as they appear.&lt;/p&gt;

&lt;p&gt;This ensures your system can reliably identify successful payments and update customer records automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production requirements and limitations
&lt;/h2&gt;

&lt;p&gt;Like Afriex virtual accounts and pool accounts, crypto wallet collection is available only in the production environment.&lt;/p&gt;

&lt;p&gt;The endpoint is not available in staging or sandbox environments.&lt;/p&gt;

&lt;p&gt;During development, you should test your payment processing logic using mocked responses and validate the end-to-end deposit flow after going live.&lt;/p&gt;

&lt;p&gt;If you provide a &lt;code&gt;customerId&lt;/code&gt;, the associated customer must be verified. Requests made with an unverified customer return a &lt;code&gt;400&lt;/code&gt; validation error instead of a wallet address.&lt;/p&gt;




&lt;h2&gt;
  
  
  Crypto wallet vs virtual account
&lt;/h2&gt;

&lt;p&gt;Both crypto wallets and virtual accounts ultimately create attributable deposits in your Afriex account, but they solve different payment collection problems.&lt;/p&gt;

&lt;p&gt;Use a virtual account when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customers pay via local bank transfer&lt;/li&gt;
&lt;li&gt;Customers prefer traditional banking rails&lt;/li&gt;
&lt;li&gt;You are collecting fiat currency payments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use a crypto wallet when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customers already hold USDT or USDC&lt;/li&gt;
&lt;li&gt;You want to accept stablecoin payments&lt;/li&gt;
&lt;li&gt;You serve freelancers, remote workers, diaspora customers, or crypto-native businesses&lt;/li&gt;
&lt;li&gt;Customers prefer blockchain-based payment methods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Virtual accounts are covered in the virtual accounts guide.&lt;/p&gt;

&lt;p&gt;The Afriex crypto wallet endpoint is designed for businesses that want the speed and accessibility of stablecoin payments while keeping settlement and accounting simple through automatic USD conversion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does Afriex support USDT on Tron?
&lt;/h3&gt;

&lt;p&gt;Yes. When you create a USDT wallet, Afriex returns wallet addresses for both the Ethereum and Tron networks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCryptoWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USDT&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;You can provide either address to your customer depending on the network they intend to use. In many cases, Tron is preferred because transaction fees are typically lower than Ethereum.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Afriex support USDC payments?
&lt;/h3&gt;

&lt;p&gt;Yes. The crypto wallet endpoint supports both USDT and USDC.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCryptoWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USDC&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;Once funds are received, Afriex automatically converts the deposit into its USD equivalent and credits your business balance.&lt;/p&gt;

&lt;h3&gt;
  
  
  How are crypto deposits settled?
&lt;/h3&gt;

&lt;p&gt;Crypto deposits are settled automatically in USD.&lt;/p&gt;

&lt;p&gt;When a customer sends USDT or USDC to a generated wallet address:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The blockchain confirms the transaction.&lt;/li&gt;
&lt;li&gt;Afriex receives the stablecoin deposit.&lt;/li&gt;
&lt;li&gt;The deposit is automatically converted into USD.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;DEPOSIT&lt;/code&gt; transaction is created in your account.&lt;/li&gt;
&lt;li&gt;Your USD balance is updated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No separate conversion request or manual swap operation is required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I receive payment notifications through webhooks?
&lt;/h3&gt;

&lt;p&gt;Currently, Afriex does not emit a dedicated webhook event when a crypto deposit is converted into USD.&lt;/p&gt;

&lt;p&gt;To detect new crypto payments, periodically query the Transactions API and look for newly created &lt;code&gt;DEPOSIT&lt;/code&gt; transactions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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="nx"&gt;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEPOSIT&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;Many production systems poll this endpoint at regular intervals and reconcile newly discovered deposits automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I create separate wallets for different customers?
&lt;/h3&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;Pass a &lt;code&gt;customerId&lt;/code&gt; when creating a wallet to generate a customer-scoped payment destination.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&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;afriex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCryptoWallet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USDT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;This makes payment attribution significantly easier because deposits can be associated directly with the customer who owns the wallet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is the crypto wallet endpoint available in sandbox mode?
&lt;/h3&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;The crypto wallet endpoint is currently available only in production environments. During development, use mocked responses and test payment reconciliation workflows before validating against live deposits.&lt;/p&gt;

&lt;p&gt;Depending on how your customers prefer to pay, you may need a combination of crypto payment collection, bank transfers, and payout infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Afriex Virtual Accounts
&lt;/h3&gt;

&lt;p&gt;Use virtual accounts when customers need to pay via local bank transfer rather than cryptocurrency.&lt;/p&gt;

&lt;p&gt;Virtual accounts provide dedicated account details that make payment collection and reconciliation easier for fiat transactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local bank transfers&lt;/li&gt;
&lt;li&gt;Customer deposits&lt;/li&gt;
&lt;li&gt;Marketplace payments&lt;/li&gt;
&lt;li&gt;Subscription collections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Read next: &lt;a href="https://dev.to/afriex/afriex-virtual-accounts-a-developers-guide-to-dedicated-and-pool-accounts-260b"&gt;Afriex Virtual Accounts: A Developer's Guide to Dedicated and Pool Accounts&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Afriex Pool Accounts
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://docs.afriex.com/api-reference/endpoint/payment-methods/pool-account" rel="noopener noreferrer"&gt;Pool accounts&lt;/a&gt; are useful when you want to collect payments into a shared account and attribute funds programmatically after receipt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-volume payment collection&lt;/li&gt;
&lt;li&gt;Marketplaces&lt;/li&gt;
&lt;li&gt;Platforms with many users&lt;/li&gt;
&lt;li&gt;Automated reconciliation workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Transactions API
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://docs.afriex.com/api-reference/endpoint/transactions/create" rel="noopener noreferrer"&gt;Transactions API&lt;/a&gt; is particularly important when working with crypto deposits because it allows you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect successful deposits&lt;/li&gt;
&lt;li&gt;Reconcile incoming payments&lt;/li&gt;
&lt;li&gt;Build internal payment ledgers&lt;/li&gt;
&lt;li&gt;Trigger business workflows after settlement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you plan to accept USDT or USDC payments in production, this endpoint will likely become a core part of your integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Customer Verification
&lt;/h3&gt;

&lt;p&gt;When creating customer-scoped wallets, ensure the associated customer has completed verification requirements.&lt;/p&gt;

&lt;p&gt;Unverified customers will receive validation errors when attempting to create crypto wallets tied to their account.&lt;/p&gt;

&lt;p&gt;Customer verification is therefore an important prerequisite for customer-specific payment collection workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The Afriex crypto wallet endpoint makes it easy to accept USDT and USDC payments without managing conversions yourself. Generate a wallet address, receive stablecoin payments, and have deposits automatically settled in USD.&lt;/p&gt;

&lt;p&gt;If you're ready to start accepting stablecoin payments, &lt;a href="https://business.afriex.com" rel="noopener noreferrer"&gt;create an Afriex account&lt;/a&gt;, complete your business setup, and begin collecting USDT and USDC with automatic USD settlement.&lt;/p&gt;

</description>
      <category>afriex</category>
      <category>crypto</category>
      <category>crossborder</category>
      <category>payments</category>
    </item>
    <item>
      <title>I Built an Afriex MCP Prompt Cookbook So Developers Never Have to Stare at a Blank Prompt Again</title>
      <dc:creator>0xSonOfUri</dc:creator>
      <pubDate>Sun, 21 Jun 2026 00:17:23 +0000</pubDate>
      <link>https://dev.to/afriex/i-built-an-afriex-mcp-prompt-cookbook-so-developers-never-have-to-stare-at-a-blank-prompt-again-1i1b</link>
      <guid>https://dev.to/afriex/i-built-an-afriex-mcp-prompt-cookbook-so-developers-never-have-to-stare-at-a-blank-prompt-again-1i1b</guid>
      <description>&lt;p&gt;A few weeks ago, I started exploring the Afriex MCP server.&lt;/p&gt;

&lt;p&gt;The setup was surprisingly straightforward.&lt;/p&gt;

&lt;p&gt;Connect your MCP client.&lt;br&gt;
Configure your API key.&lt;br&gt;
Verify the connection.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;But then I ran into a different problem.&lt;/p&gt;

&lt;p&gt;Not a technical problem.&lt;/p&gt;

&lt;p&gt;A prompt problem.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Blank Prompt Problem
&lt;/h2&gt;

&lt;p&gt;Once everything was connected, I found myself staring at an empty prompt box.&lt;/p&gt;

&lt;p&gt;What should I ask?&lt;/p&gt;

&lt;p&gt;Sure, I could retrieve balances.&lt;/p&gt;

&lt;p&gt;I could create customers.&lt;/p&gt;

&lt;p&gt;I could generate virtual accounts.&lt;/p&gt;

&lt;p&gt;But what were the most useful workflows?&lt;/p&gt;

&lt;p&gt;What were the prompts that would actually help developers build real products?&lt;/p&gt;

&lt;p&gt;This isn't a problem unique to Afriex.&lt;/p&gt;

&lt;p&gt;It's becoming a common challenge across the entire MCP ecosystem.&lt;/p&gt;

&lt;p&gt;The infrastructure exists.&lt;/p&gt;

&lt;p&gt;The tools work.&lt;/p&gt;

&lt;p&gt;But many developers don't know where to start.&lt;/p&gt;


&lt;h2&gt;
  
  
  MCP Changes How We Build
&lt;/h2&gt;

&lt;p&gt;Traditionally, integrating a payment API looked something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read documentation&lt;/li&gt;
&lt;li&gt;Find the endpoint&lt;/li&gt;
&lt;li&gt;Write HTTP requests&lt;/li&gt;
&lt;li&gt;Parse responses&lt;/li&gt;
&lt;li&gt;Build business logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With MCP, the workflow looks very different.&lt;/p&gt;

&lt;p&gt;You can simply tell your AI assistant what you want to build.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a customer onboarding flow that:

- Collects customer details
- Generates a virtual account
- Displays payment instructions

Build it using Next.js and TypeScript.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of manually stitching everything together, the AI can interact with infrastructure through the MCP server.&lt;/p&gt;

&lt;p&gt;That's incredibly powerful.&lt;/p&gt;

&lt;p&gt;But only if you know what to ask.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;That's what led me to build the:&lt;/p&gt;

&lt;h2&gt;
  
  
  Afriex MCP Prompt Cookbook
&lt;/h2&gt;

&lt;p&gt;A collection of practical, production-oriented prompts designed specifically for developers building with Afriex MCP.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Copy.&lt;/p&gt;

&lt;p&gt;Paste.&lt;/p&gt;

&lt;p&gt;Build.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of starting from scratch every time.&lt;/p&gt;

&lt;p&gt;The cookbook is open source and available on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SonOfUri/afriex-mcp-cookbook" rel="noopener noreferrer"&gt;https://github.com/SonOfUri/afriex-mcp-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to explore the prompts, use them in your own projects, and contribute new recipes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Inside
&lt;/h2&gt;

&lt;p&gt;The cookbook is organized around real-world use cases.&lt;/p&gt;

&lt;p&gt;Not API endpoints.&lt;/p&gt;

&lt;p&gt;Not documentation pages.&lt;/p&gt;

&lt;p&gt;Actual products and workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;For developers who have just connected their MCP client.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieving balances&lt;/li&gt;
&lt;li&gt;Creating customers&lt;/li&gt;
&lt;li&gt;Monitoring transactions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Virtual Accounts
&lt;/h2&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customer onboarding flows&lt;/li&gt;
&lt;li&gt;Collections systems&lt;/li&gt;
&lt;li&gt;Payment request workflows&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Payments
&lt;/h2&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment links&lt;/li&gt;
&lt;li&gt;Payout systems&lt;/li&gt;
&lt;li&gt;Payment status tracking&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Stablecoins
&lt;/h2&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;USDC deposit flows&lt;/li&gt;
&lt;li&gt;Treasury monitoring&lt;/li&gt;
&lt;li&gt;Settlement operations&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  AI Agents
&lt;/h2&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Operations agents&lt;/li&gt;
&lt;li&gt;Treasury agents&lt;/li&gt;
&lt;li&gt;Monitoring agents&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  OpenClaw Integrations
&lt;/h2&gt;

&lt;p&gt;Examples focused on combining AI agents with payment infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Favorite Prompt
&lt;/h2&gt;

&lt;p&gt;One of my favorite recipes asks Cursor to build an entire virtual account collection flow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using Afriex MCP:

Create a customer onboarding flow that:

- Collects customer details
- Creates a customer record
- Generates a virtual account
- Displays payment instructions

Build this as a modern Next.js application using TypeScript and TailwindCSS.

Show me the complete implementation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the kind of workflow that would traditionally require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reading documentation&lt;/li&gt;
&lt;li&gt;understanding endpoints&lt;/li&gt;
&lt;li&gt;designing data models&lt;/li&gt;
&lt;li&gt;wiring API calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now it can start with a single prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Think Prompt Libraries Matter
&lt;/h2&gt;

&lt;p&gt;We're entering a new phase of software development.&lt;/p&gt;

&lt;p&gt;Documentation remains important.&lt;/p&gt;

&lt;p&gt;SDKs remain important.&lt;/p&gt;

&lt;p&gt;APIs remain important.&lt;/p&gt;

&lt;p&gt;But prompts are becoming a new layer of developer experience.&lt;/p&gt;

&lt;p&gt;The best developer platforms won't just provide endpoints.&lt;/p&gt;

&lt;p&gt;They'll provide examples, recipes, workflows, and patterns that help developers move from idea to implementation faster.&lt;/p&gt;

&lt;p&gt;That's exactly what this cookbook is trying to do.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Comes Next
&lt;/h2&gt;

&lt;p&gt;The current version focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;payments&lt;/li&gt;
&lt;li&gt;virtual accounts&lt;/li&gt;
&lt;li&gt;stablecoins&lt;/li&gt;
&lt;li&gt;AI agents&lt;/li&gt;
&lt;li&gt;OpenClaw integrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Future additions will include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more production workflows&lt;/li&gt;
&lt;li&gt;community-contributed recipes&lt;/li&gt;
&lt;li&gt;example applications&lt;/li&gt;
&lt;li&gt;advanced MCP patterns&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;One of the most surprising lessons I've learned while working with MCP is that access to tools isn't enough.&lt;/p&gt;

&lt;p&gt;Developers also need examples of what's possible.&lt;/p&gt;

&lt;p&gt;That's what the Afriex MCP Prompt Cookbook aims to provide.&lt;/p&gt;

&lt;p&gt;Not more documentation.&lt;/p&gt;

&lt;p&gt;Not another SDK.&lt;/p&gt;

&lt;p&gt;A collection of practical prompts that help developers build faster.&lt;/p&gt;

&lt;p&gt;Because sometimes the hardest part isn't connecting the infrastructure.&lt;/p&gt;

&lt;p&gt;It's knowing what to ask next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Explore The Cookbook
&lt;/h2&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/SonOfUri/afriex-mcp-cookbook" rel="noopener noreferrer"&gt;https://github.com/SonOfUri/afriex-mcp-cookbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're building with Afriex MCP, I'd love to see what you create and what prompts you find most useful.&lt;/p&gt;

&lt;p&gt;Contributions, suggestions, and new recipes are always welcome.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>afriex</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
