@openbilling/dodo 0.1.0-alpha.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 George Dimitrov
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,18 @@
1
+ # @openbilling/dodo
2
+
3
+ Dodo Payments adapter for OpenBilling.
4
+
5
+ `@openbilling/dodo` provides a fetch-based Dodo Payments adapter that implements the shared `@openbilling/core` billing contract. It supports the current OpenBilling MVP workflows for hosted checkout creation, customer portal links, webhook verification, and normalized webhook mapping.
6
+
7
+ This package keeps Dodo-specific behavior visible where it matters. Checkout creation is product-based and currently requires `productId`; Dodo determines whether the checkout is one-time or recurring from the configured product.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @openbilling/core @openbilling/dodo
13
+ ```
14
+
15
+ ## Links
16
+
17
+ - Documentation: [openbilling.geodim.dev](https://openbilling.geodim.dev)
18
+ - Repository: [github.com/gndimitro/openbilling](https://github.com/gndimitro/openbilling)
package/dist/index.cjs ADDED
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DodoProviderError: () => DodoProviderError,
24
+ createDodoProvider: () => createDodoProvider
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var import_standardwebhooks = require("standardwebhooks");
28
+ var import_core = require("@openbilling/core");
29
+ var DEFAULT_BASE_URL = "https://live.dodopayments.com";
30
+ var textDecoder = new TextDecoder();
31
+ var DodoProviderError = class extends Error {
32
+ /** Provider-specific error classification. */
33
+ code;
34
+ /** HTTP status returned by Dodo when the error came from an API response. */
35
+ status;
36
+ constructor(message, code, status, options) {
37
+ super(message, options);
38
+ this.name = "DodoProviderError";
39
+ this.code = code;
40
+ if (status !== void 0) {
41
+ this.status = status;
42
+ }
43
+ }
44
+ };
45
+ function createDodoProvider(config) {
46
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
47
+ return (0, import_core.createBilling)({
48
+ async createCheckout(input) {
49
+ const response = await postJson(
50
+ `${baseUrl}/checkouts`,
51
+ config.apiKey,
52
+ buildCheckoutRequest(input)
53
+ );
54
+ if (!response.checkout_url) {
55
+ throw new DodoProviderError("Dodo checkout session did not include a checkout URL.", "api_error");
56
+ }
57
+ return {
58
+ id: response.session_id,
59
+ url: response.checkout_url,
60
+ provider: import_core.Provider.Dodo,
61
+ raw: response
62
+ };
63
+ },
64
+ async createPortalLink(input) {
65
+ const endpoint = new URL(
66
+ `${baseUrl}/customers/${encodeURIComponent(input.customerId)}/customer-portal/session`
67
+ );
68
+ endpoint.searchParams.set("return_url", input.returnUrl);
69
+ const response = await postJson(endpoint.toString(), config.apiKey);
70
+ return {
71
+ url: response.link,
72
+ provider: import_core.Provider.Dodo,
73
+ raw: response
74
+ };
75
+ },
76
+ async verifyWebhook(input) {
77
+ const headers = getRequiredWebhookHeaders(input.headers);
78
+ const secret = input.secret ?? config.webhookSecret;
79
+ const payload = toWebhookPayload(input.payload);
80
+ try {
81
+ const event = new import_standardwebhooks.Webhook(secret).verify(payload, headers);
82
+ return normalizeWebhookEvent(event);
83
+ } catch (error) {
84
+ throw new DodoProviderError("Dodo webhook signature verification failed.", "invalid_webhook_signature", void 0, {
85
+ cause: error
86
+ });
87
+ }
88
+ }
89
+ });
90
+ }
91
+ function normalizeBaseUrl(baseUrl) {
92
+ return (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
93
+ }
94
+ function buildCheckoutRequest(input) {
95
+ if (!input.productId) {
96
+ throw new DodoProviderError(
97
+ input.priceId ? "Dodo checkout sessions currently require a productId instead of a priceId." : "Dodo checkout sessions require a productId.",
98
+ "unsupported_input"
99
+ );
100
+ }
101
+ const request = {
102
+ product_cart: [
103
+ {
104
+ product_id: input.productId,
105
+ quantity: 1
106
+ }
107
+ ],
108
+ return_url: input.successUrl,
109
+ cancel_url: input.cancelUrl
110
+ };
111
+ if (input.customerId) {
112
+ request.customer_id = input.customerId;
113
+ } else if (input.customerEmail) {
114
+ request.customer = {
115
+ email: input.customerEmail
116
+ };
117
+ }
118
+ if (input.metadata) {
119
+ request.metadata = input.metadata;
120
+ }
121
+ return request;
122
+ }
123
+ function getRequiredWebhookHeaders(headers) {
124
+ const webhookId = headers?.["webhook-id"];
125
+ const webhookSignature = headers?.["webhook-signature"];
126
+ const webhookTimestamp = headers?.["webhook-timestamp"];
127
+ if (!webhookId || !webhookSignature || !webhookTimestamp) {
128
+ throw new DodoProviderError(
129
+ "Dodo webhook verification requires webhook-id, webhook-signature, and webhook-timestamp headers.",
130
+ "invalid_webhook_headers"
131
+ );
132
+ }
133
+ return {
134
+ "webhook-id": webhookId,
135
+ "webhook-signature": webhookSignature,
136
+ "webhook-timestamp": webhookTimestamp
137
+ };
138
+ }
139
+ function toWebhookPayload(payload) {
140
+ return typeof payload === "string" ? payload : textDecoder.decode(payload);
141
+ }
142
+ function normalizeWebhookEvent(event) {
143
+ const payload = event;
144
+ const customerId = payload.data?.customer?.customer_id;
145
+ switch (payload.type) {
146
+ case import_core.Payment.Succeeded:
147
+ if (!payload.data?.payment_id) {
148
+ break;
149
+ }
150
+ return {
151
+ type: import_core.Payment.Succeeded,
152
+ provider: import_core.Provider.Dodo,
153
+ paymentId: payload.data.payment_id,
154
+ ...customerId ? { customerId } : {},
155
+ raw: event
156
+ };
157
+ case import_core.Subscription.Active:
158
+ if (customerId && payload.data?.subscription_id) {
159
+ return {
160
+ type: import_core.Subscription.Active,
161
+ provider: import_core.Provider.Dodo,
162
+ customerId,
163
+ subscriptionId: payload.data.subscription_id,
164
+ raw: event
165
+ };
166
+ }
167
+ break;
168
+ case import_core.Subscription.Cancelled:
169
+ if (customerId && payload.data?.subscription_id) {
170
+ return {
171
+ type: import_core.Subscription.Cancelled,
172
+ provider: import_core.Provider.Dodo,
173
+ customerId,
174
+ subscriptionId: payload.data.subscription_id,
175
+ raw: event
176
+ };
177
+ }
178
+ break;
179
+ }
180
+ return {
181
+ type: import_core.Webhook.Unknown,
182
+ provider: import_core.Provider.Dodo,
183
+ raw: event
184
+ };
185
+ }
186
+ async function postJson(url, apiKey, body) {
187
+ const headers = {
188
+ authorization: `Bearer ${apiKey}`
189
+ };
190
+ if (body !== void 0) {
191
+ headers["content-type"] = "application/json";
192
+ }
193
+ const requestInit = {
194
+ method: "POST",
195
+ headers,
196
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
197
+ };
198
+ const response = await fetch(url, requestInit);
199
+ const responseBody = await readResponseBody(response);
200
+ if (!response.ok) {
201
+ throw createApiError(response, responseBody);
202
+ }
203
+ return responseBody;
204
+ }
205
+ async function readResponseBody(response) {
206
+ const contentType = response.headers.get("content-type") ?? "";
207
+ if (contentType.includes("application/json")) {
208
+ return response.json();
209
+ }
210
+ return response.text();
211
+ }
212
+ function createApiError(response, responseBody) {
213
+ const message = getApiErrorMessage(responseBody) ?? `Dodo API request failed with status ${response.status}${response.statusText ? ` ${response.statusText}` : ""}.`;
214
+ return new DodoProviderError(message, "api_error", response.status);
215
+ }
216
+ function getApiErrorMessage(responseBody) {
217
+ if (!responseBody || typeof responseBody !== "object") {
218
+ return void 0;
219
+ }
220
+ if ("message" in responseBody && typeof responseBody.message === "string") {
221
+ return responseBody.message;
222
+ }
223
+ if ("error" in responseBody && typeof responseBody.error === "string") {
224
+ return responseBody.error;
225
+ }
226
+ return void 0;
227
+ }
228
+ // Annotate the CommonJS export names for ESM import in node:
229
+ 0 && (module.exports = {
230
+ DodoProviderError,
231
+ createDodoProvider
232
+ });
@@ -0,0 +1,76 @@
1
+ import { BillingProvider } from '@openbilling/core';
2
+
3
+ type DodoErrorCode = "unsupported_input" | "api_error" | "invalid_webhook_headers" | "invalid_webhook_signature";
4
+ /**
5
+ * Configuration for the Dodo Payments provider adapter.
6
+ *
7
+ * The current Dodo adapter is intentionally narrow and only covers the hosted
8
+ * checkout, customer portal, and webhook flows documented in the root README.
9
+ */
10
+ interface DodoProviderConfig {
11
+ /** Secret API key used for Dodo REST API requests. */
12
+ apiKey: string;
13
+ /** Webhook signing secret used by `standardwebhooks` verification. */
14
+ webhookSecret: string;
15
+ /** Optional API host override. Defaults to Dodo's live API base URL. */
16
+ baseUrl?: string;
17
+ }
18
+ /**
19
+ * Error raised by the Dodo provider adapter.
20
+ *
21
+ * The `code` field is stable enough for app-level branching, while `status`
22
+ * is included when the failure originated from the Dodo HTTP API.
23
+ */
24
+ declare class DodoProviderError extends Error {
25
+ /** Provider-specific error classification. */
26
+ readonly code: DodoErrorCode;
27
+ /** HTTP status returned by Dodo when the error came from an API response. */
28
+ readonly status?: number;
29
+ constructor(message: string, code: DodoErrorCode, status?: number, options?: ErrorOptions);
30
+ }
31
+ /**
32
+ * Creates a fetch-based Dodo Payments adapter that implements the shared
33
+ * `@openbilling/core` billing contract.
34
+ *
35
+ * The current MVP intentionally supports a narrow Dodo surface:
36
+ * - checkout creation through `POST /checkouts`
37
+ * - customer billing management through customer portal sessions
38
+ * - webhook verification plus normalization for a small set of events
39
+ *
40
+ * Important provider caveats:
41
+ * - Dodo currently requires `productId` for checkout creation
42
+ * - `priceId` alone is not treated as a portable substitute
43
+ * - the Dodo product determines whether a checkout is one-time or recurring
44
+ * - the adapter defaults to `https://live.dodopayments.com`, with `baseUrl`
45
+ * available for test mode or custom hosts
46
+ *
47
+ * Supported normalized Dodo webhook coverage:
48
+ * - `payment.succeeded`
49
+ * - `subscription.active`
50
+ * - `subscription.cancelled`
51
+ *
52
+ * Unsupported Dodo events resolve to {@link Webhook.Unknown} instead of
53
+ * throwing purely because the event is outside the current MVP.
54
+ *
55
+ * @throws {DodoProviderError} When input is unsupported, webhook verification
56
+ * fails, or the Dodo API returns an error response.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const billing = createDodoProvider({
61
+ * apiKey: "dodo_api_key",
62
+ * webhookSecret: "whsec_123"
63
+ * });
64
+ *
65
+ * const checkout = await billing.createCheckout({
66
+ * productId: "prod_123",
67
+ * customerEmail: "demo@example.com",
68
+ * successUrl: "https://example.com/success",
69
+ * cancelUrl: "https://example.com/cancel",
70
+ * mode: "subscription"
71
+ * });
72
+ * ```
73
+ */
74
+ declare function createDodoProvider(config: DodoProviderConfig): BillingProvider;
75
+
76
+ export { type DodoProviderConfig, DodoProviderError, createDodoProvider };
@@ -0,0 +1,76 @@
1
+ import { BillingProvider } from '@openbilling/core';
2
+
3
+ type DodoErrorCode = "unsupported_input" | "api_error" | "invalid_webhook_headers" | "invalid_webhook_signature";
4
+ /**
5
+ * Configuration for the Dodo Payments provider adapter.
6
+ *
7
+ * The current Dodo adapter is intentionally narrow and only covers the hosted
8
+ * checkout, customer portal, and webhook flows documented in the root README.
9
+ */
10
+ interface DodoProviderConfig {
11
+ /** Secret API key used for Dodo REST API requests. */
12
+ apiKey: string;
13
+ /** Webhook signing secret used by `standardwebhooks` verification. */
14
+ webhookSecret: string;
15
+ /** Optional API host override. Defaults to Dodo's live API base URL. */
16
+ baseUrl?: string;
17
+ }
18
+ /**
19
+ * Error raised by the Dodo provider adapter.
20
+ *
21
+ * The `code` field is stable enough for app-level branching, while `status`
22
+ * is included when the failure originated from the Dodo HTTP API.
23
+ */
24
+ declare class DodoProviderError extends Error {
25
+ /** Provider-specific error classification. */
26
+ readonly code: DodoErrorCode;
27
+ /** HTTP status returned by Dodo when the error came from an API response. */
28
+ readonly status?: number;
29
+ constructor(message: string, code: DodoErrorCode, status?: number, options?: ErrorOptions);
30
+ }
31
+ /**
32
+ * Creates a fetch-based Dodo Payments adapter that implements the shared
33
+ * `@openbilling/core` billing contract.
34
+ *
35
+ * The current MVP intentionally supports a narrow Dodo surface:
36
+ * - checkout creation through `POST /checkouts`
37
+ * - customer billing management through customer portal sessions
38
+ * - webhook verification plus normalization for a small set of events
39
+ *
40
+ * Important provider caveats:
41
+ * - Dodo currently requires `productId` for checkout creation
42
+ * - `priceId` alone is not treated as a portable substitute
43
+ * - the Dodo product determines whether a checkout is one-time or recurring
44
+ * - the adapter defaults to `https://live.dodopayments.com`, with `baseUrl`
45
+ * available for test mode or custom hosts
46
+ *
47
+ * Supported normalized Dodo webhook coverage:
48
+ * - `payment.succeeded`
49
+ * - `subscription.active`
50
+ * - `subscription.cancelled`
51
+ *
52
+ * Unsupported Dodo events resolve to {@link Webhook.Unknown} instead of
53
+ * throwing purely because the event is outside the current MVP.
54
+ *
55
+ * @throws {DodoProviderError} When input is unsupported, webhook verification
56
+ * fails, or the Dodo API returns an error response.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const billing = createDodoProvider({
61
+ * apiKey: "dodo_api_key",
62
+ * webhookSecret: "whsec_123"
63
+ * });
64
+ *
65
+ * const checkout = await billing.createCheckout({
66
+ * productId: "prod_123",
67
+ * customerEmail: "demo@example.com",
68
+ * successUrl: "https://example.com/success",
69
+ * cancelUrl: "https://example.com/cancel",
70
+ * mode: "subscription"
71
+ * });
72
+ * ```
73
+ */
74
+ declare function createDodoProvider(config: DodoProviderConfig): BillingProvider;
75
+
76
+ export { type DodoProviderConfig, DodoProviderError, createDodoProvider };
package/dist/index.js ADDED
@@ -0,0 +1,212 @@
1
+ // src/index.ts
2
+ import { Webhook as StandardWebhook } from "standardwebhooks";
3
+ import {
4
+ Payment,
5
+ Provider,
6
+ Subscription,
7
+ Webhook,
8
+ createBilling
9
+ } from "@openbilling/core";
10
+ var DEFAULT_BASE_URL = "https://live.dodopayments.com";
11
+ var textDecoder = new TextDecoder();
12
+ var DodoProviderError = class extends Error {
13
+ /** Provider-specific error classification. */
14
+ code;
15
+ /** HTTP status returned by Dodo when the error came from an API response. */
16
+ status;
17
+ constructor(message, code, status, options) {
18
+ super(message, options);
19
+ this.name = "DodoProviderError";
20
+ this.code = code;
21
+ if (status !== void 0) {
22
+ this.status = status;
23
+ }
24
+ }
25
+ };
26
+ function createDodoProvider(config) {
27
+ const baseUrl = normalizeBaseUrl(config.baseUrl);
28
+ return createBilling({
29
+ async createCheckout(input) {
30
+ const response = await postJson(
31
+ `${baseUrl}/checkouts`,
32
+ config.apiKey,
33
+ buildCheckoutRequest(input)
34
+ );
35
+ if (!response.checkout_url) {
36
+ throw new DodoProviderError("Dodo checkout session did not include a checkout URL.", "api_error");
37
+ }
38
+ return {
39
+ id: response.session_id,
40
+ url: response.checkout_url,
41
+ provider: Provider.Dodo,
42
+ raw: response
43
+ };
44
+ },
45
+ async createPortalLink(input) {
46
+ const endpoint = new URL(
47
+ `${baseUrl}/customers/${encodeURIComponent(input.customerId)}/customer-portal/session`
48
+ );
49
+ endpoint.searchParams.set("return_url", input.returnUrl);
50
+ const response = await postJson(endpoint.toString(), config.apiKey);
51
+ return {
52
+ url: response.link,
53
+ provider: Provider.Dodo,
54
+ raw: response
55
+ };
56
+ },
57
+ async verifyWebhook(input) {
58
+ const headers = getRequiredWebhookHeaders(input.headers);
59
+ const secret = input.secret ?? config.webhookSecret;
60
+ const payload = toWebhookPayload(input.payload);
61
+ try {
62
+ const event = new StandardWebhook(secret).verify(payload, headers);
63
+ return normalizeWebhookEvent(event);
64
+ } catch (error) {
65
+ throw new DodoProviderError("Dodo webhook signature verification failed.", "invalid_webhook_signature", void 0, {
66
+ cause: error
67
+ });
68
+ }
69
+ }
70
+ });
71
+ }
72
+ function normalizeBaseUrl(baseUrl) {
73
+ return (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
74
+ }
75
+ function buildCheckoutRequest(input) {
76
+ if (!input.productId) {
77
+ throw new DodoProviderError(
78
+ input.priceId ? "Dodo checkout sessions currently require a productId instead of a priceId." : "Dodo checkout sessions require a productId.",
79
+ "unsupported_input"
80
+ );
81
+ }
82
+ const request = {
83
+ product_cart: [
84
+ {
85
+ product_id: input.productId,
86
+ quantity: 1
87
+ }
88
+ ],
89
+ return_url: input.successUrl,
90
+ cancel_url: input.cancelUrl
91
+ };
92
+ if (input.customerId) {
93
+ request.customer_id = input.customerId;
94
+ } else if (input.customerEmail) {
95
+ request.customer = {
96
+ email: input.customerEmail
97
+ };
98
+ }
99
+ if (input.metadata) {
100
+ request.metadata = input.metadata;
101
+ }
102
+ return request;
103
+ }
104
+ function getRequiredWebhookHeaders(headers) {
105
+ const webhookId = headers?.["webhook-id"];
106
+ const webhookSignature = headers?.["webhook-signature"];
107
+ const webhookTimestamp = headers?.["webhook-timestamp"];
108
+ if (!webhookId || !webhookSignature || !webhookTimestamp) {
109
+ throw new DodoProviderError(
110
+ "Dodo webhook verification requires webhook-id, webhook-signature, and webhook-timestamp headers.",
111
+ "invalid_webhook_headers"
112
+ );
113
+ }
114
+ return {
115
+ "webhook-id": webhookId,
116
+ "webhook-signature": webhookSignature,
117
+ "webhook-timestamp": webhookTimestamp
118
+ };
119
+ }
120
+ function toWebhookPayload(payload) {
121
+ return typeof payload === "string" ? payload : textDecoder.decode(payload);
122
+ }
123
+ function normalizeWebhookEvent(event) {
124
+ const payload = event;
125
+ const customerId = payload.data?.customer?.customer_id;
126
+ switch (payload.type) {
127
+ case Payment.Succeeded:
128
+ if (!payload.data?.payment_id) {
129
+ break;
130
+ }
131
+ return {
132
+ type: Payment.Succeeded,
133
+ provider: Provider.Dodo,
134
+ paymentId: payload.data.payment_id,
135
+ ...customerId ? { customerId } : {},
136
+ raw: event
137
+ };
138
+ case Subscription.Active:
139
+ if (customerId && payload.data?.subscription_id) {
140
+ return {
141
+ type: Subscription.Active,
142
+ provider: Provider.Dodo,
143
+ customerId,
144
+ subscriptionId: payload.data.subscription_id,
145
+ raw: event
146
+ };
147
+ }
148
+ break;
149
+ case Subscription.Cancelled:
150
+ if (customerId && payload.data?.subscription_id) {
151
+ return {
152
+ type: Subscription.Cancelled,
153
+ provider: Provider.Dodo,
154
+ customerId,
155
+ subscriptionId: payload.data.subscription_id,
156
+ raw: event
157
+ };
158
+ }
159
+ break;
160
+ }
161
+ return {
162
+ type: Webhook.Unknown,
163
+ provider: Provider.Dodo,
164
+ raw: event
165
+ };
166
+ }
167
+ async function postJson(url, apiKey, body) {
168
+ const headers = {
169
+ authorization: `Bearer ${apiKey}`
170
+ };
171
+ if (body !== void 0) {
172
+ headers["content-type"] = "application/json";
173
+ }
174
+ const requestInit = {
175
+ method: "POST",
176
+ headers,
177
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
178
+ };
179
+ const response = await fetch(url, requestInit);
180
+ const responseBody = await readResponseBody(response);
181
+ if (!response.ok) {
182
+ throw createApiError(response, responseBody);
183
+ }
184
+ return responseBody;
185
+ }
186
+ async function readResponseBody(response) {
187
+ const contentType = response.headers.get("content-type") ?? "";
188
+ if (contentType.includes("application/json")) {
189
+ return response.json();
190
+ }
191
+ return response.text();
192
+ }
193
+ function createApiError(response, responseBody) {
194
+ const message = getApiErrorMessage(responseBody) ?? `Dodo API request failed with status ${response.status}${response.statusText ? ` ${response.statusText}` : ""}.`;
195
+ return new DodoProviderError(message, "api_error", response.status);
196
+ }
197
+ function getApiErrorMessage(responseBody) {
198
+ if (!responseBody || typeof responseBody !== "object") {
199
+ return void 0;
200
+ }
201
+ if ("message" in responseBody && typeof responseBody.message === "string") {
202
+ return responseBody.message;
203
+ }
204
+ if ("error" in responseBody && typeof responseBody.error === "string") {
205
+ return responseBody.error;
206
+ }
207
+ return void 0;
208
+ }
209
+ export {
210
+ DodoProviderError,
211
+ createDodoProvider
212
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@openbilling/dodo",
3
+ "version": "0.1.0-alpha.1",
4
+ "homepage": "https://openbilling.geodim.dev",
5
+ "description": "Switch between Stripe and Dodo Payments without rewriting your billing logic",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/gndimitro/openbilling",
9
+ "directory": "packages/dodo"
10
+ },
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "billing",
14
+ "payments",
15
+ "stripe",
16
+ "dodo"
17
+ ],
18
+ "type": "module",
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js",
29
+ "require": "./dist/index.cjs"
30
+ }
31
+ },
32
+ "dependencies": {
33
+ "standardwebhooks": "^1.0.0",
34
+ "@openbilling/core": "0.1.0-alpha.1"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
41
+ "dev": "tsup src/index.ts --format esm,cjs --dts --clean --watch",
42
+ "test": "vitest run",
43
+ "typecheck": "tsc --project tsconfig.json --noEmit"
44
+ }
45
+ }