Case study:
Case study posts are intended to give you an idea of how to implement different features using String Theory. This is not the only way to model this feature, and it may not fully represent you business's specific requirements.
In this case study we've been tasked with being able to redeem and use gift cards for our business. We've partnered with a provider to sell the gift cards through retail and e-commerce (Blackhawk network's gift cards, or a similar provider). Our job is to be able to accept those gift cards, and apply them to a balance that the user can spend on our platform.
We have the following requirements for redeeming a gift card:
In order to start tackling these requirements, we'll need a web form to take in both the card number and the pin something like this:
Two inputs and a button is all it'll take. We could get fancy and add a bunch of validations and javascript to the form, but management wants this launched yesterday so we keep it simple for now.
Once the user submits the form our first two steps (maybe one, if they support it) is to talk to the card vendor.
Now we have a verified gift card, and an amount. How do we redeem it using String Theory? We'll use the deposit API to add value into the system. Below is the API call we'll make. Don't worry, we'll break it down after.
Post: api.string-theory.finance/deposit
{
owner: { type: "google_user", id: "12345" },
creator: { type: "google_user", id: "12345" },
amount: { unitType: "currency_micros", unitToken: "USD", unitCount: 100_000_000 },
reason: { type: "gift_card_redemption", token: "6fe0032b802a" },
idempotencyKey: { type: "gift_card", token: "1234567890" },
tags: [
{type: "refund_policy", token: "no_refunds"},
{type: "credit", token: true}
]
}
Ok, let's break it down!
google_user:12345
First up, we have the owner of the deposit. This represents the entity that the money belongs to in your system. Since the user redeeming the card is entitled to the money, we'll set them as the owner.
The creator is the entity or person who intiated this action. Since the user is the one initiating the redemption, they are our creator too.
Creator and actor fields are used to provide auditability for the system. It is important for financial audits to be able to tell who took what actions in the system.
Amount sounds pretty simple but... why are there so many fields? String Theory uses a 3 part amount model to be able to express many different kinds of assets (Amount documentation) Let's look at each one.
Unit typecurrency_micros
Type tells the system the kind of asset we're dealing with, and the maximum fractional precision of that asset. Since no computer can precisely model fractional numbers, we have to pick a precision to use.
String Theory uses micros (1/1,000,000) precision by default for currency. You can use less or more precision if you chose to, but let's stick with default for now.
Unit tokenUSD
The token tells us what specific kind of asset we're dealing with inside the general type category of currency_micros. In this case the gift card's amount is in US dollars, so we'll use the official USD currency code.
Unit count100,000,000
This is the meat of the amount, how much money is this gift card worth? The gift card is worth $100, but we're storing it as currency_micros so we need to multiply it by 1,000,000 to get the right precision.
gift_card_redemption:6fe0032b802a
The reason field represents business level information about what's happening. It should be a reference to another data model where product level information can be found.
As part of this redemption we'll want to create a gift_card_redemption record in our product database (not in String Theory) to store extra information. What you store in this record depends greatly on your business rules, but in our case we might want to store whether this was a e-commerce or physical gift card. Additionally, we should store any extra information we get from the gift card vendor on redemption (like where it was sold, if it was variable or set value gift card, etc).
It is recommended that the record for each reason contain as much information as possible about why this happened. When your CFO/finance department try to understand your money this will be the core piece of information that explains why this happened to them.
gift_card:1234567890
Idempotency is a fancy word that means an operation can happen at most once per idempotency key (idempotency documentation). This means no matter how many deposits are attempted with the same idempotency key, only one will ever succeed. By using the gift card number as our idempotency key we fulfill our requirement that a gift card can only be redeemed once.
refund_policy:no_refunds
credit:true
Tags allow you to add context information about the money which will follow it the entire time it's in your system. Tags are optional and entirely up to you. Add as many or as few as necessary to be able to understand your money.
A common rule for gift cards is that the balance isn't allowed to be refunded back to the card, or to another payment method, since that is a common type of fraud. We'll tag it with refund_policy:no_refunds to ensure any systems looking at this piece of money know that it isn't allowed to be used for refunds.
We'll also tag this money with credit:true in case our system needs to know this money should be treated as a credit instead of cash.
Now that we have deposited the money through the API our intrepid user has a balance of $100 USD in the system. Next, they go to buy a stuffed animal (did I mentioned our business sells stuffed animals?) for $65.30. They select to have their gift card balance applied as part of the purchase.
We'll take the simplest approach and say we don't care if the where the purchase money is coming from, as long as the user has enough balance. We'll do this by making a withdraw from the user's generic account.
Post: api.string-theory.finance/withdraw
{
owner: { type: "google_user", id: "12345" },
actor: { type: "google_user", id: "12345" },
amount: { unitType: "currency_micros", unitToken: "USD", unitCount: 65_300_000 },
reason: { type: "purchase", token: "6fe0032b802a" },
idempotencyKey: { type: "purchase", token: "8e0fc7c9fd8c" },
}
Let's break it down again (a bit faster this time though!)
google_user:12345
Now that we're withdrawing the owner specifies who's money we should be removing. Every owner in the system has a kind of generic account that contains all the money belonging to them. In really liberal cases you can just withdraw from this account when you don't care about the details.
This kind of withdraw is only common in extremely simple systems. Most mature systems will use targeted withdraws. See the deterministic attribution blog post for examples.
The user initiated the purchase, so they are our actor for this withdraw.
currency_micros:USD:65_300_000
We're still using USD so the unitType and unitToken remain the same. We'll take our withdraw about ($65.30) and multiply by 1,000,000 to convert it to micros.
purchase:8e0fc7c9fd8c
The reason again aims to explain why this withdraw is taking place. In this case we give it a reference to a record in our order system that contains the details about what items are being purchased, and the grand total needed.
purchase:8e0fc7c9fd8c
We also provide the purchase id as the idempotency key for this withdraw. We want to ensure that we only withdraw the money from the user's account once for this purchase, even if the system makes a mistake and tries to charge a second time.
That's it! The user now has a remaining balance of $34.70 and all is right with the world. The history graph of our grift card money will now look like this:
Remember that for both the redemption (deposit) and purchase (withdraw) it's important to provide strong idempotency keys as it's important that the user doesn't get duplicate money, or duplicate charges. Idempotency is a very powerful tool in financial systems, but it requires careful thought about how to use it. See our idempotency documentation for more information.
< Back to blog