> ## 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 a Loyalty Points System

> Learn how to implement a loyalty points system with Blnk.

export const RelatedTopics = ({title = "Related topics", items = []}) => {
  if (!items.length) {
    return null;
  }
  return <nav className="related-topics not-prose mt-20 mb-10 flex flex-col" aria-label={title}>
      <p className="related-topics-heading m-0 border-b border-zinc-200 pb-3 text-sm font-medium text-zinc-500 dark:border-white/10 dark:text-zinc-400">
        {title}
      </p>
      <ul className="related-topics-list m-0 mt-3 flex list-none flex-col gap-0.5 p-0">
        {items.map(item => {
    const isExternal = typeof item.href === "string" && (/^https?:\/\//i).test(item.href);
    return <li key={item.href} className="m-0 p-0">
              <a href={item.href} target={isExternal ? "_blank" : undefined} rel={isExternal ? "noopener noreferrer" : undefined} className="related-topics-link group inline-flex items-center gap-2 text-sm font-semibold text-zinc-700 no-underline transition-colors dark:text-zinc-300">
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="related-topics-icon shrink-0 text-zinc-400 dark:text-zinc-500" aria-hidden="true">
                  <path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
                  <path d="M14 2v4a2 2 0 0 0 2 2h4" />
                  <path d="M10 9H8" />
                  <path d="M16 13H8" />
                  <path d="M16 17H8" />
                </svg>
                <span className="relative top-px transition-colors group-hover:text-[#DD7B1B]">
                  {item.title}
                </span>
              </a>
            </li>;
  })}
      </ul>
    </nav>;
};

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, we will build a simple loyalty points system using the Blnk Ledger. Here's what we'll do:

1. Create and manage multi-asset wallets in your ledger.
2. Manage a pool of issued loyalty points.
3. Award points to customers when they make purchases.
4. Allow customers to redeem their points for discounts on future purchases.

For this tutorial, we'll use the [Blnk TypeScript SDK](/sdks/typescript) and [Blnk Go SDK](/sdks/go) for the implementation. If you prefer, you can also refer to the [API reference](/reference/create-ledger) for details on the available endpoints.

***

## Designing your map

Before writing code, it's crucial to design a [money movement map](/ledgers/money-movement-map) that outlines how money moves in your system. This serves as the blueprint for your implementation.

For our loyalty points system, we have two workflows - awarding points and redeeming points.

1. **Awarding points:**

   Points are awarded to customers who qualify for the loyalty program. A customer purchase triggers the process: funds from the `Customer Main Wallet` are used for the purchase, contributing to `@RevenueUSD`.

   Once the purchase is completed, points are awarded from the `@PointsPool` to the `Customer Points Wallet`.

   * `@RevenueUSD`: An internal balance representing the business' revenue.
   * `@PointsPool`: An internal balance tracking the total points issued to customers.

   <img src="https://mintcdn.com/blnk/aZhTjw2yAR_BF0cV/images/tutorials/loyalty-points/awarding-points-map.png?fit=max&auto=format&n=aZhTjw2yAR_BF0cV&q=85&s=1bba61466d430e56355493a89303c067" alt="Awarding loyalty points map" width="1310" height="600" data-path="images/tutorials/loyalty-points/awarding-points-map.png" />

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

2. **Redeeming points:**

   Customers can redeem points to receive discounts on purchases. When a customer applies points to a transaction, the discount is deducted from the original price.

   Once the purchase is completed, the redeemed points are transferred from the `Customer Points Wallet` to `@PointsPool`.

   <img src="https://mintcdn.com/blnk/aZhTjw2yAR_BF0cV/images/tutorials/loyalty-points/redeeming-points-map.png?fit=max&auto=format&n=aZhTjw2yAR_BF0cV&q=85&s=eba45ff729b3b3ce6e38d412322f0885" alt="Redeeming loyalty points map" width="1321" height="609" data-path="images/tutorials/loyalty-points/redeeming-points-map.png" />

<Tip>
  `@PointsPool` records all issued points using the `debit_balance` parameter and tracks all redeemed points using the `credit_balance` parameter.
</Tip>

***

## Set up your implementation

Based on our map, we'll implement the following:

1. Create the main and points balances for the customer.
2. Initiate a purchase transaction and award points to the customer.
3. Redeem the points in a second purchase transaction.

***

## Prerequisites

Before starting, ensure you have:

1. A running Blnk Core instance (e.g. at `http://localhost:5001`).
2. An API key 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%2Fquick-start%2Floyalty-points) workspace to view your ledger data.

