Contents

A promo with an expiry

Grant marketing credit that spends first and claws itself back when the deadline passes.

Source src/operations/promo.ts#L30grantPromosrc/worker/promos.ts#L49sweepExpiredPromos

You want to hand out launch credit that costs the house nothing if it goes unused. That is what a promo grant is: marketing money with a deadline, kept in its own promo balance so it never masquerades as money the user paid.

Two properties do all the work:

  • Promo spends first. Any spend draws the buyer’s promo balance before touching spendable, so granted credit is always the first to go.
  • Expiry needs no code. The background worker’s promos sweep finds grants whose expiresAt has passed and posts back whatever remains unspent. You set the deadline at grant time and never schedule anything.
import {
  createEconomy,
  credits,
  encodeAmount,
  grantPromo,
  promo,
  spend,
  spendable,
  systemActor,
  topUp,
  userActor,
} from '@pwngh/economy-lab';

import type { SnippetReport } from './context.ts';

// A promo grant is marketing money with a deadline: it lands in the promo balance, spends
// first when the user buys, and the worker's promos sweep claws back whatever is left once
// expiresAt passes. The grant and the promo-first draw are the recipe; the expiry needs no code.
export async function run(): Promise<SnippetReport> {
  const economy = await createEconomy();
  await economy.submit(
    topUp({
      idempotencyKey: 'idem_fund',
      actor: systemActor('payments'),
      userId: 'usr_p',
      amount: credits(100),
      source: 'card',
    }),
  );
  await economy.submit(
    grantPromo({
      idempotencyKey: 'promo_launch_usr_p',
      actor: systemActor('marketing'),
      userId: 'usr_p',
      amount: credits(200),
      expiresAt: Date.now() + 30 * 86_400_000, // the claw-back deadline
    }),
  );
  await economy.submit(
    spend({
      idempotencyKey: 'idem_buy',
      actor: userActor('usr_p'),
      orderId: 'ord_p1',
      buyerId: 'usr_p',
      sku: 'Starter Pack',
      price: credits(150),
      recipients: [{ sellerId: 'usr_s', shareBps: 10_000 }],
    }),
  );
  const [promoLeft, cash] = await Promise.all([
    economy.read.balance(promo('usr_p')),
    economy.read.balance(spendable('usr_p')),
  ]);
  await economy.close();

  return {
    lines: [
      'granted 200 promo (expires in 30 days) on top of 100 topped up',
      'a 150 spend drew the promo balance first',
      `promo left: ${encodeAmount(promoLeft)} · spendable untouched: ${encodeAmount(cash)}`,
    ],
    consolePath: '/market',
  };
}

The grant is restricted to system and operator actors — a user cannot grant themselves credit — and expiresAt must be a future timestamp within five years, checked at submit.

Since a promo-funded purchase is not the buyer’s money, the seller is still paid real earnings: the promo part of a sale routes from house revenue, which is exactly why an expired, unspent grant can be reclaimed without touching anyone else’s balance.

See also