@umituz/web-payment 0.0.0-stage → 1.0.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.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +349 -2
  3. package/dist/billing-adapter.interface-CDkeeowZ.d.mts +94 -0
  4. package/dist/billing-adapter.interface-m7yE50I0.d.ts +94 -0
  5. package/dist/billing-cycle.enum--hJRYlgP.d.mts +10 -0
  6. package/dist/billing-cycle.enum--hJRYlgP.d.ts +10 -0
  7. package/dist/billing-env-D4YjvA3M.d.mts +8 -0
  8. package/dist/billing-env-D4YjvA3M.d.ts +8 -0
  9. package/dist/checkout-metadata-D7Wp_DAD.d.mts +27 -0
  10. package/dist/checkout-metadata-D7Wp_DAD.d.ts +27 -0
  11. package/dist/chunk-2KV7JFYF.mjs +61 -0
  12. package/dist/chunk-2NH5DOLT.mjs +101 -0
  13. package/dist/chunk-3AXPZOCV.mjs +106 -0
  14. package/dist/chunk-4N3BASZ6.mjs +51 -0
  15. package/dist/chunk-NHKK3ENU.mjs +1128 -0
  16. package/dist/chunk-PIWNUAKW.mjs +30 -0
  17. package/dist/chunk-QFB7AOSQ.mjs +33 -0
  18. package/dist/chunk-RQTMBR74.mjs +30 -0
  19. package/dist/chunk-U45FS4FF.mjs +110 -0
  20. package/dist/chunk-X6R76FZT.mjs +0 -0
  21. package/dist/chunk-XQ2HJTTA.mjs +252 -0
  22. package/dist/core.d.mts +17 -0
  23. package/dist/core.d.ts +17 -0
  24. package/dist/core.js +425 -0
  25. package/dist/core.mjs +66 -0
  26. package/dist/http.d.mts +12 -0
  27. package/dist/http.d.ts +12 -0
  28. package/dist/http.js +115 -0
  29. package/dist/http.mjs +88 -0
  30. package/dist/index.d.mts +17 -0
  31. package/dist/index.d.ts +17 -0
  32. package/dist/index.js +1929 -0
  33. package/dist/index.mjs +153 -0
  34. package/dist/memory.d.mts +14 -0
  35. package/dist/memory.d.ts +14 -0
  36. package/dist/memory.js +130 -0
  37. package/dist/memory.mjs +6 -0
  38. package/dist/portal.d.mts +445 -0
  39. package/dist/portal.d.ts +445 -0
  40. package/dist/portal.js +2927 -0
  41. package/dist/portal.mjs +2737 -0
  42. package/dist/product.mapper-CnY1ISOp.d.ts +97 -0
  43. package/dist/product.mapper-noVl-sel.d.mts +97 -0
  44. package/dist/products.d.mts +64 -0
  45. package/dist/products.d.ts +64 -0
  46. package/dist/products.js +370 -0
  47. package/dist/products.mjs +21 -0
  48. package/dist/react.d.mts +182 -0
  49. package/dist/react.d.ts +182 -0
  50. package/dist/react.js +1874 -0
  51. package/dist/react.mjs +105 -0
  52. package/dist/server.d.mts +177 -0
  53. package/dist/server.d.ts +177 -0
  54. package/dist/server.js +622 -0
  55. package/dist/server.mjs +417 -0
  56. package/dist/visual-tokens-BCLVR85G.d.mts +94 -0
  57. package/dist/visual-tokens-BCLVR85G.d.ts +94 -0
  58. package/package.json +122 -4
  59. package/src/core.ts +81 -0
  60. package/src/domain/entities/cancellation.entity.ts +28 -0
  61. package/src/domain/entities/checkout.entity.ts +21 -0
  62. package/src/domain/entities/index.ts +10 -0
  63. package/src/domain/entities/order.entity.ts +15 -0
  64. package/src/domain/entities/product.entity.ts +16 -0
  65. package/src/domain/entities/subscription.entity.ts +24 -0
  66. package/src/domain/entities/sync.entity.ts +4 -0
  67. package/src/domain/enums/amount-type.enum.ts +10 -0
  68. package/src/domain/enums/billing-cycle.enum.ts +30 -0
  69. package/src/domain/enums/index.ts +5 -0
  70. package/src/domain/enums/payment-provider.enum.ts +14 -0
  71. package/src/domain/enums/subscription-status.enum.ts +32 -0
  72. package/src/domain/index.ts +5 -0
  73. package/src/domain/interfaces/billing-adapter.interface.ts +18 -0
  74. package/src/domain/interfaces/index.ts +1 -0
  75. package/src/domain/mappers/product.mapper.ts +176 -0
  76. package/src/domain/utils/minor-units.ts +20 -0
  77. package/src/domain/utils/percent-discount.ts +17 -0
  78. package/src/http.ts +3 -0
  79. package/src/index.ts +12 -0
  80. package/src/infrastructure/browser/hosted-checkout.ts +26 -0
  81. package/src/infrastructure/config/billing-env.ts +31 -0
  82. package/src/infrastructure/constants/billing.constants.ts +10 -0
  83. package/src/infrastructure/http/http-client.ts +83 -0
  84. package/src/infrastructure/pagination/page-iterator.ts +43 -0
  85. package/src/infrastructure/query/query-builder.ts +26 -0
  86. package/src/infrastructure/services/billing.client.ts +300 -0
  87. package/src/infrastructure/services/create-billing-client.ts +23 -0
  88. package/src/infrastructure/services/http/create-http-adapter.ts +123 -0
  89. package/src/infrastructure/services/memory/create-memory-adapter.ts +126 -0
  90. package/src/infrastructure/services/product.repository.ts +198 -0
  91. package/src/infrastructure/utils/checkout-metadata.ts +55 -0
  92. package/src/infrastructure/utils/date-renderer.ts +53 -0
  93. package/src/infrastructure/utils/money-formatter.ts +39 -0
  94. package/src/infrastructure/utils/normalization.util.ts +22 -0
  95. package/src/infrastructure/utils/pagination-clamp.ts +11 -0
  96. package/src/infrastructure/utils/receipt.util.ts +6 -0
  97. package/src/infrastructure/utils/security.util.ts +23 -0
  98. package/src/infrastructure/utils/type-guards.util.ts +23 -0
  99. package/src/infrastructure/utils/validation.util.ts +10 -0
  100. package/src/infrastructure/webhook/parse-event.ts +135 -0
  101. package/src/infrastructure/webhook/verify-webhook.ts +124 -0
  102. package/src/memory.ts +3 -0
  103. package/src/portal.ts +2 -0
  104. package/src/presentation/components/BillingHistoryList.tsx +172 -0
  105. package/src/presentation/components/BillingProvider.tsx +179 -0
  106. package/src/presentation/components/CancelSubscriptionButton.tsx +155 -0
  107. package/src/presentation/components/CheckoutButton.tsx +76 -0
  108. package/src/presentation/components/CustomerPortalLink.tsx +71 -0
  109. package/src/presentation/components/PricingTable.tsx +225 -0
  110. package/src/presentation/components/SubscriptionStatusBadge.tsx +102 -0
  111. package/src/presentation/context/billing-context.ts +30 -0
  112. package/src/presentation/hooks/useBilling.ts +3 -0
  113. package/src/presentation/hooks/useCancelSubscription.ts +47 -0
  114. package/src/presentation/hooks/useCheckoutButton.ts +49 -0
  115. package/src/presentation/hooks/useProducts.ts +76 -0
  116. package/src/presentation/navigation/navigation.strategy.ts +16 -0
  117. package/src/presentation/portal/TurnkeyBillingPage.tsx +144 -0
  118. package/src/presentation/portal/components/BillingLayout.tsx +163 -0
  119. package/src/presentation/portal/components/BillingPage.tsx +22 -0
  120. package/src/presentation/portal/components/BillingPortal.tsx +248 -0
  121. package/src/presentation/portal/components/InvoiceCard.tsx +275 -0
  122. package/src/presentation/portal/components/OverviewTab.tsx +137 -0
  123. package/src/presentation/portal/components/PaymentMethodsList.tsx +268 -0
  124. package/src/presentation/portal/components/PlanComparison.tsx +405 -0
  125. package/src/presentation/portal/components/PlanTab.tsx +197 -0
  126. package/src/presentation/portal/components/PortalButton.tsx +89 -0
  127. package/src/presentation/portal/components/PortalSpinner.tsx +30 -0
  128. package/src/presentation/portal/components/UsageCard.tsx +158 -0
  129. package/src/presentation/portal/components/index.ts +11 -0
  130. package/src/presentation/portal/constants.ts +44 -0
  131. package/src/presentation/portal/create-portal-client.ts +202 -0
  132. package/src/presentation/portal/hooks/index.ts +3 -0
  133. package/src/presentation/portal/hooks/useBilling.ts +240 -0
  134. package/src/presentation/portal/index.ts +97 -0
  135. package/src/presentation/portal/labels.tsx +218 -0
  136. package/src/presentation/portal/styles.ts +70 -0
  137. package/src/presentation/portal/types.ts +213 -0
  138. package/src/presentation/portal/utils/billing.ts +193 -0
  139. package/src/presentation/portal/utils/index.ts +27 -0
  140. package/src/presentation/portal/utils/plans.ts +71 -0
  141. package/src/presentation/theme/visual-tokens.ts +103 -0
  142. package/src/presentation/utils/cancellation-reason-catalog.ts +18 -0
  143. package/src/presentation/utils/checkout-button-visual.ts +50 -0
  144. package/src/presentation/utils/inline-error-visual.ts +21 -0
  145. package/src/presentation/utils/product-pricing.ts +33 -0
  146. package/src/presentation/utils/status-label.ts +23 -0
  147. package/src/presentation/utils/status-tone.ts +25 -0
  148. package/src/products.ts +20 -0
  149. package/src/react.tsx +63 -0
  150. package/src/server.ts +79 -0
