How to Design a Credit Payment System for Your Marketplace
Design a marketplace credit ledger with verified purchases, enforced redemption, duplicate handling, and refund rules.

Stripe
GlideA credit system lets customers buy a package and spend credits on individual services.
A hypothetical feedback service might sell 10 credits for $50. Customers then redeem credits for homepage reviews.
That model needs more than a balance field and a hidden button. The service must enforce every purchase and redemption.
Keep a ledger of credit changes
Use a ledger: one record for every credit grant, redemption, refund, or adjustment.
| Record | Essential fields |
|---|---|
| Account | Stable user ID; cached balance if needed |
| Package | Trusted Stripe price ID; integer credit amount |
| Purchase | Checkout Session ID; payment state; account ID |
| Ledger entry | Account ID; signed credit amount; unique operation ID; reason |
| Service | Service ID; trusted integer credit cost |
| Booking | Account ID; service ID; request ID; status |
Do not calculate credits from an editable browser field. Keep package values and service costs in trusted server configuration.
Confirm payment before granting credits
Use Stripe Checkout or Payment Links for the purchase screen. Link the purchase to an authenticated internal account.
Do not use a submitted email address as the sole ownership check. Verify the account mapping in your server workflow.
Verify Stripe’s webhook signature. Retrieve the Checkout Session and its line items before deciding what credits to grant.
Stripe’s fulfillment guide requires duplicate-safe fulfillment and attention to delayed payment methods.
A completed checkout can still have an unpaid asynchronous payment. Grant credits only after confirmed successful payment.
Give each purchase a unique ledger operation, such as purchase:cs_test_example. Two event types for one session must share that identity.
Store the purchase and credit grant together in one database transaction. Return success only after that transaction commits.
Enforce redemption in the database
Glide can show a balance and hide an unavailable action. That visibility rule cannot prevent an unauthorized redemption request.
Protect private rows with Glide Row Owners or your data service’s access rules.
The redemption service must derive the user from authentication. It must retrieve the trusted cost and update credits atomically.
This local PostgreSQL example demonstrates that atomic step. Run it only in an empty disposable database.
It assumes a trusted server supplies p_account, p_service, and a stable p_request. It does not implement authentication or Stripe webhooks.
CREATE TABLE credit_accounts (
id uuid PRIMARY KEY,
balance integer NOT NULL DEFAULT 0 CHECK (balance >= 0)
);
CREATE TABLE credit_services (
id uuid PRIMARY KEY,
cost integer NOT NULL CHECK (cost > 0)
);
CREATE TABLE credit_ledger (
operation_id text PRIMARY KEY,
account_id uuid NOT NULL REFERENCES credit_accounts(id),
delta integer NOT NULL CHECK (delta <> 0),
reason text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE credit_bookings (
request_id uuid PRIMARY KEY,
account_id uuid NOT NULL REFERENCES credit_accounts(id),
service_id uuid NOT NULL REFERENCES credit_services(id),
status text NOT NULL DEFAULT 'pending'
);
CREATE FUNCTION redeem_credit(
p_account uuid, p_service uuid, p_request uuid
) RETURNS uuid LANGUAGE plpgsql AS $$
DECLARE
v_balance integer;
v_cost integer;
v_existing credit_bookings%ROWTYPE;
BEGIN
SELECT balance INTO v_balance FROM credit_accounts
WHERE id = p_account FOR UPDATE;
IF NOT FOUND THEN RAISE EXCEPTION 'Unknown account'; END IF;
SELECT * INTO v_existing FROM credit_bookings
WHERE request_id = p_request;
IF FOUND THEN
IF v_existing.account_id <> p_account
OR v_existing.service_id <> p_service THEN
RAISE EXCEPTION 'Request identity mismatch';
END IF;
RETURN p_request;
END IF;
SELECT cost INTO v_cost FROM credit_services WHERE id = p_service;
IF NOT FOUND THEN RAISE EXCEPTION 'Unknown service'; END IF;
IF v_balance < v_cost THEN RAISE EXCEPTION 'Insufficient credits'; END IF;
INSERT INTO credit_bookings(request_id, account_id, service_id)
VALUES (p_request, p_account, p_service);
INSERT INTO credit_ledger(operation_id, account_id, delta, reason)
VALUES ('redeem:' || p_request, p_account, -v_cost, 'service redemption');
UPDATE credit_accounts SET balance = balance - v_cost
WHERE id = p_account;
RETURN p_request;
END;
$$;
REVOKE ALL ON FUNCTION redeem_credit(uuid, uuid, uuid) FROM PUBLIC;
PostgreSQL row locks let competing redemptions wait for the same account’s transaction.
Grant execution only to your trusted backend role after configuring table privileges. Never expose these unprotected tables directly to browser clients.
A production implementation also needs authenticated ownership checks, refund rules, and a queued delivery process.
Test the failure cases
Use Stripe test mode and fake accounts. Seed an account through a ledger grant and matching balance within one transaction.
Give it 10 credits and create a service costing 7. Submit the same redemption request twice.
Expect one booking, one negative ledger entry, and a balance of 3.
For a separate concurrency test, create a fresh account with 10 credits. Submit two distinct 7-credit requests concurrently.
Only one request can spend the original 10-credit balance. The other must fail without creating a booking.
Repeat the successful payment event. Expect one purchase grant, even when two different webhook event IDs refer to the session.
Also test an unpaid payment, an unknown price, a changed account ID, and an already-refunded purchase.
These are acceptance checks for your implementation. They do not report an executed payment or concurrency test.
Define cancellation and refund rules
Decide when credits return after a cancelled booking. Record a compensating ledger entry rather than deleting the original redemption.
Give each refund a unique operation ID. Prevent repeated refunds from increasing the balance twice.
If credits were already spent, flag a payment refund for review. Define that policy before accepting real money.
A cached balance should equal the sum of ledger entries. Reconcile it regularly and stop redemptions when reconciliation fails.
Start with marketplace payment planning, then validate one package and one service in test mode.
Original video
Keep learning
More guides, free videos and course access for existing members, all in one place.
Go to LearnRather have it built for you?
We'll match you with vetted agentic talent who build it with AI, inside the tools you already use.
Schedule a callRelated posts
Build Custom AI Agents for Other Businesses
Scope a custom business agent around one workflow, approved sources, read-only testing, and human review.

How to Use AI in Your Marketplace
Explore AI job drafts, support drafts, and marketplace recommendations, with human review and specific measures for assessing usefulness.

How to Use Stripe for Marketplace Payments
Stripe can collect your platform's own fees or support payments involving independent sellers. Those require different designs.