> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blnkfinance.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Building an Online Card Payment System

> Learn how to implement online card payments with authorization and settlement phases using inflight transactions.

export const CtaCallout = props => {
  const {title, buttonLabel, href, trackingEvent, buttonTarget, rel = "noopener noreferrer", children} = props;
  const handleCtaClick = () => {
    if (typeof window === "undefined" || !trackingEvent) {
      return;
    }
    try {
      window.dispatchEvent(new CustomEvent("blnk:docs-cta", {
        detail: {
          name: trackingEvent,
          href
        }
      }));
    } catch {}
    try {
      window.posthog?.capture?.(trackingEvent, {
        href
      });
    } catch {}
    const gaPayload = {
      cta_href: href
    };
    try {
      window.gtag?.("event", trackingEvent, gaPayload);
    } catch {}
    try {
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push({
        event: trackingEvent,
        ...gaPayload
      });
    } catch {}
  };
  const isExternal = typeof href === "string" && (/^https?:\/\//i).test(href);
  const target = buttonTarget ?? (isExternal ? "_blank" : undefined);
  const linkRel = isExternal ? rel : undefined;
  return <section className="cta-callout not-prose relative my-8 w-full min-w-0 overflow-hidden rounded-xl border border-zinc-200 p-5 dark:border-white/10">
      <div className="cta-callout-noise" aria-hidden="true" />
      <div className="cta-callout-layout">
        {title ? <div className="cta-callout-title-row">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" width="14" height="14" className="cta-callout-icon shrink-0 text-zinc-800 dark:text-zinc-200" aria-hidden="true">
              <g fill="none" fillRule="nonzero">
                <path d="M28 0v28H0V0h28ZM14.691833333333335 27.134333333333334l-0.012833333333333334 0.0023333333333333335 -0.08283333333333333 0.04083333333333334 -0.023333333333333334 0.004666666666666667 -0.016333333333333335 -0.004666666666666667 -0.08283333333333333 -0.04083333333333334c-0.011666666666666667 -0.004666666666666667 -0.022166666666666668 -0.0011666666666666668 -0.028000000000000004 0.005833333333333334l-0.004666666666666667 0.011666666666666667 -0.019833333333333335 0.49933333333333335 0.005833333333333334 0.023333333333333334 0.011666666666666667 0.015166666666666667 0.12133333333333333 0.08633333333333333 0.0175 0.004666666666666667 0.014000000000000002 -0.004666666666666667 0.12133333333333333 -0.08633333333333333 0.014000000000000002 -0.018666666666666668 0.004666666666666667 -0.019833333333333335 -0.019833333333333335 -0.4981666666666667c-0.0023333333333333335 -0.011666666666666667 -0.0105 -0.019833333333333335 -0.019833333333333335 -0.021Zm0.3091666666666667 -0.13183333333333336 -0.015166666666666667 0.0023333333333333335 -0.21583333333333335 0.1085 -0.011666666666666667 0.011666666666666667 -0.0035000000000000005 0.012833333333333334 0.021 0.5016666666666667 0.005833333333333334 0.014000000000000002 0.009333333333333334 0.008166666666666668 0.23450000000000004 0.1085c0.014000000000000002 0.004666666666666667 0.026833333333333334 0 0.03383333333333334 -0.009333333333333334l0.004666666666666667 -0.016333333333333335 -0.03966666666666667 -0.7163333333333334c-0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.023333333333333334 -0.023333333333333334 -0.025666666666666667Zm-0.8341666666666667 0.0023333333333333335a0.026833333333333334 0.026833333333334334 0 0 0 -0.0315 0.007000000000000001l-0.007000000000000001 0.016333333333333335 -0.03966666666666667 0.7163333333333334c0 0.014000000000000002 0.008166666666666668 0.023333333333333334 0.019833333333333335 0.028000000000000004l0.0175 -0.0023333333333333335 0.23450000000000004 -0.1085 0.011666666666666667 -0.009333333333333334 0.004666666666666667 -0.012833333333333334 0.019833333333333335 -0.5016666666666667 -0.0035000000000000005 -0.014000000000000002 -0.011666666666666667 -0.011666666666666667 -0.21466666666666667 -0.10733333333333334Z" strokeWidth="1.1667" />
                <path fill="currentColor" d="M14 2.916666666666667A1.75 1.75 0 0 1 15.750000000000002 4.666666666666667v6.302333333333334L21.207666666666668 7.816666666666667a1.75 1.75 0 0 1 1.75 3.031L17.5 14l5.457666666666667 3.151166666666667a1.75 1.75 0 0 1 -1.75 3.031l-5.457666666666667 -3.1500000000000004V23.333333333333336a1.75 1.75 0 0 1 -3.5 0v-6.302333333333334L6.792333333333334 20.183333333333337a1.75 1.75 0 1 1 -1.75 -3.031L10.5 14 5.042333333333334 10.848833333333333a1.75 1.75 0 0 1 1.75 -3.031l5.457666666666667 3.1500000000000004V4.666666666666667A1.75 1.75 0 0 1 14 2.916666666666667Z" strokeWidth="1.1667" />
              </g>
            </svg>
            <p className="cta-callout-title min-w-0 font-semibold text-zinc-800 dark:text-zinc-200">
              {title}
            </p>
          </div> : null}
        <div className={`cta-callout-body text-sm leading-normal text-zinc-800 dark:text-zinc-200${title ? " cta-callout-body--indented" : ""}`}>
          {children}
        </div>
        <a href={href} target={target} rel={linkRel} onClick={handleCtaClick} data-docs-cta={trackingEvent || undefined} className="cta-callout-button inline-flex items-center justify-center gap-1 rounded-full bg-white px-3 py-1.5 text-sm font-semibold transition hover:bg-zinc-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 dark:bg-white dark:hover:bg-zinc-200">
          {buttonLabel}
          <span className="cta-callout-button-arrow" aria-hidden="true">
            →
          </span>
        </a>
      </div>
    </section>;
};

In this tutorial, you’ll learn how to model online card payments using Blnk.

We cover the full flow a card issuer needs to handle: creating an authorization hold, tracking the pending transaction, settling it when the processor clears it, and voiding it when it’s cancelled.

By the end, you'll know the exact ledger steps needed to support basic card payments.

***

## How online card payments work

When a customer makes a purchase with a card, the following happens:

1. **Authorization:** When your customer tries to pay, the card network asks your system if the customer has enough balance. If yes, you reserve that amount and create an inflight transaction. The money is not taken yet, only held.

2. **Settlement:** A few hours or days later, the payment processor sends the final confirmation.

   * If the transaction is approved, you deduct the money from the customer’s balance and commit the transaction in your ledger.
   * If it is declined or reversed, you release the reserved amount by voiding the transaction.

***

## Prerequisites

Before starting, ensure you have:

1. A running Blnk Core instance (e.g. at `http://localhost:5001`).
2. [An API key](/advanced/secure-blnk) for Blnk (replace `YOUR_API_KEY` in the code examples). Required for authenticated requests.
3. Optionally, you can connect your Blnk Core to your [Blnk Cloud](https://cloud.blnkfinance.com/?utm_source=blnk_docs\&utm_medium=documentation\&utm_campaign=tutorials%2Fdigital-banking%2Fcards) workspace to view your ledger data.

Okay, let's dive in!

***

## 1: Create card account

Each card account will be represented with [a ledger](/ledgers/introduction.mdx). The card balance will be created under this ledger.

<Steps titleSize="h3">
  <Step title="Create a ledger">
    [Create a ledger](/reference/create-ledger) for the card account. This groups all of the balances for that customer in one place:

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X POST "http://localhost:5001/ledgers" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "Customers Card Ledger"
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.Ledgers.create({
        name: 'Customers Card Ledger',
      });
      ```

      ```go Go wrap theme={"system"}
      ledger, resp, err := client.Ledger.Create(blnkgo.CreateLedgerRequest{
        Name: "Customers Card Ledger",
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk ledgers create
      ```
    </CodeGroup>

    <Info>
      Save the `ledger_id` from the response. You'll need it to create balances for the card account.
    </Info>

    <img src="https://mintcdn.com/blnk/TZC-WDEljP4tkd4b/images/tutorials/cards/create-ledger.png?fit=max&auto=format&n=TZC-WDEljP4tkd4b&q=85&s=1e1032c3f3c2d2219cb1cd972940c914" className="rounded-xl" alt="Create a ledger" width="3386" height="1962" data-path="images/tutorials/cards/create-ledger.png" />
  </Step>

  <Step title="Create customer identity">
    [Create a customer identity](/reference/create-identity) to link all balances to the same customer identity:

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X POST "http://localhost:5001/identities" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "first_name": "Xavier",
          "last_name": "Woods"
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.Identity.create({
        first_name: 'Xavier',
        last_name: 'Woods',
      });
      ```

      ```go Go wrap theme={"system"}
      identity, resp, err := client.Identity.Create(blnkgo.Identity{
        FirstName: "Xavier",
        LastName:  "Woods",
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk identities create
      ```
    </CodeGroup>

    <Info>
      Save the `identity_id` from the response. You'll use this ID to link all balances to this customer.
    </Info>

    <img src="https://mintcdn.com/blnk/TZC-WDEljP4tkd4b/images/tutorials/cards/create-identity.png?fit=max&auto=format&n=TZC-WDEljP4tkd4b&q=85&s=2bc529fa80608845f6a21fb346e84dcc" className="rounded-xl" alt="Create a customer identity" width="3388" height="1954" data-path="images/tutorials/cards/create-identity.png" />
  </Step>

  <Step title="Create card balance">
    [Create a balance](/reference/create-balance) for the card account. This balance will track all card transactions:

    <CodeGroup>
      ```bash cURL wrap expandable theme={"system"}
      curl -X POST "http://localhost:5001/balances" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "ledger_id": "<customer-ledger-id>",
          "identity_id": "<xavier-identity-id>",
          "currency": "USD",
          "meta_data": {
            "last_4_digits": "1234",
            "card_scheme": "visa",
            "type": "virtual",
            "card-id": "card-id-1234"
          }
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.LedgerBalances.create({
        ledger_id: '<customer-ledger-id>',
        identity_id: '<xavier-identity-id>',
        currency: 'USD',
        meta_data: {
          last_4_digits: '1234',
          card_scheme: 'visa',
          type: 'virtual',
          'card-id': 'card-id-1234',
        },
      });
      ```

      ```go Go wrap theme={"system"}
      balance, resp, err := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
        LedgerID:   "<customer-ledger-id>",
        IdentityID: "<xavier-identity-id>",
        Currency:   "USD",
        MetaData: map[string]interface{}{
          "last_4_digits": "1234",
          "card_scheme":   "visa",
          "type":          "virtual",
          "card-id":       "card-id-1234",
        },
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk balances create
      ```
    </CodeGroup>

    <img src="https://mintcdn.com/blnk/TXzLPH9prJ_h5MuS/images/tutorials/cards/create-balance.png?fit=max&auto=format&n=TXzLPH9prJ_h5MuS&q=85&s=24019695fd2804fc035f4cd0dabe4663" className="rounded-xl" alt="Create card balance" width="3392" height="1952" data-path="images/tutorials/cards/create-balance.png" />

    <Info>
      Save the `balance_id` from the response. You'll need it for creating transactions throughout this tutorial.
    </Info>
  </Step>
</Steps>

***

## 2: Authorization

Authorizing a card transaction means telling the processor that the customer has enough funds to cover the transaction, and they can proceed with the transaction.

You can model this in Blnk with [inflight transactions](/transactions/inflight/creating-inflight).

<Steps titleSize="h3">
  <Step title="Money movement map">
    When a card transaction is authorized, money moves from the customer's card balance to `@World-USD` (representing the external merchant):

    <img src="https://mintcdn.com/blnk/jKiGmb7nTD9y-R5a/images/tutorials/cards/authorization-map.png?fit=max&auto=format&n=jKiGmb7nTD9y-R5a&q=85&s=47d564bfb4fb2d490d016a7c6e4c3259" className="rounded-xl" alt="Card authorization map" width="1028" height="410" data-path="images/tutorials/cards/authorization-map.png" />

    [Explore the map yourself here](https://map.blnkfinance.xyz/FwsQD41GqU)

    The transaction is created with `inflight: true`, which means it's pending until the payment processor clears it.
  </Step>

  <Step title="Create an inflight transaction">
    Create an inflight transaction to authorize the payment. Setting `inflight = true` puts the transaction in a pending state until it's settled or voided.

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X POST "http://localhost:5001/transactions" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "precise_amount": 200000,
          "precision": 100,
          "currency": "USD",
          "reference": "<unique-reference>",
          "source": "<customer-card_balance_id>",
          "destination": "@World-USD",
          "description": "Card transaction",
          "inflight": true
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.Transactions.create({
        precise_amount: 200000,
        precision: 100,
        currency: 'USD',
        reference: '<unique-reference>',
        source: '<customer-card_balance_id>',
        destination: '@World-USD',
        description: 'Card transaction',
        inflight: true,
      });
      ```

      ```go Go wrap theme={"system"}
      transaction, resp, err := client.Transaction.Create(blnkgo.CreateTransactionRequest{
        ParentTransaction: blnkgo.ParentTransaction{
          PreciseAmount: 200000,
          Precision:   100,
          Currency:    "USD",
          Reference:   "<unique-reference>",
          Source:      "<customer-card_balance_id>",
          Destination: "@World-USD",
          Description: "Card transaction",
        },
        Inflight: true,
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk transactions create
      ```
    </CodeGroup>

    <Note>
      `@World-USD` is an [internal balance](/balances/internal-balances) that represents money leaving and coming into your system (like payments to merchants or payments from customers). Internal balances use the `@` prefix and don't require a balance ID.
    </Note>

    Your transaction will be recorded successfully and it will be in `INFLIGHT` status (pending a commit or void action).

    <img src="https://mintcdn.com/blnk/TZC-WDEljP4tkd4b/images/tutorials/cards/card-auth.png?fit=max&auto=format&n=TZC-WDEljP4tkd4b&q=85&s=351dee1aef9901a313c6c0cc7d83bca0" className="rounded-xl" alt="Card authorization" width="3392" height="1960" data-path="images/tutorials/cards/card-auth.png" />
  </Step>

  <Step title="Handling fund reservation">
    When a transaction is inflight, the amount is reserved and cannot be used for anything else until it’s settled or voided.

    Blnk handles this automatically. Every inflight transaction reduces the customer’s available balance. If the available balance isn’t enough to cover a new transaction, the new one is rejected.

    For example:

    * Customer balance: \$5,000
    * One inflight transaction: \$2,000
    * Available balance: \$3,000

    Any new transaction above \$3,000 will fail. This ensures you never spend money that has already been reserved for pending card payments.

    <img src="https://mintcdn.com/blnk/TZC-WDEljP4tkd4b/images/tutorials/cards/limit-passed.png?fit=max&auto=format&n=TZC-WDEljP4tkd4b&q=85&s=c51c13f92b81a6dc26ff1b9d0803a692" className="rounded-xl" alt="Handling insufficient funds" width="3394" height="1960" data-path="images/tutorials/cards/limit-passed.png" />
  </Step>
</Steps>

***

## 3: Settlement

Once a transaction is settled by the payment processor, it means the money has finally been transferred to the recipient. Now, you can commit the inflight transaction.

This [moves the transaction](/transactions/transaction-lifecycle) from `INFLIGHT` (pending) to `APPLIED` (completed).

If it was rejected or cancelled, you can void the transaction instead.

<Steps titleSize="h3">
  <Step title="Money movement map">
    When you commit the inflight transaction, it finalizes the money movement that was authorized:

    <img src="https://mintcdn.com/blnk/jKiGmb7nTD9y-R5a/images/tutorials/cards/settlement-map.png?fit=max&auto=format&n=jKiGmb7nTD9y-R5a&q=85&s=9f2be91ea07a8427dd04a2006c61b40f" className="rounded-xl" alt="Card settlement map" width="1002" height="412" data-path="images/tutorials/cards/settlement-map.png" />

    The transaction status changes from `INFLIGHT` to `APPLIED`, completing the transfer.
  </Step>

  <Step title="Commit the inflight transaction">
    Commit the inflight transaction from the authorization step once clearing is received. This turns the transaction from `INFLIGHT` (pending) to `APPLIED` (completed):

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X PUT "http://localhost:5001/transactions/inflight/<transaction_id>" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "status": "commit"
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.Transactions.updateStatus('<transaction_id>', {
        status: 'commit',
      });
      ```

      ```go Go wrap theme={"system"}
      txn, resp, err := client.Transaction.Update("<transaction_id>", blnkgo.UpdateStatus{
        Status: blnkgo.InflightStatusCommit,
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk transactions update --commit <transaction_id>
      ```
    </CodeGroup>
  </Step>

  <Step title="Void a transaction">
    If a transaction is rejected or cancelled, you can void it instead of committing it:

    <CodeGroup>
      ```bash cURL wrap theme={"system"}
      curl -X PUT "http://localhost:5001/transactions/inflight/<transaction_id>" \
        -H "X-Blnk-Key: <api-key>" \
        -H "Content-Type: application/json" \
        -d '{
          "status": "void"
        }'
      ```

      ```typescript TypeScript wrap theme={"system"}
      const response = await blnk.Transactions.updateStatus('<transaction_id>', {
        status: 'void',
      });
      ```

      ```go Go wrap theme={"system"}
      txn, resp, err := client.Transaction.Update("<transaction_id>", blnkgo.UpdateStatus{
        Status: blnkgo.InflightStatusVoid,
      })
      ```

      ```bash Blnk CLI wrap theme={"system"}
      blnk transactions update --void <transaction_id>
      ```
    </CodeGroup>
  </Step>
</Steps>

***

## Conclusion

You now have a fully functional card payment system built with Blnk Finance. This system can:

* Create and manage card accounts with a ledger and card balance
* Authorize card transactions using inflight transactions to reserve funds
* Commit or void inflight transactions when they're settled or rejected

***

<CtaCallout title="Need help with your use case?" href="https://blnkfinance.com/contact/us?utm_source=blnk_docs&utm_medium=documentation&utm_campaign=home%2Finstall" buttonLabel="Speak with us" trackingEvent="clicked_pro_support">
  Get dedicated support for architecture reviews, integration planning, ledger workflows, and production deployment.
</CtaCallout>
