@adonis-agora/payments 0.2.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 +27 -0
- package/dist/commands/main.d.ts +16 -0
- package/dist/commands/main.d.ts.map +1 -0
- package/dist/commands/main.js +17 -0
- package/dist/commands/main.js.map +1 -0
- package/dist/commands/make_billable.d.ts +16 -0
- package/dist/commands/make_billable.d.ts.map +1 -0
- package/dist/commands/make_billable.js +30 -0
- package/dist/commands/make_billable.js.map +1 -0
- package/dist/commands/make_webhook_handler.d.ts +20 -0
- package/dist/commands/make_webhook_handler.d.ts.map +1 -0
- package/dist/commands/make_webhook_handler.js +34 -0
- package/dist/commands/make_webhook_handler.js.map +1 -0
- package/dist/commands/payments_sync.d.ts +20 -0
- package/dist/commands/payments_sync.d.ts.map +1 -0
- package/dist/commands/payments_sync.js +95 -0
- package/dist/commands/payments_sync.js.map +1 -0
- package/dist/commands/payments_webhook.d.ts +19 -0
- package/dist/commands/payments_webhook.d.ts.map +1 -0
- package/dist/commands/payments_webhook.js +123 -0
- package/dist/commands/payments_webhook.js.map +1 -0
- package/dist/configure.d.ts +12 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +34 -0
- package/dist/configure.js.map +1 -0
- package/dist/providers/payments_provider.d.ts +15 -0
- package/dist/providers/payments_provider.d.ts.map +1 -0
- package/dist/providers/payments_provider.js +155 -0
- package/dist/providers/payments_provider.js.map +1 -0
- package/dist/src/billing/billing_overview.d.ts +26 -0
- package/dist/src/billing/billing_overview.d.ts.map +1 -0
- package/dist/src/billing/billing_overview.js +27 -0
- package/dist/src/billing/billing_overview.js.map +1 -0
- package/dist/src/billing/billing_store.d.ts +73 -0
- package/dist/src/billing/billing_store.d.ts.map +1 -0
- package/dist/src/billing/billing_store.js +2 -0
- package/dist/src/billing/billing_store.js.map +1 -0
- package/dist/src/billing/index.d.ts +8 -0
- package/dist/src/billing/index.d.ts.map +1 -0
- package/dist/src/billing/index.js +6 -0
- package/dist/src/billing/index.js.map +1 -0
- package/dist/src/billing/lucid_billing_store.d.ts +84 -0
- package/dist/src/billing/lucid_billing_store.d.ts.map +1 -0
- package/dist/src/billing/lucid_billing_store.js +143 -0
- package/dist/src/billing/lucid_billing_store.js.map +1 -0
- package/dist/src/billing/metered_bill.d.ts +60 -0
- package/dist/src/billing/metered_bill.d.ts.map +1 -0
- package/dist/src/billing/metered_bill.js +46 -0
- package/dist/src/billing/metered_bill.js.map +1 -0
- package/dist/src/billing/mixins/index.d.ts +6 -0
- package/dist/src/billing/mixins/index.d.ts.map +1 -0
- package/dist/src/billing/mixins/index.js +6 -0
- package/dist/src/billing/mixins/index.js.map +1 -0
- package/dist/src/billing/mixins/with_billable.d.ts +23 -0
- package/dist/src/billing/mixins/with_billable.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_billable.js +33 -0
- package/dist/src/billing/mixins/with_billable.js.map +1 -0
- package/dist/src/billing/mixins/with_payment.d.ts +45 -0
- package/dist/src/billing/mixins/with_payment.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_payment.js +94 -0
- package/dist/src/billing/mixins/with_payment.js.map +1 -0
- package/dist/src/billing/mixins/with_subscription.d.ts +50 -0
- package/dist/src/billing/mixins/with_subscription.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_subscription.js +95 -0
- package/dist/src/billing/mixins/with_subscription.js.map +1 -0
- package/dist/src/billing/mixins/with_usage_event.d.ts +22 -0
- package/dist/src/billing/mixins/with_usage_event.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_usage_event.js +39 -0
- package/dist/src/billing/mixins/with_usage_event.js.map +1 -0
- package/dist/src/billing/mixins/with_webhook_event.d.ts +21 -0
- package/dist/src/billing/mixins/with_webhook_event.d.ts.map +1 -0
- package/dist/src/billing/mixins/with_webhook_event.js +42 -0
- package/dist/src/billing/mixins/with_webhook_event.js.map +1 -0
- package/dist/src/billing/webhook_dispatcher.d.ts +79 -0
- package/dist/src/billing/webhook_dispatcher.d.ts.map +1 -0
- package/dist/src/billing/webhook_dispatcher.js +125 -0
- package/dist/src/billing/webhook_dispatcher.js.map +1 -0
- package/dist/src/billing/webhook_events.d.ts +49 -0
- package/dist/src/billing/webhook_events.d.ts.map +1 -0
- package/dist/src/billing/webhook_events.js +44 -0
- package/dist/src/billing/webhook_events.js.map +1 -0
- package/dist/src/billing/webhook_processor.d.ts +39 -0
- package/dist/src/billing/webhook_processor.d.ts.map +1 -0
- package/dist/src/billing/webhook_processor.js +209 -0
- package/dist/src/billing/webhook_processor.js.map +1 -0
- package/dist/src/define_config.d.ts +251 -0
- package/dist/src/define_config.d.ts.map +1 -0
- package/dist/src/define_config.js +67 -0
- package/dist/src/define_config.js.map +1 -0
- package/dist/src/diagnostics.d.ts +155 -0
- package/dist/src/diagnostics.d.ts.map +1 -0
- package/dist/src/diagnostics.js +153 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/driver.d.ts +222 -0
- package/dist/src/driver.d.ts.map +1 -0
- package/dist/src/driver.js +2 -0
- package/dist/src/driver.js.map +1 -0
- package/dist/src/drivers/abacate.d.ts +35 -0
- package/dist/src/drivers/abacate.d.ts.map +1 -0
- package/dist/src/drivers/abacate.js +381 -0
- package/dist/src/drivers/abacate.js.map +1 -0
- package/dist/src/drivers/asaas.d.ts +36 -0
- package/dist/src/drivers/asaas.d.ts.map +1 -0
- package/dist/src/drivers/asaas.js +455 -0
- package/dist/src/drivers/asaas.js.map +1 -0
- package/dist/src/drivers/stripe.d.ts +36 -0
- package/dist/src/drivers/stripe.d.ts.map +1 -0
- package/dist/src/drivers/stripe.js +392 -0
- package/dist/src/drivers/stripe.js.map +1 -0
- package/dist/src/drivers/woovi.d.ts +49 -0
- package/dist/src/drivers/woovi.d.ts.map +1 -0
- package/dist/src/drivers/woovi.js +339 -0
- package/dist/src/drivers/woovi.js.map +1 -0
- package/dist/src/ensure_customer.d.ts +23 -0
- package/dist/src/ensure_customer.d.ts.map +1 -0
- package/dist/src/ensure_customer.js +25 -0
- package/dist/src/ensure_customer.js.map +1 -0
- package/dist/src/hooks/webhook_handlers.d.ts +50 -0
- package/dist/src/hooks/webhook_handlers.d.ts.map +1 -0
- package/dist/src/hooks/webhook_handlers.js +53 -0
- package/dist/src/hooks/webhook_handlers.js.map +1 -0
- package/dist/src/http.d.ts +24 -0
- package/dist/src/http.d.ts.map +1 -0
- package/dist/src/http.js +52 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/index.d.ts +29 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +15 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/invoice/drivers/asaas.d.ts +23 -0
- package/dist/src/invoice/drivers/asaas.d.ts.map +1 -0
- package/dist/src/invoice/drivers/asaas.js +85 -0
- package/dist/src/invoice/drivers/asaas.js.map +1 -0
- package/dist/src/invoice/drivers/enotas.d.ts +23 -0
- package/dist/src/invoice/drivers/enotas.d.ts.map +1 -0
- package/dist/src/invoice/drivers/enotas.js +83 -0
- package/dist/src/invoice/drivers/enotas.js.map +1 -0
- package/dist/src/invoice/drivers/focus.d.ts +23 -0
- package/dist/src/invoice/drivers/focus.d.ts.map +1 -0
- package/dist/src/invoice/drivers/focus.js +90 -0
- package/dist/src/invoice/drivers/focus.js.map +1 -0
- package/dist/src/invoice/drivers/plugnotas.d.ts +22 -0
- package/dist/src/invoice/drivers/plugnotas.d.ts.map +1 -0
- package/dist/src/invoice/drivers/plugnotas.js +81 -0
- package/dist/src/invoice/drivers/plugnotas.js.map +1 -0
- package/dist/src/invoice/drivers/tecnospeed.d.ts +29 -0
- package/dist/src/invoice/drivers/tecnospeed.d.ts.map +1 -0
- package/dist/src/invoice/drivers/tecnospeed.js +87 -0
- package/dist/src/invoice/drivers/tecnospeed.js.map +1 -0
- package/dist/src/invoice/emit_invoice.d.ts +45 -0
- package/dist/src/invoice/emit_invoice.d.ts.map +1 -0
- package/dist/src/invoice/emit_invoice.js +72 -0
- package/dist/src/invoice/emit_invoice.js.map +1 -0
- package/dist/src/invoice/invoice_manager.d.ts +12 -0
- package/dist/src/invoice/invoice_manager.d.ts.map +1 -0
- package/dist/src/invoice/invoice_manager.js +35 -0
- package/dist/src/invoice/invoice_manager.js.map +1 -0
- package/dist/src/invoice/invoice_provider.d.ts +48 -0
- package/dist/src/invoice/invoice_provider.d.ts.map +1 -0
- package/dist/src/invoice/invoice_provider.js +2 -0
- package/dist/src/invoice/invoice_provider.js.map +1 -0
- package/dist/src/money.d.ts +11 -0
- package/dist/src/money.d.ts.map +1 -0
- package/dist/src/money.js +14 -0
- package/dist/src/money.js.map +1 -0
- package/dist/src/payments_manager.d.ts +40 -0
- package/dist/src/payments_manager.d.ts.map +1 -0
- package/dist/src/payments_manager.js +94 -0
- package/dist/src/payments_manager.js.map +1 -0
- package/dist/src/services/main.d.ts +6 -0
- package/dist/src/services/main.d.ts.map +1 -0
- package/dist/src/services/main.js +17 -0
- package/dist/src/services/main.js.map +1 -0
- package/dist/src/telescope/index.d.ts +3 -0
- package/dist/src/telescope/index.d.ts.map +1 -0
- package/dist/src/telescope/index.js +2 -0
- package/dist/src/telescope/index.js.map +1 -0
- package/dist/src/telescope/payments_watcher.d.ts +27 -0
- package/dist/src/telescope/payments_watcher.d.ts.map +1 -0
- package/dist/src/telescope/payments_watcher.js +58 -0
- package/dist/src/telescope/payments_watcher.js.map +1 -0
- package/dist/src/telescope/telescope-sdk.d.ts +21 -0
- package/dist/src/telescope/telescope-sdk.d.ts.map +1 -0
- package/dist/src/telescope/telescope-sdk.js +11 -0
- package/dist/src/telescope/telescope-sdk.js.map +1 -0
- package/dist/src/testing/fake_payments_driver.d.ts +64 -0
- package/dist/src/testing/fake_payments_driver.d.ts.map +1 -0
- package/dist/src/testing/fake_payments_driver.js +171 -0
- package/dist/src/testing/fake_payments_driver.js.map +1 -0
- package/dist/src/testing/in_memory_billing_store.d.ts +110 -0
- package/dist/src/testing/in_memory_billing_store.d.ts.map +1 -0
- package/dist/src/testing/in_memory_billing_store.js +136 -0
- package/dist/src/testing/in_memory_billing_store.js.map +1 -0
- package/dist/src/testing/index.d.ts +6 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +4 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/mutable_clock.d.ts +12 -0
- package/dist/src/testing/mutable_clock.d.ts.map +1 -0
- package/dist/src/testing/mutable_clock.js +20 -0
- package/dist/src/testing/mutable_clock.js.map +1 -0
- package/dist/src/types.d.ts +178 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/webhook_handlers.d.ts +76 -0
- package/dist/src/webhook_handlers.d.ts.map +1 -0
- package/dist/src/webhook_handlers.js +128 -0
- package/dist/src/webhook_handlers.js.map +1 -0
- package/dist/src/webhook_security.d.ts +29 -0
- package/dist/src/webhook_security.d.ts.map +1 -0
- package/dist/src/webhook_security.js +53 -0
- package/dist/src/webhook_security.js.map +1 -0
- package/dist/stubs/config/payments.stub +101 -0
- package/dist/stubs/database/migrations/create_billing_tables.stub +90 -0
- package/dist/stubs/main.d.ts +9 -0
- package/dist/stubs/main.d.ts.map +1 -0
- package/dist/stubs/main.js +16 -0
- package/dist/stubs/main.js.map +1 -0
- package/dist/stubs/make/billable/main.stub +28 -0
- package/dist/stubs/make/webhook_handler/main.stub +29 -0
- package/package.json +150 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { WebhookEvent } from '../types.js';
|
|
2
|
+
import type { WebhookProcessor } from './webhook_processor.js';
|
|
3
|
+
/**
|
|
4
|
+
* Strategy used to run a webhook event through the {@link WebhookProcessor}.
|
|
5
|
+
*
|
|
6
|
+
* - `durable` — the event is dispatched as a fire-and-forget durable workflow run
|
|
7
|
+
* (`@adonis-agora/durable`), so processing survives crashes and retries deterministically.
|
|
8
|
+
* - `queue` — the event is dispatched as an `@adonisjs/queue` job (`queueDispatch`).
|
|
9
|
+
* - `in-process` — the event runs inline (awaited); on failure it is retried with
|
|
10
|
+
* exponential backoff in the background.
|
|
11
|
+
* - `auto` — uses `durable` when its provider is registered, else `queue` when the queue
|
|
12
|
+
* provider is, else `in-process`.
|
|
13
|
+
*/
|
|
14
|
+
export type WebhookDispatchMode = 'durable' | 'queue' | 'in-process' | 'auto';
|
|
15
|
+
export interface WebhookDispatcherOptions {
|
|
16
|
+
processor: WebhookProcessor;
|
|
17
|
+
/**
|
|
18
|
+
* How webhooks are processed. `'auto'` (default) lazily resolves
|
|
19
|
+
* `@adonis-agora/durable` and uses it when installed AND available (the app has the
|
|
20
|
+
* durable provider registered), else `@adonisjs/queue` when available, else
|
|
21
|
+
* `in-process`. `'durable'`/`'queue'` require their backend (throw when missing).
|
|
22
|
+
* `'in-process'` never touches either.
|
|
23
|
+
*/
|
|
24
|
+
mode?: WebhookDispatchMode;
|
|
25
|
+
/**
|
|
26
|
+
* In-process retry settings (exponential backoff). Ignored when durable/queue processes
|
|
27
|
+
* the event, which rely on their own retries.
|
|
28
|
+
*/
|
|
29
|
+
retries?: {
|
|
30
|
+
/** Max attempts including the first. Default 5. */
|
|
31
|
+
max?: number;
|
|
32
|
+
/** Base delay in ms. Default 500. */
|
|
33
|
+
baseDelayMs?: number;
|
|
34
|
+
/** Max delay in ms. Default 30_000. */
|
|
35
|
+
maxDelayMs?: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Optional availability check used in `auto` mode: resolves to `true` only when durable
|
|
39
|
+
* is installed AND its engine is resolvable in the app (provider registered). Defaults
|
|
40
|
+
* to "installed" only. The provider injects the real check.
|
|
41
|
+
*/
|
|
42
|
+
durableAvailable?: () => Promise<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* Dispatch a webhook event as an `@adonisjs/queue` job. The provider wires this (it owns
|
|
45
|
+
* the job class, its container binding and the queue-manager registration). Absent =
|
|
46
|
+
* queue mode is unavailable.
|
|
47
|
+
*/
|
|
48
|
+
queueDispatch?: (event: WebhookEvent) => Promise<{
|
|
49
|
+
jobId?: string;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
/** The minimal durable surface we use: a workflow class with a static `dispatch`. */
|
|
53
|
+
export interface DurableWorkflowLike {
|
|
54
|
+
dispatch(input: {
|
|
55
|
+
event: WebhookEvent;
|
|
56
|
+
}): Promise<{
|
|
57
|
+
runId: string;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Runs webhook events through the {@link WebhookProcessor}. In `auto` mode (the default)
|
|
62
|
+
* it tries to import `@adonis-agora/durable` lazily — only when the app has it installed
|
|
63
|
+
* do webhooks run as durable workflows; otherwise they fall back to an in-process
|
|
64
|
+
* dispatcher with exponential-backoff retries.
|
|
65
|
+
*/
|
|
66
|
+
export declare class WebhookDispatcher {
|
|
67
|
+
#private;
|
|
68
|
+
constructor(options: WebhookDispatcherOptions);
|
|
69
|
+
/**
|
|
70
|
+
* Dispatch a webhook event for processing. Resolves once the event is accepted (durable)
|
|
71
|
+
* or fully processed (in-process).
|
|
72
|
+
*/
|
|
73
|
+
dispatch(event: WebhookEvent): Promise<{
|
|
74
|
+
runId?: string;
|
|
75
|
+
}>;
|
|
76
|
+
/** Whether durable-backed processing is active. */
|
|
77
|
+
get mode(): WebhookDispatchMode;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=webhook_dispatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook_dispatcher.d.ts","sourceRoot":"","sources":["../../../src/billing/webhook_dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;;;;;;;GAUG;AACH,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,MAAM,CAAC;AAE9E,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,CAAC,KAAK,EAAE,YAAY,KAAK,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtE;AAED,qFAAqF;AACrF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,YAAY,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtE;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;;gBAUhB,OAAO,EAAE,wBAAwB;IAS7C;;;OAGG;IACG,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAUhE,mDAAmD;IACnD,IAAI,IAAI,IAAI,mBAAmB,CAE9B;CAkFF"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs webhook events through the {@link WebhookProcessor}. In `auto` mode (the default)
|
|
3
|
+
* it tries to import `@adonis-agora/durable` lazily — only when the app has it installed
|
|
4
|
+
* do webhooks run as durable workflows; otherwise they fall back to an in-process
|
|
5
|
+
* dispatcher with exponential-backoff retries.
|
|
6
|
+
*/
|
|
7
|
+
export class WebhookDispatcher {
|
|
8
|
+
#processor;
|
|
9
|
+
#mode;
|
|
10
|
+
#maxAttempts;
|
|
11
|
+
#baseDelayMs;
|
|
12
|
+
#maxDelayMs;
|
|
13
|
+
#workflow;
|
|
14
|
+
#durableChecked = false;
|
|
15
|
+
#durableAvailable;
|
|
16
|
+
constructor(options) {
|
|
17
|
+
this.#processor = options.processor;
|
|
18
|
+
this.#mode = options.mode ?? 'auto';
|
|
19
|
+
this.#maxAttempts = options.retries?.max ?? 5;
|
|
20
|
+
this.#baseDelayMs = options.retries?.baseDelayMs ?? 500;
|
|
21
|
+
this.#maxDelayMs = options.retries?.maxDelayMs ?? 30_000;
|
|
22
|
+
this.#durableAvailable = options.durableAvailable;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Dispatch a webhook event for processing. Resolves once the event is accepted (durable)
|
|
26
|
+
* or fully processed (in-process).
|
|
27
|
+
*/
|
|
28
|
+
async dispatch(event) {
|
|
29
|
+
if (await this.#useDurable()) {
|
|
30
|
+
const workflow = await this.#resolveDurable();
|
|
31
|
+
const { runId } = await workflow.dispatch({ event });
|
|
32
|
+
return { runId };
|
|
33
|
+
}
|
|
34
|
+
return this.#processWithRetry(event);
|
|
35
|
+
}
|
|
36
|
+
/** Whether durable-backed processing is active. */
|
|
37
|
+
get mode() {
|
|
38
|
+
return this.#mode;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Decide whether durable handles this event. `auto` performs the lazy import once and
|
|
42
|
+
* caches the answer; `durable` forces it (throwing when missing); `in-process` never.
|
|
43
|
+
*/
|
|
44
|
+
async #useDurable() {
|
|
45
|
+
if (this.#mode === 'in-process')
|
|
46
|
+
return false;
|
|
47
|
+
if (this.#mode === 'durable')
|
|
48
|
+
return true;
|
|
49
|
+
// 'auto'
|
|
50
|
+
if (this.#durableChecked)
|
|
51
|
+
return this.#workflow !== undefined;
|
|
52
|
+
this.#durableChecked = true;
|
|
53
|
+
try {
|
|
54
|
+
if (this.#durableAvailable && !(await this.#durableAvailable())) {
|
|
55
|
+
this.#workflow = undefined;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
await import('@adonis-agora/durable');
|
|
59
|
+
this.#workflow = await this.#buildWorkflow();
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
this.#workflow = undefined;
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Lazily build a durable workflow class that runs the event through the shared
|
|
69
|
+
* processor hook. Only called when durable is actually installed.
|
|
70
|
+
*/
|
|
71
|
+
async #buildWorkflow() {
|
|
72
|
+
const durable = await import('@adonis-agora/durable');
|
|
73
|
+
const { BaseWorkflow } = durable;
|
|
74
|
+
const processor = this.#processor;
|
|
75
|
+
// The workflow's `run(ctx, input)` is the durable body; `dispatch` (static, inherited)
|
|
76
|
+
// enqueues it fire-and-forget. Input is the webhook event.
|
|
77
|
+
const workflow = class PaymentsWebhookWorkflow extends BaseWorkflow {
|
|
78
|
+
async run(_ctx, input) {
|
|
79
|
+
await processor.process(input.event);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return workflow;
|
|
83
|
+
}
|
|
84
|
+
async #resolveDurable() {
|
|
85
|
+
const workflow = this.#workflow ?? (await this.#buildWorkflow());
|
|
86
|
+
if (!workflow) {
|
|
87
|
+
throw new Error('[payments] billing.durable is set to true/auto but @adonis-agora/durable is not installed. ' +
|
|
88
|
+
'Install it (`node ace add @adonis-agora/durable`) or set billing.durable to false.');
|
|
89
|
+
}
|
|
90
|
+
return workflow;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* In-process fallback: run through the processor, retrying with exponential backoff on
|
|
94
|
+
* failure (up to `#maxAttempts`). Retries run in the background — the first attempt is
|
|
95
|
+
* awaited so the webhook endpoint can respond.
|
|
96
|
+
*/
|
|
97
|
+
async #processWithRetry(event) {
|
|
98
|
+
try {
|
|
99
|
+
await this.#processor.process(event);
|
|
100
|
+
return {};
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
// Retry in the background (don't block the webhook response). The processor itself
|
|
104
|
+
// publishes `webhook.failed` on the diagnostics channel.
|
|
105
|
+
void this.#retry(event, 2);
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async #retry(event, attempt) {
|
|
110
|
+
if (attempt > this.#maxAttempts)
|
|
111
|
+
return;
|
|
112
|
+
const delay = Math.min(this.#baseDelayMs * 2 ** (attempt - 2), this.#maxDelayMs);
|
|
113
|
+
await sleep(delay);
|
|
114
|
+
try {
|
|
115
|
+
await this.#processor.process(event);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
await this.#retry(event, attempt + 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function sleep(ms) {
|
|
123
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=webhook_dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook_dispatcher.js","sourceRoot":"","sources":["../../../src/billing/webhook_dispatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { WebhookEvent } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The canonical normalized webhook event types. Every driver's `#mapWebhookType` maps
|
|
4
|
+
* its gateway events onto these, and the `WebhookProcessor`'s built-in sync switches on
|
|
5
|
+
* them. Shared constant so a typo in one driver can't silently degrade to the no-op
|
|
6
|
+
* branch.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WEBHOOK_EVENT_TYPES: readonly ["payment.succeeded", "payment.failed", "payment.refunded", "payment.updated", "subscription.created", "subscription.updated", "subscription.canceled"];
|
|
9
|
+
export type WebhookEventType = (typeof WEBHOOK_EVENT_TYPES)[number];
|
|
10
|
+
/** Shape the built-in sync handlers expect from a payment event's `data`. */
|
|
11
|
+
export interface PaymentWebhookData {
|
|
12
|
+
gatewayId: string;
|
|
13
|
+
amount: number;
|
|
14
|
+
currency: string;
|
|
15
|
+
customerId?: string;
|
|
16
|
+
subscriptionId?: string;
|
|
17
|
+
/**
|
|
18
|
+
* The `externalReference` the app set on the charge/subscription, echoed back by the
|
|
19
|
+
* gateway. First-class so app handlers route payments to their local records without
|
|
20
|
+
* digging into `event.raw` — the reason {@link ChargeInput.externalReference} exists.
|
|
21
|
+
*/
|
|
22
|
+
externalReference?: string;
|
|
23
|
+
/** Gateway-echoed metadata, when the gateway includes it in the webhook payload. */
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
/** Shape the built-in sync handlers expect from a subscription event's `data`. */
|
|
27
|
+
export interface SubscriptionWebhookData {
|
|
28
|
+
gatewayId: string;
|
|
29
|
+
customerId: string;
|
|
30
|
+
status: string;
|
|
31
|
+
planId?: string;
|
|
32
|
+
trialEndsAt?: string;
|
|
33
|
+
endsAt?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Guard: is this event's `data` shaped like a payment event? The drivers normalize their
|
|
37
|
+
* payloads onto this shape (see the Asaas driver's `#mapWebhookData`); the processor
|
|
38
|
+
* checks it before writing a row so a malformed event can't produce garbage rows.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isPaymentWebhookData(data: unknown): data is PaymentWebhookData;
|
|
41
|
+
/** Guard: is this event's `data` shaped like a subscription event? */
|
|
42
|
+
export declare function isSubscriptionWebhookData(data: unknown): data is SubscriptionWebhookData;
|
|
43
|
+
/** Assert a webhook event's type is one of the canonical types (or a passthrough). */
|
|
44
|
+
export declare function isWebhookEventType(type: string): type is WebhookEventType;
|
|
45
|
+
/** Narrow a {@link WebhookEvent} to one of the canonical types. */
|
|
46
|
+
export declare function asWebhookEvent(event: WebhookEvent): WebhookEvent<unknown> & {
|
|
47
|
+
type: WebhookEventType;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=webhook_events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook_events.d.ts","sourceRoot":"","sources":["../../../src/billing/webhook_events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,kKAQtB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,6EAA6E;AAC7E,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,kFAAkF;AAClF,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,kBAAkB,CAQ9E;AAED,sEAAsE;AACtE,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,uBAAuB,CAIxF;AAED,sFAAsF;AACtF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,gBAAgB,CAEzE;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,GAClB,YAAY,CAAC,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAEpD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical normalized webhook event types. Every driver's `#mapWebhookType` maps
|
|
3
|
+
* its gateway events onto these, and the `WebhookProcessor`'s built-in sync switches on
|
|
4
|
+
* them. Shared constant so a typo in one driver can't silently degrade to the no-op
|
|
5
|
+
* branch.
|
|
6
|
+
*/
|
|
7
|
+
export const WEBHOOK_EVENT_TYPES = [
|
|
8
|
+
'payment.succeeded',
|
|
9
|
+
'payment.failed',
|
|
10
|
+
'payment.refunded',
|
|
11
|
+
'payment.updated',
|
|
12
|
+
'subscription.created',
|
|
13
|
+
'subscription.updated',
|
|
14
|
+
'subscription.canceled',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Guard: is this event's `data` shaped like a payment event? The drivers normalize their
|
|
18
|
+
* payloads onto this shape (see the Asaas driver's `#mapWebhookData`); the processor
|
|
19
|
+
* checks it before writing a row so a malformed event can't produce garbage rows.
|
|
20
|
+
*/
|
|
21
|
+
export function isPaymentWebhookData(data) {
|
|
22
|
+
if (typeof data !== 'object' || data === null)
|
|
23
|
+
return false;
|
|
24
|
+
const d = data;
|
|
25
|
+
return (typeof d.gatewayId === 'string' &&
|
|
26
|
+
typeof d.amount === 'number' &&
|
|
27
|
+
typeof d.currency === 'string');
|
|
28
|
+
}
|
|
29
|
+
/** Guard: is this event's `data` shaped like a subscription event? */
|
|
30
|
+
export function isSubscriptionWebhookData(data) {
|
|
31
|
+
if (typeof data !== 'object' || data === null)
|
|
32
|
+
return false;
|
|
33
|
+
const d = data;
|
|
34
|
+
return typeof d.gatewayId === 'string' && typeof d.customerId === 'string';
|
|
35
|
+
}
|
|
36
|
+
/** Assert a webhook event's type is one of the canonical types (or a passthrough). */
|
|
37
|
+
export function isWebhookEventType(type) {
|
|
38
|
+
return WEBHOOK_EVENT_TYPES.includes(type);
|
|
39
|
+
}
|
|
40
|
+
/** Narrow a {@link WebhookEvent} to one of the canonical types. */
|
|
41
|
+
export function asWebhookEvent(event) {
|
|
42
|
+
return event;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=webhook_events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook_events.js","sourceRoot":"","sources":["../../../src/billing/webhook_events.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,mBAAmB;IACnB,gBAAgB;IAChB,kBAAkB;IAClB,iBAAiB;IACjB,sBAAsB;IACtB,sBAAsB;IACtB,uBAAuB;CACf,CAAC;AA+BX;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAa;IAChD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC5D,MAAM,CAAC,GAAG,IAA+B,CAAC;IAC1C,OAAO,CACL,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;QAC/B,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAC5B,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAC/B,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,yBAAyB,CAAC,IAAa;IACrD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC5D,MAAM,CAAC,GAAG,IAA+B,CAAC;IAC1C,OAAO,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,CAAC;AAC7E,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAQ,mBAAyC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACnE,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAC5B,KAAmB;IAEnB,OAAO,KAA2D,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { PaymentsDriver } from '../driver.js';
|
|
2
|
+
import type { WebhookEvent } from '../types.js';
|
|
3
|
+
import type { BillingStore } from './billing_store.js';
|
|
4
|
+
/**
|
|
5
|
+
* A handler for a gateway webhook event type (e.g. `'invoice.payment_succeeded'`).
|
|
6
|
+
* Receives the normalized event plus the raw gateway payload. Return normally to mark the
|
|
7
|
+
* event processed; throw to mark it failed (and trigger the dispatcher's retry).
|
|
8
|
+
*/
|
|
9
|
+
export type WebhookHandler = (event: WebhookEvent) => void | Promise<void>;
|
|
10
|
+
export interface WebhookProcessorOptions {
|
|
11
|
+
store: BillingStore;
|
|
12
|
+
/**
|
|
13
|
+
* The driver that received the webhook — used for lookups and sync. Optional: the
|
|
14
|
+
* built-in handlers persist through the store and don't call the gateway.
|
|
15
|
+
*/
|
|
16
|
+
driver?: PaymentsDriver;
|
|
17
|
+
/** Map of gateway event type → handler. Apps register their business logic here. */
|
|
18
|
+
handlers?: Record<string, WebhookHandler>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Routes normalized webhook events to handlers, enforcing idempotency through the
|
|
22
|
+
* `billing_webhook_events` ledger (keyed by gateway event id), and keeps the local
|
|
23
|
+
* billing tables in sync (payments, subscriptions).
|
|
24
|
+
*
|
|
25
|
+
* Built-in handlers keep the store in sync and publish the normalized business events on
|
|
26
|
+
* the `@adonis-agora/diagnostics` channel (`agora:payments:payment.succeeded`, ...) —
|
|
27
|
+
* apps react with `onDiagnostic('payments', ...)` (or the `handlers` below). App
|
|
28
|
+
* handlers (via `handlers`) run after the built-in sync and can dispatch work.
|
|
29
|
+
*/
|
|
30
|
+
export declare class WebhookProcessor {
|
|
31
|
+
#private;
|
|
32
|
+
constructor(options: WebhookProcessorOptions);
|
|
33
|
+
/**
|
|
34
|
+
* Process a webhook event. Returns `false` when the event was already processed
|
|
35
|
+
* (idempotent replay). Throws when the event handler throws.
|
|
36
|
+
*/
|
|
37
|
+
process(event: WebhookEvent): Promise<boolean>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=webhook_processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook_processor.d.ts","sourceRoot":"","sources":["../../../src/billing/webhook_processor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE3E,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,YAAY,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CAC3C;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAgB;;gBAKf,OAAO,EAAE,uBAAuB;IAM5C;;;OAGG;IACG,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;CA6LrD"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { publishPayments } from '../diagnostics.js';
|
|
2
|
+
import { isPaymentWebhookData, isSubscriptionWebhookData } from './webhook_events.js';
|
|
3
|
+
/**
|
|
4
|
+
* Routes normalized webhook events to handlers, enforcing idempotency through the
|
|
5
|
+
* `billing_webhook_events` ledger (keyed by gateway event id), and keeps the local
|
|
6
|
+
* billing tables in sync (payments, subscriptions).
|
|
7
|
+
*
|
|
8
|
+
* Built-in handlers keep the store in sync and publish the normalized business events on
|
|
9
|
+
* the `@adonis-agora/diagnostics` channel (`agora:payments:payment.succeeded`, ...) —
|
|
10
|
+
* apps react with `onDiagnostic('payments', ...)` (or the `handlers` below). App
|
|
11
|
+
* handlers (via `handlers`) run after the built-in sync and can dispatch work.
|
|
12
|
+
*/
|
|
13
|
+
export class WebhookProcessor {
|
|
14
|
+
#store;
|
|
15
|
+
#driver;
|
|
16
|
+
#handlers;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.#store = options.store;
|
|
19
|
+
this.#driver = options.driver;
|
|
20
|
+
this.#handlers = options.handlers ?? {};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Process a webhook event. Returns `false` when the event was already processed
|
|
24
|
+
* (idempotent replay). Throws when the event handler throws.
|
|
25
|
+
*/
|
|
26
|
+
async process(event) {
|
|
27
|
+
const ledger = await this.#store.recordWebhookEvent({
|
|
28
|
+
gatewayEventId: event.id,
|
|
29
|
+
provider: event.provider,
|
|
30
|
+
type: event.type,
|
|
31
|
+
payload: event.raw,
|
|
32
|
+
});
|
|
33
|
+
// Already seen → idempotent replay, skip.
|
|
34
|
+
if (ledger === null)
|
|
35
|
+
return false;
|
|
36
|
+
publishPayments('webhook.received', {
|
|
37
|
+
id: event.id,
|
|
38
|
+
provider: event.provider,
|
|
39
|
+
type: event.type,
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
await this.#runBuiltIn(event);
|
|
43
|
+
const handler = this.#handlers[event.type];
|
|
44
|
+
if (handler)
|
|
45
|
+
await handler(event);
|
|
46
|
+
await this.#store.markWebhookProcessed(ledger.id);
|
|
47
|
+
publishPayments('webhook.processed', {
|
|
48
|
+
id: event.id,
|
|
49
|
+
provider: event.provider,
|
|
50
|
+
type: event.type,
|
|
51
|
+
});
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
await this.#store.markWebhookFailed(ledger.id, message);
|
|
57
|
+
publishPayments('webhook.failed', {
|
|
58
|
+
id: event.id,
|
|
59
|
+
provider: event.provider,
|
|
60
|
+
type: event.type,
|
|
61
|
+
error: message,
|
|
62
|
+
});
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Built-in sync handlers, keyed by the normalized event type (shared constants). */
|
|
67
|
+
#runBuiltIn(event) {
|
|
68
|
+
switch (event.type) {
|
|
69
|
+
case 'payment.succeeded':
|
|
70
|
+
return this.#onPaymentSucceeded(event);
|
|
71
|
+
case 'payment.failed':
|
|
72
|
+
return this.#onPaymentFailed(event);
|
|
73
|
+
case 'payment.refunded':
|
|
74
|
+
return this.#onPaymentRefunded(event);
|
|
75
|
+
case 'subscription.created':
|
|
76
|
+
case 'subscription.updated':
|
|
77
|
+
return this.#onSubscriptionChanged(event);
|
|
78
|
+
case 'subscription.canceled':
|
|
79
|
+
return this.#onSubscriptionCanceled(event);
|
|
80
|
+
default:
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async #onPaymentSucceeded(event) {
|
|
85
|
+
if (!isPaymentWebhookData(event.data)) {
|
|
86
|
+
throw new Error(`[payments] Malformed payment.succeeded payload for event ${event.id}.`);
|
|
87
|
+
}
|
|
88
|
+
const { gatewayId, amount, currency, customerId, subscriptionId } = event.data;
|
|
89
|
+
await this.#store.savePayment({
|
|
90
|
+
gatewayId,
|
|
91
|
+
provider: event.provider,
|
|
92
|
+
status: 'paid',
|
|
93
|
+
amount,
|
|
94
|
+
currency,
|
|
95
|
+
...(customerId !== undefined ? { customerId } : {}),
|
|
96
|
+
...(subscriptionId !== undefined ? { subscriptionId } : {}),
|
|
97
|
+
paidAt: new Date(),
|
|
98
|
+
payload: event.raw,
|
|
99
|
+
});
|
|
100
|
+
publishPayments('payment.succeeded', {
|
|
101
|
+
gatewayId,
|
|
102
|
+
provider: event.provider,
|
|
103
|
+
amount,
|
|
104
|
+
currency,
|
|
105
|
+
...(event.data.externalReference !== undefined
|
|
106
|
+
? { externalReference: event.data.externalReference }
|
|
107
|
+
: {}),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async #onPaymentFailed(event) {
|
|
111
|
+
if (!isPaymentWebhookData(event.data)) {
|
|
112
|
+
throw new Error(`[payments] Malformed payment.failed payload for event ${event.id}.`);
|
|
113
|
+
}
|
|
114
|
+
const { gatewayId, amount, currency, customerId, subscriptionId } = event.data;
|
|
115
|
+
await this.#store.savePayment({
|
|
116
|
+
gatewayId,
|
|
117
|
+
provider: event.provider,
|
|
118
|
+
status: 'failed',
|
|
119
|
+
amount,
|
|
120
|
+
currency,
|
|
121
|
+
...(customerId !== undefined ? { customerId } : {}),
|
|
122
|
+
...(subscriptionId !== undefined ? { subscriptionId } : {}),
|
|
123
|
+
payload: event.raw,
|
|
124
|
+
});
|
|
125
|
+
publishPayments('payment.failed', {
|
|
126
|
+
gatewayId,
|
|
127
|
+
provider: event.provider,
|
|
128
|
+
amount,
|
|
129
|
+
currency,
|
|
130
|
+
...(event.data.externalReference !== undefined
|
|
131
|
+
? { externalReference: event.data.externalReference }
|
|
132
|
+
: {}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async #onPaymentRefunded(event) {
|
|
136
|
+
if (!isPaymentWebhookData(event.data)) {
|
|
137
|
+
throw new Error(`[payments] Malformed payment.refunded payload for event ${event.id}.`);
|
|
138
|
+
}
|
|
139
|
+
const { gatewayId, amount, currency } = event.data;
|
|
140
|
+
const existing = await this.#store.findPaymentByGatewayId(gatewayId);
|
|
141
|
+
if (existing) {
|
|
142
|
+
await this.#store.savePayment({
|
|
143
|
+
gatewayId,
|
|
144
|
+
provider: event.provider,
|
|
145
|
+
status: 'refunded',
|
|
146
|
+
amount,
|
|
147
|
+
currency,
|
|
148
|
+
...(existing.customerId !== undefined ? { customerId: existing.customerId } : {}),
|
|
149
|
+
...(existing.subscriptionId !== undefined
|
|
150
|
+
? { subscriptionId: existing.subscriptionId }
|
|
151
|
+
: {}),
|
|
152
|
+
payload: event.raw,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
publishPayments('payment.refunded', {
|
|
156
|
+
gatewayId,
|
|
157
|
+
provider: event.provider,
|
|
158
|
+
amount,
|
|
159
|
+
currency,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async #onSubscriptionChanged(event) {
|
|
163
|
+
if (!isSubscriptionWebhookData(event.data)) {
|
|
164
|
+
throw new Error(`[payments] Malformed subscription payload for event ${event.id}.`);
|
|
165
|
+
}
|
|
166
|
+
const data = event.data;
|
|
167
|
+
await this.#store.saveSubscription({
|
|
168
|
+
gatewayId: data.gatewayId,
|
|
169
|
+
provider: event.provider,
|
|
170
|
+
customerId: data.customerId,
|
|
171
|
+
status: data.status,
|
|
172
|
+
planId: data.planId ?? '',
|
|
173
|
+
...(data.trialEndsAt !== undefined ? { trialEndsAt: new Date(data.trialEndsAt) } : {}),
|
|
174
|
+
...(data.endsAt !== undefined ? { endsAt: new Date(data.endsAt) } : {}),
|
|
175
|
+
payload: event.raw,
|
|
176
|
+
});
|
|
177
|
+
const type = event.type === 'subscription.created' ? 'subscription.created' : 'subscription.updated';
|
|
178
|
+
publishPayments(type, {
|
|
179
|
+
gatewayId: data.gatewayId,
|
|
180
|
+
provider: event.provider,
|
|
181
|
+
customerId: data.customerId,
|
|
182
|
+
status: data.status,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async #onSubscriptionCanceled(event) {
|
|
186
|
+
if (!isSubscriptionWebhookData(event.data)) {
|
|
187
|
+
throw new Error(`[payments] Malformed subscription.canceled payload for event ${event.id}.`);
|
|
188
|
+
}
|
|
189
|
+
const data = event.data;
|
|
190
|
+
const existing = await this.#store.findSubscriptionByGatewayId(data.gatewayId);
|
|
191
|
+
if (existing) {
|
|
192
|
+
await this.#store.saveSubscription({
|
|
193
|
+
gatewayId: data.gatewayId,
|
|
194
|
+
provider: event.provider,
|
|
195
|
+
customerId: data.customerId,
|
|
196
|
+
status: 'canceled',
|
|
197
|
+
planId: existing.planId,
|
|
198
|
+
...(data.trialEndsAt !== undefined ? { trialEndsAt: new Date(data.trialEndsAt) } : {}),
|
|
199
|
+
...(data.endsAt !== undefined ? { endsAt: new Date(data.endsAt) } : {}),
|
|
200
|
+
payload: event.raw,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
publishPayments('subscription.canceled', {
|
|
204
|
+
gatewayId: data.gatewayId,
|
|
205
|
+
provider: event.provider,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=webhook_processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook_processor.js","sourceRoot":"","sources":["../../../src/billing/webhook_processor.ts"],"names":[],"mappings":"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"}
|