ShipNext

Payment And Entitlement Tables

Payment customers, subscriptions, orders, webhook idempotency, entitlements, and quota transactions.

Payment and entitlement schemas live in:

src/core/db/schema/sqlite/payment.schema.ts
src/core/db/schema/pg/payment.schema.ts

These tables cover the full chain from payment provider customer to subscription/order, webhook idempotency, entitlements, and quota audit logs.

Groups

GroupTablesDescription
Payment identitypayment_customersMaps app users to provider customers
Subscriptions and orderspayment_subscriptions, payment_ordersLocal facts for recurring and one-time payments
Webhook idempotencypayment_webhook_eventsPrevents repeated event processing
Entitlement assetsuser_entitlementsCurrent usable resources
Quota auditquota_transactionsGrant, consume, rollback, and expiration records

Plan definitions are not stored in the database. They live in:

src/modules/billing/config/plan.ts

payment_customers

FieldDescription
user_idApp user ID, unique
customer_idProvider customer ID, unique
emailEmail used to create the customer
metadataJSON string with extra context
created_at / updated_atTimestamps

Checkout and webhook flows ensure a customer exists.

payment_subscriptions

FieldDescription
user_idOwner user
subscription_idProvider subscription ID, unique
plan_idLocal plan ID such as starter or pro
price_idProvider Price ID
statusactive, trialing, canceled, etc.
current_period_start / current_period_endCurrent cycle
cancel_at_period_end / canceled_atCancellation state
trial_endTrial end timestamp
metadataJSON context

Checkout creation uses subscription checks to avoid duplicate active subscriptions.

payment_orders

FieldDescription
order_idProvider order/payment ID, unique
plan_id / price_idLocal plan and provider price
amount / currencyAmount in the smallest currency unit
statuscompleted, failed, refunded, etc.
metadataJSON context

Subscriptions and orders should be synchronized by webhook, not only Checkout redirect.

payment_webhook_events

FieldDescription
id{provider}:{eventId}
providerPayment provider such as stripe
event_idProvider event ID
event_typeProvider event type
statusprocessing, processed, or failed
errorFailure message

Handled in src/modules/billing/webhooks/webhook-processor.ts.

user_entitlements

FieldDescription
user_idOwner user
typeDURATION or QUOTA
resource_typeCREDIT, STORAGE, etc.
balanceRemaining balance; DURATION usually uses -1
priorityLower numbers consumed first
valid_from / valid_untilValidity period
sourceSUBSCRIPTION, TOPUP, LIFETIME
source_idSubscription ID, order ID, or source ID

DURATION means unlimited usage while valid. QUOTA means consumable balance.

quota_transactions

FieldDescription
transaction_idGroups related quota rows
actionBUY_TOPUP, SUB_GRANT, CONSUME, REFUND, EXPIRE
amountChange amount; consumption is usually negative
balance_snapshotBalance after the operation
entitlement_idRelated entitlement
statusPENDING, SUCCESS, FAILED
descriptionHuman-readable detail

Use:

prepareQuota(userId, resourceType, amount);
commitQuota(transactionId);
rollbackQuota(transactionId);

for work that may fail after reserving quota.

Lifecycle

User starts Checkout

POST /api/payment/checkout checks the user and resolves planId + priceId.

Provider sends webhook

POST /api/payment/webhook verifies the signature and writes payment_webhook_events.

Sync local facts

The processor updates customers, subscriptions, or orders.

Grant or refresh entitlements

The matching price's entitlements create user_entitlements and quota_transactions.

Business consumes quota

Quota services consume active QUOTA entitlements by priority and expiration.

Guidance

  • Add plans in plan.ts, not directly in the database.
  • Add new resource types in EntitlementResourceType first.
  • Webhook URL is /api/payment/webhook without locale prefixes.
  • Store money amounts in the smallest currency unit, such as cents for USD.

On this page