skills-ws
Version:
87 agent skills for AI coding assistants — marketing, growth, web3, dev, design & operations. Built for OpenClaw, Claude Code, Cursor, and Codex.
79 lines (60 loc) • 3.09 kB
Markdown
- 8. Recovery, Reconciliation & Test Clocks
- Nightly reconciliation (catch drift / missed events)
- Test Clocks (simulate renewals, trials, dunning)
## 8. Recovery, Reconciliation & Test Clocks
Webhooks are the source of truth, but deliveries can be missed (downtime, a handler bug, a dropped 5xx). Add a periodic reconciliation sweep, and use Test Clocks to exercise time-dependent flows deterministically.
### Nightly reconciliation (catch drift / missed events)
```typescript
// scripts/reconcile.ts — run on a daily cron
import { stripe } from '../lib/stripe';
import { db } from '../lib/db';
async function reconcile() {
// stripe-node list results are async-iterable and auto-paginate across pages.
for await (const sub of stripe.subscriptions.list({ status: 'all', limit: 100 })) {
const periodEnd = sub.items.data[0].current_period_end; // item, not subscription (Basil-line)
await db.user.updateMany({
where: { stripeCustomerId: sub.customer as string },
data: {
subscriptionStatus: sub.status,
currentPeriodEnd: new Date(periodEnd * 1000),
cancelAtPeriodEnd: sub.cancel_at_period_end,
},
});
}
}
reconcile().catch((e) => { console.error(e); process.exit(1); });
```
To re-drive missed events instead of polling state, list events and replay them through the same handler: `stripe.events.list({ type: 'customer.subscription.*' })` (Stripe retains events for 30 days) — the `ProcessedWebhook` claim in §4 makes replay safe and idempotent.
Test Clocks let you fast-forward time so renewals, `trial_will_end`, and failed-payment retries fire on demand instead of waiting real days. Attach the customer to the clock **at creation**.
```typescript
// scripts/test-clock-renewal.ts (test mode only)
import { stripe } from '../lib/stripe';
async function run() {
const clock = await stripe.testHelpers.testClocks.create({
frozen_time: Math.floor(Date.now() / 1000),
name: 'renewal test',
});
const customer = await stripe.customers.create({
test_clock: clock.id, // must be set at creation; can't attach later
payment_method: 'pm_card_visa',
invoice_settings: { default_payment_method: 'pm_card_visa' },
});
const sub = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: process.env.TEST_PRICE_ID! }],
trial_period_days: 7,
});
// Jump to just after trial end → trial_will_end (3 days prior) + first renewal invoice fire.
await stripe.testHelpers.testClocks.advance({
id: clock.id,
frozen_time: Math.floor(Date.now() / 1000) + 8 * 24 * 3600,
});
// Poll clock.status until 'ready', then assert your webhook handler updated the DB.
console.log('Advanced clock for', sub.id);
}
run().catch((e) => { console.error(e); process.exit(1); });
```
> Method names are stripe-node `testHelpers.testClocks.*`; the underlying API is `/v1/test_helpers/test_clocks`. Test Clocks are test-mode only and a clock can advance at most ~2 years total.
---