@adonis-agora/payments 0.2.0 → 0.3.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.
- package/CHANGELOG.md +2347 -0
- package/dist/assets/spa/assets/index-CT5Zon6P.css +1 -0
- package/dist/assets/spa/assets/index-CvEbJEnq.js +41 -0
- package/dist/assets/spa/assets/index-CvEbJEnq.js.map +1 -0
- package/dist/assets/spa/index.html +20 -0
- package/dist/commands/main.d.ts +4 -3
- package/dist/commands/main.d.ts.map +1 -1
- package/dist/commands/main.js +9 -2
- package/dist/commands/main.js.map +1 -1
- package/dist/commands/payments_health.d.ts +31 -0
- package/dist/commands/payments_health.d.ts.map +1 -0
- package/dist/commands/payments_health.js +113 -0
- package/dist/commands/payments_health.js.map +1 -0
- package/dist/commands/payments_sync.d.ts +18 -4
- package/dist/commands/payments_sync.d.ts.map +1 -1
- package/dist/commands/payments_sync.js +136 -40
- package/dist/commands/payments_sync.js.map +1 -1
- package/dist/configure.d.ts +12 -5
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +18 -5
- package/dist/configure.js.map +1 -1
- package/dist/providers/dashboard_provider.d.ts +97 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -0
- package/dist/providers/dashboard_provider.js +430 -0
- package/dist/providers/dashboard_provider.js.map +1 -0
- package/dist/providers/payments_client_provider.d.ts +41 -0
- package/dist/providers/payments_client_provider.d.ts.map +1 -0
- package/dist/providers/payments_client_provider.js +95 -0
- package/dist/providers/payments_client_provider.js.map +1 -0
- package/dist/providers/payments_provider.d.ts +35 -0
- package/dist/providers/payments_provider.d.ts.map +1 -1
- package/dist/providers/payments_provider.js +250 -36
- package/dist/providers/payments_provider.js.map +1 -1
- package/dist/src/billing/billing_health.d.ts +85 -0
- package/dist/src/billing/billing_health.d.ts.map +1 -0
- package/dist/src/billing/billing_health.js +155 -0
- package/dist/src/billing/billing_health.js.map +1 -0
- package/dist/src/billing/billing_overview.d.ts +4 -3
- package/dist/src/billing/billing_overview.d.ts.map +1 -1
- package/dist/src/billing/billing_overview.js +14 -5
- package/dist/src/billing/billing_overview.js.map +1 -1
- package/dist/src/billing/billing_store.d.ts +634 -5
- package/dist/src/billing/billing_store.d.ts.map +1 -1
- package/dist/src/billing/billing_store.js +18 -1
- package/dist/src/billing/billing_store.js.map +1 -1
- package/dist/src/billing/index.d.ts +2 -1
- package/dist/src/billing/index.d.ts.map +1 -1
- package/dist/src/billing/index.js +1 -0
- package/dist/src/billing/index.js.map +1 -1
- package/dist/src/billing/list_query.d.ts +16 -0
- package/dist/src/billing/list_query.d.ts.map +1 -0
- package/dist/src/billing/list_query.js +28 -0
- package/dist/src/billing/list_query.js.map +1 -0
- package/dist/src/billing/lucid_billing_store.d.ts +122 -4
- package/dist/src/billing/lucid_billing_store.d.ts.map +1 -1
- package/dist/src/billing/lucid_billing_store.js +811 -13
- package/dist/src/billing/lucid_billing_store.js.map +1 -1
- package/dist/src/billing/mixins/index.d.ts +3 -0
- package/dist/src/billing/mixins/index.d.ts.map +1 -1
- package/dist/src/billing/mixins/index.js +3 -0
- package/dist/src/billing/mixins/index.js.map +1 -1
- package/dist/src/billing/mixins/with_audit_event.d.ts +48 -0
- package/dist/src/billing/mixins/with_audit_event.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_audit_event.js +77 -0
- package/dist/src/billing/mixins/with_audit_event.js.map +1 -0
- package/dist/src/billing/mixins/with_customer.d.ts +39 -0
- package/dist/src/billing/mixins/with_customer.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_customer.js +68 -0
- package/dist/src/billing/mixins/with_customer.js.map +1 -0
- package/dist/src/billing/mixins/with_dispute.d.ts +68 -0
- package/dist/src/billing/mixins/with_dispute.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_dispute.js +83 -0
- package/dist/src/billing/mixins/with_dispute.js.map +1 -0
- package/dist/src/billing/mixins/with_payment.d.ts +42 -0
- package/dist/src/billing/mixins/with_payment.d.ts.map +1 -1
- package/dist/src/billing/mixins/with_payment.js +29 -3
- package/dist/src/billing/mixins/with_payment.js.map +1 -1
- package/dist/src/billing/mixins/with_subscription.d.ts +7 -0
- package/dist/src/billing/mixins/with_subscription.d.ts.map +1 -1
- package/dist/src/billing/mixins/with_subscription.js +15 -1
- package/dist/src/billing/mixins/with_subscription.js.map +1 -1
- package/dist/src/billing/mixins/with_usage_event.d.ts +7 -0
- package/dist/src/billing/mixins/with_usage_event.d.ts.map +1 -1
- package/dist/src/billing/mixins/with_usage_event.js +15 -1
- package/dist/src/billing/mixins/with_usage_event.js.map +1 -1
- package/dist/src/billing/mixins/with_webhook_event.d.ts +18 -0
- package/dist/src/billing/mixins/with_webhook_event.d.ts.map +1 -1
- package/dist/src/billing/mixins/with_webhook_event.js +18 -1
- package/dist/src/billing/mixins/with_webhook_event.js.map +1 -1
- package/dist/src/billing/resolve_dispatch.d.ts +22 -0
- package/dist/src/billing/resolve_dispatch.d.ts.map +1 -0
- package/dist/src/billing/resolve_dispatch.js +44 -0
- package/dist/src/billing/resolve_dispatch.js.map +1 -0
- package/dist/src/billing/resolve_store.d.ts +18 -0
- package/dist/src/billing/resolve_store.d.ts.map +1 -0
- package/dist/src/billing/resolve_store.js +35 -0
- package/dist/src/billing/resolve_store.js.map +1 -0
- package/dist/src/billing/schema.d.ts +108 -0
- package/dist/src/billing/schema.d.ts.map +1 -0
- package/dist/src/billing/schema.js +350 -0
- package/dist/src/billing/schema.js.map +1 -0
- package/dist/src/billing/webhook_dispatcher.d.ts +95 -16
- package/dist/src/billing/webhook_dispatcher.d.ts.map +1 -1
- package/dist/src/billing/webhook_dispatcher.js +186 -33
- package/dist/src/billing/webhook_dispatcher.js.map +1 -1
- package/dist/src/billing/webhook_events.d.ts +67 -1
- package/dist/src/billing/webhook_events.d.ts.map +1 -1
- package/dist/src/billing/webhook_events.js +15 -0
- package/dist/src/billing/webhook_events.js.map +1 -1
- package/dist/src/billing/webhook_processor.d.ts.map +1 -1
- package/dist/src/billing/webhook_processor.js +325 -5
- package/dist/src/billing/webhook_processor.js.map +1 -1
- package/dist/src/client/define_config.d.ts +150 -0
- package/dist/src/client/define_config.d.ts.map +1 -0
- package/dist/src/client/define_config.js +98 -0
- package/dist/src/client/define_config.js.map +1 -0
- package/dist/src/client/handlers.d.ts +71 -0
- package/dist/src/client/handlers.d.ts.map +1 -0
- package/dist/src/client/handlers.js +119 -0
- package/dist/src/client/handlers.js.map +1 -0
- package/dist/src/client/index.d.ts +16 -0
- package/dist/src/client/index.d.ts.map +1 -0
- package/dist/src/client/index.js +13 -0
- package/dist/src/client/index.js.map +1 -0
- package/dist/src/client/statuses.d.ts +38 -0
- package/dist/src/client/statuses.d.ts.map +1 -0
- package/dist/src/client/statuses.js +42 -0
- package/dist/src/client/statuses.js.map +1 -0
- package/dist/src/dashboard/actions.d.ts +143 -0
- package/dist/src/dashboard/actions.d.ts.map +1 -0
- package/dist/src/dashboard/actions.js +112 -0
- package/dist/src/dashboard/actions.js.map +1 -0
- package/dist/src/dashboard/auth.d.ts +136 -0
- package/dist/src/dashboard/auth.d.ts.map +1 -0
- package/dist/src/dashboard/auth.js +152 -0
- package/dist/src/dashboard/auth.js.map +1 -0
- package/dist/src/dashboard/define_config.d.ts +80 -0
- package/dist/src/dashboard/define_config.d.ts.map +1 -0
- package/dist/src/dashboard/define_config.js +83 -0
- package/dist/src/dashboard/define_config.js.map +1 -0
- package/dist/src/dashboard/handlers.d.ts +281 -0
- package/dist/src/dashboard/handlers.d.ts.map +1 -0
- package/dist/src/dashboard/handlers.js +960 -0
- package/dist/src/dashboard/handlers.js.map +1 -0
- package/dist/src/dashboard/index.d.ts +18 -0
- package/dist/src/dashboard/index.d.ts.map +1 -0
- package/dist/src/dashboard/index.js +14 -0
- package/dist/src/dashboard/index.js.map +1 -0
- package/dist/src/dashboard/login_page.d.ts +23 -0
- package/dist/src/dashboard/login_page.d.ts.map +1 -0
- package/dist/src/dashboard/login_page.js +170 -0
- package/dist/src/dashboard/login_page.js.map +1 -0
- package/dist/src/dashboard/period.d.ts +31 -0
- package/dist/src/dashboard/period.d.ts.map +1 -0
- package/dist/src/dashboard/period.js +44 -0
- package/dist/src/dashboard/period.js.map +1 -0
- package/dist/src/dashboard/session_cookie.d.ts +42 -0
- package/dist/src/dashboard/session_cookie.d.ts.map +1 -0
- package/dist/src/dashboard/session_cookie.js +97 -0
- package/dist/src/dashboard/session_cookie.js.map +1 -0
- package/dist/src/dashboard/spa.d.ts +24 -0
- package/dist/src/dashboard/spa.d.ts.map +1 -0
- package/dist/src/dashboard/spa.js +52 -0
- package/dist/src/dashboard/spa.js.map +1 -0
- package/dist/src/define_config.d.ts +244 -9
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +97 -1
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/diagnostics.d.ts +287 -7
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +317 -1
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/driver.d.ts +139 -5
- package/dist/src/driver.d.ts.map +1 -1
- package/dist/src/drivers/abacate.d.ts +12 -2
- package/dist/src/drivers/abacate.d.ts.map +1 -1
- package/dist/src/drivers/abacate.js +112 -19
- package/dist/src/drivers/abacate.js.map +1 -1
- package/dist/src/drivers/adyen.d.ts +164 -0
- package/dist/src/drivers/adyen.d.ts.map +1 -0
- package/dist/src/drivers/adyen.js +678 -0
- package/dist/src/drivers/adyen.js.map +1 -0
- package/dist/src/drivers/asaas.d.ts +52 -2
- package/dist/src/drivers/asaas.d.ts.map +1 -1
- package/dist/src/drivers/asaas.js +364 -29
- package/dist/src/drivers/asaas.js.map +1 -1
- package/dist/src/drivers/dodo.d.ts +135 -0
- package/dist/src/drivers/dodo.d.ts.map +1 -0
- package/dist/src/drivers/dodo.js +864 -0
- package/dist/src/drivers/dodo.js.map +1 -0
- package/dist/src/drivers/efi.d.ts +98 -0
- package/dist/src/drivers/efi.d.ts.map +1 -0
- package/dist/src/drivers/efi.js +628 -0
- package/dist/src/drivers/efi.js.map +1 -0
- package/dist/src/drivers/infinitepay.d.ts +156 -0
- package/dist/src/drivers/infinitepay.d.ts.map +1 -0
- package/dist/src/drivers/infinitepay.js +321 -0
- package/dist/src/drivers/infinitepay.js.map +1 -0
- package/dist/src/drivers/lemonsqueezy.d.ts +125 -0
- package/dist/src/drivers/lemonsqueezy.d.ts.map +1 -0
- package/dist/src/drivers/lemonsqueezy.js +632 -0
- package/dist/src/drivers/lemonsqueezy.js.map +1 -0
- package/dist/src/drivers/mercadopago.d.ts +97 -0
- package/dist/src/drivers/mercadopago.d.ts.map +1 -0
- package/dist/src/drivers/mercadopago.js +844 -0
- package/dist/src/drivers/mercadopago.js.map +1 -0
- package/dist/src/drivers/mollie.d.ts +125 -0
- package/dist/src/drivers/mollie.d.ts.map +1 -0
- package/dist/src/drivers/mollie.js +812 -0
- package/dist/src/drivers/mollie.js.map +1 -0
- package/dist/src/drivers/paddle.d.ts +148 -0
- package/dist/src/drivers/paddle.d.ts.map +1 -0
- package/dist/src/drivers/paddle.js +738 -0
- package/dist/src/drivers/paddle.js.map +1 -0
- package/dist/src/drivers/pagarme.d.ts +97 -0
- package/dist/src/drivers/pagarme.d.ts.map +1 -0
- package/dist/src/drivers/pagarme.js +829 -0
- package/dist/src/drivers/pagarme.js.map +1 -0
- package/dist/src/drivers/pagbank.d.ts +57 -0
- package/dist/src/drivers/pagbank.d.ts.map +1 -0
- package/dist/src/drivers/pagbank.js +499 -0
- package/dist/src/drivers/pagbank.js.map +1 -0
- package/dist/src/drivers/paypal.d.ts +120 -0
- package/dist/src/drivers/paypal.d.ts.map +1 -0
- package/dist/src/drivers/paypal.js +891 -0
- package/dist/src/drivers/paypal.js.map +1 -0
- package/dist/src/drivers/polar.d.ts +118 -0
- package/dist/src/drivers/polar.d.ts.map +1 -0
- package/dist/src/drivers/polar.js +604 -0
- package/dist/src/drivers/polar.js.map +1 -0
- package/dist/src/drivers/razorpay.d.ts +103 -0
- package/dist/src/drivers/razorpay.d.ts.map +1 -0
- package/dist/src/drivers/razorpay.js +867 -0
- package/dist/src/drivers/razorpay.js.map +1 -0
- package/dist/src/drivers/shared.d.ts +33 -0
- package/dist/src/drivers/shared.d.ts.map +1 -0
- package/dist/src/drivers/shared.js +37 -0
- package/dist/src/drivers/shared.js.map +1 -0
- package/dist/src/drivers/square.d.ts +133 -0
- package/dist/src/drivers/square.d.ts.map +1 -0
- package/dist/src/drivers/square.js +905 -0
- package/dist/src/drivers/square.js.map +1 -0
- package/dist/src/drivers/stripe.d.ts +40 -3
- package/dist/src/drivers/stripe.d.ts.map +1 -1
- package/dist/src/drivers/stripe.js +806 -25
- package/dist/src/drivers/stripe.js.map +1 -1
- package/dist/src/drivers/woovi.d.ts +10 -3
- package/dist/src/drivers/woovi.d.ts.map +1 -1
- package/dist/src/drivers/woovi.js +162 -29
- package/dist/src/drivers/woovi.js.map +1 -1
- package/dist/src/ensure_customer.d.ts +34 -1
- package/dist/src/ensure_customer.d.ts.map +1 -1
- package/dist/src/ensure_customer.js +29 -4
- package/dist/src/ensure_customer.js.map +1 -1
- package/dist/src/http.d.ts +37 -3
- package/dist/src/http.d.ts.map +1 -1
- package/dist/src/http.js +69 -5
- package/dist/src/http.js.map +1 -1
- package/dist/src/index.d.ts +40 -10
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +22 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/invoice/emit_invoice.d.ts.map +1 -1
- package/dist/src/invoice/emit_invoice.js +31 -11
- package/dist/src/invoice/emit_invoice.js.map +1 -1
- package/dist/src/money.d.ts +23 -5
- package/dist/src/money.d.ts.map +1 -1
- package/dist/src/money.js +72 -7
- package/dist/src/money.js.map +1 -1
- package/dist/src/payments_manager.d.ts +8 -1
- package/dist/src/payments_manager.d.ts.map +1 -1
- package/dist/src/payments_manager.js +168 -6
- package/dist/src/payments_manager.js.map +1 -1
- package/dist/src/services/main.d.ts +38 -0
- package/dist/src/services/main.d.ts.map +1 -1
- package/dist/src/services/main.js +130 -0
- package/dist/src/services/main.js.map +1 -1
- package/dist/src/telescope/index.d.ts +2 -0
- package/dist/src/telescope/index.d.ts.map +1 -1
- package/dist/src/telescope/index.js +1 -0
- package/dist/src/telescope/index.js.map +1 -1
- package/dist/src/telescope/payments_watcher.d.ts +10 -4
- package/dist/src/telescope/payments_watcher.d.ts.map +1 -1
- package/dist/src/telescope/payments_watcher.js +65 -13
- package/dist/src/telescope/payments_watcher.js.map +1 -1
- package/dist/src/telescope/register.d.ts +62 -0
- package/dist/src/telescope/register.d.ts.map +1 -0
- package/dist/src/telescope/register.js +48 -0
- package/dist/src/telescope/register.js.map +1 -0
- package/dist/src/testing/fake_payments.d.ts +62 -0
- package/dist/src/testing/fake_payments.d.ts.map +1 -0
- package/dist/src/testing/fake_payments.js +82 -0
- package/dist/src/testing/fake_payments.js.map +1 -0
- package/dist/src/testing/in_memory_billing_store.d.ts +151 -2
- package/dist/src/testing/in_memory_billing_store.d.ts.map +1 -1
- package/dist/src/testing/in_memory_billing_store.js +517 -4
- package/dist/src/testing/in_memory_billing_store.js.map +1 -1
- package/dist/src/testing/index.d.ts +2 -1
- package/dist/src/testing/index.d.ts.map +1 -1
- package/dist/src/testing/index.js +1 -0
- package/dist/src/testing/index.js.map +1 -1
- package/dist/src/types.d.ts +161 -5
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +30 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/webhook_handlers.d.ts +96 -2
- package/dist/src/webhook_handlers.d.ts.map +1 -1
- package/dist/src/webhook_handlers.js +91 -10
- package/dist/src/webhook_handlers.js.map +1 -1
- package/dist/src/webhook_security.d.ts +94 -3
- package/dist/src/webhook_security.d.ts.map +1 -1
- package/dist/src/webhook_security.js +173 -9
- package/dist/src/webhook_security.js.map +1 -1
- package/dist/stubs/config/payments.stub +13 -0
- package/dist/stubs/config/payments_client.stub +42 -0
- package/dist/stubs/config/payments_dashboard.stub +38 -0
- package/dist/stubs/database/migrations/create_billing_tables.stub +35 -77
- package/dist/stubs/make/webhook_handler/main.stub +29 -11
- package/package.json +60 -25
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The billing schema, as standalone idempotent functions.
|
|
3
|
+
*
|
|
4
|
+
* This is the ecosystem convention — a lib owns its own schema — and it is why
|
|
5
|
+
* `@adonis-agora/durable` exports `createDurableTables` and `@adonis-agora/authz` exports
|
|
6
|
+
* `createAuthzTables`. This package did not follow it, and the cost showed up quickly: every
|
|
7
|
+
* column added after the first release became another migration file with hand-written
|
|
8
|
+
* `hasColumn` / `hasTable` guards, and an app adopting the library had three files to publish
|
|
9
|
+
* and run before it could take a single payment.
|
|
10
|
+
*
|
|
11
|
+
* Two paths, one DDL. {@link LucidBillingStore} calls {@link createBillingTables} once on
|
|
12
|
+
* first use (`autoCreateSchema`, on by default), and the published migration stub calls the
|
|
13
|
+
* SAME function — so the two can never drift, which is the whole reason the DDL lives here
|
|
14
|
+
* rather than in a stub the store knows nothing about.
|
|
15
|
+
*
|
|
16
|
+
* The DDL is portable `rawQuery` (`CREATE TABLE IF NOT EXISTS`) rather than Knex's schema
|
|
17
|
+
* builder, matching the posture authz takes: it keeps the `@adonisjs/lucid` coupling to the
|
|
18
|
+
* `rawQuery` surface, and it is idempotent by construction — which is what lets an install
|
|
19
|
+
* that already ran the old Knex migration call this and have it do nothing.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Every table the billing layer owns.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately NOT configurable, unlike authz's table names. The models are the source of
|
|
25
|
+
* truth here (`BillingModels` lets an app swap any of them), and DDL that could name a table
|
|
26
|
+
* the models do not read is a way to create an empty table beside a missing one. An app that
|
|
27
|
+
* renames a table swaps the model and writes its own migration.
|
|
28
|
+
*/
|
|
29
|
+
export const BILLING_TABLES = [
|
|
30
|
+
'billing_customers',
|
|
31
|
+
'billing_subscriptions',
|
|
32
|
+
'billing_payments',
|
|
33
|
+
'billing_webhook_events',
|
|
34
|
+
'billing_disputes',
|
|
35
|
+
'billing_usage_events',
|
|
36
|
+
'billing_audit_events',
|
|
37
|
+
];
|
|
38
|
+
/** Best-effort dialect name; `undefined` when it cannot be read. */
|
|
39
|
+
export function detectDialect(db) {
|
|
40
|
+
try {
|
|
41
|
+
const direct = db.dialect?.name;
|
|
42
|
+
if (typeof direct === 'string' && direct.length > 0)
|
|
43
|
+
return direct;
|
|
44
|
+
return db.connection?.()?.dialect?.name;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function isPostgres(dialect) {
|
|
51
|
+
return !!dialect && /postgres|pg|redshift/i.test(dialect);
|
|
52
|
+
}
|
|
53
|
+
function isMysql(dialect) {
|
|
54
|
+
return !!dialect && /mysql|mariadb/i.test(dialect);
|
|
55
|
+
}
|
|
56
|
+
function typesFor(dialect) {
|
|
57
|
+
if (isPostgres(dialect))
|
|
58
|
+
return { timestamp: 'TIMESTAMPTZ', json: 'JSONB' };
|
|
59
|
+
if (isMysql(dialect))
|
|
60
|
+
return { timestamp: 'DATETIME', json: 'JSON' };
|
|
61
|
+
// SQLite: no native JSON or timestamp types. It stores both as TEXT and Lucid serializes
|
|
62
|
+
// on the way in, which is why the models declare `prepare`/`consume` rather than trusting
|
|
63
|
+
// the column type.
|
|
64
|
+
return { timestamp: 'DATETIME', json: 'TEXT' };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a secondary index, idempotently.
|
|
68
|
+
*
|
|
69
|
+
* Postgres and SQLite take `CREATE INDEX IF NOT EXISTS`. **MySQL does not** — it fails with
|
|
70
|
+
* a syntax error — so there the statement is attempted and a duplicate-name error is
|
|
71
|
+
* swallowed. That is narrower than it looks: the only way this statement fails on a second
|
|
72
|
+
* run is that the index is already there, which is the outcome asked for. Any other failure
|
|
73
|
+
* still throws.
|
|
74
|
+
*/
|
|
75
|
+
async function createIndex(db, dialect, name, table, columns) {
|
|
76
|
+
if (!isMysql(dialect)) {
|
|
77
|
+
await db.rawQuery(`CREATE INDEX IF NOT EXISTS ${name} ON ${table} (${columns})`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
await db.rawQuery(`CREATE INDEX ${name} ON ${table} (${columns})`);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
85
|
+
if (!/duplicate key name|already exists/i.test(message))
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Add a column that was introduced AFTER its table shipped, idempotently.
|
|
91
|
+
*
|
|
92
|
+
* `CREATE TABLE IF NOT EXISTS` is a no-op on a table that already exists, so it cannot carry
|
|
93
|
+
* a new column to an install that ran an earlier version. Without this, upgrading the package
|
|
94
|
+
* would silently leave the old schema in place and the first query naming the new column
|
|
95
|
+
* would fail — which is the failure mode a library owning its own schema exists to prevent.
|
|
96
|
+
*
|
|
97
|
+
* Postgres has `ADD COLUMN IF NOT EXISTS`. SQLite and MySQL do not, so there the statement is
|
|
98
|
+
* attempted and a duplicate-column error is swallowed: the only way this fails twice is that
|
|
99
|
+
* the column is already there, which is the outcome asked for. Anything else still throws.
|
|
100
|
+
*/
|
|
101
|
+
async function addColumn(db, dialect, table, column, type) {
|
|
102
|
+
if (isPostgres(dialect)) {
|
|
103
|
+
await db.rawQuery(`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${column} ${type}`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
await db.rawQuery(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
if (!/duplicate column|already exists/i.test(message))
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Create every billing table, idempotently.
|
|
117
|
+
*
|
|
118
|
+
* Safe to call from a Lucid migration's `up()`, and safe to call repeatedly at boot — which
|
|
119
|
+
* is exactly what {@link LucidBillingStore} does when `autoCreateSchema` is on. An install
|
|
120
|
+
* that already ran the older Knex migrations gets no-ops: every statement is
|
|
121
|
+
* `IF NOT EXISTS`.
|
|
122
|
+
*
|
|
123
|
+
* It also carries columns added AFTER a table shipped — see the block at the end. That is
|
|
124
|
+
* what makes this a real upgrade path rather than a first-install convenience: an app that
|
|
125
|
+
* ran the 0.2.0 migration and then upgrades gets the columns the new code reads, without
|
|
126
|
+
* having to be told to publish and run a second migration.
|
|
127
|
+
*/
|
|
128
|
+
export async function createBillingTables(db) {
|
|
129
|
+
const dialect = detectDialect(db);
|
|
130
|
+
const t = typesFor(dialect);
|
|
131
|
+
const run = (sql) => db.rawQuery(sql);
|
|
132
|
+
await run(`CREATE TABLE IF NOT EXISTS billing_customers (
|
|
133
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
134
|
+
owner_type VARCHAR(255),
|
|
135
|
+
owner_id VARCHAR(255),
|
|
136
|
+
gateway_id VARCHAR(255) UNIQUE,
|
|
137
|
+
provider VARCHAR(255),
|
|
138
|
+
email VARCHAR(255),
|
|
139
|
+
name VARCHAR(255),
|
|
140
|
+
tax_id VARCHAR(255),
|
|
141
|
+
metadata ${t.json},
|
|
142
|
+
created_at ${t.timestamp} NOT NULL,
|
|
143
|
+
updated_at ${t.timestamp} NOT NULL
|
|
144
|
+
)`);
|
|
145
|
+
await run(`CREATE TABLE IF NOT EXISTS billing_subscriptions (
|
|
146
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
147
|
+
gateway_id VARCHAR(255) UNIQUE,
|
|
148
|
+
provider VARCHAR(255),
|
|
149
|
+
status VARCHAR(255),
|
|
150
|
+
plan_id VARCHAR(255),
|
|
151
|
+
customer_id VARCHAR(255),
|
|
152
|
+
trial_ends_at ${t.timestamp},
|
|
153
|
+
ends_at ${t.timestamp},
|
|
154
|
+
payload ${t.json},
|
|
155
|
+
created_at ${t.timestamp} NOT NULL,
|
|
156
|
+
updated_at ${t.timestamp} NOT NULL
|
|
157
|
+
)`);
|
|
158
|
+
await run(`CREATE TABLE IF NOT EXISTS billing_payments (
|
|
159
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
160
|
+
gateway_id VARCHAR(255) UNIQUE,
|
|
161
|
+
provider VARCHAR(255),
|
|
162
|
+
status VARCHAR(255),
|
|
163
|
+
amount BIGINT,
|
|
164
|
+
currency VARCHAR(3),
|
|
165
|
+
customer_id VARCHAR(255),
|
|
166
|
+
subscription_id VARCHAR(255),
|
|
167
|
+
external_reference VARCHAR(255),
|
|
168
|
+
refunded_amount BIGINT,
|
|
169
|
+
payload ${t.json},
|
|
170
|
+
paid_at ${t.timestamp},
|
|
171
|
+
created_at ${t.timestamp} NOT NULL,
|
|
172
|
+
updated_at ${t.timestamp} NOT NULL
|
|
173
|
+
)`);
|
|
174
|
+
// The key an app actually looks a payment up by — its own order id, not the gateway's. An
|
|
175
|
+
// unindexed lookup here is a sequential scan on a table that grows with every charge.
|
|
176
|
+
await run(`CREATE TABLE IF NOT EXISTS billing_webhook_events (
|
|
177
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
178
|
+
gateway_event_id VARCHAR(255) UNIQUE,
|
|
179
|
+
provider VARCHAR(255),
|
|
180
|
+
type VARCHAR(255),
|
|
181
|
+
status VARCHAR(255),
|
|
182
|
+
payload ${t.json},
|
|
183
|
+
normalized ${t.json},
|
|
184
|
+
error TEXT,
|
|
185
|
+
created_at ${t.timestamp} NOT NULL,
|
|
186
|
+
updated_at ${t.timestamp} NOT NULL
|
|
187
|
+
)`);
|
|
188
|
+
await run(`CREATE TABLE IF NOT EXISTS billing_disputes (
|
|
189
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
190
|
+
gateway_id VARCHAR(255) UNIQUE,
|
|
191
|
+
payment_gateway_id VARCHAR(255),
|
|
192
|
+
provider VARCHAR(255),
|
|
193
|
+
status VARCHAR(255),
|
|
194
|
+
reason VARCHAR(255),
|
|
195
|
+
amount BIGINT,
|
|
196
|
+
currency VARCHAR(3),
|
|
197
|
+
evidence_due_by ${t.timestamp},
|
|
198
|
+
outcome VARCHAR(255),
|
|
199
|
+
opened_at ${t.timestamp},
|
|
200
|
+
closed_at ${t.timestamp},
|
|
201
|
+
payload ${t.json},
|
|
202
|
+
created_at ${t.timestamp} NOT NULL,
|
|
203
|
+
updated_at ${t.timestamp} NOT NULL
|
|
204
|
+
)`);
|
|
205
|
+
// The deadline read — open disputes ordered by the window closing soonest — scans exactly
|
|
206
|
+
// these two columns, in this order.
|
|
207
|
+
// Added in 0.4.0. A NEW table, not a new column, which is why it needs nothing from the ALTER
|
|
208
|
+
// block below: `CREATE TABLE IF NOT EXISTS` carries a table to an existing install exactly as
|
|
209
|
+
// well as to a fresh one. Only columns added to a table that already shipped have the upgrade
|
|
210
|
+
// problem, and only they belong down there.
|
|
211
|
+
await run(`CREATE TABLE IF NOT EXISTS billing_audit_events (
|
|
212
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
213
|
+
action VARCHAR(255),
|
|
214
|
+
actor VARCHAR(255),
|
|
215
|
+
provider VARCHAR(255),
|
|
216
|
+
subject_type VARCHAR(255),
|
|
217
|
+
subject_id VARCHAR(255),
|
|
218
|
+
amount BIGINT,
|
|
219
|
+
currency VARCHAR(3),
|
|
220
|
+
message TEXT,
|
|
221
|
+
metadata ${t.json},
|
|
222
|
+
created_at ${t.timestamp} NOT NULL,
|
|
223
|
+
updated_at ${t.timestamp} NOT NULL
|
|
224
|
+
)`);
|
|
225
|
+
await run(`CREATE TABLE IF NOT EXISTS billing_usage_events (
|
|
226
|
+
id VARCHAR(36) PRIMARY KEY,
|
|
227
|
+
subscription_id VARCHAR(255),
|
|
228
|
+
customer_id VARCHAR(255),
|
|
229
|
+
meter VARCHAR(255),
|
|
230
|
+
quantity INTEGER DEFAULT 1,
|
|
231
|
+
metadata ${t.json},
|
|
232
|
+
recorded_at ${t.timestamp} NOT NULL,
|
|
233
|
+
updated_at ${t.timestamp} NOT NULL
|
|
234
|
+
)`);
|
|
235
|
+
// ── Columns added after their table shipped ────────────────────────────────────────────
|
|
236
|
+
//
|
|
237
|
+
// Placed here, BEFORE the indexes, and the order is load-bearing: an install on an older
|
|
238
|
+
// version has `billing_payments` without `external_reference`, so `CREATE TABLE IF NOT
|
|
239
|
+
// EXISTS` skips the table and indexing a column that does not exist yet fails the whole
|
|
240
|
+
// call. The integration suite caught exactly that, against a real Postgres, on the first
|
|
241
|
+
// run after this function was written.
|
|
242
|
+
//
|
|
243
|
+
// These columns are already declared in the CREATE statements above, so on a FRESH database
|
|
244
|
+
// every statement here is a no-op. They are repeated as ALTERs for the install that already
|
|
245
|
+
// has the table — which is what makes this an upgrade path rather than a first-install
|
|
246
|
+
// convenience.
|
|
247
|
+
//
|
|
248
|
+
// Anything added to a CREATE statement from now on belongs here too, on the same day. A
|
|
249
|
+
// column that exists only above works on every machine that has not shipped yet.
|
|
250
|
+
// 0.3.0 — the app's own reference on a payment, and the normalized event on the ledger row.
|
|
251
|
+
await addColumn(db, dialect, 'billing_payments', 'external_reference', 'VARCHAR(255)');
|
|
252
|
+
await addColumn(db, dialect, 'billing_webhook_events', 'normalized', t.json);
|
|
253
|
+
// 0.4.0 — how much of a payment has been refunded, in the same integer minor units as
|
|
254
|
+
// `amount`. A PARTIAL refund had nowhere to be recorded before this, so the only two
|
|
255
|
+
// options were to overwrite the status with `refunded` (writing off the whole charge) or to
|
|
256
|
+
// drop the event — and the library dropped it, leaving revenue overstated forever.
|
|
257
|
+
// `BIGINT`, matching `amount`, so net revenue is one subtraction and never a division.
|
|
258
|
+
await addColumn(db, dialect, 'billing_payments', 'refunded_amount', 'BIGINT');
|
|
259
|
+
// ── Indexes ───────────────────────────────────────────────────────────────────────────
|
|
260
|
+
//
|
|
261
|
+
// Last, and that is load-bearing twice over. Every table above has to exist, and so does
|
|
262
|
+
// every column added by the block above — `billing_payments_external_reference_idx` names
|
|
263
|
+
// a column an older install does not have until that ALTER runs. Getting this wrong fails
|
|
264
|
+
// the whole call and leaves the schema half-built on every boot; the integration suite
|
|
265
|
+
// caught both orderings against a real Postgres.
|
|
266
|
+
await createIndex(db, dialect, 'billing_customers_owner_idx', 'billing_customers', 'owner_type, owner_id');
|
|
267
|
+
await createIndex(db, dialect, 'billing_subscriptions_customer_idx', 'billing_subscriptions', 'customer_id');
|
|
268
|
+
await createIndex(db, dialect, 'billing_payments_customer_idx', 'billing_payments', 'customer_id');
|
|
269
|
+
await createIndex(db, dialect, 'billing_payments_subscription_idx', 'billing_payments', 'subscription_id');
|
|
270
|
+
await createIndex(db, dialect, 'billing_payments_external_reference_idx', 'billing_payments', 'external_reference');
|
|
271
|
+
await createIndex(db, dialect, 'billing_disputes_payment_idx', 'billing_disputes', 'payment_gateway_id');
|
|
272
|
+
await createIndex(db, dialect, 'billing_disputes_deadline_idx', 'billing_disputes', 'status, evidence_due_by');
|
|
273
|
+
// The two ways the audit trail is read: "what did the endpoint reject in the last day"
|
|
274
|
+
// (the health check) and "what has anyone done to THIS payment" (the timeline).
|
|
275
|
+
await createIndex(db, dialect, 'billing_audit_events_action_idx', 'billing_audit_events', 'action, created_at');
|
|
276
|
+
await createIndex(db, dialect, 'billing_audit_events_subject_idx', 'billing_audit_events', 'subject_type, subject_id');
|
|
277
|
+
await createIndex(db, dialect, 'billing_usage_subscription_meter_idx', 'billing_usage_events', 'subscription_id, meter');
|
|
278
|
+
await createIndex(db, dialect, 'billing_usage_customer_meter_idx', 'billing_usage_events', 'customer_id, meter');
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Callbacks that forget a store's memoized "the schema exists" answer.
|
|
282
|
+
*
|
|
283
|
+
* A `LucidBillingStore` creates the tables once and caches the promise, so after
|
|
284
|
+
* {@link dropBillingTables} the store still believes the tables are there and every
|
|
285
|
+
* subsequent query fails with `relation ... does not exist`. The store registers here so the
|
|
286
|
+
* drop can tell it otherwise — a Set of callbacks rather than an import, because the store
|
|
287
|
+
* already imports this module and the reverse would be a cycle.
|
|
288
|
+
*/
|
|
289
|
+
const schemaCacheResets = new Set();
|
|
290
|
+
/**
|
|
291
|
+
* Register a "forget the cached schema" callback. Returns the unregister function.
|
|
292
|
+
* Internal — {@link LucidBillingStore} is the only caller.
|
|
293
|
+
*/
|
|
294
|
+
export function registerBillingSchemaCache(reset) {
|
|
295
|
+
schemaCacheResets.add(reset);
|
|
296
|
+
return () => {
|
|
297
|
+
schemaCacheResets.delete(reset);
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Drop every billing table, newest-dependency-first.
|
|
302
|
+
*
|
|
303
|
+
* Only for a migration's `down()` and for tests. There is no auto-drop and there will not be
|
|
304
|
+
* one: a library that can delete a payments table on a config typo is a library that will.
|
|
305
|
+
*
|
|
306
|
+
* Every live store is told to forget its cached schema afterwards, so a store built before
|
|
307
|
+
* the drop re-creates the tables on its next query instead of querying tables that are gone.
|
|
308
|
+
*/
|
|
309
|
+
export async function dropBillingTables(db) {
|
|
310
|
+
for (const table of [...BILLING_TABLES].reverse()) {
|
|
311
|
+
await db.rawQuery(`DROP TABLE IF EXISTS ${table}`);
|
|
312
|
+
}
|
|
313
|
+
for (const reset of schemaCacheResets)
|
|
314
|
+
reset();
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Empty every billing table, keeping the schema.
|
|
318
|
+
*
|
|
319
|
+
* What a test suite actually needs between groups, and the reason it is here rather than in
|
|
320
|
+
* every app: dropping the tables invalidates the store (see above), while leaving the ROWS
|
|
321
|
+
* in place means one test's webhook ledger deduplicates the next test's event — the
|
|
322
|
+
* library's own idempotency working perfectly, against the suite. The app that found this
|
|
323
|
+
* hand-wrote it with a `to_regclass` guard; this is that, dialect-agnostically.
|
|
324
|
+
*
|
|
325
|
+
* Order is the reverse of creation, so a foreign key never blocks a delete. `DELETE FROM`
|
|
326
|
+
* rather than `TRUNCATE`: it is the one statement every dialect here spells the same way,
|
|
327
|
+
* and these tables are test-sized.
|
|
328
|
+
*/
|
|
329
|
+
export async function truncateBillingTables(db) {
|
|
330
|
+
for (const table of [...BILLING_TABLES].reverse()) {
|
|
331
|
+
// A table that was never created is not an error to a suite that only wants it empty —
|
|
332
|
+
// and asking `information_schema` per table per group costs more than the delete does.
|
|
333
|
+
try {
|
|
334
|
+
await db.rawQuery(`DELETE FROM ${table}`);
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
if (!isMissingTableError(error))
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/** Whether a driver error is "that table does not exist" rather than a real failure. */
|
|
343
|
+
function isMissingTableError(error) {
|
|
344
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
345
|
+
return (/does not exist|no such table|doesn't exist|Unknown table|Invalid object name/i.test(message) ||
|
|
346
|
+
// Postgres `undefined_table`, MySQL `ER_NO_SUCH_TABLE`.
|
|
347
|
+
error?.code === '42P01' ||
|
|
348
|
+
error?.errno === 1146);
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/billing/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,20 +5,19 @@ import type { WebhookProcessor } from './webhook_processor.js';
|
|
|
5
5
|
*
|
|
6
6
|
* - `durable` — the event is dispatched as a fire-and-forget durable workflow run
|
|
7
7
|
* (`@adonis-agora/durable`), so processing survives crashes and retries deterministically.
|
|
8
|
-
* - `queue` — the event is dispatched as an `@adonisjs/queue` job (`queueDispatch`).
|
|
9
8
|
* - `in-process` — the event runs inline (awaited); on failure it is retried with
|
|
10
9
|
* exponential backoff in the background.
|
|
11
10
|
* - `auto` — uses `durable` when its provider is registered, else `queue` when the queue
|
|
12
11
|
* provider is, else `in-process`.
|
|
13
12
|
*/
|
|
14
|
-
export type WebhookDispatchMode = 'durable' | '
|
|
13
|
+
export type WebhookDispatchMode = 'durable' | 'in-process' | 'auto';
|
|
15
14
|
export interface WebhookDispatcherOptions {
|
|
16
15
|
processor: WebhookProcessor;
|
|
17
16
|
/**
|
|
18
17
|
* How webhooks are processed. `'auto'` (default) lazily resolves
|
|
19
18
|
* `@adonis-agora/durable` and uses it when installed AND available (the app has the
|
|
20
19
|
* durable provider registered), else `@adonisjs/queue` when available, else
|
|
21
|
-
* `in-process`. `'durable'
|
|
20
|
+
* `in-process`. `'durable'` requires its backend (throws when missing).
|
|
22
21
|
* `'in-process'` never touches either.
|
|
23
22
|
*/
|
|
24
23
|
mode?: WebhookDispatchMode;
|
|
@@ -41,21 +40,54 @@ export interface WebhookDispatcherOptions {
|
|
|
41
40
|
*/
|
|
42
41
|
durableAvailable?: () => Promise<boolean>;
|
|
43
42
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
43
|
+
* Resolve the app's durable {@link DurableEngineLike}. The provider wires this from the
|
|
44
|
+
* container; without it there is no engine to register the workflow ON, and durable mode
|
|
45
|
+
* cannot work — see {@link DurableEngineLike} for why registration is not optional.
|
|
47
46
|
*/
|
|
48
|
-
|
|
49
|
-
jobId?: string;
|
|
50
|
-
}>;
|
|
47
|
+
durableEngine?: () => Promise<DurableEngineLike>;
|
|
51
48
|
}
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The slice of `@adonis-agora/durable`'s `WorkflowEngine` this needs.
|
|
51
|
+
*
|
|
52
|
+
* Both halves are load-bearing. `start` alone is not enough: the engine looks the workflow
|
|
53
|
+
* up by NAME in its registry and throws `workflow … is not registered` for anything it has
|
|
54
|
+
* not been told about, so the webhook workflow has to be REGISTERED before the first
|
|
55
|
+
* dispatch. This class used to build an anonymous `class extends BaseWorkflow` and call its
|
|
56
|
+
* static `dispatch`, which registers nothing and has no `static workflow = { name }` — so
|
|
57
|
+
* every delivery threw `workflow class PaymentsWebhookWorkflow has no registered name`, was
|
|
58
|
+
* collected as a failed event, and the route answered 500. In an app with durable installed
|
|
59
|
+
* — which is exactly what `'auto'` selects — that was every webhook, forever.
|
|
60
|
+
*/
|
|
61
|
+
export interface DurableEngineLike {
|
|
62
|
+
register(name: string, version: string, fn: (ctx: unknown, input: never) => Promise<void>): void;
|
|
63
|
+
start(name: string, input: unknown, runId: string): Promise<unknown>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The durable workflow's identity. Stable and explicit: the engine registry is keyed by
|
|
67
|
+
* name, an in-flight run records the name+version it started on, and a name that changed
|
|
68
|
+
* between deploys would strand runs mid-flight.
|
|
69
|
+
*/
|
|
70
|
+
export declare const WEBHOOK_WORKFLOW_NAME = "payments-webhook";
|
|
71
|
+
export declare const WEBHOOK_WORKFLOW_VERSION = "1";
|
|
72
|
+
/** One event of a delivery that did not get through, and why. */
|
|
73
|
+
export interface WebhookDeliveryFailure {
|
|
74
|
+
event: WebhookEvent;
|
|
75
|
+
error: Error;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* What became of one webhook DELIVERY — which may carry several events (Adyen's
|
|
79
|
+
* `notificationItems`, Efí's `pix` array).
|
|
80
|
+
*
|
|
81
|
+
* The route turns this into the HTTP status: any failure at all means a non-2xx, because
|
|
82
|
+
* a 2xx is a promise to the gateway that it never has to send this again.
|
|
83
|
+
*/
|
|
84
|
+
export interface WebhookDeliveryResult {
|
|
85
|
+
/** How many events the delivery carried. */
|
|
86
|
+
total: number;
|
|
87
|
+
/** How many were processed (in-process) or accepted by durable/queue. */
|
|
88
|
+
dispatched: number;
|
|
89
|
+
/** The ones that threw, in delivery order. Empty on a clean delivery. */
|
|
90
|
+
failures: WebhookDeliveryFailure[];
|
|
59
91
|
}
|
|
60
92
|
/**
|
|
61
93
|
* Runs webhook events through the {@link WebhookProcessor}. In `auto` mode (the default)
|
|
@@ -73,7 +105,54 @@ export declare class WebhookDispatcher {
|
|
|
73
105
|
dispatch(event: WebhookEvent): Promise<{
|
|
74
106
|
runId?: string;
|
|
75
107
|
}>;
|
|
108
|
+
/**
|
|
109
|
+
* Dispatch a whole DELIVERY: one event, or the several a batched envelope carried.
|
|
110
|
+
*
|
|
111
|
+
* **The loop lives here, not in the route.** Everything it has to get right — the ledger
|
|
112
|
+
* claim, the retry policy, which backend runs the work — is already this class's job; the
|
|
113
|
+
* route's job is to turn the outcome into a status code. It also makes the batch testable
|
|
114
|
+
* without standing up an HTTP context.
|
|
115
|
+
*
|
|
116
|
+
* **Sequential, deliberately.** Two events in one envelope routinely touch the same row
|
|
117
|
+
* (an Adyen AUTHORISATION and its CAPTURE; a Pix and its devolução), and running them
|
|
118
|
+
* concurrently would race the ledger claim and the payment upsert against each other,
|
|
119
|
+
* which is how you get a `paid` row overwritten by the `pending` that preceded it. The
|
|
120
|
+
* gateway's order is the order the money moved in, so it is the order they run in.
|
|
121
|
+
*
|
|
122
|
+
* **One failure does not cancel its siblings.** Event 2 of 4 throwing says nothing about
|
|
123
|
+
* events 3 and 4 — they are different payments, and refusing to process them because a
|
|
124
|
+
* neighbour failed loses money for a reason unrelated to them. So every event is attempted
|
|
125
|
+
* and the failures are COLLECTED. The caller is then responsible for the other half:
|
|
126
|
+
* reporting a non-2xx so the gateway redelivers, since a 200 tells it the failed one is
|
|
127
|
+
* done and it is never sent again. The redelivery re-runs only the failed event — the
|
|
128
|
+
* ledger claims a `failed` row again, while the ones that succeeded answer `null` and skip.
|
|
129
|
+
*/
|
|
130
|
+
dispatchAll(events: WebhookEvent | WebhookEvent[]): Promise<WebhookDeliveryResult>;
|
|
76
131
|
/** Whether durable-backed processing is active. */
|
|
77
132
|
get mode(): WebhookDispatchMode;
|
|
133
|
+
/**
|
|
134
|
+
* Resolve when nothing this dispatcher accepted is still being processed.
|
|
135
|
+
*
|
|
136
|
+
* **The asymmetry this fixes.** In `in-process` mode `dispatchAll` resolves when the event
|
|
137
|
+
* has been PROCESSED, so a test can assert straight after it. In `durable` mode it
|
|
138
|
+
* resolves when the event has been ACCEPTED — the run executes out of band — so the same
|
|
139
|
+
* assertion races the workflow. Every app that pairs `billing.dispatcher` with durable
|
|
140
|
+
* writes the same polling `waitFor(() => ...)` helper to paper over it, and then cannot
|
|
141
|
+
* write a NEGATIVE assertion at all: "nothing was granted" against a poll is a timed
|
|
142
|
+
* sleep, which is slow when it passes and lies when the machine is loaded.
|
|
143
|
+
*
|
|
144
|
+
* `await dispatcher.flushWebhooks()` is that assertion made possible: after it, an
|
|
145
|
+
* unchanged row means the webhook genuinely changed nothing.
|
|
146
|
+
*
|
|
147
|
+
* It waits for the background in-process RETRIES too, which have exactly the same problem
|
|
148
|
+
* — the first attempt is awaited and the retries are not.
|
|
149
|
+
*
|
|
150
|
+
* Only reaches work this process accepted. A worker-role deployment runs the events in
|
|
151
|
+
* another process, and no in-process await can see those; the timeout says so rather than
|
|
152
|
+
* hanging.
|
|
153
|
+
*/
|
|
154
|
+
flushWebhooks(options?: {
|
|
155
|
+
timeoutMs?: number;
|
|
156
|
+
}): Promise<void>;
|
|
78
157
|
}
|
|
79
158
|
//# sourceMappingURL=webhook_dispatcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook_dispatcher.d.ts","sourceRoot":"","sources":["../../../src/billing/webhook_dispatcher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhook_dispatcher.d.ts","sourceRoot":"","sources":["../../../src/billing/webhook_dispatcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,YAAY,GAAG,MAAM,CAAC;AAEpE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,gBAAgB,CAAC;IAC5B;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B;;;OAGG;IACH,OAAO,CAAC,EAAE;QACR,mDAAmD;QACnD,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,qCAAqC;QACrC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,uCAAuC;QACvC,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAClD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACjG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtE;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AACxD,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,iEAAiE;AACjE,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;CACd;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,QAAQ,EAAE,sBAAsB,EAAE,CAAC;CACpC;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;;gBAqBhB,OAAO,EAAE,wBAAwB;IAU7C;;;OAGG;IACG,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAKhE;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA+CxF,mDAAmD;IACnD,IAAI,IAAI,IAAI,mBAAmB,CAE9B;IA8GD;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,aAAa,CAAC,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAmBzE"}
|