package/dist/server.js ADDED
@@ -0,0 +1,622 @@
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/server.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ API_BASE_URL: () => API_BASE_URL,
24
+ BillingApiError: () => BillingApiError,
25
+ BillingClient: () => BillingClient,
26
+ CHECKOUT_ID_PARAM: () => CHECKOUT_ID_PARAM,
27
+ CHECKOUT_ID_PLACEHOLDER: () => CHECKOUT_ID_PLACEHOLDER,
28
+ DEFAULT_WEBHOOK_TOLERANCE_MS: () => DEFAULT_WEBHOOK_TOLERANCE_MS,
29
+ PAYMENT_PROVIDER: () => PAYMENT_PROVIDER,
30
+ PAYMENT_PROVIDER_VALUES: () => PAYMENT_PROVIDER_VALUES,
31
+ PolarBillingClient: () => PolarBillingClient,
32
+ SANDBOX_API_BASE_URL: () => SANDBOX_API_BASE_URL,
33
+ asCheckoutCompleted: () => asCheckoutCompleted,
34
+ asOrderPaid: () => asOrderPaid,
35
+ asSubscriptionChanged: () => asSubscriptionChanged,
36
+ buildCheckoutMetadata: () => buildCheckoutMetadata,
37
+ createBillingClient: () => createBillingClient,
38
+ createPaymentClient: () => createPaymentClient,
39
+ createPolarClient: () => createPolarClient,
40
+ formatReceiptNumber: () => formatReceiptNumber,
41
+ isBillingApiError: () => isBillingApiError,
42
+ isKnownPaymentProvider: () => isKnownPaymentProvider,
43
+ parsePolarWebhookEvent: () => parsePolarWebhookEvent,
44
+ parseProductIds: () => parseProductIds,
45
+ parseWebhookEvent: () => parseWebhookEvent,
46
+ productIdOf: () => productIdOf,
47
+ readCheckoutMetadata: () => readCheckoutMetadata,
48
+ sandboxEnabled: () => sandboxEnabled,
49
+ successUrlWithCheckoutId: () => successUrlWithCheckoutId,
50
+ verifyPolarWebhook: () => verifyPolarWebhook,
51
+ verifyWebhook: () => verifyWebhook
52
+ });
53
+ module.exports = __toCommonJS(server_exports);
54
+
55
+ // src/infrastructure/constants/billing.constants.ts
56
+ var API_BASE_URL = "https://api.polar.sh/v1";
57
+
58
+ // src/infrastructure/http/http-client.ts
59
+ var DEFAULT_TIMEOUT_MS = 15e3;
60
+ var FetchHttpClient = class {
61
+ async request(request) {
62
+ const init = {
63
+ method: request.method,
64
+ headers: {
65
+ Accept: "application/json",
66
+ ...request.headers ?? {}
67
+ }
68
+ };
69
+ if (request.body !== void 0) {
70
+ init.body = JSON.stringify(request.body);
71
+ init.headers["Content-Type"] = "application/json";
72
+ }
73
+ const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
74
+ const controller = new AbortController();
75
+ let timedOut = false;
76
+ const timer = setTimeout(() => {
77
+ timedOut = true;
78
+ controller.abort();
79
+ }, timeoutMs);
80
+ const forwardAbort = () => controller.abort();
81
+ if (request.signal) {
82
+ if (request.signal.aborted) controller.abort();
83
+ else request.signal.addEventListener("abort", forwardAbort);
84
+ }
85
+ init.signal = controller.signal;
86
+ let response;
87
+ try {
88
+ response = await fetch(request.url, init);
89
+ } catch (error) {
90
+ if (timedOut && error instanceof Error && error.name === "AbortError") {
91
+ throw new DOMException(`Request timed out after ${timeoutMs}ms`, "TimeoutError");
92
+ }
93
+ throw error;
94
+ } finally {
95
+ clearTimeout(timer);
96
+ request.signal?.removeEventListener("abort", forwardAbort);
97
+ }
98
+ const bodyText = await response.text();
99
+ let data = null;
100
+ if (bodyText.length > 0) {
101
+ try {
102
+ data = JSON.parse(bodyText);
103
+ } catch {
104
+ data = null;
105
+ }
106
+ }
107
+ return {
108
+ status: response.status,
109
+ ok: response.ok,
110
+ data,
111
+ text: async () => bodyText
112
+ };
113
+ }
114
+ };
115
+
116
+ // src/infrastructure/services/product.repository.ts
117
+ var BillingApiError = class _BillingApiError extends Error {
118
+ constructor(message, status, body) {
119
+ super(message);
120
+ this.status = status;
121
+ this.body = body;
122
+ this.name = "BillingApiError";
123
+ Object.setPrototypeOf(this, _BillingApiError.prototype);
124
+ }
125
+ };
126
+ function isBillingApiError(error) {
127
+ return error instanceof BillingApiError;
128
+ }
129
+ var defaultClient = new FetchHttpClient();
130
+
131
+ // src/infrastructure/services/billing.client.ts
132
+ var SANDBOX_API_BASE_URL = "https://sandbox-api.polar.sh/v1";
133
+ function resolveBaseUrl(config) {
134
+ if (config.baseUrl) return config.baseUrl;
135
+ return config.sandbox ? SANDBOX_API_BASE_URL : API_BASE_URL;
136
+ }
137
+ function mapCheckout(dto) {
138
+ return {
139
+ id: dto.id,
140
+ url: dto.url ?? "",
141
+ status: dto.status,
142
+ customerId: dto.customer_id ?? void 0,
143
+ customerEmail: dto.customer_email ?? void 0,
144
+ subscriptionId: dto.subscription_id ?? null,
145
+ metadata: dto.metadata ?? void 0
146
+ };
147
+ }
148
+ function mapSubscription(dto) {
149
+ return {
150
+ id: dto.id,
151
+ status: dto.status,
152
+ productId: dto.product_id,
153
+ amountCents: dto.amount,
154
+ currency: dto.currency,
155
+ currentPeriodStart: dto.current_period_start ?? void 0,
156
+ currentPeriodEnd: dto.current_period_end ?? void 0,
157
+ cancelAtPeriodEnd: dto.cancel_at_period_end,
158
+ canceledAt: dto.canceled_at ?? null,
159
+ endsAt: dto.ends_at ?? null,
160
+ startedAt: dto.started_at ?? void 0,
161
+ customerId: dto.customer_id ?? "",
162
+ metadata: dto.metadata ?? {}
163
+ };
164
+ }
165
+ var PolarBillingClient = class {
166
+ provider = "polar";
167
+ http;
168
+ baseUrl;
169
+ headers;
170
+ constructor(config) {
171
+ this.http = config.http ?? new FetchHttpClient();
172
+ this.baseUrl = resolveBaseUrl(config).replace(/\/$/, "");
173
+ this.headers = { Authorization: `Bearer ${config.apiToken}` };
174
+ }
175
+ async request(method, path, body) {
176
+ const response = await this.http.request({
177
+ method,
178
+ url: `${this.baseUrl}${path}`,
179
+ headers: this.headers,
180
+ body
181
+ });
182
+ if (!response.ok) {
183
+ throw new BillingApiError(
184
+ `Billing API ${method} ${path} failed with ${response.status}`,
185
+ response.status,
186
+ await response.text()
187
+ );
188
+ }
189
+ return response.data ?? {};
190
+ }
191
+ async createProduct(params) {
192
+ const dto = await this.request("POST", "/products", {
193
+ name: params.name,
194
+ recurring_interval: null,
195
+ ...params.description ? { description: params.description } : {},
196
+ prices: [
197
+ {
198
+ amount_type: "fixed",
199
+ price_currency: params.currency ?? "usd",
200
+ price_amount: params.amountCents
201
+ }
202
+ ],
203
+ ...params.metadata ? { metadata: params.metadata } : {}
204
+ });
205
+ return { id: dto.id, name: dto.name ?? params.name };
206
+ }
207
+ async createCheckout(params) {
208
+ const dto = await this.request("POST", "/checkouts", {
209
+ products: [params.productId],
210
+ ...params.amountCents !== void 0 ? {
211
+ prices: {
212
+ [params.productId]: [
213
+ {
214
+ amount_type: "fixed",
215
+ price_currency: params.currency ?? "usd",
216
+ price_amount: params.amountCents
217
+ }
218
+ ]
219
+ }
220
+ } : {},
221
+ ...params.successUrl ? { success_url: params.successUrl } : {},
222
+ ...params.customerId ? { customer_id: params.customerId } : {},
223
+ ...params.customerEmail ? { customer_email: params.customerEmail } : {},
224
+ ...params.externalCustomerId ? { external_customer_id: params.externalCustomerId } : {},
225
+ ...params.allowDiscountCodes !== void 0 ? { allow_discount_codes: params.allowDiscountCodes } : {},
226
+ ...params.metadata ? { metadata: params.metadata } : {}
227
+ });
228
+ return mapCheckout(dto);
229
+ }
230
+ async getCheckout(id) {
231
+ const dto = await this.request("GET", `/checkouts/${encodeURIComponent(id)}`);
232
+ return mapCheckout(dto);
233
+ }
234
+ async createPortalSession(customerId, returnUrl) {
235
+ const dto = await this.request(
236
+ "POST",
237
+ "/customer-sessions",
238
+ {
239
+ customer_id: customerId,
240
+ ...returnUrl ? { return_url: returnUrl } : {}
241
+ }
242
+ );
243
+ const url = dto.customer_portal_url;
244
+ if (!url) {
245
+ throw new BillingApiError("Billing customer portal session returned no URL", 500, JSON.stringify(dto));
246
+ }
247
+ return { url };
248
+ }
249
+ async getSubscription(id) {
250
+ const dto = await this.request("GET", `/subscriptions/${encodeURIComponent(id)}`);
251
+ return mapSubscription(dto);
252
+ }
253
+ async listSubscriptions(filter = {}) {
254
+ const query = new URLSearchParams();
255
+ if (filter.customerId) query.set("customer_id", filter.customerId);
256
+ if (filter.productId) query.set("product_id", filter.productId);
257
+ const suffix = query.size > 0 ? `?${query.toString()}` : "";
258
+ const first = await this.request("GET", `/subscriptions${suffix}`);
259
+ const records = [...first.items ?? []];
260
+ const maxPage = typeof first.pagination?.max_page === "number" ? first.pagination.max_page : 1;
261
+ for (let page = 2; page <= maxPage; page += 1) {
262
+ const next = await this.request(
263
+ "GET",
264
+ `/subscriptions${suffix}${suffix ? "&" : "?"}page=${page}`
265
+ );
266
+ records.push(...next.items ?? []);
267
+ }
268
+ return records.map(mapSubscription);
269
+ }
270
+ async updateSubscription(id, patch) {
271
+ const body = patch.cancelAtPeriodEnd !== void 0 ? { cancel_at_period_end: patch.cancelAtPeriodEnd } : void 0;
272
+ const dto = await this.request(
273
+ "PATCH",
274
+ `/subscriptions/${encodeURIComponent(id)}`,
275
+ body
276
+ );
277
+ return mapSubscription(dto);
278
+ }
279
+ };
280
+ var BillingClient = PolarBillingClient;
281
+ function createPaymentClient(config) {
282
+ if (config.provider === "stripe") {
283
+ throw new Error('Stripe provider client is not yet supported. Use provider "polar" or custom adapter.');
284
+ }
285
+ return new PolarBillingClient(config);
286
+ }
287
+
288
+ // src/domain/enums/payment-provider.enum.ts
289
+ var PAYMENT_PROVIDER = {
290
+ POLAR: "polar",
291
+ STRIPE: "stripe"
292
+ };
293
+ var PAYMENT_PROVIDER_VALUES = Object.values(PAYMENT_PROVIDER);
294
+ var PAYMENT_PROVIDER_SET = new Set(PAYMENT_PROVIDER_VALUES);
295
+ function isKnownPaymentProvider(value) {
296
+ return typeof value === "string" && PAYMENT_PROVIDER_SET.has(value);
297
+ }
298
+
299
+ // src/infrastructure/webhook/verify-webhook.ts
300
+ var KNOWN_SECRET_PREFIXES = ["whsec_"];
301
+ function headerValue(headers, name) {
302
+ if (typeof headers.get === "function") {
303
+ const value = headers.get(name);
304
+ return value ?? void 0;
305
+ }
306
+ const record = headers;
307
+ return record[name] ?? record[name.toLowerCase()];
308
+ }
309
+ function stripSecretPrefix(secret) {
310
+ for (const prefix of KNOWN_SECRET_PREFIXES) {
311
+ if (secret.startsWith(prefix)) return secret.slice(prefix.length);
312
+ }
313
+ return secret;
314
+ }
315
+ function base64ToBytes(value) {
316
+ try {
317
+ const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
318
+ const binary = atob(normalized);
319
+ const bytes = new Uint8Array(binary.length);
320
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
321
+ return bytes;
322
+ } catch {
323
+ return null;
324
+ }
325
+ }
326
+ function encodeUtf8(value) {
327
+ return new TextEncoder().encode(value);
328
+ }
329
+ async function hmacSha256(keyBytes, payload) {
330
+ const cryptoRef = globalThis.crypto;
331
+ if (!cryptoRef?.subtle) {
332
+ throw new Error("WebCrypto (crypto.subtle) is not available in this runtime");
333
+ }
334
+ const key = await cryptoRef.subtle.importKey(
335
+ "raw",
336
+ keyBytes,
337
+ { name: "HMAC", hash: "SHA-256" },
338
+ false,
339
+ ["sign"]
340
+ );
341
+ const signature = await cryptoRef.subtle.sign("HMAC", key, encodeUtf8(payload));
342
+ const bytes = new Uint8Array(signature);
343
+ let binary = "";
344
+ for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
345
+ return btoa(binary);
346
+ }
347
+ function keyCandidates(secret) {
348
+ const stripped = stripSecretPrefix(secret);
349
+ const decoded = base64ToBytes(stripped);
350
+ const candidates = [];
351
+ if (decoded && decoded.length > 0) candidates.push(decoded);
352
+ const raw = encodeUtf8(secret);
353
+ if (raw.length > 0) candidates.push(raw);
354
+ const rawStripped = encodeUtf8(stripped);
355
+ if (rawStripped.length > 0 && rawStripped.length !== raw.length) candidates.push(rawStripped);
356
+ return candidates;
357
+ }
358
+ function signaturesFromHeader(header) {
359
+ return header.split(",").map((part) => part.trim()).filter((part) => part.length > 0).map((part) => part.startsWith("v1,") ? part.slice(3) : part);
360
+ }
361
+ var DEFAULT_WEBHOOK_TOLERANCE_MS = 5 * 60 * 1e3;
362
+ async function verifyWebhook(rawBody, headers, secret, options = {}) {
363
+ const id = headerValue(headers, "webhook-id");
364
+ const timestamp = headerValue(headers, "webhook-timestamp");
365
+ const signatureHeader = headerValue(headers, "webhook-signature");
366
+ if (!id || !timestamp || !signatureHeader || !secret) return false;
367
+ if (options.toleranceMs !== void 0) {
368
+ const age = Math.abs(Date.now() - Number(timestamp) * 1e3);
369
+ if (!Number.isFinite(age) || age > options.toleranceMs) return false;
370
+ }
371
+ const expectedSignatures = signaturesFromHeader(signatureHeader);
372
+ if (expectedSignatures.length === 0) return false;
373
+ const payload = `${id}.${timestamp}.${rawBody}`;
374
+ for (const key of keyCandidates(secret)) {
375
+ const computed = await hmacSha256(key, payload);
376
+ for (const expected of expectedSignatures) {
377
+ if (timingSafeEqual(computed, expected)) return true;
378
+ }
379
+ }
380
+ return false;
381
+ }
382
+ function timingSafeEqual(a, b) {
383
+ if (a.length !== b.length) return false;
384
+ let mismatch = 0;
385
+ for (let i = 0; i < a.length; i += 1) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
386
+ return mismatch === 0;
387
+ }
388
+ var verifyPolarWebhook = verifyWebhook;
389
+
390
+ // src/domain/utils/minor-units.ts
391
+ var MINOR_UNIT_DIVISOR = 100;
392
+ var ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
393
+ "JPY",
394
+ "KRW",
395
+ "VND",
396
+ "CLP",
397
+ "PYG",
398
+ "XAF",
399
+ "XOF",
400
+ "BIF",
401
+ "DJF",
402
+ "GNF",
403
+ "XPF"
404
+ ]);
405
+ function minorUnitDivisor(currency) {
406
+ return ZERO_DECIMAL_CURRENCIES.has(currency.toUpperCase()) ? 1 : MINOR_UNIT_DIVISOR;
407
+ }
408
+ function toMajorUnits(amount, currency) {
409
+ return amount / minorUnitDivisor(currency);
410
+ }
411
+
412
+ // src/infrastructure/webhook/parse-event.ts
413
+ var SUBSCRIPTION_EVENT_TYPES = /* @__PURE__ */ new Set([
414
+ "subscription.active",
415
+ "subscription.canceled",
416
+ "subscription.updated",
417
+ "subscription.uncanceled",
418
+ "subscription.revoked"
419
+ ]);
420
+ function readString(value) {
421
+ return typeof value === "string" && value.length > 0 ? value : null;
422
+ }
423
+ function readNumber(value) {
424
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
425
+ }
426
+ function readObject(value) {
427
+ return value !== null && typeof value === "object" ? value : null;
428
+ }
429
+ function readMetadata(value) {
430
+ const metadata = readObject(value);
431
+ if (!metadata)
432
+ return {};
433
+ return Object.fromEntries(Object.entries(metadata).filter((entry) => typeof entry[1] === "string"));
434
+ }
435
+ function parseWebhookEvent(rawBody) {
436
+ let parsed;
437
+ try {
438
+ parsed = JSON.parse(rawBody);
439
+ } catch {
440
+ return null;
441
+ }
442
+ const envelope = readObject(parsed);
443
+ const type = envelope ? readString(envelope.type) : null;
444
+ const data = envelope ? readObject(envelope.data) : null;
445
+ if (!envelope || !type || !data)
446
+ return null;
447
+ return { type, data };
448
+ }
449
+ function asCheckoutCompleted(event) {
450
+ if (event.type !== "checkout.completed")
451
+ return null;
452
+ const data = event.data;
453
+ const product = readObject(data.product);
454
+ const customer = readObject(data.customer);
455
+ const minorUnits = readNumber(data.total_amount ?? data.amount);
456
+ const currency = readString(data.currency);
457
+ return {
458
+ checkoutId: readString(data.id),
459
+ productId: readString(data.product_id) ?? (product ? readString(product.id) : null),
460
+ customerEmail: readString(data.customer_email) ?? (customer ? readString(customer.email) : null),
461
+ customerName: readString(data.customer_name) ?? (customer ? readString(customer.name) : null),
462
+ amount: minorUnits !== null && currency ? toMajorUnits(minorUnits, currency) : null,
463
+ currency,
464
+ metadata: readMetadata(data.metadata),
465
+ createdAt: readString(data.created_at) ?? readString(data.createdAt)
466
+ };
467
+ }
468
+ function asSubscriptionChanged(event) {
469
+ if (!SUBSCRIPTION_EVENT_TYPES.has(event.type))
470
+ return null;
471
+ const data = event.data;
472
+ const minorUnits = readNumber(data.amount);
473
+ const currency = readString(data.currency);
474
+ return {
475
+ subscriptionId: readString(data.id),
476
+ status: readString(data.status),
477
+ productId: readString(data.product_id),
478
+ amount: minorUnits !== null && currency ? toMajorUnits(minorUnits, currency) : null,
479
+ currency,
480
+ currentPeriodEnd: readString(data.current_period_end),
481
+ cancelAtPeriodEnd: data.cancel_at_period_end === true,
482
+ canceledAt: readString(data.canceled_at),
483
+ endsAt: readString(data.ends_at),
484
+ customerId: readString(data.customer_id),
485
+ metadata: readMetadata(data.metadata)
486
+ };
487
+ }
488
+ function asOrderPaid(event) {
489
+ if (event.type !== "order.paid")
490
+ return null;
491
+ const data = event.data;
492
+ const product = readObject(data.product);
493
+ const customer = readObject(data.customer);
494
+ const minorUnits = readNumber(data.amount);
495
+ const currency = readString(data.currency);
496
+ return {
497
+ orderId: readString(data.id),
498
+ productId: readString(data.product_id) ?? (product ? readString(product.id) : null),
499
+ productName: readString(data.product_name) ?? (product ? readString(product.name) : null),
500
+ amount: minorUnits !== null && currency ? toMajorUnits(minorUnits, currency) : null,
501
+ currency,
502
+ customerEmail: readString(data.customer_email) ?? (customer ? readString(customer.email) : null),
503
+ metadata: readMetadata(data.metadata),
504
+ createdAt: readString(data.created_at) ?? readString(data.paid_at)
505
+ };
506
+ }
507
+ var parsePolarWebhookEvent = parseWebhookEvent;
508
+
509
+ // src/infrastructure/config/billing-env.ts
510
+ var CHECKOUT_ID_PARAM = "checkout_id";
511
+ var CHECKOUT_ID_PLACEHOLDER = "{CHECKOUT_ID}";
512
+ function parseProductIds(raw) {
513
+ const map = /* @__PURE__ */ new Map();
514
+ if (typeof raw !== "string" || raw.length === 0) return map;
515
+ let parsed;
516
+ try {
517
+ parsed = JSON.parse(raw);
518
+ } catch {
519
+ return map;
520
+ }
521
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return map;
522
+ for (const [key, value] of Object.entries(parsed)) {
523
+ if (typeof value === "string" && value.length > 0) map.set(key, value);
524
+ }
525
+ return map;
526
+ }
527
+ function productIdOf(ids, productKey) {
528
+ return ids.get(productKey) ?? null;
529
+ }
530
+ function successUrlWithCheckoutId(origin, returnPath) {
531
+ const separator = returnPath.includes("?") ? "&" : "?";
532
+ return `${origin}${returnPath}${separator}${CHECKOUT_ID_PARAM}=${CHECKOUT_ID_PLACEHOLDER}`;
533
+ }
534
+
535
+ // src/infrastructure/services/create-billing-client.ts
536
+ function sandboxEnabled(raw) {
537
+ if (typeof raw === "boolean") return raw;
538
+ return raw === "true";
539
+ }
540
+ function createPolarClient(config) {
541
+ return new PolarBillingClient({
542
+ apiToken: config.apiToken ?? "",
543
+ ...sandboxEnabled(config.sandbox) ? { sandbox: true } : {}
544
+ });
545
+ }
546
+ function createBillingClient(config) {
547
+ return createPolarClient(config);
548
+ }
549
+
550
+ // src/infrastructure/utils/checkout-metadata.ts
551
+ function asNonEmptyString(value) {
552
+ if (typeof value !== "string" || value.length === 0) return void 0;
553
+ return value;
554
+ }
555
+ function buildCheckoutMetadata(input) {
556
+ const meta = {
557
+ userId: input.userId,
558
+ itemType: input.itemType,
559
+ itemId: input.itemId
560
+ };
561
+ if (input.couponCode && input.couponCode.trim().length > 0) {
562
+ meta.couponCode = input.couponCode.trim();
563
+ }
564
+ if (input.extra) {
565
+ for (const [key, value] of Object.entries(input.extra)) {
566
+ if (typeof value === "string" && value.length > 0) {
567
+ meta[key] = value;
568
+ }
569
+ }
570
+ }
571
+ return meta;
572
+ }
573
+ function readCheckoutMetadata(metadata, allowedItemTypes) {
574
+ if (metadata === void 0) return null;
575
+ const userId = asNonEmptyString(metadata.userId);
576
+ const itemType = asNonEmptyString(metadata.itemType);
577
+ const itemId = asNonEmptyString(metadata.itemId);
578
+ if (!userId || !itemType || !itemId) return null;
579
+ if (!allowedItemTypes.includes(itemType)) return null;
580
+ const couponCode = asNonEmptyString(metadata.couponCode);
581
+ return { userId, itemType, itemId, ...couponCode ? { couponCode } : {} };
582
+ }
583
+
584
+ // src/infrastructure/utils/receipt.util.ts
585
+ function formatReceiptNumber(id, prefix = "RCP") {
586
+ if (!id || typeof id !== "string") return `${prefix}-00000000`;
587
+ const clean = id.replace(/[^a-zA-Z0-9]/g, "");
588
+ const tail = clean.length >= 8 ? clean.slice(-8) : clean.padStart(8, "0");
589
+ return `${prefix}-${tail.toUpperCase()}`;
590
+ }
591
+ // Annotate the CommonJS export names for ESM import in node:
592
+ 0 && (module.exports = {
593
+ API_BASE_URL,
594
+ BillingApiError,
595
+ BillingClient,
596
+ CHECKOUT_ID_PARAM,
597
+ CHECKOUT_ID_PLACEHOLDER,
598
+ DEFAULT_WEBHOOK_TOLERANCE_MS,
599
+ PAYMENT_PROVIDER,
600
+ PAYMENT_PROVIDER_VALUES,
601
+ PolarBillingClient,
602
+ SANDBOX_API_BASE_URL,
603
+ asCheckoutCompleted,
604
+ asOrderPaid,
605
+ asSubscriptionChanged,
606
+ buildCheckoutMetadata,
607
+ createBillingClient,
608
+ createPaymentClient,
609
+ createPolarClient,
610
+ formatReceiptNumber,
611
+ isBillingApiError,
612
+ isKnownPaymentProvider,
613
+ parsePolarWebhookEvent,
614
+ parseProductIds,
615
+ parseWebhookEvent,
616
+ productIdOf,
617
+ readCheckoutMetadata,
618
+ sandboxEnabled,
619
+ successUrlWithCheckoutId,
620
+ verifyPolarWebhook,
621
+ verifyWebhook
622
+ });