
Gift Cards
- 129 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Add ecommerce gift card purchase, redemption, balance tracking, partial use, and expiration rules integrated with checkout, refunds, and customer account flows.
About
gift-cards skill from finsilabs/awesome-ecommerce-skills covers end-to-end stored-value gift cards: code generation, purchase flows, balance ledgers, checkout redemption, partial spends, expirations, and refund-safe accounting for ecommerce stores.
- Issuance and redemption APIs
- Balance ledger modeling
- Checkout integration
- Partial redemption support
- Refund and void handling
Gift Cards by the numbers
- 129 all-time installs (skills.sh)
- Ranked #2,750 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill gift-cardsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 129 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Add ecommerce gift card purchase, redemption, balance tracking, partial use, and expiration rules integrated with checkout, refunds, and customer account flows.
Files
Gift Cards
Overview
Gift cards let customers purchase store credit to give as gifts or to use themselves. They function as a form of payment at checkout — a customer can pay part of an order with a gift card and the remainder with a credit card, and any unused balance stays on the card. All major e-commerce platforms include native gift card functionality; custom implementations are only needed for headless storefronts or very specific accounting requirements.
When to Use This Skill
- When adding gift cards as a purchasable product that customers can send to others
- When implementing store credit as a refund mechanism in place of cash refunds
- When building bulk corporate gift card programs for B2B clients
- When allowing customers to split payment between a gift card and a credit card at checkout
- When you need a full balance history for accounting reconciliation or customer support
Core Instructions
Step 1: Determine the merchant's platform and choose the right tool
| Platform | Recommendation | Notes |
|---|---|---|
| Shopify | Use Shopify's built-in gift cards (available on all plans except Basic) | Native integration with checkout, balance tracking, and email delivery — no app needed |
| WooCommerce | WooCommerce Gift Cards plugin (official, ~$49/year) or YITH WooCommerce Gift Cards (~$80/year) | Core WooCommerce does not include gift cards; these plugins are well-maintained and widely used |
| BigCommerce | BigCommerce Gift Certificates (built in, all plans) | Native support — create, sell, and redeem gift certificates from the admin panel |
| Custom / Headless | Build with ledger-based balance tracking | See Custom section below |
Step 2: Set up gift cards on your platform
---
Shopify
Shopify gift cards are available on Shopify, Advanced, and Plus plans (not Basic). They are issued as a product and redeemed at checkout using a 16-character code.
Enable and create gift cards: 1. In your Shopify admin, go to Products → Gift cards 2. Click Add gift card product 3. Set denominations (e.g., $25, $50, $100) — each denomination is a product variant 4. Add a title, description, and image for the gift card product 5. Click Save
Issuing a gift card to a customer:
- Via sale: When a customer purchases a gift card, Shopify automatically generates a unique code and emails it to the recipient
- Manually: Go to Customers → find the customer → Gift cards tab → Issue gift card; set the value and expiry date
Setting expiry dates: 1. In Settings → Gift cards 2. Enable gift card expiry and set the default expiration period 3. Note: Some jurisdictions prohibit gift card expiry (check local law before enabling)
Bulk gift cards for B2B or marketing: 1. Go to Products → Gift cards → Export to download existing codes 2. Use the Shopify Admin API (POST /admin/api/2024-04/gift_cards.json) to bulk-create gift cards programmatically 3. The API response includes the code — distribute via your email platform
Store credit as a refund: When issuing a refund, Shopify allows you to refund to a gift card instead of the original payment method: 1. Go to Orders → open the order → Refund 2. Under "Refund to", select Gift card and specify the amount 3. Shopify creates a new gift card and emails the code to the customer
---
WooCommerce
Install the WooCommerce Gift Cards extension (from WooCommerce.com) or YITH WooCommerce Gift Cards. These are the most feature-complete options.
Setup with WooCommerce Gift Cards: 1. Install and activate the plugin 2. Go to Products → Add New and set the product type to Gift card 3. Under Gift card data:
- Set delivery type: email delivery (for digital) or printed card
- Set available amounts or allow custom amounts
- Configure the email template that sends the code to the recipient
4. Publish the product
Redemption: The plugin adds a "Gift card" field to the checkout page. Customers enter their code and the balance is deducted from the order total. Partial redemption is supported — remaining balance stays on the card.
Store credit as refund: 1. Open the order → Refund 2. The plugin adds a "Refund to gift card" option 3. A new gift card code is generated and emailed to the customer
Balance inquiry: Both WooCommerce Gift Cards and YITH provide a balance inquiry shortcode you can add to a page: [woo_gift_card_balance_check]
---
BigCommerce
BigCommerce calls these "Gift Certificates" and includes them natively.
Create and sell gift certificates: 1. Go to Marketing → Gift Certificates → Create Gift Certificate 2. Fill in:
- To (recipient name and email)
- From (sender name and email)
- Amount: fixed value
- Expiry date (optional)
3. BigCommerce sends the certificate code to the recipient via email
Selling gift certificates as a product: 1. Go to Products → Gift Certificates 2. Enable the gift certificate product page 3. Customers can purchase certificates in custom or fixed amounts directly from your storefront
Redemption: Gift certificate codes appear as a payment option at checkout. Partial redemption is supported — the remaining balance is saved on the certificate code.
---
Custom / Headless
For headless storefronts, implement a ledger-based gift card system. The ledger pattern records every debit and credit as an immutable transaction row — never update a balance column directly.
CREATE TABLE gift_cards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(32) NOT NULL UNIQUE,
initial_value_cents INTEGER NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
issued_to VARCHAR(255),
expires_at TIMESTAMPTZ,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE gift_card_ledger (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
card_id UUID NOT NULL REFERENCES gift_cards(id),
amount_cents INTEGER NOT NULL, -- positive = credit, negative = debit
type VARCHAR(16) NOT NULL CHECK (type IN ('issue', 'redeem', 'refund', 'void')),
order_id UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);Balance calculation — always derived from the ledger, never from a stored column:
async function getBalance(cardCode: string): Promise<number> {
const card = await db.giftCards.findByCode(cardCode.toUpperCase());
if (!card || !card.is_active) throw new Error('CARD_NOT_FOUND');
if (card.expires_at && card.expires_at < new Date()) throw new Error('CARD_EXPIRED');
const result = await db.raw(
'SELECT COALESCE(SUM(amount_cents), 0) AS balance FROM gift_card_ledger WHERE card_id = ?',
[card.id]
);
return Math.max(0, parseInt(result.rows[0].balance, 10));
}Partial redemption with row-level locking to prevent concurrent over-redemption:
async function redeemGiftCard(code: string, orderId: string, orderTotalCents: number) {
return db.transaction(async tx => {
// Lock the card row to prevent concurrent redemptions
const card = await tx.raw(
'SELECT * FROM gift_cards WHERE UPPER(code) = ? FOR UPDATE',
[code.toUpperCase()]
).then(r => r.rows[0]);
if (!card || !card.is_active) throw new Error('CARD_NOT_FOUND');
const balance = await getBalance(code);
const appliedCents = Math.min(balance, orderTotalCents);
if (appliedCents === 0) throw new Error('ZERO_BALANCE');
await tx.giftCardLedger.insert({
card_id: card.id,
amount_cents: -appliedCents,
type: 'redeem',
order_id: orderId,
});
return { appliedCents, remainingBalance: balance - appliedCents };
});
}Best Practices
- Use an append-only ledger — never update a balance column; record every debit and credit as a transaction row for a full audit trail (required for accounting reconciliation)
- Generate codes without ambiguous characters — omit
0,O,1,Ifrom the character set to prevent customer confusion when reading codes from email - Never expose full card codes in URLs or logs — partial masking (
ABCD-xxxx-xxxx-MNOP) is safe for display; full codes belong only in the issuance email - Set accounting liabilities on issuance — gift card balances are a deferred revenue liability until redeemed; ensure your accounting integration records this correctly
- Check local regulations before setting expiry dates — many US states and other jurisdictions restrict or prohibit gift card expiration; verify before enabling
- Test the refund-to-gift-card flow — this is a common customer service scenario; ensure the newly issued card is accessible and redeemable before going live
Common Pitfalls
| Problem | Solution |
|---|---|
| Two simultaneous checkouts both succeed using the same card | Use a row-level lock (SELECT ... FOR UPDATE) inside a database transaction before reading the balance (custom builds); platforms handle this natively |
| Balance goes negative due to rounding in split payment | Use Math.min(balance, orderTotal) — never apply more than the current balance |
| Customer cannot find their card after a refund re-credits it | After refunding to a gift card, ensure the card is reactivated if it was previously depleted and deactivated |
| Gift card codes appear in server access logs | Never include the code as a URL path parameter; use a POST body or a hashed lookup token |
| Gift card purchased but code not delivered | Platform-native gift cards send automatically; for custom implementations, use transactional email with delivery tracking and a resend option in your customer service panel |
Related Skills
- @coupon-management
- @loyalty-points-system
- @stripe-integration
- @returns-management
- @checkout-flow-optimization
{
"context": "Tests whether the agent designs the gift card database schema using an append-only ledger pattern with two tables, correct column types, a case-insensitive unique index on the code, and no stored balance column.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Two separate tables",
"max_score": 5,
"description": "Schema includes two distinct tables: one for gift card records and one for transactions/ledger entries (not combined into one table, not using a single 'balance' column)"
},
{
"name": "No stored balance column",
"max_score": 15,
"description": "The gift card table does NOT contain a 'balance' or 'current_balance' column — balance is derived only from summing transactions"
},
{
"name": "Code column uniqueness",
"max_score": 5,
"description": "The gift card code field is declared UNIQUE (or has a unique constraint) on the gift cards table"
},
{
"name": "Amounts as integer cents",
"max_score": 10,
"description": "Monetary amount fields (initial_value, transaction amount) are defined as INTEGER (not DECIMAL, FLOAT, or NUMERIC with scale > 0), representing cents"
},
{
"name": "Foreign key on transactions",
"max_score": 10,
"description": "The transactions table has a card_id column with a foreign key reference to the gift cards table's primary key"
},
{
"name": "Transaction amount sign semantics",
"max_score": 5,
"description": "The transactions table amount column comment or schema indicates positive = credit (issuance/reload) and negative = debit (redemption), OR the schema/code shows this sign convention in use"
},
{
"name": "Transaction type constraint",
"max_score": 15,
"description": "The transactions table type column uses a CHECK constraint (or enum) limiting values to exactly: 'issue', 'redeem', 'reload', 'refund', 'void', 'expiration' — no fewer and no additional values"
},
{
"name": "Case-insensitive code index",
"max_score": 15,
"description": "A unique index is created on UPPER(code) (or a case-insensitive expression) rather than on the raw code column"
},
{
"name": "Descending transaction index",
"max_score": 10,
"description": "An index exists on the transactions table keyed by (card_id, created_at DESC) or equivalent — enabling fast per-card transaction lookups in reverse chronological order"
},
{
"name": "is_active flag",
"max_score": 10,
"description": "The gift cards table includes an is_active (or equivalent active/enabled) boolean column defaulting to true"
}
]
}
Gift Card Database Schema for Sprout Home & Garden
Problem Description
Sprout Home & Garden is a mid-sized e-commerce retailer preparing to launch a digital gift card product ahead of the holiday season. The platform is built on PostgreSQL. The engineering team has been tasked with designing the persistence layer for gift cards before the rest of the feature is implemented.
The product requirements are as follows: customers should be able to purchase gift cards for others, cards can be partially used across multiple orders (so a $100 card could be used for a $60 purchase and still have $40 left), and the finance team has strict requirements that every balance change be traceable for accounting reconciliation and customer support inquiries. The customer support team needs to be able to look up the full history of how any given card's balance changed over time. There is also a need to handle card expiration in a way that is reversible and auditable.
Cards will be identified by alphanumeric codes that customers type in during checkout, so lookups must work regardless of whether the customer types the code in upper or lower case.
Output Specification
Write the SQL migration file(s) needed to create the gift card schema in PostgreSQL. Save the output as migration.sql.
Include all tables, constraints, and indexes your design requires. Add brief SQL comments where the purpose of a column or design decision is not self-evident.
{
"context": "Tests whether the agent implements gift card redemption using row-level locking (SELECT FOR UPDATE), correct partial-use balance capping (Math.min), negative debit amounts, deactivation on full depletion, balance via SUM of transactions, and proper refund reactivation.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Row-level lock on redemption",
"max_score": 15,
"description": "The redemption function uses SELECT ... FOR UPDATE (or equivalent row-level locking) on the gift card row inside a transaction before reading the balance"
},
{
"name": "Redemption inside transaction",
"max_score": 10,
"description": "The entire redemption operation (lock, balance check, ledger insert, possible deactivation) is wrapped in a single database transaction"
},
{
"name": "Partial-use cap with Math.min",
"max_score": 15,
"description": "The amount applied to an order is Math.min(cardBalance, orderTotal) — never exceeds the card's available balance"
},
{
"name": "Balance via SUM of transactions",
"max_score": 10,
"description": "The card balance is computed by summing the amount column from the transactions ledger table — NOT read from a stored balance column"
},
{
"name": "Balance floored at zero",
"max_score": 5,
"description": "The computed balance is floored at 0 (e.g. Math.max(0, sum)) so it can never be returned as a negative number"
},
{
"name": "Redemption amount is negative",
"max_score": 10,
"description": "The ledger transaction inserted for a redemption uses a negative integer for the amount field (debit), not a positive one"
},
{
"name": "Type 'redeem' on redemption",
"max_score": 5,
"description": "The ledger transaction inserted for a redemption uses the type value 'redeem'"
},
{
"name": "Deactivate on full depletion",
"max_score": 10,
"description": "After a redemption that fully exhausts the card balance (appliedCents === balance), the card's is_active field is set to false"
},
{
"name": "Reactivate card on refund",
"max_score": 10,
"description": "When refunding to a card that has is_active=false, the refund function sets is_active=true before or during the refund transaction"
},
{
"name": "Refund amount is positive",
"max_score": 5,
"description": "The ledger transaction inserted for a refund uses a positive integer for the amount field (credit), not a negative one"
},
{
"name": "Type 'refund' on refund",
"max_score": 5,
"description": "The ledger transaction inserted for a refund uses the type value 'refund'"
}
]
}
Gift Card Checkout Integration for BrightThreads
Problem Description
BrightThreads is a fashion retailer that recently launched gift cards and is now rolling out the checkout integration. The team has run into two production issues they need your help resolving:
Issue 1 — Race condition during flash sales: During a recent flash sale, the same gift card was successfully applied to two different orders at nearly the same time, resulting in the card going negative. The current implementation reads the balance and then inserts the redemption transaction as two separate steps with no locking, allowing concurrent requests to both see the old balance before either debit is recorded.
Issue 2 — Missing balance after refunds: Several customers who received refunds back to their gift cards reported that their cards appeared to have zero balance in the UI even though a refund was issued. Investigation showed that when a card reaches zero balance the system marks it in a way that causes subsequent lookups to short-circuit and skip the balance calculation.
Your task is to implement the corrected redeemGiftCard and refundToGiftCard TypeScript functions. Customers should be able to apply a gift card even when it doesn't cover the full order total (the remainder should be charged to their other payment method), and refunds should always restore the correct spendable balance.
Assume the following are available as imports:
db— a database client with.transaction(),.raw(sql, params),.giftCards.findByCode(),.giftCards.update(), and.giftCardTransactions.insert()methods- The
getGiftCardBalance(code)helper is already implemented and returns{ card, balanceCents }
Output Specification
Write a TypeScript file named giftCardCheckout.ts containing: 1. The redeemGiftCard(code, orderId, orderTotalCents) function — returns { appliedCents, remainingBalance, remainingOrderTotal } 2. The refundToGiftCard(code, orderId, refundCents) function — returns void
Include any necessary TypeScript interfaces. The file should compile without errors assuming the db and getGiftCardBalance imports exist.
{
"context": "Tests whether the agent generates gift card codes in the correct XXXX-XXXX-XXXX-XXXX format using a safe character set (no ambiguous characters), uses cryptographically secure randomness, wraps issuance in a database transaction, sends a properly-structured email after issuance, and stores monetary values in integer cents.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Code format: 4×4 groups",
"max_score": 15,
"description": "Generated codes follow the pattern XXXX-XXXX-XXXX-XXXX — exactly 4 alphanumeric groups of 4 characters separated by hyphens (16 chars + 3 hyphens = 19 total characters)"
},
{
"name": "No ambiguous characters",
"max_score": 15,
"description": "The character set used for code generation does NOT include the characters '0' (zero), 'O' (letter O), '1' (one), or 'I' (letter I)"
},
{
"name": "Cryptographic randomness",
"max_score": 10,
"description": "Uses Node.js crypto.randomBytes() (or equivalent cryptographically secure source) for generating random bytes — NOT Math.random()"
},
{
"name": "Atomic issuance transaction",
"max_score": 10,
"description": "The gift card row and the initial transaction row are inserted inside a single database transaction (so both succeed or both fail together)"
},
{
"name": "Initial 'issue' transaction",
"max_score": 10,
"description": "An 'issue' type transaction row is inserted into the transactions ledger table when the card is created, with amount equal to the card's initial value"
},
{
"name": "Amounts in integer cents",
"max_score": 10,
"description": "Monetary values are stored and passed as integers representing cents (e.g. 5000 for $50.00), not as decimal numbers"
},
{
"name": "Email sent after issuance",
"max_score": 5,
"description": "An email is sent to the card recipient (issued_to address) after the card is successfully created"
},
{
"name": "Email includes code",
"max_score": 5,
"description": "The email payload includes the gift card code field"
},
{
"name": "Email includes formatted value",
"max_score": 5,
"description": "The email payload includes a human-readable formatted monetary value (e.g. '$50.00'), not just the raw cents integer"
},
{
"name": "Email includes expiresAt",
"max_score": 10,
"description": "The email payload includes an expiry date field, with a fallback string value of 'Never' when the card has no expiry"
},
{
"name": "Email includes shopUrl",
"max_score": 5,
"description": "The email payload includes a shop URL field for the recipient to use the card"
}
]
}
Gift Card Issuance Service for PetStuff Online
Problem Description
PetStuff Online is a direct-to-consumer pet supply store that has just approved a gift card feature for Q4. When a customer purchases a gift card, the platform needs to: generate a unique code for the card, persist it to the database, and automatically send the recipient an email so they can use it.
The engineering lead has flagged a concern from a past incident at another company: gift card codes ended up in server access logs because they were passed as URL path parameters, exposing customer credit to anyone with log access. The new implementation should be designed with this in mind. The team also wants codes to be practical for customers to use — cards may be printed, shown on screens, or read aloud during customer support calls.
The store's database already has a gift_cards table and a gift_card_transactions ledger table (similar to a double-entry bookkeeping approach). Your task is to implement the TypeScript service function that handles card issuance end-to-end: generating the code, writing to the database, and dispatching the notification email.
Assume the following are available as imports:
db— a database client with.transaction(),.giftCards.insert(), and.giftCardTransactions.insert()methodsemailService— an email client with.send({ to, template, data })methodcryptofrom Node.js standard library
Output Specification
Write a TypeScript file named issueGiftCard.ts containing: 1. The generateGiftCardCode() function 2. The issueGiftCard(params) function that creates the card, records the ledger entry, and sends the email
The file should be self-contained and runnable. Include the type definitions for any interfaces used.
{
"name": "finsi/gift-cards",
"version": "0.1.0",
"summary": "Gift card issuance, redemption, balance tracking, and partial-use handling",
"skills": {
"gift-cards": {
"path": "SKILL.md"
}
}
}