***

## Create your ledgers

First, we need to create ledgers to organize our wallets:

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  # Create main wallet ledger
  curl -X POST "http://localhost:5001/ledgers" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Customer Main Wallets",
      "meta_data": {
          "description": "Ledger for customer USD wallets"
      }
    }'

  # Create points wallet ledger
  curl -X POST "http://localhost:5001/ledgers" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Customer Points Wallets",
      "meta_data": {
          "description": "Ledger for customer loyalty points"
      }
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function createLedgers() {
    // Create a ledger for customer main wallets (USD)
    const mainLedger = await blnk.Ledgers.create({
      name: "Customer Main Wallets",
      meta_data: {
        description: "Ledger for customer USD wallets"
      }
    });
    
    // Create a ledger for customer points wallets
    const pointsLedger = await blnk.Ledgers.create({
      name: "Customer Points Wallets",
      meta_data: {
        description: "Ledger for customer loyalty points"
      }
    });
    
    console.log("Created main wallet ledger:", mainLedger.data.ledger_id);
    console.log("Created points wallet ledger:", pointsLedger.data.ledger_id);
    
    return {
      mainLedgerId: mainLedger.data.ledger_id,
      pointsLedgerId: pointsLedger.data.ledger_id
    };
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func createLedgers() (mainLedgerID, pointsLedgerID string, err error) {
      client := getClient()

      mainLedger, _, err := client.Ledger.Create(
          blnkgo.CreateLedgerRequest{
              Name: "Customer Main Wallets",
              MetaData: blnkgo.MetaData{
                  "description": "Ledger for customer USD wallets",
              },
          },
      )
      if err != nil {
          return "", "", err
      }

      pointsLedger, _, err := client.Ledger.Create(
          blnkgo.CreateLedgerRequest{
              Name: "Customer Points Wallets",
              MetaData: blnkgo.MetaData{
                  "description": "Ledger for customer loyalty points",
              },
          },
      )
      if err != nil {
          return "", "", err
      }

      fmt.Println("Created main wallet ledger:", mainLedger.LedgerID)
      fmt.Println("Created points wallet ledger:", pointsLedger.LedgerID)
      return mainLedger.LedgerID, pointsLedger.LedgerID, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  # Create a ledger for customer main wallets (USD)
  main_ledger = blnk.ledgers.create({
    "name": "Customer Main Wallets",
    "meta_data": {
      "description": "Ledger for customer USD wallets",
    },
  })

  # Create a ledger for customer points wallets
  points_ledger = blnk.ledgers.create({
    "name": "Customer Points Wallets",
    "meta_data": {
      "description": "Ledger for customer loyalty points",
    },
  })

  print("Created main wallet ledger:", main_ledger.data["ledger_id"])
  print("Created points wallet ledger:", points_ledger.data["ledger_id"])
  ```

  ```java Java wrap expandable theme={"system"}
  ApiResponse<JsonNode> main_ledger = blnk.ledgers().create(
      CreateLedger.create()
          .name("Customer Main Wallets")
          .metaData(Map.of(
              "description",
              "Ledger for customer USD wallets"
          ))
  );

  ApiResponse<JsonNode> points_ledger = blnk.ledgers().create(
      CreateLedger.create()
          .name("Customer Points Wallets")
          .metaData(Map.of(
              "description",
              "Ledger for customer loyalty points"
          ))
  );
  System.out.println(
      "Created main wallet ledger: " + main_ledger.data().get("ledger_id").asText()
  );
  System.out.println(
      "Created points wallet ledger: " + points_ledger.data().get("ledger_id").asText()
  );
  ```
</CodeGroup>

***

## Create balances for customer funds and loyalty points

Blnk Finance supports tracking of different assets (e.g., USD and POINTS) in your ledger.

Now, let's create the main wallet and points wallet for the customer:

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  # Create main wallet
  curl -X POST "http://localhost:5001/balances" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "ledger_id": "<main-ledger-id>",
      "identity_id": "<customer-identity-id>",
      "currency": "USD",
      "meta_data": {
          "wallet_type": "main",
          "status": "active"
      }
    }'

  # Create points wallet
  curl -X POST "http://localhost:5001/balances" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "ledger_id": "<points-ledger-id>",
      "identity_id": "<customer-identity-id>",
      "currency": "POINTS",
      "meta_data": {
          "wallet_type": "points",
          "status": "active"
      }
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function createCustomerWallets(mainLedgerId, pointsLedgerId, identityId) {
    // Create main wallet (USD)
    const mainWallet = await blnk.LedgerBalances.create({
      ledger_id: mainLedgerId,
      identity_id: identityId,
      currency: "USD",
      meta_data: {
        wallet_type: "main",
        status: "active"
      }
    });
    
    // Create points wallet
    const pointsWallet = await blnk.LedgerBalances.create({
      ledger_id: pointsLedgerId,
      identity_id: identityId,
      currency: "POINTS",
      meta_data: {
        wallet_type: "points",
        status: "active"
      }
    });
    
    console.log("Created main wallet:", mainWallet.data.balance_id);
    console.log("Created points wallet:", pointsWallet.data.balance_id);
    
    return {
      mainWalletId: mainWallet.data.balance_id,
      pointsWalletId: pointsWallet.data.balance_id
    };
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func createCustomerWallets(
      mainLedgerID, pointsLedgerID, identityID string,
  ) (mainWalletID, pointsWalletID string, err error) {
      client := getClient()

      mainWallet, _, err := client.LedgerBalance.Create(
          blnkgo.CreateLedgerBalanceRequest{
              LedgerID: mainLedgerID,
              IdentityID: identityID,
              Currency: "USD",
              MetaData: blnkgo.MetaData{
                  "wallet_type": "main",
                  "status": "active",
              },
          },
      )
      if err != nil {
          return "", "", err
      }

      pointsWallet, _, err := client.LedgerBalance.Create(
          blnkgo.CreateLedgerBalanceRequest{
              LedgerID: pointsLedgerID,
              IdentityID: identityID,
              Currency: "POINTS",
              MetaData: blnkgo.MetaData{
                  "wallet_type": "points",
                  "status": "active",
              },
          },
      )
      if err != nil {
          return "", "", err
      }

      fmt.Println("Created main wallet:", mainWallet.BalanceID)
      fmt.Println("Created points wallet:", pointsWallet.BalanceID)
      return mainWallet.BalanceID, pointsWallet.BalanceID, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  # Create main wallet (USD)
  main_wallet = blnk.ledger_balances.create({
    "ledger_id": main_ledger_id,
    "identity_id": identity_id,
    "currency": "USD",
    "meta_data": {
      "wallet_type": "main",
      "status": "active",
    },
  })

  # Create points wallet
  points_wallet = blnk.ledger_balances.create({
    "ledger_id": points_ledger_id,
    "identity_id": identity_id,
    "currency": "POINTS",
    "meta_data": {
      "wallet_type": "points",
      "status": "active",
    },
  })

  print("Created main wallet:", main_wallet.data["balance_id"])
  print("Created points wallet:", points_wallet.data["balance_id"])
  ```

  ```java Java wrap expandable theme={"system"}
  ApiResponse<JsonNode> main_wallet = blnk.ledgerBalances().create(
      CreateLedgerBalance.create()
          .ledgerId(main_ledger_id)
          .identityId(identity_id)
          .currency("USD")
          .metaData(Map.of(
              "wallet_type", "main",
              "status", "active"
          ))
  );

  ApiResponse<JsonNode> points_wallet = blnk.ledgerBalances().create(
      CreateLedgerBalance.create()
          .ledgerId(points_ledger_id)
          .identityId(identity_id)
          .currency("POINTS")
          .metaData(Map.of(
              "wallet_type", "points",
              "status", "active"
          ))
  );
  System.out.println(
      "Created main wallet: " + main_wallet.data().get("balance_id").asText()
  );
  System.out.println(
      "Created points wallet: " + points_wallet.data().get("balance_id").asText()
  );
  ```
</CodeGroup>

***

## Awarding points after a purchase

When a customer makes a purchase, we need to:

1. Process the payment from the customer's main wallet to the revenue account.
2. Award loyalty points based on the purchase amount.

<Tip>
  Ensure your customer main wallet is funded. Learn how: [Wallet Funding →](/tutorials/quick-start/wallet-management#funding-the-main-wallet)
</Tip>

Record the purchase payment and award loyalty points:

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  # Process purchase
  curl -X POST "http://localhost:5001/transactions" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "precise_amount": 10000,
      "precision": 100,
      "reference": "purchase-123456789",
      "currency": "USD",
      "source": "<customer-main-balance-id>",
      "destination": "@RevenueUSD",
      "description": "Product purchase",
      "meta_data": {
          "transaction_type": "purchase"
      }
    }'

  # Award points
  curl -X POST "http://localhost:5001/transactions" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "precise_amount": 10,
      "precision": 1,
      "reference": "points-purchase-123456789",
      "currency": "POINTS",
      "source": "@PointsPool",
      "destination": "<customer-points-balance-id>",
      "description": "Loyalty points award",
      "allow_overdraft": true,
      "meta_data": {
          "purchase_reference": "purchase-123456789",
          "purchase_amount": 100
      }
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function processPurchaseAndAwardPoints(
    mainWalletId,
    pointsWalletId,
    purchaseAmount,
    pointsToAward,
  ) {
    // Generate a unique reference for this purchase
    const purchaseReference = `purchase-${Date.now()}`;
    
    // Step 1: Process the purchase payment
    const purchase = await blnk.Transactions.create({
      precise_amount: purchaseAmount * 100,
      precision: 100,
      reference: purchaseReference,
      currency: "USD",
      source: mainWalletId,
      destination: "@RevenueUSD",
      description: "Product purchase",
      meta_data: {
        transaction_type: "purchase"
      }
    });
    
    // Step 2: Award loyalty points
    const pointsAward = await blnk.Transactions.create({
      precise_amount: pointsToAward,
      precision: 1,  // Points typically don't have decimal places
      reference: `points-${purchaseReference}`,
      currency: "POINTS",
      source: "@PointsPool",
      destination: pointsWalletId,
      description: "Loyalty points award",
      allow_overdraft: true,  // Points pool can have negative balance
      meta_data: {
        purchase_reference: purchaseReference,
        purchase_amount: purchaseAmount
      }
    });
    
    console.log("Purchase processed:", purchase.data.transaction_id);
    console.log("Points awarded:", pointsAward.data.transaction_id);
    
    return {
      purchaseId: purchase.data.transaction_id,
      pointsAwardId: pointsAward.data.transaction_id
    };
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func processPurchaseAndAwardPoints(
      mainWalletID, pointsWalletID string,
      purchaseAmount, pointsToAward float64,
  ) (purchaseID, pointsAwardID string, err error) {
      client := getClient()
      purchaseReference := fmt.Sprintf("purchase-%d", time.Now().Unix())

      purchase, _, err := client.Transaction.Create(
          blnkgo.CreateTransactionRequest{
              ParentTransaction: blnkgo.ParentTransaction{
                  PreciseAmount: purchaseAmount * 100,
                  Precision: 100,
                  Reference: purchaseReference,
                  Currency: "USD",
                  Source: mainWalletID,
                  Destination: "@RevenueUSD",
                  Description: "Product purchase",
                  MetaData: blnkgo.MetaData{
                      "transaction_type": "purchase",
                  },
              },
          },
      )
      if err != nil {
          return "", "", err
      }

      pointsAward, _, err := client.Transaction.Create(
          blnkgo.CreateTransactionRequest{
              ParentTransaction: blnkgo.ParentTransaction{
                  PreciseAmount: pointsToAward,
                  Precision: 1,
                  Reference: "points-" + purchaseReference,
                  Currency: "POINTS",
                  Source: "@PointsPool",
                  Destination: pointsWalletID,
                  Description: "Loyalty points award",
                  MetaData: blnkgo.MetaData{
                      "purchase_reference": purchaseReference,
                      "purchase_amount": purchaseAmount,
                  },
              },
              AllowOverdraft: true,
          },
      )
      if err != nil {
          return "", "", err
      }

      fmt.Println("Purchase processed:", purchase.TransactionID)
      fmt.Println("Points awarded:", pointsAward.TransactionID)
      return purchase.TransactionID, pointsAward.TransactionID, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  from datetime import datetime

  # Generate a unique reference for this purchase
  purchase_reference = f"purchase-{int(datetime.now().timestamp())}"

  # Step 1: Process the purchase payment
  purchase = blnk.transactions.create({
    "precise_amount": purchase_amount * 100,
    "precision": 100,
    "reference": purchase_reference,
    "currency": "USD",
    "source": main_wallet_id,
    "destination": "@RevenueUSD",
    "description": "Product purchase",
    "meta_data": {
      "transaction_type": "purchase",
    },
  })

  # Step 2: Award loyalty points
  points_award = blnk.transactions.create({
    "precise_amount": points_to_award,
    "precision": 1,  # Points typically don't have decimal places
    "reference": f"points-{purchase_reference}",
    "currency": "POINTS",
    "source": "@PointsPool",
    "destination": points_wallet_id,
    "description": "Loyalty points award",
    "allow_overdraft": True,  # Points pool can have negative balance
    "meta_data": {
      "purchase_reference": purchase_reference,
      "purchase_amount": purchase_amount,
    },
  })

  print("Purchase processed:", purchase.data["transaction_id"])
  print("Points awarded:", points_award.data["transaction_id"])
  ```

  ```java Java wrap expandable theme={"system"}
  String purchase_reference = "purchase-<timestamp>";

  ApiResponse<JsonNode> purchase = blnk.transactions().create(
      CreateTransactions.create()
          .preciseAmount((purchase_amount * 100))
          .precision(100)
          .reference(purchase_reference)
          .currency("USD")
          .source(main_wallet_id)
          .destination("@RevenueUSD")
          .description("Product purchase")
          .metaData(Map.of("transaction_type", "purchase"))
  );

  ApiResponse<JsonNode> points_award = blnk.transactions().create(
      CreateTransactions.create()
          .preciseAmount(points_to_award)
          .precision(1)
          .reference("points-" + purchase_reference)
          .currency("POINTS")
          .source("@PointsPool")
          .destination(points_wallet_id)
          .description("Loyalty points award")
          .allowOverdraft(true)
          .metaData(Map.of(
              "purchase_reference", purchase_reference,
              "purchase_amount", purchase_amount
          ))
  );
  System.out.println(
      "Purchase processed: " + purchase.data().get("transaction_id").asText()
  );
  System.out.println(
      "Points awarded: " + points_award.data().get("transaction_id").asText()
  );
  ```
</CodeGroup>

<Tip>
  As shown above, you can set custom precision for each currency, like 100 for USD or 1 for POINTS in our example.

  For ledger accuracy, ensure the source and destination currencies match when posting transactions.
</Tip>

***

## Redeeming points during a purchase

When you make a purchase, you can use your loyalty points to get a discount. In our example, 10 points = 1 USD.

For example, a customer has 500 points and wants to buy something that costs \$100:

1. They choose to use 200 points from their `Points Wallet`.
2. Those 200 points convert to \$20 (at the 10 points = \$1 rate)
3. Their final cost to pay from their `Main Wallet` would be \$80 instead of \$100.

Behind the scenes, here's what we'll do:

1. Take points from the `Customer Points Wallet` to `@PointsPool`.
2. Convert the points to its equivalent dollar value, apply it as discount, and calculate the final cost for the customer.
3. Charge the `Customer Main Wallet` with the final discounted cost.

Redeem points and charge the discounted purchase amount:

<CodeGroup>
  ```bash cURL wrap expandable theme={"system"}
  # Redeem points
  curl -X POST "http://localhost:5001/transactions" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "precise_amount": 200,
      "precision": 1,
      "reference": "redemption-123456789",
      "currency": "POINTS",
      "source": "<customer-points-balance-id>",
      "destination": "@PointsPool",
      "description": "Points redemption for discount",
      "meta_data": {
          "transaction_type": "redemption",
          "convert_to_usd": 20
      }
    }'

  # Process discounted purchase
  curl -X POST "http://localhost:5001/transactions" \
    -H "X-blnk-key: <api-key>" \
    -H "Content-Type: application/json" \
    -d '{
      "precise_amount": 8000,
      "precision": 100,
      "reference": "purchase-redemption-123456789",
      "currency": "USD",
      "source": "<customer-main-balance-id>",
      "destination": "@RevenueUSD",
      "description": "Discounted purchase with points",
      "meta_data": {
          "redemption_reference": "redemption-123456789",
          "original_amount": 100,
          "points_redeemed": 200
      }
    }'
  ```

  ```typescript TypeScript wrap expandable theme={"system"}
  async function redeemPointsForDiscount(
    mainWalletId,
    pointsWalletId,
    purchaseAmount,
    pointsToRedeem,
  ) {
    // Calculate points conversion to dollar: 10 points = 1 dollar
    const convertedAmount = pointsToRedeem / 10;
    
    // Generate a unique reference for this redemption
    const redemptionReference = `redemption-${Date.now()}`;
    
    // Step 1: Redeem points
    const pointsRedemption = await blnk.Transactions.create({
      precise_amount: pointsToRedeem,
      precision: 1,
      reference: redemptionReference,
      currency: "POINTS",
      source: pointsWalletId,
      destination: "@PointsPool",
      description: "Points redemption for discount",
      meta_data: {
        transaction_type: "redemption",
        convert_to_usd: convertedAmount
      }
    });
    
    // Step 2: Process the discounted purchase
    const discountedAmount = purchaseAmount - convertedAmount;
    
    const discountedPurchase = await blnk.Transactions.create({
      precise_amount: discountedAmount * 100,
      precision: 100,
      reference: `purchase-${redemptionReference}`,
      currency: "USD",
      source: mainWalletId,
      destination: "@RevenueUSD",
      description: "Discounted purchase with points",
      meta_data: {
        redemption_reference: redemptionReference,
        original_amount: purchaseAmount,
        points_redeemed: pointsToRedeem
      }
    });
    
    console.log("Points redeemed:", pointsRedemption.data.transaction_id);
    console.log("Discounted purchase processed:", discountedPurchase.data.transaction_id);
    
    return {
      redemptionId: pointsRedemption.data.transaction_id,
      purchaseId: discountedPurchase.data.transaction_id
    };
  }
  ```

  ```go Go wrap expandable theme={"system"}
  func redeemPointsForDiscount(
      mainWalletID, pointsWalletID string,
      purchaseAmount, pointsToRedeem float64,
  ) (redemptionID, purchaseID string, err error) {
      client := getClient()
      convertedAmount := pointsToRedeem / 10
      redemptionReference := fmt.Sprintf("redemption-%d", time.Now().Unix())

      pointsRedemption, _, err := client.Transaction.Create(
          blnkgo.CreateTransactionRequest{
              ParentTransaction: blnkgo.ParentTransaction{
                  PreciseAmount: pointsToRedeem,
                  Precision: 1,
                  Reference: redemptionReference,
                  Currency: "POINTS",
                  Source: pointsWalletID,
                  Destination: "@PointsPool",
                  Description: "Points redemption for discount",
                  MetaData: blnkgo.MetaData{
                      "transaction_type": "redemption",
                      "convert_to_usd": convertedAmount,
                  },
              },
          },
      )
      if err != nil {
          return "", "", err
      }

      discountedAmount := purchaseAmount - convertedAmount
      discountedPurchase, _, err := client.Transaction.Create(
          blnkgo.CreateTransactionRequest{
              ParentTransaction: blnkgo.ParentTransaction{
                  PreciseAmount: discountedAmount * 100,
                  Precision: 100,
                  Reference: "purchase-" + redemptionReference,
                  Currency: "USD",
                  Source: mainWalletID,
                  Destination: "@RevenueUSD",
                  Description: "Discounted purchase with points",
                  MetaData: blnkgo.MetaData{
                      "redemption_reference": redemptionReference,
                      "original_amount": purchaseAmount,
                      "points_redeemed": pointsToRedeem,
                  },
              },
          },
      )
      if err != nil {
          return "", "", err
      }

      fmt.Println("Points redeemed:", pointsRedemption.TransactionID)
      fmt.Println("Discounted purchase processed:", discountedPurchase.TransactionID)
      return pointsRedemption.TransactionID, discountedPurchase.TransactionID, nil
  }
  ```

  ```python Python wrap expandable theme={"system"}
  from datetime import datetime

  # Calculate points conversion to dollar: 10 points = 1 dollar
  converted_amount = points_to_redeem / 10

  # Generate a unique reference for this redemption
  redemption_reference = f"redemption-{int(datetime.now().timestamp())}"

  # Step 1: Redeem points
  points_redemption = blnk.transactions.create({
    "precise_amount": points_to_redeem,
    "precision": 1,
    "reference": redemption_reference,
    "currency": "POINTS",
    "source": points_wallet_id,
    "destination": "@PointsPool",
    "description": "Points redemption for discount",
    "meta_data": {
      "transaction_type": "redemption",
      "convert_to_usd": converted_amount,
    },
  })

  # Step 2: Process the discounted purchase
  discounted_amount = purchase_amount - converted_amount

  discounted_purchase = blnk.transactions.create({
    "precise_amount": discounted_amount * 100,
    "precision": 100,
    "reference": f"purchase-{redemption_reference}",
    "currency": "USD",
    "source": main_wallet_id,
    "destination": "@RevenueUSD",
    "description": "Discounted purchase with points",
    "meta_data": {
      "redemption_reference": redemption_reference,
      "original_amount": purchase_amount,
      "points_redeemed": points_to_redeem,
    },
  })

  print("Points redeemed:", points_redemption.data["transaction_id"])
  print("Discounted purchase processed:", discounted_purchase.data["transaction_id"])
  ```

  ```java Java wrap expandable theme={"system"}
  var converted_amount = (points_to_redeem / 10);
  String redemption_reference = "redemption-<timestamp>";

  ApiResponse<JsonNode> points_redemption = blnk.transactions().create(
      CreateTransactions.create()
          .preciseAmount(points_to_redeem)
          .precision(1)
          .reference(redemption_reference)
          .currency("POINTS")
          .source(points_wallet_id)
          .destination("@PointsPool")
          .description("Points redemption for discount")
          .metaData(Map.of(
              "transaction_type", "redemption",
              "convert_to_usd", converted_amount
          ))
  );

  ApiResponse<JsonNode> discounted_purchase = blnk.transactions().create(
      CreateTransactions.create()
          .preciseAmount((discounted_amount * 100))
          .precision(100)
          .reference("purchase-" + redemption_reference)
          .currency("USD")
          .source(main_wallet_id)
          .destination("@RevenueUSD")
          .description("Discounted purchase with points")
          .metaData(Map.of(
              "redemption_reference", redemption_reference,
              "original_amount", purchase_amount,
              "points_redeemed", points_to_redeem
          ))
  );
  System.out.println(
      "Points redeemed: " + points_redemption.data().get("transaction_id").asText()
  );
  System.out.println(
      "Discounted purchase processed: " + discounted_purchase.data().get("transaction_id").asText()
  );
  ```
</CodeGroup>

***

## Conclusion

You now have a basic loyalty points system built with the Blnk Ledger. This system enables you to award points to customers based on their purchases and allows them to redeem those points for discounts.

The system is flexible and can be extended with additional features like:

* Points expiry management
* Tiered rewards based on customer spending levels
* Special promotions with bonus points
* Point transfers between customers

By leveraging Blnk's transaction system, you can ensure all point movements are accurately tracked and reconciled, providing a reliable foundation for your loyalty program.

***

<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>

***

<RelatedTopics
  items={[
{ title: "Balances", href: "/balances/introduction" },
{ title: "Precision", href: "/transactions/precision" },
{ title: "Record a transaction", href: "/transactions/introduction" },
]}
/>
