@aquarian-metals/coin-moebius-dodopayments 3.0.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/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +120 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +357 -0
- package/dist/server.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aquarian-metals
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @aquarian-metals/coin-moebius-dodopayments
|
|
2
|
+
|
|
3
|
+
[Dodo Payments](https://dodopayments.com) provider for [Coin Moebius](https://github.com/aquarian-metals/coin-moebius).
|
|
4
|
+
|
|
5
|
+
Dodo Payments is a Merchant of Record: it hosts the checkout page, handles
|
|
6
|
+
sales tax, and remits to you. A single Dodo checkout can mix one-time and
|
|
7
|
+
subscription products, so this one package covers both — one-time charges land
|
|
8
|
+
as `kind: 'payment'` events, recurring billing lands as `kind: 'subscription'`.
|
|
9
|
+
|
|
10
|
+
## Client (browser)
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { createDodoPaymentsProvider } from '@aquarian-metals/coin-moebius-dodopayments';
|
|
14
|
+
import { createPaymentManager } from '@aquarian-metals/coin-moebius';
|
|
15
|
+
|
|
16
|
+
const dodo = createDodoPaymentsProvider({
|
|
17
|
+
// Your own serverless endpoint that creates a Dodo checkout session with
|
|
18
|
+
// your API key and returns `{ url: checkout_url }`.
|
|
19
|
+
checkoutEndpoint: '/api/checkout/dodopayments',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const manager = createPaymentManager({ providers: [dodo] });
|
|
23
|
+
await manager.initiate({ productId: 'pro', amount: 9.99, currency: 'USD' });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`initiate` fires `onPending` and then redirects the buyer to Dodo's hosted
|
|
27
|
+
checkout. The terminal `success` signal arrives via the webhook on the server
|
|
28
|
+
side; subscribe to it buyer-side with `manager.subscribeToStatus(...)` if you
|
|
29
|
+
want in-page completion notice.
|
|
30
|
+
|
|
31
|
+
## Server (Node / serverless / Workers)
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { createDodoPaymentsVerifier } from '@aquarian-metals/coin-moebius-dodopayments/server';
|
|
35
|
+
|
|
36
|
+
const verify = createDodoPaymentsVerifier({
|
|
37
|
+
webhookSecret: process.env.DODO_WEBHOOK_SECRET, // the whsec_… value
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// `rawBody` MUST be the unparsed request body string (or bytes). Standard
|
|
41
|
+
// Webhooks signs the exact payload, so a pre-parsed object cannot be verified.
|
|
42
|
+
const event = await verify(rawBody, request.headers);
|
|
43
|
+
if (event?.kind === 'payment' && event.status === 'success') {
|
|
44
|
+
// fulfill
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> **Never import `/server` into a browser bundle.** It performs signature
|
|
49
|
+
> verification and belongs only in your serverless functions or Worker.
|
|
50
|
+
|
|
51
|
+
### Signature scheme
|
|
52
|
+
|
|
53
|
+
Dodo signs webhooks with the [Standard Webhooks](https://www.standardwebhooks.com/)
|
|
54
|
+
specification: base64 HMAC-SHA256 over `${webhook-id}.${webhook-timestamp}.${body}`,
|
|
55
|
+
keyed by the base64-decoded `whsec_…` secret, compared against each `v1,<sig>`
|
|
56
|
+
token in the `webhook-signature` header. The verifier also rejects deliveries
|
|
57
|
+
whose signed `webhook-timestamp` is outside a 5-minute tolerance window
|
|
58
|
+
(configurable via `toleranceSeconds`) for replay protection.
|
|
59
|
+
|
|
60
|
+
### Event mapping
|
|
61
|
+
|
|
62
|
+
| Dodo event | SDK event |
|
|
63
|
+
| ------------------------------------------------------ | -------------------------------------------- |
|
|
64
|
+
| `payment.succeeded` | payment → `success` |
|
|
65
|
+
| `payment.processing` | payment → `pending` |
|
|
66
|
+
| `payment.failed`, `payment.cancelled` | payment → `failed` |
|
|
67
|
+
| `refund.succeeded` | payment → `refunded` |
|
|
68
|
+
| `dispute.opened` | payment → `disputed` |
|
|
69
|
+
| `subscription.active` | subscription → `subscription.created` |
|
|
70
|
+
| `subscription.renewed` | subscription → `subscription.renewed` |
|
|
71
|
+
| `subscription.failed` | subscription → `subscription.payment_failed` |
|
|
72
|
+
| `subscription.cancelled`, `subscription.expired` | subscription → `subscription.canceled` |
|
|
73
|
+
| `subscription.on_hold`, `subscription.plan_changed`, … | subscription → `subscription.updated` |
|
|
74
|
+
|
|
75
|
+
Refund and dispute events key on the original `payment_id`, so you can link
|
|
76
|
+
them back to the original transaction. Amounts are converted from Dodo's minor
|
|
77
|
+
units (cents) to major units. Event types the SDK doesn't model (payouts,
|
|
78
|
+
license keys, `refund.failed`, dispute resolution follow-ups) resolve to
|
|
79
|
+
`null`; read `event.raw` if you need them.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT — see [LICENSE](./LICENSE).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dodo Payments client-side provider for Coin Moebius.
|
|
3
|
+
*
|
|
4
|
+
* Hosted-checkout flow only. Calls the configured checkout endpoint on your
|
|
5
|
+
* own backend, receives a `checkout_url`, and redirects the buyer there.
|
|
6
|
+
* Dodo Payments is a Merchant of Record: it hosts the payment page, collects
|
|
7
|
+
* tax, and remits to you, so the buyer never touches your origin for the
|
|
8
|
+
* payment itself. One Dodo checkout session can mix one-time and subscription
|
|
9
|
+
* products, so this single provider covers both flows — the recurring side is
|
|
10
|
+
* surfaced server-side as `kind: 'subscription'` events.
|
|
11
|
+
*
|
|
12
|
+
* The server-side webhook verifier lives at `./server` so this client-only
|
|
13
|
+
* entry doesn't pull any Node/Web crypto into browser bundles.
|
|
14
|
+
*
|
|
15
|
+
* import { createDodoPaymentsProvider } from '@aquarian-metals/coin-moebius-dodopayments';
|
|
16
|
+
* const dodo = createDodoPaymentsProvider({
|
|
17
|
+
* checkoutEndpoint: '/api/checkout/dodopayments',
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const manager = createPaymentManager({ providers: [dodo] });
|
|
21
|
+
* await manager.initiate({ productId: 'pro', amount: 9.99, currency: 'USD' });
|
|
22
|
+
*/
|
|
23
|
+
import type { PaymentProvider } from '@aquarian-metals/coin-moebius-core';
|
|
24
|
+
/** Client-side config. The Dodo API key + webhook secret stay server-side. */
|
|
25
|
+
export interface DodoPaymentsProviderConfig {
|
|
26
|
+
/** Full URL of the checkout endpoint that returns `{ url: checkout_url }`. */
|
|
27
|
+
checkoutEndpoint: string;
|
|
28
|
+
/** Optional fetch override — used by tests. Defaults to global `fetch`. */
|
|
29
|
+
fetcher?: typeof fetch;
|
|
30
|
+
/** Optional navigation override — used by tests. Defaults to `location.assign`. */
|
|
31
|
+
navigate?: (url: string) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build a `PaymentProvider` registered as `id: 'dodopayments'`. Returns a
|
|
35
|
+
* `PaymentResult` with status `'pending'` immediately after redirect, since
|
|
36
|
+
* settlement happens on Dodo's hosted page and the terminal signal lands via
|
|
37
|
+
* the webhook on the server side. Consumers wanting buyer-side completion
|
|
38
|
+
* notice should also call `manager.subscribeToStatus(paymentId, …)` after
|
|
39
|
+
* `initiate` resolves.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createDodoPaymentsProvider(config: DodoPaymentsProviderConfig): PaymentProvider;
|
|
42
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EACX,eAAe,EAGf,MAAM,oCAAoC,CAAC;AAE5C,8EAA8E;AAC9E,MAAM,WAAW,0BAA0B;IAC1C,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;IACzB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAQD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,0BAA0B,GAAG,eAAe,CAuD9F"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a `PaymentProvider` registered as `id: 'dodopayments'`. Returns a
|
|
3
|
+
* `PaymentResult` with status `'pending'` immediately after redirect, since
|
|
4
|
+
* settlement happens on Dodo's hosted page and the terminal signal lands via
|
|
5
|
+
* the webhook on the server side. Consumers wanting buyer-side completion
|
|
6
|
+
* notice should also call `manager.subscribeToStatus(paymentId, …)` after
|
|
7
|
+
* `initiate` resolves.
|
|
8
|
+
*/
|
|
9
|
+
export function createDodoPaymentsProvider(config) {
|
|
10
|
+
const fetcher = config.fetcher ?? globalThis.fetch.bind(globalThis);
|
|
11
|
+
const navigate = config.navigate ??
|
|
12
|
+
((url) => {
|
|
13
|
+
window.location.assign(url);
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
id: 'dodopayments',
|
|
17
|
+
name: 'Dodo Payments',
|
|
18
|
+
async initiate(options, callbacks) {
|
|
19
|
+
try {
|
|
20
|
+
const body = {
|
|
21
|
+
productId: options.productId,
|
|
22
|
+
amount: options.amount,
|
|
23
|
+
currency: options.currency,
|
|
24
|
+
};
|
|
25
|
+
if (options.metadata)
|
|
26
|
+
body.metadata = options.metadata;
|
|
27
|
+
const response = await fetcher(config.checkoutEndpoint, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/json' },
|
|
30
|
+
body: JSON.stringify(body),
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(`coin-moebius/dodopayments: checkout endpoint responded ${response.status}`);
|
|
34
|
+
}
|
|
35
|
+
const payload = (await response.json());
|
|
36
|
+
if (!payload.url) {
|
|
37
|
+
throw new Error('coin-moebius/dodopayments: checkout response missing `url`');
|
|
38
|
+
}
|
|
39
|
+
// Fire a pending event so the SDK's listeners can update UI before
|
|
40
|
+
// we navigate away. The buyer's onSuccess lands via the
|
|
41
|
+
// status-polling channel after the webhook clears server-side.
|
|
42
|
+
const result = {
|
|
43
|
+
status: 'pending',
|
|
44
|
+
paymentId: payload.paymentId ?? '',
|
|
45
|
+
provider: 'dodopayments',
|
|
46
|
+
amount: options.amount,
|
|
47
|
+
currency: options.currency,
|
|
48
|
+
metadata: options.metadata ?? {},
|
|
49
|
+
timestamp: Date.now(),
|
|
50
|
+
};
|
|
51
|
+
callbacks.onPending?.(result);
|
|
52
|
+
assertSafeRedirectUrl(payload.url);
|
|
53
|
+
navigate(payload.url);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
callbacks.onError(err instanceof Error ? err : new Error(String(err)));
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function assertSafeRedirectUrl(url) {
|
|
62
|
+
const parsed = new URL(url);
|
|
63
|
+
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
|
|
64
|
+
throw new Error(`coin-moebius/dodopayments: redirect URL scheme "${parsed.protocol}" is not allowed`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4CA;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAkC;IAC5E,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpE,MAAM,QAAQ,GACb,MAAM,CAAC,QAAQ;QACf,CAAC,CAAC,GAAW,EAAE,EAAE;YAChB,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IAEJ,OAAO;QACN,EAAE,EAAE,cAAc;QAClB,IAAI,EAAE,eAAe;QACrB,KAAK,CAAC,QAAQ,CAAC,OAAwB,EAAE,SAAS;YACjD,IAAI,CAAC;gBACJ,MAAM,IAAI,GAA4B;oBACrC,SAAS,EAAE,OAAO,CAAC,SAAS;oBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;iBAC1B,CAAC;gBACF,IAAI,OAAO,CAAC,QAAQ;oBAAE,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;gBAEvD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE;oBACvD,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;oBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;iBAC1B,CAAC,CAAC;gBACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;oBAClB,MAAM,IAAI,KAAK,CACd,0DAA0D,QAAQ,CAAC,MAAM,EAAE,CAC3E,CAAC;gBACH,CAAC;gBACD,MAAM,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAqB,CAAC;gBAC5D,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;oBAClB,MAAM,IAAI,KAAK,CAAC,4DAA4D,CAAC,CAAC;gBAC/E,CAAC;gBAED,mEAAmE;gBACnE,wDAAwD;gBACxD,+DAA+D;gBAC/D,MAAM,MAAM,GAAkB;oBAC7B,MAAM,EAAE,SAAS;oBACjB,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,EAAE;oBAClC,QAAQ,EAAE,cAAc;oBACxB,MAAM,EAAE,OAAO,CAAC,MAAM;oBACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE;oBAChC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;iBACrB,CAAC;gBACF,SAAS,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC,CAAC;gBAC9B,qBAAqB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACnC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACvB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACd,SAAS,CAAC,OAAO,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACxE,CAAC;QACF,CAAC;KACD,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,GAAW;IACzC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CACd,mDAAmD,MAAM,CAAC,QAAQ,kBAAkB,CACpF,CAAC;IACH,CAAC;AACF,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dodo Payments server-side webhook verifier. Dodo signs webhooks with the
|
|
3
|
+
* [Standard Webhooks](https://www.standardwebhooks.com/) scheme (the same
|
|
4
|
+
* scheme Svix popularized), so verification is:
|
|
5
|
+
*
|
|
6
|
+
* 1. Read the `webhook-id`, `webhook-timestamp`, and `webhook-signature`
|
|
7
|
+
* headers.
|
|
8
|
+
* 2. Reject the delivery if `webhook-timestamp` is outside the tolerance
|
|
9
|
+
* window (replay protection — Standard Webhooks bakes a signed timestamp
|
|
10
|
+
* into every delivery, so unlike NOWPayments we don't need the caller to
|
|
11
|
+
* dedupe by id to be replay-safe; we still recommend it for idempotency).
|
|
12
|
+
* 3. Build the signed content as `${webhook-id}.${webhook-timestamp}.${rawBody}`.
|
|
13
|
+
* 4. HMAC-SHA256 it with the webhook secret. The `whsec_`-prefixed secret is
|
|
14
|
+
* base64 AFTER the prefix; decode it to raw key bytes first.
|
|
15
|
+
* 5. base64-encode the digest and compare (constant time) against each
|
|
16
|
+
* space-delimited `v1,<sig>` token in the `webhook-signature` header. The
|
|
17
|
+
* header carries multiple signatures during key rotation; a match on any
|
|
18
|
+
* one passes.
|
|
19
|
+
*
|
|
20
|
+
* **Raw body is required.** Standard Webhooks signs the exact bytes Dodo sent.
|
|
21
|
+
* Re-serializing a parsed object would change key order / whitespace and break
|
|
22
|
+
* the signature, so this verifier only accepts the raw request body as a
|
|
23
|
+
* string or byte buffer and throws (fails closed) on a pre-parsed object — the
|
|
24
|
+
* same contract the Stripe verifier enforces.
|
|
25
|
+
*
|
|
26
|
+
* The verifier maps Dodo's event stream onto the SDK's canonical
|
|
27
|
+
* `WebhookEvent` union. One-time payments, refunds, and disputes become
|
|
28
|
+
* `kind: 'payment'`; subscription lifecycle events become `kind: 'subscription'`.
|
|
29
|
+
* Event types the SDK doesn't model (payouts, license keys, informational
|
|
30
|
+
* dispute follow-ups) resolve to `null` so consumers can skip them without
|
|
31
|
+
* polluting their transaction store.
|
|
32
|
+
*/
|
|
33
|
+
import type { WebhookEvent } from '@aquarian-metals/coin-moebius-core';
|
|
34
|
+
/** Default replay-tolerance window: reject deliveries whose signed timestamp is more than this many seconds from now. */
|
|
35
|
+
export declare const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
36
|
+
/** Server-side config. `webhookSecret` is the `whsec_…` value from Dodo's dashboard webhook settings. */
|
|
37
|
+
export interface DodoPaymentsVerifierConfig {
|
|
38
|
+
webhookSecret: string;
|
|
39
|
+
/**
|
|
40
|
+
* Replay-tolerance window in seconds. Deliveries whose `webhook-timestamp`
|
|
41
|
+
* is further than this from the current time are rejected. Defaults to
|
|
42
|
+
* {@link DEFAULT_WEBHOOK_TOLERANCE_SECONDS} (5 minutes), matching the
|
|
43
|
+
* Standard Webhooks reference implementations.
|
|
44
|
+
*/
|
|
45
|
+
toleranceSeconds?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The shape of a Dodo Payments webhook envelope. Mirrors the documented
|
|
49
|
+
* payload. Fields we don't read are still allowed via the index signature so
|
|
50
|
+
* future additions don't break verification.
|
|
51
|
+
*/
|
|
52
|
+
export interface DodoWebhookPayload {
|
|
53
|
+
business_id: string;
|
|
54
|
+
/** Event identifier, e.g. `payment.succeeded`, `subscription.active`. */
|
|
55
|
+
type: string;
|
|
56
|
+
/** ISO 8601 dispatch time. */
|
|
57
|
+
timestamp: string;
|
|
58
|
+
data: DodoEventData;
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
}
|
|
61
|
+
/** The inner `data` object. `payload_type` discriminates the resource. */
|
|
62
|
+
export interface DodoEventData {
|
|
63
|
+
payload_type: string;
|
|
64
|
+
payment_id?: string;
|
|
65
|
+
subscription_id?: string;
|
|
66
|
+
/** Smallest currency unit (e.g. cents) for payment payloads. */
|
|
67
|
+
total_amount?: number;
|
|
68
|
+
/** Smallest currency unit for refund payloads. */
|
|
69
|
+
amount?: number;
|
|
70
|
+
/** Smallest currency unit per cycle for subscription payloads. */
|
|
71
|
+
recurring_pre_tax_amount?: number;
|
|
72
|
+
currency?: string;
|
|
73
|
+
status?: string;
|
|
74
|
+
product_id?: string;
|
|
75
|
+
customer?: {
|
|
76
|
+
customer_id?: string;
|
|
77
|
+
email?: string;
|
|
78
|
+
name?: string;
|
|
79
|
+
};
|
|
80
|
+
next_billing_date?: string;
|
|
81
|
+
metadata?: Record<string, unknown>;
|
|
82
|
+
[key: string]: unknown;
|
|
83
|
+
}
|
|
84
|
+
/** Options for {@link getDodoPortalUrl}. */
|
|
85
|
+
export interface DodoPortalOptions {
|
|
86
|
+
/** Dodo API key (Bearer). */
|
|
87
|
+
apiKey: string;
|
|
88
|
+
/** API host, e.g. `https://test.dodopayments.com` or `https://live.dodopayments.com`. */
|
|
89
|
+
apiBase: string;
|
|
90
|
+
/** The Dodo customer handle (`cus_…`). */
|
|
91
|
+
customerId: string;
|
|
92
|
+
/** Where the portal's back button returns the buyer to. */
|
|
93
|
+
returnUrl?: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Create a Dodo-hosted Customer Portal session and return its URL. The buyer
|
|
97
|
+
* manages their subscription (cancel, update card, view invoices) inside
|
|
98
|
+
* Dodo's branded UI; the merchant never sees card details.
|
|
99
|
+
*
|
|
100
|
+
* Mirrors `getStripePortalUrl` from the Stripe provider so a consumer dogfooding
|
|
101
|
+
* both rails calls the same shape. Hits
|
|
102
|
+
* `POST /customers/{id}/customer-portal/session` and reads the `link` field.
|
|
103
|
+
*/
|
|
104
|
+
export declare function getDodoPortalUrl(opts: DodoPortalOptions): Promise<string>;
|
|
105
|
+
/**
|
|
106
|
+
* Build a Standard Webhooks verifier for Dodo Payments. The returned function
|
|
107
|
+
* matches the `Verifier` contract from `@aquarian-metals/coin-moebius-server`:
|
|
108
|
+
* `(rawBody, headers) => Promise<WebhookEvent | null>`. Register it with a
|
|
109
|
+
* verifier registry, or call it directly inside your webhook handler.
|
|
110
|
+
*/
|
|
111
|
+
export declare function createDodoPaymentsVerifier(config: DodoPaymentsVerifierConfig): (rawBody: unknown, headers?: unknown) => Promise<WebhookEvent | null>;
|
|
112
|
+
/**
|
|
113
|
+
* Compute the Standard Webhooks signature for a delivery: base64 HMAC-SHA256
|
|
114
|
+
* of `${id}.${timestamp}.${body}` keyed by the base64-decoded webhook secret.
|
|
115
|
+
* Exported so callers with non-standard rawBody pipelines can verify with the
|
|
116
|
+
* same routine without re-importing internals. Returns the bare base64 digest
|
|
117
|
+
* (no `v1,` prefix).
|
|
118
|
+
*/
|
|
119
|
+
export declare function computeDodoSignature(id: string, timestamp: string, body: string, webhookSecret: string): Promise<string>;
|
|
120
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAIX,YAAY,EACZ,MAAM,oCAAoC,CAAC;AAE5C,yHAAyH;AACzH,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAErD,yGAAyG;AACzG,MAAM,WAAW,0BAA0B;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,aAAa,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,4CAA4C;AAC5C,MAAM,WAAW,iBAAiB;IACjC,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkB/E;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,0BAA0B,IAI3E,SAAS,OAAO,EAChB,UAAU,OAAO,KACf,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAuC/B;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACzC,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAYjB"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dodo Payments server-side webhook verifier. Dodo signs webhooks with the
|
|
3
|
+
* [Standard Webhooks](https://www.standardwebhooks.com/) scheme (the same
|
|
4
|
+
* scheme Svix popularized), so verification is:
|
|
5
|
+
*
|
|
6
|
+
* 1. Read the `webhook-id`, `webhook-timestamp`, and `webhook-signature`
|
|
7
|
+
* headers.
|
|
8
|
+
* 2. Reject the delivery if `webhook-timestamp` is outside the tolerance
|
|
9
|
+
* window (replay protection — Standard Webhooks bakes a signed timestamp
|
|
10
|
+
* into every delivery, so unlike NOWPayments we don't need the caller to
|
|
11
|
+
* dedupe by id to be replay-safe; we still recommend it for idempotency).
|
|
12
|
+
* 3. Build the signed content as `${webhook-id}.${webhook-timestamp}.${rawBody}`.
|
|
13
|
+
* 4. HMAC-SHA256 it with the webhook secret. The `whsec_`-prefixed secret is
|
|
14
|
+
* base64 AFTER the prefix; decode it to raw key bytes first.
|
|
15
|
+
* 5. base64-encode the digest and compare (constant time) against each
|
|
16
|
+
* space-delimited `v1,<sig>` token in the `webhook-signature` header. The
|
|
17
|
+
* header carries multiple signatures during key rotation; a match on any
|
|
18
|
+
* one passes.
|
|
19
|
+
*
|
|
20
|
+
* **Raw body is required.** Standard Webhooks signs the exact bytes Dodo sent.
|
|
21
|
+
* Re-serializing a parsed object would change key order / whitespace and break
|
|
22
|
+
* the signature, so this verifier only accepts the raw request body as a
|
|
23
|
+
* string or byte buffer and throws (fails closed) on a pre-parsed object — the
|
|
24
|
+
* same contract the Stripe verifier enforces.
|
|
25
|
+
*
|
|
26
|
+
* The verifier maps Dodo's event stream onto the SDK's canonical
|
|
27
|
+
* `WebhookEvent` union. One-time payments, refunds, and disputes become
|
|
28
|
+
* `kind: 'payment'`; subscription lifecycle events become `kind: 'subscription'`.
|
|
29
|
+
* Event types the SDK doesn't model (payouts, license keys, informational
|
|
30
|
+
* dispute follow-ups) resolve to `null` so consumers can skip them without
|
|
31
|
+
* polluting their transaction store.
|
|
32
|
+
*/
|
|
33
|
+
/** Default replay-tolerance window: reject deliveries whose signed timestamp is more than this many seconds from now. */
|
|
34
|
+
export const DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
35
|
+
/**
|
|
36
|
+
* Create a Dodo-hosted Customer Portal session and return its URL. The buyer
|
|
37
|
+
* manages their subscription (cancel, update card, view invoices) inside
|
|
38
|
+
* Dodo's branded UI; the merchant never sees card details.
|
|
39
|
+
*
|
|
40
|
+
* Mirrors `getStripePortalUrl` from the Stripe provider so a consumer dogfooding
|
|
41
|
+
* both rails calls the same shape. Hits
|
|
42
|
+
* `POST /customers/{id}/customer-portal/session` and reads the `link` field.
|
|
43
|
+
*/
|
|
44
|
+
export async function getDodoPortalUrl(opts) {
|
|
45
|
+
const base = opts.apiBase.replace(/\/$/, '');
|
|
46
|
+
const url = new URL(`${base}/customers/${opts.customerId}/customer-portal/session`);
|
|
47
|
+
if (opts.returnUrl)
|
|
48
|
+
url.searchParams.set('return_url', opts.returnUrl);
|
|
49
|
+
const response = await fetch(url, {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: { Authorization: `Bearer ${opts.apiKey}`, 'Content-Type': 'application/json' },
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new Error(`coin-moebius/dodopayments: customer-portal session failed (${response.status})`);
|
|
55
|
+
}
|
|
56
|
+
const payload = (await response.json());
|
|
57
|
+
if (!payload.link) {
|
|
58
|
+
throw new Error('coin-moebius/dodopayments: customer-portal response missing `link`');
|
|
59
|
+
}
|
|
60
|
+
return payload.link;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build a Standard Webhooks verifier for Dodo Payments. The returned function
|
|
64
|
+
* matches the `Verifier` contract from `@aquarian-metals/coin-moebius-server`:
|
|
65
|
+
* `(rawBody, headers) => Promise<WebhookEvent | null>`. Register it with a
|
|
66
|
+
* verifier registry, or call it directly inside your webhook handler.
|
|
67
|
+
*/
|
|
68
|
+
export function createDodoPaymentsVerifier(config) {
|
|
69
|
+
const tolerance = config.toleranceSeconds ?? DEFAULT_WEBHOOK_TOLERANCE_SECONDS;
|
|
70
|
+
return async function verifyDodoWebhook(rawBody, headers) {
|
|
71
|
+
if (!config.webhookSecret) {
|
|
72
|
+
throw new Error('coin-moebius/dodopayments: webhookSecret missing on verifier config');
|
|
73
|
+
}
|
|
74
|
+
const headerRecord = (headers ?? {});
|
|
75
|
+
const id = headerValue(headerRecord, 'webhook-id') ?? headerValue(headerRecord, 'svix-id');
|
|
76
|
+
const timestamp = headerValue(headerRecord, 'webhook-timestamp') ?? headerValue(headerRecord, 'svix-timestamp');
|
|
77
|
+
const signatureHeader = headerValue(headerRecord, 'webhook-signature') ?? headerValue(headerRecord, 'svix-signature');
|
|
78
|
+
if (!id || !timestamp || !signatureHeader) {
|
|
79
|
+
throw new Error('coin-moebius/dodopayments: missing webhook-id / webhook-timestamp / webhook-signature header');
|
|
80
|
+
}
|
|
81
|
+
assertTimestampFresh(timestamp, tolerance);
|
|
82
|
+
// Standard Webhooks signs the exact bytes Dodo sent. We need the raw
|
|
83
|
+
// body string verbatim — re-serializing a parsed object would change
|
|
84
|
+
// the bytes and break the signature, so reject objects (fail closed).
|
|
85
|
+
const rawString = toRawString(rawBody);
|
|
86
|
+
const expected = await computeDodoSignature(id, timestamp, rawString, config.webhookSecret);
|
|
87
|
+
if (!signatureHeaderMatches(signatureHeader, expected)) {
|
|
88
|
+
throw new Error('coin-moebius/dodopayments: invalid signature');
|
|
89
|
+
}
|
|
90
|
+
let payload;
|
|
91
|
+
try {
|
|
92
|
+
payload = JSON.parse(rawString);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new Error('coin-moebius/dodopayments: body is not valid JSON');
|
|
96
|
+
}
|
|
97
|
+
return toWebhookEvent(payload);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Compute the Standard Webhooks signature for a delivery: base64 HMAC-SHA256
|
|
102
|
+
* of `${id}.${timestamp}.${body}` keyed by the base64-decoded webhook secret.
|
|
103
|
+
* Exported so callers with non-standard rawBody pipelines can verify with the
|
|
104
|
+
* same routine without re-importing internals. Returns the bare base64 digest
|
|
105
|
+
* (no `v1,` prefix).
|
|
106
|
+
*/
|
|
107
|
+
export async function computeDodoSignature(id, timestamp, body, webhookSecret) {
|
|
108
|
+
const secretBytes = decodeSecret(webhookSecret);
|
|
109
|
+
const message = new TextEncoder().encode(`${id}.${timestamp}.${body}`);
|
|
110
|
+
const key = await crypto.subtle.importKey('raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
111
|
+
const sigBuf = await crypto.subtle.sign('HMAC', key, message);
|
|
112
|
+
return bytesToBase64(new Uint8Array(sigBuf));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The Standard Webhooks secret is `whsec_<base64>`. The `whsec_` prefix is a
|
|
116
|
+
* human-readable label, not part of the key — strip it, then base64-decode the
|
|
117
|
+
* remainder to the raw HMAC key bytes. Secrets without the prefix are decoded
|
|
118
|
+
* as-is for tolerance.
|
|
119
|
+
*/
|
|
120
|
+
function decodeSecret(secret) {
|
|
121
|
+
const b64 = secret.startsWith('whsec_') ? secret.slice('whsec_'.length) : secret;
|
|
122
|
+
return base64ToBytes(b64);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The `webhook-signature` header is a space-delimited list of `<version>,<sig>`
|
|
126
|
+
* tokens (e.g. `v1,abc= v1,def=`) to support zero-downtime key rotation. We
|
|
127
|
+
* compare our expected base64 digest against each `v1` token in constant time;
|
|
128
|
+
* a match on any one passes.
|
|
129
|
+
*/
|
|
130
|
+
function signatureHeaderMatches(header, expected) {
|
|
131
|
+
for (const token of header.split(' ')) {
|
|
132
|
+
const comma = token.indexOf(',');
|
|
133
|
+
if (comma === -1)
|
|
134
|
+
continue;
|
|
135
|
+
const version = token.slice(0, comma);
|
|
136
|
+
const sig = token.slice(comma + 1);
|
|
137
|
+
if (version === 'v1' && timingSafeStringEqual(sig, expected))
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
function assertTimestampFresh(timestamp, toleranceSeconds) {
|
|
143
|
+
const ts = Number(timestamp);
|
|
144
|
+
if (!Number.isFinite(ts)) {
|
|
145
|
+
throw new Error('coin-moebius/dodopayments: webhook-timestamp is not a number');
|
|
146
|
+
}
|
|
147
|
+
const nowSeconds = Date.now() / 1000;
|
|
148
|
+
if (Math.abs(nowSeconds - ts) > toleranceSeconds) {
|
|
149
|
+
throw new Error('coin-moebius/dodopayments: webhook-timestamp outside tolerance (replay?)');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function toRawString(rawBody) {
|
|
153
|
+
if (typeof rawBody === 'string')
|
|
154
|
+
return rawBody;
|
|
155
|
+
if (rawBody instanceof Uint8Array)
|
|
156
|
+
return new TextDecoder().decode(rawBody);
|
|
157
|
+
if (rawBody instanceof ArrayBuffer)
|
|
158
|
+
return new TextDecoder().decode(new Uint8Array(rawBody));
|
|
159
|
+
throw new Error('coin-moebius/dodopayments: raw body must be the unparsed request string or bytes — ' +
|
|
160
|
+
'Standard Webhooks signs the exact payload, so a pre-parsed object cannot be verified');
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Map a Dodo webhook envelope onto the SDK's `WebhookEvent` union. Returns
|
|
164
|
+
* `null` for event types the SDK doesn't model (payouts, license keys,
|
|
165
|
+
* informational dispute follow-ups, failed refunds).
|
|
166
|
+
*/
|
|
167
|
+
function toWebhookEvent(payload) {
|
|
168
|
+
const type = payload.type;
|
|
169
|
+
if (type.startsWith('subscription.')) {
|
|
170
|
+
return toSubscriptionEvent(payload);
|
|
171
|
+
}
|
|
172
|
+
const paymentStatus = mapPaymentEvent(type);
|
|
173
|
+
if (!paymentStatus)
|
|
174
|
+
return null;
|
|
175
|
+
return toPaymentEvent(payload, paymentStatus);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Map a Dodo payment/refund/dispute event type onto `PaymentResult.status`.
|
|
179
|
+
* Returns `null` for events that don't represent a payment state change the
|
|
180
|
+
* SDK surfaces (e.g. `refund.failed`, dispute lifecycle follow-ups, payouts).
|
|
181
|
+
*
|
|
182
|
+
* - `payment.succeeded` → success
|
|
183
|
+
* - `payment.processing` → pending
|
|
184
|
+
* - `payment.failed` / `.cancelled` → failed
|
|
185
|
+
* - `refund.succeeded` → refunded
|
|
186
|
+
* - `dispute.opened` → disputed
|
|
187
|
+
*
|
|
188
|
+
* Dispute resolution events (`dispute.won`, `dispute.lost`, `dispute.accepted`,
|
|
189
|
+
* etc.) are intentionally not mapped: the SDK's status enum can't represent
|
|
190
|
+
* "won/lost", and re-emitting `disputed` would double-count. Consumers needing
|
|
191
|
+
* the full dispute lifecycle can read `event.raw`.
|
|
192
|
+
*/
|
|
193
|
+
function mapPaymentEvent(type) {
|
|
194
|
+
switch (type) {
|
|
195
|
+
case 'payment.succeeded':
|
|
196
|
+
return 'success';
|
|
197
|
+
case 'payment.processing':
|
|
198
|
+
return 'pending';
|
|
199
|
+
case 'payment.failed':
|
|
200
|
+
case 'payment.cancelled':
|
|
201
|
+
return 'failed';
|
|
202
|
+
case 'refund.succeeded':
|
|
203
|
+
return 'refunded';
|
|
204
|
+
case 'dispute.opened':
|
|
205
|
+
return 'disputed';
|
|
206
|
+
default:
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function toPaymentEvent(payload, status) {
|
|
211
|
+
const data = payload.data;
|
|
212
|
+
// Refunds and disputes reference the original `payment_id`, so we key every
|
|
213
|
+
// payment-family event on it for cross-event linking (refund/dispute back to
|
|
214
|
+
// the original payment), mirroring the Stripe verifier's PaymentIntent link.
|
|
215
|
+
const paymentId = data.payment_id ?? data.subscription_id ?? '';
|
|
216
|
+
// Refund payloads carry the refunded `amount`; payment/dispute payloads carry
|
|
217
|
+
// `total_amount`. Both are minor units.
|
|
218
|
+
const minor = status === 'refunded' ? (data.amount ?? data.total_amount) : data.total_amount;
|
|
219
|
+
return {
|
|
220
|
+
kind: 'payment',
|
|
221
|
+
status,
|
|
222
|
+
paymentId,
|
|
223
|
+
provider: 'dodopayments',
|
|
224
|
+
amount: minorToMajor(minor),
|
|
225
|
+
currency: (data.currency ?? 'USD').toUpperCase(),
|
|
226
|
+
metadata: {
|
|
227
|
+
...(data.metadata ?? {}),
|
|
228
|
+
email: data.customer?.email,
|
|
229
|
+
dodoEventType: payload.type,
|
|
230
|
+
dodoStatus: data.status,
|
|
231
|
+
},
|
|
232
|
+
timestamp: Date.now(),
|
|
233
|
+
raw: payload,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Map a Dodo `subscription.*` event onto the cross-provider `SubscriptionEvent`
|
|
238
|
+
* shape.
|
|
239
|
+
*
|
|
240
|
+
* - `subscription.active` → subscription.created (initial activation)
|
|
241
|
+
* - `subscription.renewed` → subscription.renewed
|
|
242
|
+
* - `subscription.failed` → subscription.payment_failed (mandate/first charge failed)
|
|
243
|
+
* - `subscription.on_hold` → subscription.updated (paused after dunning)
|
|
244
|
+
* - `subscription.cancelled` → subscription.canceled
|
|
245
|
+
* - `subscription.expired` → subscription.canceled (reached end of term)
|
|
246
|
+
* - `subscription.plan_changed` → subscription.updated
|
|
247
|
+
* - everything else (`subscription.updated`, future types) → subscription.updated
|
|
248
|
+
*/
|
|
249
|
+
function toSubscriptionEvent(payload) {
|
|
250
|
+
const data = payload.data;
|
|
251
|
+
const type = mapSubscriptionEventType(payload.type);
|
|
252
|
+
const customerRef = data.customer?.customer_id ?? data.customer?.email ?? null;
|
|
253
|
+
return {
|
|
254
|
+
kind: 'subscription',
|
|
255
|
+
type,
|
|
256
|
+
subscriptionId: data.subscription_id ?? '',
|
|
257
|
+
provider: 'dodopayments',
|
|
258
|
+
productId: data.product_id ?? null,
|
|
259
|
+
customerRef,
|
|
260
|
+
status: mapSubscriptionStatus(data.status),
|
|
261
|
+
currentPeriodEnd: parseIsoToUnixSeconds(data.next_billing_date),
|
|
262
|
+
amount: minorToMajor(data.recurring_pre_tax_amount),
|
|
263
|
+
currency: (data.currency ?? 'USD').toUpperCase(),
|
|
264
|
+
metadata: {
|
|
265
|
+
...(data.metadata ?? {}),
|
|
266
|
+
email: data.customer?.email,
|
|
267
|
+
dodoEventType: payload.type,
|
|
268
|
+
dodoStatus: data.status,
|
|
269
|
+
},
|
|
270
|
+
timestamp: Date.now(),
|
|
271
|
+
raw: payload,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function mapSubscriptionEventType(type) {
|
|
275
|
+
switch (type) {
|
|
276
|
+
case 'subscription.active':
|
|
277
|
+
return 'subscription.created';
|
|
278
|
+
case 'subscription.renewed':
|
|
279
|
+
return 'subscription.renewed';
|
|
280
|
+
case 'subscription.failed':
|
|
281
|
+
return 'subscription.payment_failed';
|
|
282
|
+
case 'subscription.cancelled':
|
|
283
|
+
case 'subscription.expired':
|
|
284
|
+
return 'subscription.canceled';
|
|
285
|
+
default:
|
|
286
|
+
// subscription.on_hold, subscription.plan_changed, subscription.updated, …
|
|
287
|
+
return 'subscription.updated';
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Map Dodo's subscription `status` onto our neutral `SubscriptionStatus`.
|
|
292
|
+
* Provider-specific reasons stay in `metadata.dodoStatus` untouched.
|
|
293
|
+
*/
|
|
294
|
+
function mapSubscriptionStatus(status) {
|
|
295
|
+
switch (status) {
|
|
296
|
+
case 'active':
|
|
297
|
+
return 'active';
|
|
298
|
+
case 'on_hold':
|
|
299
|
+
case 'failed':
|
|
300
|
+
return 'past_due';
|
|
301
|
+
case 'paused':
|
|
302
|
+
return 'paused';
|
|
303
|
+
case 'cancelled':
|
|
304
|
+
case 'expired':
|
|
305
|
+
return 'canceled';
|
|
306
|
+
default:
|
|
307
|
+
return 'unknown';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Convert a minor-unit integer (cents) to a major-unit decimal. Mirrors the
|
|
312
|
+
* Stripe verifier's unconditional `/100`: the SDK's other fiat provider takes
|
|
313
|
+
* the same shortcut rather than carrying a per-currency exponent table, so we
|
|
314
|
+
* stay consistent. Zero-decimal currencies (JPY, etc.) would need a divisor of
|
|
315
|
+
* 1; revisit here and in the Stripe verifier together if that case ships.
|
|
316
|
+
*/
|
|
317
|
+
function minorToMajor(minor) {
|
|
318
|
+
return (minor ?? 0) / 100;
|
|
319
|
+
}
|
|
320
|
+
/** Parse an ISO 8601 timestamp to Unix seconds. Returns `null` when absent or unparseable. */
|
|
321
|
+
function parseIsoToUnixSeconds(iso) {
|
|
322
|
+
if (!iso)
|
|
323
|
+
return null;
|
|
324
|
+
const ms = Date.parse(iso);
|
|
325
|
+
return Number.isNaN(ms) ? null : Math.floor(ms / 1000);
|
|
326
|
+
}
|
|
327
|
+
function headerValue(headers, name) {
|
|
328
|
+
const lower = name.toLowerCase();
|
|
329
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
330
|
+
if (key.toLowerCase() === lower)
|
|
331
|
+
return value;
|
|
332
|
+
}
|
|
333
|
+
return undefined;
|
|
334
|
+
}
|
|
335
|
+
function timingSafeStringEqual(a, b) {
|
|
336
|
+
if (a.length !== b.length)
|
|
337
|
+
return false;
|
|
338
|
+
let mismatch = 0;
|
|
339
|
+
for (let i = 0; i < a.length; i++) {
|
|
340
|
+
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
341
|
+
}
|
|
342
|
+
return mismatch === 0;
|
|
343
|
+
}
|
|
344
|
+
function base64ToBytes(b64) {
|
|
345
|
+
const binary = atob(b64);
|
|
346
|
+
const bytes = new Uint8Array(binary.length);
|
|
347
|
+
for (let i = 0; i < binary.length; i++)
|
|
348
|
+
bytes[i] = binary.charCodeAt(i);
|
|
349
|
+
return bytes;
|
|
350
|
+
}
|
|
351
|
+
function bytesToBase64(bytes) {
|
|
352
|
+
let binary = '';
|
|
353
|
+
for (const b of bytes)
|
|
354
|
+
binary += String.fromCharCode(b);
|
|
355
|
+
return btoa(binary);
|
|
356
|
+
}
|
|
357
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aquarian-metals/coin-moebius-dodopayments",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Dodo Payments provider for Coin Moebius — Merchant-of-Record hosted checkout for one-time and subscription payments, plus a server-only Standard Webhooks verifier under the ./server subpath.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"payments",
|
|
7
|
+
"dodopayments",
|
|
8
|
+
"merchant-of-record",
|
|
9
|
+
"checkout",
|
|
10
|
+
"subscriptions",
|
|
11
|
+
"webhook",
|
|
12
|
+
"standard-webhooks",
|
|
13
|
+
"coin-moebius"
|
|
14
|
+
],
|
|
15
|
+
"author": "aquarian-metals",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"homepage": "https://github.com/aquarian-metals/coin-moebius#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/aquarian-metals/coin-moebius/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/aquarian-metals/coin-moebius.git",
|
|
24
|
+
"directory": "packages/providers/dodopayments"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"files": [
|
|
31
|
+
"dist"
|
|
32
|
+
],
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./server": {
|
|
39
|
+
"types": "./dist/server.d.ts",
|
|
40
|
+
"import": "./dist/server.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@aquarian-metals/coin-moebius-core": "^3.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"typescript": "~5.8.0"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|