@ssheleg/sheleg-dev 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/README.md +101 -1
  3. package/SECURITY.md +190 -52
  4. package/bin/sheleg-dev.js +18 -0
  5. package/package.json +2 -2
  6. package/plugins/sheleg-dev/.claude-plugin/plugin.json +1 -1
  7. package/plugins/sheleg-dev/hooks/hooks.json +29 -0
  8. package/plugins/sheleg-dev/hooks/lib/moneygate.js +587 -0
  9. package/plugins/sheleg-dev/hooks/money-gate.js +68 -0
  10. package/plugins/sheleg-dev/skills/ad-tracking/SKILL.md +7 -3
  11. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/README.md +76 -0
  12. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/assert-dedup-contract.mjs +249 -0
  13. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/manifest.json +75 -0
  14. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-capi-from-thank-you-page.json +32 -0
  15. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-capi-server.json +26 -0
  16. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/purchase-pixel-browser.json +12 -0
  17. package/plugins/sheleg-dev/skills/ad-tracking/fixtures/reference-emitter.mjs +129 -0
  18. package/plugins/sheleg-dev/skills/ad-tracking/references/meta-linkedin.md +13 -1
  19. package/plugins/sheleg-dev/skills/crypto-payments/SKILL.md +68 -0
  20. package/plugins/sheleg-dev/skills/crypto-payments/references/heleket-provider.md +349 -10
  21. package/plugins/sheleg-dev/skills/stripe-billing/SKILL.md +1 -1
  22. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/README.md +110 -0
  23. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/assert-money-invariants.mjs +404 -0
  24. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/charge-refunded-partial.json +52 -0
  25. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/charge-refunded-remainder.json +63 -0
  26. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-async-payment-failed.json +43 -0
  27. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-completed-async-unpaid.json +43 -0
  28. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/checkout-session-completed-paid.json +42 -0
  29. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle-next-period.json +70 -0
  30. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle-redelivery.json +70 -0
  31. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-cycle.json +61 -0
  32. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/invoice-paid-subscription-update-proration.json +90 -0
  33. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/manifest.json +172 -0
  34. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/reference-handler.mjs +315 -0
  35. package/plugins/sheleg-dev/skills/stripe-billing/references/testing-and-local-dev.md +38 -2
  36. package/plugins/sheleg-dev/skills/stripe-billing/references/webhook-events.md +43 -1
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env node
2
+ // The assertions a correct Stripe webhook handler must pass, and the proof that each one
3
+ // can fail.
4
+ //
5
+ // node assert-money-invariants.mjs # run them against the reference handler
6
+ // node assert-money-invariants.mjs --self-test # break the handler one rule at a time
7
+ //
8
+ // `--self-test` is the half that makes this evidence rather than decoration. For every
9
+ // invariant it names the rule whose removal MUST turn that invariant red, deletes exactly
10
+ // that rule, and fails if the assertion still passes. A check nobody has watched failing
11
+ // is indistinguishable from a check that cannot fail.
12
+ //
13
+ // It also prints the whole invariant x mutant matrix, because two rules that both explain
14
+ // a passing fixture mean neither is tested. Where `breaks` holds one rule, that fixture
15
+ // isolates it. Where it holds more, the overlap is written down instead of assumed away
16
+ // (`README.md` -> *What masks what*).
17
+ //
18
+ // To point this at YOUR handler: replace `createStore`/`createHandler` below with your
19
+ // own, keep the same three entry points (`deliver(event)`, `reconcile(...)`, and a store
20
+ // you can read counts off), and delete the `--self-test` block only after you have your
21
+ // own way of watching each assertion fail.
22
+ //
23
+ // No network, no filesystem writes, no clock. The only reads are the JSON fixtures beside
24
+ // this file.
25
+
26
+ import { readFileSync } from 'node:fs';
27
+ import { fileURLToPath } from 'node:url';
28
+ import path from 'node:path';
29
+ import assert from 'node:assert/strict';
30
+ import {
31
+ createStore, createHandler, subscriptionIdOf, RULES, CREDITS_PER_PERIOD,
32
+ } from './reference-handler.mjs';
33
+
34
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
35
+ const fixture = (name) => JSON.parse(readFileSync(path.join(HERE, name), 'utf8'));
36
+
37
+ const CYCLE_JAN = 'invoice-paid-subscription-cycle.json';
38
+ const CYCLE_JAN_REDELIVERY = 'invoice-paid-subscription-cycle-redelivery.json';
39
+ const CYCLE_FEB = 'invoice-paid-subscription-cycle-next-period.json';
40
+ const PRORATION = 'invoice-paid-subscription-update-proration.json';
41
+ const REFUND_PARTIAL = 'charge-refunded-partial.json';
42
+ const REFUND_REMAINDER = 'charge-refunded-remainder.json';
43
+ const SESSION_UNPAID = 'checkout-session-completed-async-unpaid.json';
44
+ const SESSION_FAILED = 'checkout-session-async-payment-failed.json';
45
+ const SESSION_PAID = 'checkout-session-completed-paid.json';
46
+
47
+ /** A fresh store and handler per invariant: no assertion inherits another's state. */
48
+ function harness(without = []) {
49
+ const store = createStore();
50
+ const handler = createHandler(store, { without });
51
+ return { store, handler };
52
+ }
53
+
54
+ function seedOneOffPurchase(store) {
55
+ // The reader's own database row for a $90 credit pack, bought before the refunds.
56
+ store.purchases.set('pi_PLACEHOLDER_creditpack', { amount: 9000, refundedTotal: 0 });
57
+ }
58
+
59
+ function periodOfCycle(event) {
60
+ const line = event.data.object.lines.data[0];
61
+ return { start: line.period.start, end: line.period.end };
62
+ }
63
+
64
+ // ---------------------------------------------------------------------- invariants
65
+ //
66
+ // `breaks` is a MEASURED list, not an intention: `--self-test` fails if the set of
67
+ // mutants that turn an invariant red is anything other than this.
68
+
69
+ export const INVARIANTS = [
70
+ {
71
+ id: 'renewal-grants-exactly-once',
72
+ fixtures: [CYCLE_JAN],
73
+ states: 'a paid renewal becomes exactly one grant, and the mirror carries its period',
74
+ breaks: ['grant-on-renewal'],
75
+ async run({ store, handler }) {
76
+ const event = fixture(CYCLE_JAN);
77
+ const response = await handler.deliver(event);
78
+ assert.equal(response.status, 200);
79
+ assert.deepEqual(response.body, { received: true });
80
+ assert.equal(store.grants.length, 1, 'a paid renewal granted nothing');
81
+ assert.equal(store.credits.get('usr_PLACEHOLDER_alice'), CREDITS_PER_PERIOD);
82
+ const mirror = store.subscriptions.get('sub_PLACEHOLDER_alice');
83
+ assert.equal(mirror.periodStart, 1767225600);
84
+ assert.equal(mirror.periodEnd, 1769904000, 'the period is not the line period');
85
+ },
86
+ },
87
+ {
88
+ id: 'proration-invoice-grants-nothing',
89
+ fixtures: [CYCLE_JAN, PRORATION],
90
+ states: 'billing_reason subscription_update is a $40 proration, not a month of product',
91
+ breaks: ['billing-reason', 'grant-on-renewal'],
92
+ async run({ store, handler }) {
93
+ await handler.deliver(fixture(CYCLE_JAN));
94
+ const response = await handler.deliver(fixture(PRORATION));
95
+ assert.equal(response.status, 200, 'a proration invoice must not fail the endpoint');
96
+ assert.equal(store.grants.length, 1,
97
+ 'the proration granted a second period — a user who adds and removes a seat '
98
+ + 'four times has been given four months of product');
99
+ assert.ok(
100
+ store.log.some((l) => l.decision === 'skipped: subscription_update'),
101
+ 'nothing recorded WHY the proration was skipped',
102
+ );
103
+ },
104
+ },
105
+ {
106
+ id: 'sequential-redelivery-grants-once',
107
+ fixtures: [CYCLE_JAN, CYCLE_JAN_REDELIVERY],
108
+ states: 'the same evt_ delivered twice grants once AND runs its side effects once',
109
+ breaks: ['claim', 'grant-on-renewal'],
110
+ async run({ store, handler }) {
111
+ const first = await handler.deliver(fixture(CYCLE_JAN));
112
+ const second = await handler.deliver(fixture(CYCLE_JAN_REDELIVERY));
113
+ assert.deepEqual(first.body, { received: true });
114
+ assert.deepEqual(second.body, { received: true, duplicate: true },
115
+ 'the second delivery was not recognised as a duplicate');
116
+ assert.equal(store.grants.length, 1, 'the redelivery granted a second time');
117
+ assert.equal(store.notifications.length, 1, '"your renewal" was sent twice');
118
+ assert.equal(store.conversions.length, 1,
119
+ 'the server-side conversion fired twice — the revenue is counted twice');
120
+ },
121
+ },
122
+ {
123
+ id: 'concurrent-redelivery-grants-once',
124
+ fixtures: [CYCLE_JAN, CYCLE_JAN_REDELIVERY],
125
+ states: 'two deliveries in flight at once grant once — the claim is an INSERT, not a SELECT',
126
+ breaks: ['claim', 'grant-on-renewal'],
127
+ async run({ store, handler }) {
128
+ // 40 ms apart in production; here, both before either has finished its first read.
129
+ // This is the fixture that separates the event claim from the grant marker: the
130
+ // marker reads before it writes, and a read is a round trip.
131
+ const [a, b] = await Promise.all([
132
+ handler.deliver(fixture(CYCLE_JAN)),
133
+ handler.deliver(fixture(CYCLE_JAN_REDELIVERY)),
134
+ ]);
135
+ const bodies = [a.body, b.body];
136
+ assert.equal(store.grants.length, 1, 'both deliveries granted');
137
+ assert.equal(store.credits.get('usr_PLACEHOLDER_alice'), CREDITS_PER_PERIOD,
138
+ 'the user was credited twice for one renewal');
139
+ assert.equal(store.conversions.length, 1, 'the conversion fired twice');
140
+ assert.equal(bodies.filter((x) => x.duplicate === true).length, 1,
141
+ 'exactly one of the two deliveries must answer duplicate');
142
+ },
143
+ },
144
+ {
145
+ id: 'reconciliation-does-not-regrant',
146
+ fixtures: [CYCLE_JAN],
147
+ states: 'the nightly repair reuses the webhook marker — a nightly job is not a nightly gift',
148
+ breaks: ['grant-marker', 'grant-on-renewal'],
149
+ async run({ store, handler }) {
150
+ const event = fixture(CYCLE_JAN);
151
+ await handler.deliver(event);
152
+ const subId = subscriptionIdOf(event.data.object);
153
+ const outcome = await handler.reconcile(subId, periodOfCycle(event), {
154
+ userId: 'usr_PLACEHOLDER_alice',
155
+ });
156
+ assert.equal(outcome.granted, false, 'reconciliation granted a period the webhook granted');
157
+ assert.equal(store.grants.length, 1);
158
+ assert.equal(store.credits.get('usr_PLACEHOLDER_alice'), CREDITS_PER_PERIOD);
159
+ },
160
+ },
161
+ {
162
+ id: 'out-of-order-pair-does-not-rewind-state',
163
+ fixtures: [CYCLE_FEB, CYCLE_JAN],
164
+ states: 'February delivered before January leaves February in the mirror, and grants both',
165
+ breaks: ['ordering', 'grant-on-renewal'],
166
+ async run({ store, handler }) {
167
+ await handler.deliver(fixture(CYCLE_FEB)); // arrives first
168
+ await handler.deliver(fixture(CYCLE_JAN)); // the earlier period, delivered late
169
+ assert.equal(store.grants.length, 2, 'both periods were paid, so both are owed');
170
+ const mirror = store.subscriptions.get('sub_PLACEHOLDER_alice');
171
+ assert.equal(mirror.periodStart, 1769904000,
172
+ 'the mirror was rewound to the older period — state derived from arrival order');
173
+ assert.equal(mirror.periodEnd, 1772323200,
174
+ 'the renewal date now points at a period that already ended');
175
+ },
176
+ },
177
+ {
178
+ id: 'refund-total-is-cumulative',
179
+ fixtures: [REFUND_PARTIAL, REFUND_REMAINDER],
180
+ states: 'amount_refunded is the total so far: two partials claw back $90, not $130',
181
+ breaks: ['cumulative-refund'],
182
+ async run({ store, handler }) {
183
+ seedOneOffPurchase(store);
184
+ await handler.deliver(fixture(REFUND_PARTIAL));
185
+ await handler.deliver(fixture(REFUND_REMAINDER));
186
+ const total = store.clawbacks.reduce((sum, c) => sum + c.amount, 0);
187
+ assert.equal(total, 9000,
188
+ `clawed back ${total} minor units against a 9000 charge — amount_refunded was read `
189
+ + 'as an increment');
190
+ assert.deepEqual(store.clawbacks.map((c) => c.amount), [4000, 5000]);
191
+ assert.equal(store.purchases.get('pi_PLACEHOLDER_creditpack').refundedTotal, 9000);
192
+ },
193
+ },
194
+ {
195
+ id: 'sequential-redelivery-grants-once-by-count-alone',
196
+ fixtures: [CYCLE_JAN, CYCLE_JAN_REDELIVERY],
197
+ states: 'the same pair judged ONLY by the grant count — masked, and here to prove it',
198
+ // Deliberately weaker than `sequential-redelivery-grants-once`, and kept because the
199
+ // measurement is the finding: no SINGLE removed rule turns this red. Delete the event
200
+ // claim and the per-period marker still refuses the second grant; delete the marker
201
+ // and the claim still refuses the second delivery. A suite that judged a redelivery by
202
+ // its grant count alone would ship with no idempotency and stay green.
203
+ breaks: [['claim', 'grant-marker'], 'grant-on-renewal'],
204
+ async run({ store, handler }) {
205
+ await handler.deliver(fixture(CYCLE_JAN));
206
+ await handler.deliver(fixture(CYCLE_JAN_REDELIVERY));
207
+ assert.equal(store.grants.length, 1, 'the redelivery granted a second time');
208
+ },
209
+ },
210
+ {
211
+ id: 'duplicate-refund-claws-back-once',
212
+ fixtures: [REFUND_PARTIAL],
213
+ states: 'the same charge.refunded twice claws back $40 once — also masked',
214
+ // The second masked pair, and the reason the claim's own discriminating fixture is the
215
+ // CONCURRENT delivery rather than any duplicate: the compare-and-swap's
216
+ // `increment <= 0` catches a sequential replay by itself, and the claim catches it
217
+ // without the swap. Neither is tested by this fixture alone.
218
+ breaks: [['claim', 'cumulative-refund']],
219
+ async run({ store, handler }) {
220
+ seedOneOffPurchase(store);
221
+ await handler.deliver(fixture(REFUND_PARTIAL));
222
+ await handler.deliver(fixture(REFUND_PARTIAL));
223
+ const total = store.clawbacks.reduce((sum, c) => sum + c.amount, 0);
224
+ assert.equal(total, 4000, `clawed back ${total} for one $40 refund`);
225
+ },
226
+ },
227
+ {
228
+ id: 'unpaid-session-grants-nothing',
229
+ fixtures: [SESSION_UNPAID, SESSION_FAILED],
230
+ states: 'the redirect proves a browser: an unpaid async session grants and converts nothing',
231
+ breaks: ['async-gate'],
232
+ async run({ store, handler }) {
233
+ const completed = await handler.deliver(fixture(SESSION_UNPAID));
234
+ assert.equal(completed.status, 200, 'an unpaid session must still answer 200');
235
+ assert.equal(store.grants.length, 0,
236
+ 'a session whose payment_status is unpaid was granted — the money has not moved');
237
+ assert.equal(store.conversions.length, 0,
238
+ 'a purchase conversion was reported for a charge that has not cleared');
239
+ await handler.deliver(fixture(SESSION_FAILED));
240
+ assert.equal(store.grants.length, 0, 'the failed async payment still granted');
241
+ assert.equal(store.conversions.length, 0);
242
+ assert.equal(store.credits.get('usr_PLACEHOLDER_bob'), undefined);
243
+ },
244
+ },
245
+ {
246
+ id: 'paid-session-grants-once',
247
+ fixtures: [SESSION_PAID],
248
+ states: 'the positive control: a card session that DID clear grants exactly once',
249
+ // Without it, a handler that refuses every checkout session satisfies the assertion
250
+ // above and this pack would have taught the opposite defect.
251
+ breaks: ['grant-on-checkout'],
252
+ async run({ store, handler }) {
253
+ const response = await handler.deliver(fixture(SESSION_PAID));
254
+ assert.deepEqual(response.body, { received: true });
255
+ assert.equal(store.grants.length, 1, 'a cleared card session granted nothing');
256
+ assert.equal(store.credits.get('usr_PLACEHOLDER_carol'), CREDITS_PER_PERIOD);
257
+ assert.ok(store.subscriptions.has('sub_PLACEHOLDER_carol'));
258
+ },
259
+ },
260
+ {
261
+ id: 'conversion-id-survives-the-session',
262
+ fixtures: [CYCLE_JAN],
263
+ states: 'the conversion id comes from subscription metadata, so browser and server share it',
264
+ breaks: ['conversion-id-from-metadata', 'grant-on-renewal'],
265
+ async run({ store, handler }) {
266
+ await handler.deliver(fixture(CYCLE_JAN));
267
+ assert.equal(store.conversions.length, 1);
268
+ const [conversion] = store.conversions;
269
+ assert.equal(conversion.eventId, 'evtid_PLACEHOLDER_purchase_jan',
270
+ 'the server generated its own id — nothing will ever deduplicate against the pixel');
271
+ assert.equal(conversion.eventName, 'Purchase', 'the event name is not byte-equal');
272
+ assert.equal(conversion.source, 'webhook');
273
+ assert.equal(conversion.value, 29);
274
+ assert.equal(conversion.currency, 'USD');
275
+ },
276
+ },
277
+ ];
278
+
279
+ // ------------------------------------------------------------------------- runners
280
+
281
+ async function runOne(invariant, without) {
282
+ const context = harness(without);
283
+ try {
284
+ await invariant.run(context);
285
+ return null;
286
+ } catch (error) {
287
+ return error.message.split('\n')[0];
288
+ }
289
+ }
290
+
291
+ async function runAll() {
292
+ let failed = 0;
293
+ for (const invariant of INVARIANTS) {
294
+ const why = await runOne(invariant, []);
295
+ if (why) {
296
+ failed += 1;
297
+ console.error(`FAIL ${invariant.id}\n ${why}`);
298
+ } else {
299
+ console.log(`pass ${invariant.id} — ${invariant.states}`);
300
+ }
301
+ }
302
+ if (failed) {
303
+ console.error(`\n${failed} of ${INVARIANTS.length} money invariants failed`);
304
+ return 1;
305
+ }
306
+ console.log(`\nOK: ${INVARIANTS.length} money invariants hold against the handler`);
307
+ return 0;
308
+ }
309
+
310
+ /**
311
+ * The broadest mutant: a handler that grants nothing at all. It breaks every assertion
312
+ * about a grant, so it is excluded when asking which fixture ISOLATES a rule — otherwise
313
+ * it would appear beside every answer and hide the ones that matter.
314
+ */
315
+ const BROAD = ['grant-on-renewal'];
316
+
317
+ const key = (mutant) => [...mutant].sort().join('+');
318
+
319
+ async function selfTest() {
320
+ const problems = [];
321
+
322
+ // Every single rule, plus each multi-rule mutant some invariant declares. A combination
323
+ // exists only where the measurement forced it: where two rules cover each other, no
324
+ // single removal turns the assertion red, and pretending otherwise is the failure mode
325
+ // this block was written to make visible.
326
+ const combos = [];
327
+ for (const invariant of INVARIANTS) {
328
+ for (const declared of invariant.breaks) {
329
+ if (!Array.isArray(declared)) continue;
330
+ if (!combos.some((c) => key(c) === key(declared))) combos.push(declared);
331
+ }
332
+ }
333
+ const mutants = [...RULES.map((rule) => [rule]), ...combos];
334
+
335
+ const matrix = [];
336
+ for (const invariant of INVARIANTS) {
337
+ const singles = [];
338
+ for (const rule of RULES) {
339
+ if (await runOne(invariant, [rule])) singles.push(rule);
340
+ }
341
+ // Minimal mutants only: a combination containing a rule that already breaks the
342
+ // assertion on its own says nothing new.
343
+ const minimalCombos = [];
344
+ for (const combo of combos) {
345
+ if (combo.some((rule) => singles.includes(rule))) continue;
346
+ if (await runOne(invariant, combo)) minimalCombos.push(combo);
347
+ }
348
+ const broke = [...singles.map((r) => [r]), ...minimalCombos];
349
+ matrix.push({ id: invariant.id, broke, singles });
350
+
351
+ const expected = invariant.breaks
352
+ .map((b) => key(Array.isArray(b) ? b : [b])).sort().join(', ');
353
+ const measured = broke.map(key).sort().join(', ');
354
+ if (expected !== measured) {
355
+ problems.push(
356
+ `${invariant.id}: declares breaks [${expected || '-'}], measured [${measured || '-'}]`,
357
+ );
358
+ }
359
+ if (broke.length === 0) {
360
+ problems.push(`${invariant.id}: no mutant breaks it — the assertion cannot fail`);
361
+ }
362
+ }
363
+
364
+ const width = Math.max(...matrix.map((row) => row.id.length));
365
+ console.log(`\ninvariant x mutant — ${mutants.length} mutants, `
366
+ + 'and the minimal removal that turns each assertion red\n');
367
+ for (const row of matrix) {
368
+ console.log(` ${row.id.padEnd(width)} ${row.broke.map(key).join(', ') || '(nothing)'}`);
369
+ }
370
+
371
+ console.log('\nisolation — the fixture that separates each rule from every other\n');
372
+ for (const rule of RULES) {
373
+ const isolating = matrix.filter((row) => {
374
+ const exact = row.broke.filter((m) => m.length === 1 && m[0] === rule);
375
+ if (exact.length !== 1) return false;
376
+ // The broad baseline is allowed to co-occur: it breaks every grant assertion by
377
+ // construction, so it hides nothing. Any OTHER co-occurring rule does.
378
+ return row.broke
379
+ .filter((m) => !(m.length === 1 && m[0] === rule))
380
+ .every((m) => m.every((r) => BROAD.includes(r)));
381
+ });
382
+ if (isolating.length) {
383
+ console.log(` ${rule.padEnd(28)} ${isolating.map((r) => r.id).join(', ')}`);
384
+ } else {
385
+ problems.push(`no fixture isolates ${rule} — every assertion it breaks is also `
386
+ + 'broken by another rule, so neither is proven');
387
+ }
388
+ }
389
+
390
+ if (problems.length) {
391
+ console.error(`\nFAIL: ${problems.length} problem(s) with the assertion pack itself`);
392
+ for (const p of problems) console.error(` - ${p}`);
393
+ return 1;
394
+ }
395
+ console.log(`\nOK: ${INVARIANTS.length} assertions, each watched failing; `
396
+ + `${RULES.length} rules, each isolated by a fixture`);
397
+ return 0;
398
+ }
399
+
400
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
401
+ if (isMain) {
402
+ const mode = process.argv.includes('--self-test') ? selfTest : runAll;
403
+ process.exit(await mode());
404
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "id": "evt_PLACEHOLDER_refund_first",
3
+ "object": "event",
4
+ "api_version": "2026-07-29.dahlia",
5
+ "created": 1770076800,
6
+ "livemode": false,
7
+ "pending_webhooks": 1,
8
+ "request": {
9
+ "id": "req_PLACEHOLDER",
10
+ "idempotency_key": null
11
+ },
12
+ "type": "charge.refunded",
13
+ "data": {
14
+ "object": {
15
+ "id": "ch_PLACEHOLDER_creditpack",
16
+ "object": "charge",
17
+ "amount": 9000,
18
+ "amount_captured": 9000,
19
+ "amount_refunded": 4000,
20
+ "captured": true,
21
+ "paid": true,
22
+ "refunded": false,
23
+ "status": "succeeded",
24
+ "currency": "usd",
25
+ "customer": "cus_PLACEHOLDER_alice",
26
+ "invoice": null,
27
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
28
+ "metadata": {
29
+ "userId": "usr_PLACEHOLDER_alice",
30
+ "productId": "prod_PLACEHOLDER_credits_90"
31
+ },
32
+ "refunds": {
33
+ "object": "list",
34
+ "has_more": false,
35
+ "total_count": 1,
36
+ "data": [
37
+ {
38
+ "id": "re_PLACEHOLDER_first",
39
+ "object": "refund",
40
+ "amount": 4000,
41
+ "charge": "ch_PLACEHOLDER_creditpack",
42
+ "created": 1770076800,
43
+ "currency": "usd",
44
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
45
+ "reason": "requested_by_customer",
46
+ "status": "succeeded"
47
+ }
48
+ ]
49
+ }
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "id": "evt_PLACEHOLDER_refund_second",
3
+ "object": "event",
4
+ "api_version": "2026-07-29.dahlia",
5
+ "created": 1770163200,
6
+ "livemode": false,
7
+ "pending_webhooks": 1,
8
+ "request": {
9
+ "id": "req_PLACEHOLDER",
10
+ "idempotency_key": null
11
+ },
12
+ "type": "charge.refunded",
13
+ "data": {
14
+ "object": {
15
+ "id": "ch_PLACEHOLDER_creditpack",
16
+ "object": "charge",
17
+ "amount": 9000,
18
+ "amount_captured": 9000,
19
+ "amount_refunded": 9000,
20
+ "captured": true,
21
+ "paid": true,
22
+ "refunded": true,
23
+ "status": "succeeded",
24
+ "currency": "usd",
25
+ "customer": "cus_PLACEHOLDER_alice",
26
+ "invoice": null,
27
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
28
+ "metadata": {
29
+ "userId": "usr_PLACEHOLDER_alice",
30
+ "productId": "prod_PLACEHOLDER_credits_90"
31
+ },
32
+ "refunds": {
33
+ "object": "list",
34
+ "has_more": false,
35
+ "total_count": 2,
36
+ "data": [
37
+ {
38
+ "id": "re_PLACEHOLDER_second",
39
+ "object": "refund",
40
+ "amount": 5000,
41
+ "charge": "ch_PLACEHOLDER_creditpack",
42
+ "created": 1770163200,
43
+ "currency": "usd",
44
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
45
+ "reason": "requested_by_customer",
46
+ "status": "succeeded"
47
+ },
48
+ {
49
+ "id": "re_PLACEHOLDER_first",
50
+ "object": "refund",
51
+ "amount": 4000,
52
+ "charge": "ch_PLACEHOLDER_creditpack",
53
+ "created": 1770076800,
54
+ "currency": "usd",
55
+ "payment_intent": "pi_PLACEHOLDER_creditpack",
56
+ "reason": "requested_by_customer",
57
+ "status": "succeeded"
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "id": "evt_PLACEHOLDER_session_bob_failed",
3
+ "object": "event",
4
+ "api_version": "2026-07-29.dahlia",
5
+ "created": 1767571200,
6
+ "livemode": false,
7
+ "pending_webhooks": 1,
8
+ "request": {
9
+ "id": null,
10
+ "idempotency_key": null
11
+ },
12
+ "type": "checkout.session.async_payment_failed",
13
+ "data": {
14
+ "object": {
15
+ "id": "cs_PLACEHOLDER_bob",
16
+ "object": "checkout.session",
17
+ "mode": "subscription",
18
+ "status": "complete",
19
+ "payment_status": "unpaid",
20
+ "amount_subtotal": 2900,
21
+ "amount_total": 2900,
22
+ "currency": "usd",
23
+ "customer": "cus_PLACEHOLDER_bob",
24
+ "customer_details": {
25
+ "email": "placeholder@example.invalid"
26
+ },
27
+ "invoice": "in_PLACEHOLDER_bob",
28
+ "payment_intent": null,
29
+ "payment_method_types": [
30
+ "sepa_debit",
31
+ "card"
32
+ ],
33
+ "subscription": "sub_PLACEHOLDER_bob",
34
+ "success_url": "https://example.invalid/after?session_id={CHECKOUT_SESSION_ID}",
35
+ "metadata": {
36
+ "userId": "usr_PLACEHOLDER_bob",
37
+ "productId": "prod_PLACEHOLDER_pro",
38
+ "quantity": "1",
39
+ "conversionEventId": "evtid_PLACEHOLDER_purchase_bob"
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "id": "evt_PLACEHOLDER_session_bob_completed",
3
+ "object": "event",
4
+ "api_version": "2026-07-29.dahlia",
5
+ "created": 1767312000,
6
+ "livemode": false,
7
+ "pending_webhooks": 1,
8
+ "request": {
9
+ "id": null,
10
+ "idempotency_key": null
11
+ },
12
+ "type": "checkout.session.completed",
13
+ "data": {
14
+ "object": {
15
+ "id": "cs_PLACEHOLDER_bob",
16
+ "object": "checkout.session",
17
+ "mode": "subscription",
18
+ "status": "complete",
19
+ "payment_status": "unpaid",
20
+ "amount_subtotal": 2900,
21
+ "amount_total": 2900,
22
+ "currency": "usd",
23
+ "customer": "cus_PLACEHOLDER_bob",
24
+ "customer_details": {
25
+ "email": "placeholder@example.invalid"
26
+ },
27
+ "invoice": "in_PLACEHOLDER_bob",
28
+ "payment_intent": null,
29
+ "payment_method_types": [
30
+ "sepa_debit",
31
+ "card"
32
+ ],
33
+ "subscription": "sub_PLACEHOLDER_bob",
34
+ "success_url": "https://example.invalid/after?session_id={CHECKOUT_SESSION_ID}",
35
+ "metadata": {
36
+ "userId": "usr_PLACEHOLDER_bob",
37
+ "productId": "prod_PLACEHOLDER_pro",
38
+ "quantity": "1",
39
+ "conversionEventId": "evtid_PLACEHOLDER_purchase_bob"
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "id": "evt_PLACEHOLDER_session_carol_completed",
3
+ "object": "event",
4
+ "api_version": "2026-07-29.dahlia",
5
+ "created": 1767398400,
6
+ "livemode": false,
7
+ "pending_webhooks": 1,
8
+ "request": {
9
+ "id": null,
10
+ "idempotency_key": null
11
+ },
12
+ "type": "checkout.session.completed",
13
+ "data": {
14
+ "object": {
15
+ "id": "cs_PLACEHOLDER_carol",
16
+ "object": "checkout.session",
17
+ "mode": "subscription",
18
+ "status": "complete",
19
+ "payment_status": "paid",
20
+ "amount_subtotal": 2900,
21
+ "amount_total": 2900,
22
+ "currency": "usd",
23
+ "customer": "cus_PLACEHOLDER_carol",
24
+ "customer_details": {
25
+ "email": "placeholder@example.invalid"
26
+ },
27
+ "invoice": "in_PLACEHOLDER_carol",
28
+ "payment_intent": "pi_PLACEHOLDER_carol",
29
+ "payment_method_types": [
30
+ "card"
31
+ ],
32
+ "subscription": "sub_PLACEHOLDER_carol",
33
+ "success_url": "https://example.invalid/after?session_id={CHECKOUT_SESSION_ID}",
34
+ "metadata": {
35
+ "userId": "usr_PLACEHOLDER_carol",
36
+ "productId": "prod_PLACEHOLDER_pro",
37
+ "quantity": "1",
38
+ "conversionEventId": "evtid_PLACEHOLDER_purchase_carol"
39
+ }
40
+ }
41
+ }
42
+ }