Tutorials
Loyalty points
Learn how to implement loyalty points with Blnk.
Loyalty points are a balance a customer earns on a purchase and spends on a later one. Awarding points issues them from a pool, and redeeming points returns them to that pool.
This tutorial shows how to implement that with Blnk.
Step
Design the money movement
Before we start building, we need to define our money movement. Alice pays for a purchase from her USD balance, and we award points from a pool into her points balance.
A purchase of $100.00 moves from Alice to @RevenueUSD. We then issue 10 points from @PointsPool to her points balance, one point for every $10 she spends.
When she buys again, those points move back to @PointsPool. Ten points are worth $1.00, so a $20.00 purchase costs her $19.00 from her USD balance.
Step
Create a ledger
Before we start recording transactions, we need to set up our ledger to track Alice’s USD balance and her points balance.
For our example, she holds both in one ledger. We’ll create that ledger for the two balances.
Create the ledger:
curl -X POST "CORE_URL/ledgers" \
-H "X-blnk-key: CORE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "{{name}}",
"meta_data": {
"description": "{{description}}"
}
}'const response = await blnk.Ledgers.create({
name: '{{name}}',
meta_data: {
description: '{{description}}',
},
});ledger, _, _ := client.Ledger.Create(blnkgo.CreateLedgerRequest{
Name: "{{name}}",
MetaData: blnkgo.MetaData{
"description": "{{description}}",
},
})response = blnk.ledgers.create({
"name": "{{name}}",
"meta_data": {
"description": "{{description}}",
},
})ApiResponse<JsonNode> response = blnk.ledgers().create(
CreateLedger.create()
.name("{{name}}")
.metaData(Map.of(
"description", "{{description}}"
)));Step
Create an identity
An identity is how we record the customer. We pass Alice’s type, name, email, and phone number.
Anything else we want to keep goes in metadata. We’ll store her customer id there as customer_id.
Create the identity:
curl -X POST "CORE_URL/identities" \
-H "X-blnk-key: CORE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"identity_type": "individual",
"first_name": "{{first_name}}",
"last_name": "{{last_name}}",
"email_address": "{{email_address}}",
"phone_number": "{{phone_number}}",
"meta_data": {
"customer_id": "{{customer_id}}"
}
}'const response = await blnk.Identity.create({
identity_type: 'individual',
first_name: '{{first_name}}',
last_name: '{{last_name}}',
email_address: '{{email_address}}',
phone_number: '{{phone_number}}',
meta_data: {
customer_id: '{{customer_id}}',
},
});identity, _, _ := client.Identity.Create(blnkgo.Identity{
IdentityType: blnkgo.Individual,
FirstName: "{{first_name}}",
LastName: "{{last_name}}",
EmailAddress: "{{email_address}}",
PhoneNumber: "{{phone_number}}",
MetaData: blnkgo.MetaData{
"customer_id": "{{customer_id}}",
},
})response = blnk.identity.create({
"identity_type": "individual",
"first_name": "{{first_name}}",
"last_name": "{{last_name}}",
"email_address": "{{email_address}}",
"phone_number": "{{phone_number}}",
"meta_data": {
"customer_id": "{{customer_id}}",
},
})ApiResponse<JsonNode> response = blnk.identity().create(
IdentityData.create()
.identityType("individual")
.firstName("{{first_name}}")
.lastName("{{last_name}}")
.emailAddress("{{email_address}}")
.phoneNumber("{{phone_number}}")
.metaData(Map.of("customer_id", "{{customer_id}}")));Step
Create the wallet
Now that we have Alice in our ledger, we can create a USD balance for her, stored in the ledger from step 2.
We’ll use the ledger_id and identity_id from the earlier steps, set the currency, and submit. Blnk creates the balance and attaches it to Alice.
Create the wallet:
curl -X POST "CORE_URL/balances" \
-H "X-blnk-key: CORE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"ledger_id": "{{ledger_id}}",
"identity_id": "{{identity_id}}",
"currency": "{{currency}}"
}'const response = await blnk.LedgerBalances.create({
ledger_id: '{{ledger_id}}',
identity_id: '{{identity_id}}',
currency: '{{currency}}',
});balance, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
LedgerID: "{{ledger_id}}",
IdentityID: "{{identity_id}}",
Currency: "{{currency}}",
})response = blnk.ledger_balances.create({
"ledger_id": "{{ledger_id}}",
"identity_id": "{{identity_id}}",
"currency": "{{currency}}",
})ApiResponse<JsonNode> response = blnk.ledgerBalances().create(
CreateLedgerBalance.create()
.ledgerId("{{ledger_id}}")
.identityId("{{identity_id}}")
.currency("{{currency}}"));Step
Create the points balance
Alice also needs a balance for points, in the same ledger and on the same identity. We’ll set the currency to POINTS and submit.
Create the points balance:
curl -X POST "CORE_URL/balances" \
-H "X-blnk-key: CORE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"ledger_id": "{{ledger_id}}",
"identity_id": "{{identity_id}}",
"currency": "{{currency}}"
}'const response = await blnk.LedgerBalances.create({
ledger_id: '{{ledger_id}}',
identity_id: '{{identity_id}}',
currency: '{{currency}}',
});balance, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
LedgerID: "{{ledger_id}}",
IdentityID: "{{identity_id}}",
Currency: "{{currency}}",
})response = blnk.ledger_balances.create({
"ledger_id": "{{ledger_id}}",
"identity_id": "{{identity_id}}",
"currency": "{{currency}}",
})ApiResponse<JsonNode> response = blnk.ledgerBalances().create(
CreateLedgerBalance.create()
.ledgerId("{{ledger_id}}")
.identityId("{{identity_id}}")
.currency("{{currency}}"));Step
Record a deposit
Alice deposits $120.00 via an ACH transfer so she can pay for both purchases. The destination is her USD balance, and the source is @WorldUSD_ACH.
The amount is a decimal, and we multiply it by precision 100 to get precise_amount. The reference and the description are already set.
Record the deposit:
curl -X POST "CORE_URL/transactions" \
-H "X-blnk-key: CORE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"precise_amount": {{precise_amount}},
"precision": 100,
"reference": "DEP-20261019-090000",
"description": "ACH deposit",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}",
"allow_overdraft": true
}'const amount = {{amount}};
const precision = 100;
const precise_amount = Math.round(amount * precision);
const response = await blnk.Transactions.create({
precise_amount,
precision,
reference: 'DEP-20261019-090000',
description: 'ACH deposit',
currency: '{{currency}}',
source: '{{source}}',
destination: '{{destination}}',
allow_overdraft: true,
});amount := {{amount}}
precision := 100
preciseAmount := big.NewInt(int64(math.Round(amount * float64(precision))))
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
ParentTransaction: blnkgo.ParentTransaction{
PreciseAmount: preciseAmount,
Precision: precision,
Reference: "DEP-20261019-090000",
Description: "ACH deposit",
Currency: "{{currency}}",
Source: "{{source}}",
Destination: "{{destination}}",
},
AllowOverdraft: true,
})amount = {{amount}}
precision = 100
precise_amount = round(amount * precision)
response = blnk.transactions.create({
"precise_amount": precise_amount,
"precision": precision,
"reference": "DEP-20261019-090000",
"description": "ACH deposit",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}",
"allow_overdraft": True,
})double amount = {{amount}};
int precision = 100;
int preciseAmount = (int) Math.round(amount * precision);
ApiResponse<JsonNode> response = blnk.transactions().create(
CreateTransactions.create()
.preciseAmount(preciseAmount)
.precision(precision)
.reference("DEP-20261019-090000")
.description("ACH deposit")
.currency("{{currency}}")
.source("{{source}}")
.destination("{{destination}}")
.allowOverdraft(true));Step
Record a purchase
Alice buys something for $100.00. The source is her USD balance, and the destination is @RevenueUSD.
The reference and the description are already set. After this purchase she has $20.00 left, which covers the discounted purchase later.
Record the purchase:
curl -X POST "CORE_URL/transactions" \
-H "X-blnk-key: CORE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"precise_amount": {{precise_amount}},
"precision": 100,
"reference": "PUR-20261019-103000",
"description": "Product purchase",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}"
}'const amount = {{amount}};
const precision = 100;
const precise_amount = Math.round(amount * precision);
const response = await blnk.Transactions.create({
precise_amount,
precision,
reference: 'PUR-20261019-103000',
description: 'Product purchase',
currency: '{{currency}}',
source: '{{source}}',
destination: '{{destination}}',
});amount := {{amount}}
precision := 100
preciseAmount := big.NewInt(int64(math.Round(amount * float64(precision))))
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
ParentTransaction: blnkgo.ParentTransaction{
PreciseAmount: preciseAmount,
Precision: precision,
Reference: "PUR-20261019-103000",
Description: "Product purchase",
Currency: "{{currency}}",
Source: "{{source}}",
Destination: "{{destination}}",
},
})amount = {{amount}}
precision = 100
precise_amount = round(amount * precision)
response = blnk.transactions.create({
"precise_amount": precise_amount,
"precision": precision,
"reference": "PUR-20261019-103000",
"description": "Product purchase",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}",
})double amount = {{amount}};
int precision = 100;
int preciseAmount = (int) Math.round(amount * precision);
ApiResponse<JsonNode> response = blnk.transactions().create(
CreateTransactions.create()
.preciseAmount(preciseAmount)
.precision(precision)
.reference("PUR-20261019-103000")
.description("Product purchase")
.currency("{{currency}}")
.source("{{source}}")
.destination("{{destination}}"));Step
Award the points
The purchase earns her 10 points, one for every $10. We move those points from @PointsPool to her points balance.
Points use precision 1, so 10 points is precise_amount 10. We’ll store the purchase reference in metadata as purchase_reference.
Award the points:
curl -X POST "CORE_URL/transactions" \
-H "X-blnk-key: CORE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"precise_amount": {{precise_amount}},
"precision": 1,
"reference": "PTS-20261019-103000",
"description": "Loyalty points award",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}",
"allow_overdraft": true,
"meta_data": {
"purchase_reference": "{{purchase_reference}}"
}
}'const amount = {{amount}};
const precision = 1;
const precise_amount = Math.round(amount * precision);
const response = await blnk.Transactions.create({
precise_amount,
precision,
reference: 'PTS-20261019-103000',
description: 'Loyalty points award',
currency: '{{currency}}',
source: '{{source}}',
destination: '{{destination}}',
allow_overdraft: true,
meta_data: {
purchase_reference: '{{purchase_reference}}',
},
});amount := {{amount}}
precision := 1
preciseAmount := big.NewInt(int64(math.Round(amount * float64(precision))))
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
ParentTransaction: blnkgo.ParentTransaction{
PreciseAmount: preciseAmount,
Precision: precision,
Reference: "PTS-20261019-103000",
Description: "Loyalty points award",
Currency: "{{currency}}",
Source: "{{source}}",
Destination: "{{destination}}",
MetaData: blnkgo.MetaData{
"purchase_reference": "{{purchase_reference}}",
},
},
AllowOverdraft: true,
})amount = {{amount}}
precision = 1
precise_amount = round(amount * precision)
response = blnk.transactions.create({
"precise_amount": precise_amount,
"precision": precision,
"reference": "PTS-20261019-103000",
"description": "Loyalty points award",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}",
"allow_overdraft": True,
"meta_data": {
"purchase_reference": "{{purchase_reference}}",
},
})double amount = {{amount}};
int precision = 1;
int preciseAmount = (int) Math.round(amount * precision);
ApiResponse<JsonNode> response = blnk.transactions().create(
CreateTransactions.create()
.preciseAmount(preciseAmount)
.precision(precision)
.reference("PTS-20261019-103000")
.description("Loyalty points award")
.currency("{{currency}}")
.source("{{source}}")
.destination("{{destination}}")
.allowOverdraft(true)
.metaData(Map.of("purchase_reference", "{{purchase_reference}}")));Step
Redeem the points
Alice uses those 10 points on a later purchase. Ten points are worth $1.00, so we move them from her points balance back to @PointsPool.
The reference and the description are already set. Her points balance is 0 after this, and the dollar discount is applied on the next step.
Redeem the points:
curl -X POST "CORE_URL/transactions" \
-H "X-blnk-key: CORE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"precise_amount": {{precise_amount}},
"precision": 1,
"reference": "RDM-20261019-140000",
"description": "Points redemption",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}"
}'const amount = {{amount}};
const precision = 1;
const precise_amount = Math.round(amount * precision);
const response = await blnk.Transactions.create({
precise_amount,
precision,
reference: 'RDM-20261019-140000',
description: 'Points redemption',
currency: '{{currency}}',
source: '{{source}}',
destination: '{{destination}}',
});amount := {{amount}}
precision := 1
preciseAmount := big.NewInt(int64(math.Round(amount * float64(precision))))
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
ParentTransaction: blnkgo.ParentTransaction{
PreciseAmount: preciseAmount,
Precision: precision,
Reference: "RDM-20261019-140000",
Description: "Points redemption",
Currency: "{{currency}}",
Source: "{{source}}",
Destination: "{{destination}}",
},
})amount = {{amount}}
precision = 1
precise_amount = round(amount * precision)
response = blnk.transactions.create({
"precise_amount": precise_amount,
"precision": precision,
"reference": "RDM-20261019-140000",
"description": "Points redemption",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}",
})double amount = {{amount}};
int precision = 1;
int preciseAmount = (int) Math.round(amount * precision);
ApiResponse<JsonNode> response = blnk.transactions().create(
CreateTransactions.create()
.preciseAmount(preciseAmount)
.precision(precision)
.reference("RDM-20261019-140000")
.description("Points redemption")
.currency("{{currency}}")
.source("{{source}}")
.destination("{{destination}}"));Step
Record the discounted purchase
The item costs $20.00, and the points cover $1.00. She pays the remaining $19.00 from her USD balance to @RevenueUSD.
The reference and the description are already set. This leaves $1.00 in her USD balance.
Record the discounted purchase:
curl -X POST "CORE_URL/transactions" \
-H "X-blnk-key: CORE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"precise_amount": {{precise_amount}},
"precision": 100,
"reference": "PUR-20261019-140000",
"description": "Discounted purchase",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}"
}'const amount = {{amount}};
const precision = 100;
const precise_amount = Math.round(amount * precision);
const response = await blnk.Transactions.create({
precise_amount,
precision,
reference: 'PUR-20261019-140000',
description: 'Discounted purchase',
currency: '{{currency}}',
source: '{{source}}',
destination: '{{destination}}',
});amount := {{amount}}
precision := 100
preciseAmount := big.NewInt(int64(math.Round(amount * float64(precision))))
transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
ParentTransaction: blnkgo.ParentTransaction{
PreciseAmount: preciseAmount,
Precision: precision,
Reference: "PUR-20261019-140000",
Description: "Discounted purchase",
Currency: "{{currency}}",
Source: "{{source}}",
Destination: "{{destination}}",
},
})amount = {{amount}}
precision = 100
precise_amount = round(amount * precision)
response = blnk.transactions.create({
"precise_amount": precise_amount,
"precision": precision,
"reference": "PUR-20261019-140000",
"description": "Discounted purchase",
"currency": "{{currency}}",
"source": "{{source}}",
"destination": "{{destination}}",
})double amount = {{amount}};
int precision = 100;
int preciseAmount = (int) Math.round(amount * precision);
ApiResponse<JsonNode> response = blnk.transactions().create(
CreateTransactions.create()
.preciseAmount(preciseAmount)
.precision(precision)
.reference("PUR-20261019-140000")
.description("Discounted purchase")
.currency("{{currency}}")
.source("{{source}}")
.destination("{{destination}}"));Step
Confirmation
We should now have $1.00 in Alice’s USD balance and no points left. The $19.00 purchase went to @RevenueUSD, and the 10 points went back to @PointsPool.
If you have a Blnk Cloud account or an Enterprise license, you can confirm this by checking the dashboard or using the CLI.
Print every balance:
blnk balances list