@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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared webhook-auth primitives for the drivers. Every gateway signs webhooks
|
|
3
3
|
* differently (Stripe: SDK HMAC; AbacatePay: HMAC-SHA256; Woovi: HMAC-SHA1 or
|
|
4
|
-
* RSA-SHA256; Asaas: shared token
|
|
5
|
-
* same — they live here so every driver verifies with the same, timing-safe code.
|
|
4
|
+
* RSA-SHA256; Asaas: shared token; PagBank: a bare SHA-256 over token+body), but the
|
|
5
|
+
* comparison and digest mechanics are the same — they live here so every driver verifies with the same, timing-safe code.
|
|
6
6
|
*/
|
|
7
7
|
/** Length-independent, timing-safe string comparison. */
|
|
8
8
|
export declare function safeCompare(received: string, expected: string): boolean;
|
|
@@ -11,7 +11,24 @@ export declare function safeCompare(received: string, expected: string): boolean
|
|
|
11
11
|
* timing-safe. `digest` matches the gateway's encoding (`base64` for AbacatePay and
|
|
12
12
|
* Woovi's HMAC mode).
|
|
13
13
|
*/
|
|
14
|
-
export declare function verifyHmacSignature(rawBody: string, signature: string | undefined, secret: string, algorithm: 'sha256' | 'sha1'
|
|
14
|
+
export declare function verifyHmacSignature(rawBody: string, signature: string | undefined, secret: string, algorithm: 'sha256' | 'sha1',
|
|
15
|
+
/**
|
|
16
|
+
* How the gateway encodes the digest. Base64 is the default because the first gateways
|
|
17
|
+
* here used it; hex is at least as common (Paddle, Lemon Squeezy, Razorpay, Mercado Pago,
|
|
18
|
+
* Mollie all send hex), and getting it wrong fails CLOSED — every signature mismatches —
|
|
19
|
+
* so it is safe to leave to the caller.
|
|
20
|
+
*/
|
|
21
|
+
encoding?: 'base64' | 'hex'): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The same check over a payload the gateway builds rather than the raw body.
|
|
24
|
+
*
|
|
25
|
+
* Several schemes sign a CONSTRUCTED string — Paddle's `ts:body`, Standard Webhooks'
|
|
26
|
+
* `id.timestamp.body`, Square's `notificationUrl + body`, Mercado Pago's id/request/ts
|
|
27
|
+
* manifest. The signed bytes are the whole security property, so building them is the one
|
|
28
|
+
* step a driver must own; this only removes the reason to hand-roll the comparison, which
|
|
29
|
+
* is where a `===` creeps back in and reintroduces a timing side channel.
|
|
30
|
+
*/
|
|
31
|
+
export declare function verifyHmacOverPayload(payload: string, signature: string | undefined, secret: string, algorithm: 'sha256' | 'sha1', encoding?: 'base64' | 'hex'): boolean;
|
|
15
32
|
/**
|
|
16
33
|
* Verify Woovi's recommended `x-webhook-signature`: an RSA-SHA256 signature made with
|
|
17
34
|
* Woovi's private key, verified here against the public key published at
|
|
@@ -26,4 +43,78 @@ export declare function verifyRsaSha256Signature(rawBody: string, signature: str
|
|
|
26
43
|
* set. Drivers only reach this when their credential is configured.
|
|
27
44
|
*/
|
|
28
45
|
export declare function requireMatchingCredential(received: string | undefined, expected: string, provider: string, what: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Verify PagBank's `x-authenticity-token`: the hex SHA-256 of `<token>-<rawBody>`, where
|
|
48
|
+
* `<token>` is the very same Bearer token that authenticates API calls.
|
|
49
|
+
*
|
|
50
|
+
* Read that construction again, because it is not an HMAC. It is a secret **prefix**
|
|
51
|
+
* hashed with a Merkle–Damgård function, which is the textbook setup for a
|
|
52
|
+
* length-extension forgery: anyone who has seen one valid (body, token) pair can compute
|
|
53
|
+
* a valid token for that body plus a suffix, without ever learning the secret. What saves
|
|
54
|
+
* it in practice is that the forged body is the original body followed by binary padding,
|
|
55
|
+
* and `JSON.parse` rejects that — so a forgery has to survive the parser, not just the
|
|
56
|
+
* hash. It is still weaker than the HMAC every other gateway here uses, and the secret is
|
|
57
|
+
* shared with the API credential, so rotating one rotates the other.
|
|
58
|
+
*
|
|
59
|
+
* The comparison is timing-safe and case-insensitive (PagBank sends lowercase hex).
|
|
60
|
+
*/
|
|
61
|
+
export declare function verifyPagBankAuthenticityToken(rawBody: string, received: string | undefined, token: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Verify a [Standard Webhooks](https://www.standardwebhooks.com/) signature.
|
|
64
|
+
*
|
|
65
|
+
* The scheme: sign `` `${webhook-id}.${webhook-timestamp}.${rawBody}` `` with HMAC-SHA256,
|
|
66
|
+
* base64. The `webhook-signature` header carries a SPACE-SEPARATED list of `v1,<sig>` — a
|
|
67
|
+
* list, because a secret being rotated is signed with both keys at once, so checking only
|
|
68
|
+
* the first entry breaks every rotation.
|
|
69
|
+
*
|
|
70
|
+
* `keyEncoding` is the part that bites, and neither vendor documents it — both derivations
|
|
71
|
+
* below were read out of their own SDK source:
|
|
72
|
+
*
|
|
73
|
+
* - `'base64'` (the spec default) — strip the `whsec_` prefix and base64-DECODE the rest.
|
|
74
|
+
* Dodo Payments does this.
|
|
75
|
+
* - `'raw'` — use the secret's UTF-8 bytes as-is, `whsec_` prefix included. Polar does this
|
|
76
|
+
* (its SDK base64-encodes the secret before handing it to the reference library, which
|
|
77
|
+
* decodes it straight back).
|
|
78
|
+
*
|
|
79
|
+
* Getting it wrong fails closed — every signature mismatches — so the cost is a rejected
|
|
80
|
+
* webhook, not an accepted forgery. Which is why each driver pins its own derivation with a
|
|
81
|
+
* test that signs using the OTHER one and asserts rejection.
|
|
82
|
+
*
|
|
83
|
+
* `toleranceSeconds` bounds replay. It defaults to 5 minutes, the spec's own recommendation;
|
|
84
|
+
* pass `0` to skip the check. Note the idempotency ledger already makes a replayed event a
|
|
85
|
+
* no-op — this is the outer guard, not the only one.
|
|
86
|
+
*/
|
|
87
|
+
export declare function verifyStandardWebhookSignature(options: {
|
|
88
|
+
rawBody: string;
|
|
89
|
+
headers: Record<string, string | string[] | undefined>;
|
|
90
|
+
secret: string;
|
|
91
|
+
keyEncoding: 'base64' | 'raw';
|
|
92
|
+
toleranceSeconds?: number;
|
|
93
|
+
/** Overridable clock, so the tolerance is testable. */
|
|
94
|
+
now?: Date;
|
|
95
|
+
}): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Refuse to boot when a configured driver CAN authenticate webhook deliveries but was given
|
|
98
|
+
* nothing to authenticate them with.
|
|
99
|
+
*
|
|
100
|
+
* The hole: the library mounts `POST /payments/webhook/:provider` for every configured
|
|
101
|
+
* provider, and a driver whose credential slot is empty verifies nothing — so anyone who
|
|
102
|
+
* knows the URL can post `{"event":"PAYMENT_RECEIVED", ...}` and the built-in sync marks a
|
|
103
|
+
* payment paid. Nothing logged it, nothing warned, and the app's own webhook-security tests
|
|
104
|
+
* passed *because* they only asserted that a good signature is accepted.
|
|
105
|
+
*
|
|
106
|
+
* This package already fails closed in the two comparable places — the dashboard throws at
|
|
107
|
+
* boot on a missing session secret, the dispatcher throws on a mode it cannot serve — and
|
|
108
|
+
* this is the same decision for the one endpoint the public internet can reach.
|
|
109
|
+
*
|
|
110
|
+
* The opt-out is explicit and per-provider, because terminating verification upstream is a
|
|
111
|
+
* real deployment (mutual TLS at the edge, an API gateway that checks the signature) and it
|
|
112
|
+
* is exactly what Efí's driver does by design. What it must not be is the default.
|
|
113
|
+
*/
|
|
114
|
+
export declare function assertWebhookVerification(drivers: Iterable<[string, {
|
|
115
|
+
provider?: string;
|
|
116
|
+
webhookVerification?: string;
|
|
117
|
+
}]>, options?: {
|
|
118
|
+
allowUnverifiedWebhooks?: boolean | readonly string[];
|
|
119
|
+
}): void;
|
|
29
120
|
//# sourceMappingURL=webhook_security.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook_security.d.ts","sourceRoot":"","sources":["../../src/webhook_security.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhook_security.d.ts","sourceRoot":"","sources":["../../src/webhook_security.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AAEH,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAIvE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,QAAQ,GAAG,MAAM;AAC5B;;;;;GAKG;AACH,QAAQ,GAAE,QAAQ,GAAG,KAAgB,GACpC,OAAO,CAST;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,QAAQ,GAAG,MAAM,EAC5B,QAAQ,GAAE,QAAQ,GAAG,KAAgB,GACpC,OAAO,CAST;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,EAAE,MAAM,GAChB,OAAO,CAgBT;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,IAAI,CAUN;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,GACZ,OAAO,CAST;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,uDAAuD;IACvD,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GAAG,OAAO,CAuCV;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,EAChF,OAAO,GAAE;IAAE,uBAAuB,CAAC,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CAAA;CAAO,GACtE,IAAI,CAoBN"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { createHmac, verify as cryptoVerify, timingSafeEqual } from 'node:crypto';
|
|
1
|
+
import { createHash, createHmac, verify as cryptoVerify, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { reportWebhookVerification } from './diagnostics.js';
|
|
3
|
+
import { headerValue } from './http.js';
|
|
2
4
|
/**
|
|
3
5
|
* Shared webhook-auth primitives for the drivers. Every gateway signs webhooks
|
|
4
6
|
* differently (Stripe: SDK HMAC; AbacatePay: HMAC-SHA256; Woovi: HMAC-SHA1 or
|
|
5
|
-
* RSA-SHA256; Asaas: shared token
|
|
6
|
-
* same — they live here so every driver verifies with the same, timing-safe code.
|
|
7
|
+
* RSA-SHA256; Asaas: shared token; PagBank: a bare SHA-256 over token+body), but the
|
|
8
|
+
* comparison and digest mechanics are the same — they live here so every driver verifies with the same, timing-safe code.
|
|
7
9
|
*/
|
|
8
10
|
/** Length-independent, timing-safe string comparison. */
|
|
9
11
|
export function safeCompare(received, expected) {
|
|
@@ -16,11 +18,41 @@ export function safeCompare(received, expected) {
|
|
|
16
18
|
* timing-safe. `digest` matches the gateway's encoding (`base64` for AbacatePay and
|
|
17
19
|
* Woovi's HMAC mode).
|
|
18
20
|
*/
|
|
19
|
-
export function verifyHmacSignature(rawBody, signature, secret, algorithm
|
|
20
|
-
|
|
21
|
+
export function verifyHmacSignature(rawBody, signature, secret, algorithm,
|
|
22
|
+
/**
|
|
23
|
+
* How the gateway encodes the digest. Base64 is the default because the first gateways
|
|
24
|
+
* here used it; hex is at least as common (Paddle, Lemon Squeezy, Razorpay, Mercado Pago,
|
|
25
|
+
* Mollie all send hex), and getting it wrong fails CLOSED — every signature mismatches —
|
|
26
|
+
* so it is safe to leave to the caller.
|
|
27
|
+
*/
|
|
28
|
+
encoding = 'base64') {
|
|
29
|
+
if (signature === undefined || signature === '') {
|
|
30
|
+
reportWebhookVerification(`hmac-${algorithm}`, false);
|
|
21
31
|
return false;
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
}
|
|
33
|
+
const expected = createHmac(algorithm, secret).update(rawBody, 'utf8').digest(encoding);
|
|
34
|
+
const ok = safeCompare(signature, expected);
|
|
35
|
+
reportWebhookVerification(`hmac-${algorithm}`, ok);
|
|
36
|
+
return ok;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The same check over a payload the gateway builds rather than the raw body.
|
|
40
|
+
*
|
|
41
|
+
* Several schemes sign a CONSTRUCTED string — Paddle's `ts:body`, Standard Webhooks'
|
|
42
|
+
* `id.timestamp.body`, Square's `notificationUrl + body`, Mercado Pago's id/request/ts
|
|
43
|
+
* manifest. The signed bytes are the whole security property, so building them is the one
|
|
44
|
+
* step a driver must own; this only removes the reason to hand-roll the comparison, which
|
|
45
|
+
* is where a `===` creeps back in and reintroduces a timing side channel.
|
|
46
|
+
*/
|
|
47
|
+
export function verifyHmacOverPayload(payload, signature, secret, algorithm, encoding = 'base64') {
|
|
48
|
+
if (signature === undefined || signature === '') {
|
|
49
|
+
reportWebhookVerification(`hmac-${algorithm}-over-payload`, false);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const expected = createHmac(algorithm, secret).update(payload, 'utf8').digest(encoding);
|
|
53
|
+
const ok = safeCompare(signature, expected);
|
|
54
|
+
reportWebhookVerification(`hmac-${algorithm}-over-payload`, ok);
|
|
55
|
+
return ok;
|
|
24
56
|
}
|
|
25
57
|
/**
|
|
26
58
|
* Verify Woovi's recommended `x-webhook-signature`: an RSA-SHA256 signature made with
|
|
@@ -29,12 +61,16 @@ export function verifyHmacSignature(rawBody, signature, secret, algorithm) {
|
|
|
29
61
|
* PEM document (the dashboard shows it base64-encoded).
|
|
30
62
|
*/
|
|
31
63
|
export function verifyRsaSha256Signature(rawBody, signature, publicKey) {
|
|
32
|
-
if (signature === undefined || signature === '')
|
|
64
|
+
if (signature === undefined || signature === '') {
|
|
65
|
+
reportWebhookVerification('rsa-sha256', false);
|
|
33
66
|
return false;
|
|
67
|
+
}
|
|
34
68
|
const pem = publicKey.includes('-----BEGIN')
|
|
35
69
|
? publicKey
|
|
36
70
|
: Buffer.from(publicKey, 'base64').toString('utf8');
|
|
37
|
-
|
|
71
|
+
const ok = cryptoVerify('RSA-SHA256', Buffer.from(rawBody, 'utf8'), pem, Buffer.from(signature, 'base64'));
|
|
72
|
+
reportWebhookVerification('rsa-sha256', ok);
|
|
73
|
+
return ok;
|
|
38
74
|
}
|
|
39
75
|
/**
|
|
40
76
|
* Enforce a configured webhook credential against the request headers. Returns the
|
|
@@ -44,10 +80,138 @@ export function verifyRsaSha256Signature(rawBody, signature, publicKey) {
|
|
|
44
80
|
*/
|
|
45
81
|
export function requireMatchingCredential(received, expected, provider, what) {
|
|
46
82
|
if (received === undefined || received === '') {
|
|
83
|
+
reportWebhookVerification('shared-token', false);
|
|
47
84
|
throw new Error(`[payments] Missing webhook ${what} on ${provider} request.`);
|
|
48
85
|
}
|
|
49
86
|
if (!safeCompare(received, expected)) {
|
|
87
|
+
reportWebhookVerification('shared-token', false);
|
|
50
88
|
throw new Error(`[payments] Invalid ${provider} webhook ${what}.`);
|
|
51
89
|
}
|
|
90
|
+
reportWebhookVerification('shared-token', true);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Verify PagBank's `x-authenticity-token`: the hex SHA-256 of `<token>-<rawBody>`, where
|
|
94
|
+
* `<token>` is the very same Bearer token that authenticates API calls.
|
|
95
|
+
*
|
|
96
|
+
* Read that construction again, because it is not an HMAC. It is a secret **prefix**
|
|
97
|
+
* hashed with a Merkle–Damgård function, which is the textbook setup for a
|
|
98
|
+
* length-extension forgery: anyone who has seen one valid (body, token) pair can compute
|
|
99
|
+
* a valid token for that body plus a suffix, without ever learning the secret. What saves
|
|
100
|
+
* it in practice is that the forged body is the original body followed by binary padding,
|
|
101
|
+
* and `JSON.parse` rejects that — so a forgery has to survive the parser, not just the
|
|
102
|
+
* hash. It is still weaker than the HMAC every other gateway here uses, and the secret is
|
|
103
|
+
* shared with the API credential, so rotating one rotates the other.
|
|
104
|
+
*
|
|
105
|
+
* The comparison is timing-safe and case-insensitive (PagBank sends lowercase hex).
|
|
106
|
+
*/
|
|
107
|
+
export function verifyPagBankAuthenticityToken(rawBody, received, token) {
|
|
108
|
+
if (received === undefined || received === '') {
|
|
109
|
+
reportWebhookVerification('sha256-token-prefix', false);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
const expected = createHash('sha256').update(`${token}-${rawBody}`, 'utf8').digest('hex');
|
|
113
|
+
const ok = safeCompare(received.toLowerCase(), expected);
|
|
114
|
+
reportWebhookVerification('sha256-token-prefix', ok);
|
|
115
|
+
return ok;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Verify a [Standard Webhooks](https://www.standardwebhooks.com/) signature.
|
|
119
|
+
*
|
|
120
|
+
* The scheme: sign `` `${webhook-id}.${webhook-timestamp}.${rawBody}` `` with HMAC-SHA256,
|
|
121
|
+
* base64. The `webhook-signature` header carries a SPACE-SEPARATED list of `v1,<sig>` — a
|
|
122
|
+
* list, because a secret being rotated is signed with both keys at once, so checking only
|
|
123
|
+
* the first entry breaks every rotation.
|
|
124
|
+
*
|
|
125
|
+
* `keyEncoding` is the part that bites, and neither vendor documents it — both derivations
|
|
126
|
+
* below were read out of their own SDK source:
|
|
127
|
+
*
|
|
128
|
+
* - `'base64'` (the spec default) — strip the `whsec_` prefix and base64-DECODE the rest.
|
|
129
|
+
* Dodo Payments does this.
|
|
130
|
+
* - `'raw'` — use the secret's UTF-8 bytes as-is, `whsec_` prefix included. Polar does this
|
|
131
|
+
* (its SDK base64-encodes the secret before handing it to the reference library, which
|
|
132
|
+
* decodes it straight back).
|
|
133
|
+
*
|
|
134
|
+
* Getting it wrong fails closed — every signature mismatches — so the cost is a rejected
|
|
135
|
+
* webhook, not an accepted forgery. Which is why each driver pins its own derivation with a
|
|
136
|
+
* test that signs using the OTHER one and asserts rejection.
|
|
137
|
+
*
|
|
138
|
+
* `toleranceSeconds` bounds replay. It defaults to 5 minutes, the spec's own recommendation;
|
|
139
|
+
* pass `0` to skip the check. Note the idempotency ledger already makes a replayed event a
|
|
140
|
+
* no-op — this is the outer guard, not the only one.
|
|
141
|
+
*/
|
|
142
|
+
export function verifyStandardWebhookSignature(options) {
|
|
143
|
+
const id = headerValue(options.headers, 'webhook-id');
|
|
144
|
+
const timestamp = headerValue(options.headers, 'webhook-timestamp');
|
|
145
|
+
const signature = headerValue(options.headers, 'webhook-signature');
|
|
146
|
+
if (!id || !timestamp || !signature) {
|
|
147
|
+
reportWebhookVerification('standard-webhooks', false);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const tolerance = options.toleranceSeconds ?? 300;
|
|
151
|
+
if (tolerance > 0) {
|
|
152
|
+
const sent = Number(timestamp);
|
|
153
|
+
if (!Number.isFinite(sent)) {
|
|
154
|
+
reportWebhookVerification('standard-webhooks', false);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const nowSeconds = Math.floor((options.now ?? new Date()).getTime() / 1000);
|
|
158
|
+
if (Math.abs(nowSeconds - sent) > tolerance) {
|
|
159
|
+
reportWebhookVerification('standard-webhooks', false);
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const key = options.keyEncoding === 'raw'
|
|
164
|
+
? Buffer.from(options.secret, 'utf8')
|
|
165
|
+
: Buffer.from(options.secret.replace(/^whsec_/, ''), 'base64');
|
|
166
|
+
const expected = createHmac('sha256', key)
|
|
167
|
+
.update(`${id}.${timestamp}.${options.rawBody}`, 'utf8')
|
|
168
|
+
.digest('base64');
|
|
169
|
+
// Every `v1,<sig>` entry is checked: during a secret rotation the gateway signs with both
|
|
170
|
+
// the old and the new key, and only one of them can match.
|
|
171
|
+
const ok = signature
|
|
172
|
+
.split(' ')
|
|
173
|
+
.filter((part) => part.startsWith('v1,'))
|
|
174
|
+
.some((part) => safeCompare(part.slice(3), expected));
|
|
175
|
+
reportWebhookVerification('standard-webhooks', ok);
|
|
176
|
+
return ok;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Refuse to boot when a configured driver CAN authenticate webhook deliveries but was given
|
|
180
|
+
* nothing to authenticate them with.
|
|
181
|
+
*
|
|
182
|
+
* The hole: the library mounts `POST /payments/webhook/:provider` for every configured
|
|
183
|
+
* provider, and a driver whose credential slot is empty verifies nothing — so anyone who
|
|
184
|
+
* knows the URL can post `{"event":"PAYMENT_RECEIVED", ...}` and the built-in sync marks a
|
|
185
|
+
* payment paid. Nothing logged it, nothing warned, and the app's own webhook-security tests
|
|
186
|
+
* passed *because* they only asserted that a good signature is accepted.
|
|
187
|
+
*
|
|
188
|
+
* This package already fails closed in the two comparable places — the dashboard throws at
|
|
189
|
+
* boot on a missing session secret, the dispatcher throws on a mode it cannot serve — and
|
|
190
|
+
* this is the same decision for the one endpoint the public internet can reach.
|
|
191
|
+
*
|
|
192
|
+
* The opt-out is explicit and per-provider, because terminating verification upstream is a
|
|
193
|
+
* real deployment (mutual TLS at the edge, an API gateway that checks the signature) and it
|
|
194
|
+
* is exactly what Efí's driver does by design. What it must not be is the default.
|
|
195
|
+
*/
|
|
196
|
+
export function assertWebhookVerification(drivers, options = {}) {
|
|
197
|
+
const allow = options.allowUnverifiedWebhooks;
|
|
198
|
+
if (allow === true)
|
|
199
|
+
return;
|
|
200
|
+
const allowed = new Set(Array.isArray(allow) ? allow : []);
|
|
201
|
+
for (const [name, driver] of drivers) {
|
|
202
|
+
if (driver.webhookVerification !== 'unconfigured')
|
|
203
|
+
continue;
|
|
204
|
+
if (allowed.has(name) || (driver.provider !== undefined && allowed.has(driver.provider))) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
throw new Error([
|
|
208
|
+
`[payments] The "${name}" driver can verify webhook deliveries but has no credential`,
|
|
209
|
+
`configured, so \`POST /payments/webhook/${driver.provider ?? name}\` would accept any`,
|
|
210
|
+
'request that reaches it — including one that marks a payment paid.',
|
|
211
|
+
"Set the gateway's webhook secret/token (see the provider docs page for the env var),",
|
|
212
|
+
'or, if verification is genuinely terminated upstream, say so explicitly with',
|
|
213
|
+
`\`allowUnverifiedWebhooks: ['${name}']\` in config/payments.ts.`,
|
|
214
|
+
].join(' '));
|
|
215
|
+
}
|
|
52
216
|
}
|
|
53
217
|
//# sourceMappingURL=webhook_security.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook_security.js","sourceRoot":"","sources":["../../src/webhook_security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"webhook_security.js","sourceRoot":"","sources":["../../src/webhook_security.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,6 +37,19 @@ export default defineConfig({
|
|
|
37
37
|
// // Recommended RSA signature (x-webhook-signature):
|
|
38
38
|
// webhookPublicKey: env.get('WOOVI_PUBLIC_KEY'),
|
|
39
39
|
// }),
|
|
40
|
+
// pagbank: payments.pagbank({
|
|
41
|
+
// token: env.get('PAGBANK_TOKEN'),
|
|
42
|
+
// // Webhooks are verified with the SAME token (x-authenticity-token); there is no
|
|
43
|
+
// // separate secret. Only turn verification off for the sandbox, never in production.
|
|
44
|
+
// notificationUrls: [`${env.get('APP_URL')}/payments/webhook/pagbank`],
|
|
45
|
+
// }),
|
|
46
|
+
// efi: payments.efi({
|
|
47
|
+
// clientId: env.get('EFI_CLIENT_ID'),
|
|
48
|
+
// clientSecret: env.get('EFI_CLIENT_SECRET'),
|
|
49
|
+
// pixKey: env.get('EFI_PIX_KEY'),
|
|
50
|
+
// // Efí's Pix API needs mutual TLS: the .p12 from the dashboard, as a path or Buffer.
|
|
51
|
+
// certificate: env.get('EFI_CERTIFICATE'),
|
|
52
|
+
// }),
|
|
40
53
|
},
|
|
41
54
|
|
|
42
55
|
/**
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{{{
|
|
2
|
+
exports({ to: app.configPath('payments_client.ts') })
|
|
3
|
+
}}}
|
|
4
|
+
import { defineConfig } from '@adonis-agora/payments/client'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the browser-facing payment status endpoint — the one
|
|
8
|
+
* \`usePaymentStatus()\` from \`@adonis-agora/payments-react\` polls while a Pix
|
|
9
|
+
* QR code or a boleto is on screen.
|
|
10
|
+
*
|
|
11
|
+
* DISABLED BY DEFAULT. Unlike the dashboard, this endpoint is reachable by every
|
|
12
|
+
* logged-in browser, so you turn it on deliberately.
|
|
13
|
+
*
|
|
14
|
+
* The three hooks below are the whole authorization story:
|
|
15
|
+
*
|
|
16
|
+
* - \`authorize\` — is this request allowed at all (default: there is a
|
|
17
|
+
* resolved user on \`ctx.auth\`; no user is a 401).
|
|
18
|
+
* - \`owner\` — who is asking (default: \`{ type: 'User', id: user.id }\`).
|
|
19
|
+
* - \`authorizeReference\` — may this caller see THIS payment. The default checks
|
|
20
|
+
* the payment's gateway customer against the one the owner
|
|
21
|
+
* holds in \`billing_customers\`. If you never recorded that
|
|
22
|
+
* mapping with \`ensureCustomer(..., { store, owner })\`, the
|
|
23
|
+
* default DENIES — write your own hook against your own
|
|
24
|
+
* tables instead.
|
|
25
|
+
*/
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
// Turn it on. Nothing is mounted while this is false.
|
|
28
|
+
// enabled: true,
|
|
29
|
+
// path: '/payments/client',
|
|
30
|
+
// authorize: async (ctx) => {
|
|
31
|
+
// await ctx.auth.check()
|
|
32
|
+
// return ctx.auth.user !== undefined
|
|
33
|
+
// },
|
|
34
|
+
// owner: async (ctx) => {
|
|
35
|
+
// const user = ctx.auth.user
|
|
36
|
+
// return user ? { type: 'User', id: String(user.id) } : null
|
|
37
|
+
// },
|
|
38
|
+
// authorizeReference: async (ctx, reference) => {
|
|
39
|
+
// const order = await Order.findBy('gatewayPaymentId', reference)
|
|
40
|
+
// return order?.userId === ctx.auth.user?.id
|
|
41
|
+
// },
|
|
42
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{{{
|
|
2
|
+
exports({ to: app.configPath('payments_dashboard.ts') })
|
|
3
|
+
}}}
|
|
4
|
+
import { defineConfig } from '@adonis-agora/payments/dashboard'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the @adonis-agora/payments dashboard.
|
|
8
|
+
*
|
|
9
|
+
* By default the dashboard is open OUTSIDE production, and in production it
|
|
10
|
+
* requires a bearer token equal to \`PAYMENTS_DASHBOARD_TOKEN\` (deny if unset).
|
|
11
|
+
* Replace \`authorize\` with your own guard (session, role, IP allow-list, …).
|
|
12
|
+
*
|
|
13
|
+
* Set \`enabled: false\` to remove it entirely — no routes are registered at all.
|
|
14
|
+
*
|
|
15
|
+
* \`dashboardAuth\` is an ADDITIVE, optional session gate on top of \`authorize\` —
|
|
16
|
+
* a signed cookie minted via a built-in login page (Mode B: \`login\`) and/or a
|
|
17
|
+
* "open the console from your app" endpoint (Mode A: \`session\`). At least one
|
|
18
|
+
* of the two is required when \`dashboardAuth\` is set.
|
|
19
|
+
*/
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
// enabled: true,
|
|
22
|
+
// path: '/payments-dashboard',
|
|
23
|
+
// currency: 'BRL',
|
|
24
|
+
// authorize: (ctx) => ctx.auth.user?.isAdmin === true,
|
|
25
|
+
// dashboardAuth: {
|
|
26
|
+
// secret: env.get('PAYMENTS_DASHBOARD_SECRET'),
|
|
27
|
+
// ttl: '8h',
|
|
28
|
+
// // Mode B — standalone login page.
|
|
29
|
+
// login: async (username, password) => {
|
|
30
|
+
// if (username === 'admin' && password === env.get('PAYMENTS_DASHBOARD_PASSWORD')) {
|
|
31
|
+
// return { id: 'admin', name: 'Admin' }
|
|
32
|
+
// }
|
|
33
|
+
// return null
|
|
34
|
+
// },
|
|
35
|
+
// // Mode A — mint a session for an already-authenticated host request.
|
|
36
|
+
// // session: async (request) => (request.user?.isAdmin ? { id: request.user.id } : null),
|
|
37
|
+
// },
|
|
38
|
+
})
|
|
@@ -2,89 +2,47 @@
|
|
|
2
2
|
exports({ to: app.migrationsPath(`${new Date().getTime()}_create_billing_tables.ts`) })
|
|
3
3
|
}}}
|
|
4
4
|
import { BaseSchema } from '@adonisjs/lucid/schema'
|
|
5
|
+
import { createBillingTables, dropBillingTables } from '@adonis-agora/payments'
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* The billing tables, for an app that would rather run DDL explicitly than have the library
|
|
9
|
+
* do it.
|
|
10
|
+
*
|
|
11
|
+
* **You may not need this file.** `@adonis-agora/payments` creates its own tables on first
|
|
12
|
+
* use, the same way `@adonis-agora/durable` and `@adonis-agora/authz` do — that is the
|
|
13
|
+
* ecosystem convention, and it is on by default. This migration exists for the case where
|
|
14
|
+
* that is not wanted: a shared database you do not own, a team that reviews every DDL, a
|
|
15
|
+
* deploy that runs migrations as a separate step.
|
|
16
|
+
*
|
|
17
|
+
* If you keep it, turn the automatic path off so there is one owner:
|
|
18
|
+
*
|
|
19
|
+
* // config/payments.ts
|
|
20
|
+
* billing: { autoCreateSchema: false }
|
|
21
|
+
*
|
|
22
|
+
* Leaving both on is harmless — the DDL is idempotent, so whichever runs second does
|
|
23
|
+
* nothing — but "who creates this table" should have one answer.
|
|
24
|
+
*
|
|
25
|
+
* Both paths call the SAME `createBillingTables`, which is the point: a schema defined twice
|
|
26
|
+
* is a schema that drifts, and this package spent a release finding the consequences of two
|
|
27
|
+
* things that were supposed to agree and did not.
|
|
28
|
+
*
|
|
29
|
+
* `this.defer` is how a Lucid migration hands over the query client.
|
|
30
|
+
*
|
|
31
|
+
* `createBillingTables` is idempotent in both directions: it creates what is missing AND
|
|
32
|
+
* applies the guarded `ALTER`s for columns the library added after a table shipped. So
|
|
33
|
+
* re-running this migration after upgrading the package carries those columns — which is
|
|
34
|
+
* why running it twice is safe and why there is no second file.
|
|
35
|
+
*/
|
|
6
36
|
export default class extends BaseSchema {
|
|
7
|
-
protected tableName = 'billing_customers'
|
|
8
|
-
|
|
9
37
|
async up() {
|
|
10
|
-
this.
|
|
11
|
-
|
|
12
|
-
table.string('owner_type')
|
|
13
|
-
table.string('owner_id')
|
|
14
|
-
table.string('gateway_id').unique()
|
|
15
|
-
table.string('provider')
|
|
16
|
-
table.string('email').nullable()
|
|
17
|
-
table.string('name').nullable()
|
|
18
|
-
table.string('tax_id').nullable()
|
|
19
|
-
table.jsonb('metadata').nullable()
|
|
20
|
-
table.timestamp('created_at', { useTz: true }).notNullable()
|
|
21
|
-
table.timestamp('updated_at', { useTz: true }).notNullable()
|
|
22
|
-
table.index(['owner_type', 'owner_id'], 'billing_customers_owner_idx')
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
this.schema.createTable('billing_subscriptions', (table) => {
|
|
26
|
-
table.uuid('id').primary()
|
|
27
|
-
table.string('gateway_id').unique()
|
|
28
|
-
table.string('provider')
|
|
29
|
-
table.string('status')
|
|
30
|
-
table.string('plan_id')
|
|
31
|
-
table.string('customer_id').nullable()
|
|
32
|
-
table.timestamp('trial_ends_at', { useTz: true }).nullable()
|
|
33
|
-
table.timestamp('ends_at', { useTz: true }).nullable()
|
|
34
|
-
table.jsonb('payload').nullable()
|
|
35
|
-
table.timestamp('created_at', { useTz: true }).notNullable()
|
|
36
|
-
table.timestamp('updated_at', { useTz: true }).notNullable()
|
|
37
|
-
table.index(['customer_id'], 'billing_subscriptions_customer_idx')
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
this.schema.createTable('billing_payments', (table) => {
|
|
41
|
-
table.uuid('id').primary()
|
|
42
|
-
table.string('gateway_id').unique()
|
|
43
|
-
table.string('provider')
|
|
44
|
-
table.string('status')
|
|
45
|
-
table.bigInteger('amount')
|
|
46
|
-
table.string('currency', 3)
|
|
47
|
-
table.string('customer_id').nullable()
|
|
48
|
-
table.string('subscription_id').nullable()
|
|
49
|
-
table.jsonb('payload').nullable()
|
|
50
|
-
table.timestamp('paid_at', { useTz: true }).nullable()
|
|
51
|
-
table.timestamp('created_at', { useTz: true }).notNullable()
|
|
52
|
-
table.timestamp('updated_at', { useTz: true }).notNullable()
|
|
53
|
-
table.index(['customer_id'], 'billing_payments_customer_idx')
|
|
54
|
-
table.index(['subscription_id'], 'billing_payments_subscription_idx')
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
this.schema.createTable('billing_webhook_events', (table) => {
|
|
58
|
-
table.uuid('id').primary()
|
|
59
|
-
table.string('gateway_event_id').unique()
|
|
60
|
-
table.string('provider')
|
|
61
|
-
table.string('type')
|
|
62
|
-
table.string('status')
|
|
63
|
-
table.jsonb('payload').nullable()
|
|
64
|
-
table.text('error').nullable()
|
|
65
|
-
table.timestamp('created_at', { useTz: true }).notNullable()
|
|
66
|
-
table.timestamp('updated_at', { useTz: true }).notNullable()
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
this.schema.createTable('billing_usage_events', (table) => {
|
|
70
|
-
table.uuid('id').primary()
|
|
71
|
-
table.string('subscription_id').nullable()
|
|
72
|
-
table.string('customer_id').nullable()
|
|
73
|
-
table.string('meter')
|
|
74
|
-
table.integer('quantity').defaultTo(1)
|
|
75
|
-
table.jsonb('metadata').nullable()
|
|
76
|
-
table.timestamp('recorded_at', { useTz: true }).notNullable()
|
|
77
|
-
table.timestamp('updated_at', { useTz: true }).notNullable()
|
|
78
|
-
table.index(['subscription_id', 'meter'], 'billing_usage_subscription_meter_idx')
|
|
79
|
-
table.index(['customer_id', 'meter'], 'billing_usage_customer_meter_idx')
|
|
38
|
+
this.defer(async (db) => {
|
|
39
|
+
await createBillingTables(db)
|
|
80
40
|
})
|
|
81
41
|
}
|
|
82
42
|
|
|
83
43
|
async down() {
|
|
84
|
-
this.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.schema.dropTable('billing_webhook_events')
|
|
88
|
-
this.schema.dropTable('billing_usage_events')
|
|
44
|
+
this.defer(async (db) => {
|
|
45
|
+
await dropBillingTables(db)
|
|
46
|
+
})
|
|
89
47
|
}
|
|
90
48
|
}
|
|
@@ -6,24 +6,42 @@
|
|
|
6
6
|
})
|
|
7
7
|
}}}
|
|
8
8
|
import { inject } from '@adonisjs/core'
|
|
9
|
-
import type {
|
|
9
|
+
import type { WebhookEventFor } from '@adonis-agora/payments'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Webhook handler
|
|
13
|
-
* (
|
|
14
|
-
*
|
|
12
|
+
* Webhook handler for `{{ entity.name }}`, run by the library's own route
|
|
13
|
+
* (`POST /payments/webhook/:provider`) inside the WebhookProcessor. Throwing marks the
|
|
14
|
+
* event `failed` in the ledger, the dispatcher retries, and the route answers 500 so the
|
|
15
|
+
* gateway redelivers — a 2xx over a failed event is the payment lost.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
+
* The class is resolved from the container, so constructor injection works: add a
|
|
18
|
+
* dependency and `@inject()` supplies it.
|
|
17
19
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
+
* `eventType` is checked at BOOT: a type in the `payment.*`/`subscription.*` namespace that
|
|
21
|
+
* is not one of the normalized `WEBHOOK_EVENT_TYPES` throws, because a handler nothing ever
|
|
22
|
+
* calls is a grant that silently never happens while the delivery is recorded as processed.
|
|
23
|
+
*
|
|
24
|
+
* Webhooks must answer fast. Do the heavy work — the grant, the activation — in a durable
|
|
25
|
+
* workflow and only dispatch from here:
|
|
26
|
+
*
|
|
27
|
+
* await this.engine.start(MyWorkflow, { externalReference }, randomUUID())
|
|
20
28
|
*/
|
|
21
29
|
@inject()
|
|
22
30
|
export default class {{ handlerName }}Handler {
|
|
23
31
|
static readonly eventType = '{{ entity.name }}'
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
/**
|
|
34
|
+
* `WebhookEventFor<'{{ entity.name }}'>` types `event.data` from the event type — a payment
|
|
35
|
+
* payload for the payment events, a dispute payload for the dispute ones — so nothing here
|
|
36
|
+
* needs the `event.data as PaymentWebhookData` cast that used to open every handler, and
|
|
37
|
+
* reading a payment field off a dispute is a compile error rather than `undefined`.
|
|
38
|
+
*/
|
|
39
|
+
async handle(event: WebhookEventFor<'{{ entity.name }}'>): Promise<void> {
|
|
40
|
+
// `externalReference` is the id YOUR app set on the charge, echoed back by the
|
|
41
|
+
// gateway — the routing key from this event to your own row.
|
|
42
|
+
const { externalReference } = event.data
|
|
43
|
+
if (!externalReference) return
|
|
44
|
+
|
|
45
|
+
void externalReference
|
|
28
46
|
}
|
|
29
|
-
}
|
|
47
|
+
}
|