@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.
- package/LICENSE +21 -0
- package/README.md +349 -2
- package/dist/billing-adapter.interface-CDkeeowZ.d.mts +94 -0
- package/dist/billing-adapter.interface-m7yE50I0.d.ts +94 -0
- package/dist/billing-cycle.enum--hJRYlgP.d.mts +10 -0
- package/dist/billing-cycle.enum--hJRYlgP.d.ts +10 -0
- package/dist/billing-env-D4YjvA3M.d.mts +8 -0
- package/dist/billing-env-D4YjvA3M.d.ts +8 -0
- package/dist/checkout-metadata-D7Wp_DAD.d.mts +27 -0
- package/dist/checkout-metadata-D7Wp_DAD.d.ts +27 -0
- package/dist/chunk-2KV7JFYF.mjs +61 -0
- package/dist/chunk-2NH5DOLT.mjs +101 -0
- package/dist/chunk-3AXPZOCV.mjs +106 -0
- package/dist/chunk-4N3BASZ6.mjs +51 -0
- package/dist/chunk-NHKK3ENU.mjs +1128 -0
- package/dist/chunk-PIWNUAKW.mjs +30 -0
- package/dist/chunk-QFB7AOSQ.mjs +33 -0
- package/dist/chunk-RQTMBR74.mjs +30 -0
- package/dist/chunk-U45FS4FF.mjs +110 -0
- package/dist/chunk-X6R76FZT.mjs +0 -0
- package/dist/chunk-XQ2HJTTA.mjs +252 -0
- package/dist/core.d.mts +17 -0
- package/dist/core.d.ts +17 -0
- package/dist/core.js +425 -0
- package/dist/core.mjs +66 -0
- package/dist/http.d.mts +12 -0
- package/dist/http.d.ts +12 -0
- package/dist/http.js +115 -0
- package/dist/http.mjs +88 -0
- package/dist/index.d.mts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +1929 -0
- package/dist/index.mjs +153 -0
- package/dist/memory.d.mts +14 -0
- package/dist/memory.d.ts +14 -0
- package/dist/memory.js +130 -0
- package/dist/memory.mjs +6 -0
- package/dist/portal.d.mts +445 -0
- package/dist/portal.d.ts +445 -0
- package/dist/portal.js +2927 -0
- package/dist/portal.mjs +2737 -0
- package/dist/product.mapper-CnY1ISOp.d.ts +97 -0
- package/dist/product.mapper-noVl-sel.d.mts +97 -0
- package/dist/products.d.mts +64 -0
- package/dist/products.d.ts +64 -0
- package/dist/products.js +370 -0
- package/dist/products.mjs +21 -0
- package/dist/react.d.mts +182 -0
- package/dist/react.d.ts +182 -0
- package/dist/react.js +1874 -0
- package/dist/react.mjs +105 -0
- package/dist/server.d.mts +177 -0
- package/dist/server.d.ts +177 -0
- package/dist/server.js +622 -0
- package/dist/server.mjs +417 -0
- package/dist/visual-tokens-BCLVR85G.d.mts +94 -0
- package/dist/visual-tokens-BCLVR85G.d.ts +94 -0
- package/package.json +122 -4
- package/src/core.ts +81 -0
- package/src/domain/entities/cancellation.entity.ts +28 -0
- package/src/domain/entities/checkout.entity.ts +21 -0
- package/src/domain/entities/index.ts +10 -0
- package/src/domain/entities/order.entity.ts +15 -0
- package/src/domain/entities/product.entity.ts +16 -0
- package/src/domain/entities/subscription.entity.ts +24 -0
- package/src/domain/entities/sync.entity.ts +4 -0
- package/src/domain/enums/amount-type.enum.ts +10 -0
- package/src/domain/enums/billing-cycle.enum.ts +30 -0
- package/src/domain/enums/index.ts +5 -0
- package/src/domain/enums/payment-provider.enum.ts +14 -0
- package/src/domain/enums/subscription-status.enum.ts +32 -0
- package/src/domain/index.ts +5 -0
- package/src/domain/interfaces/billing-adapter.interface.ts +18 -0
- package/src/domain/interfaces/index.ts +1 -0
- package/src/domain/mappers/product.mapper.ts +176 -0
- package/src/domain/utils/minor-units.ts +20 -0
- package/src/domain/utils/percent-discount.ts +17 -0
- package/src/http.ts +3 -0
- package/src/index.ts +12 -0
- package/src/infrastructure/browser/hosted-checkout.ts +26 -0
- package/src/infrastructure/config/billing-env.ts +31 -0
- package/src/infrastructure/constants/billing.constants.ts +10 -0
- package/src/infrastructure/http/http-client.ts +83 -0
- package/src/infrastructure/pagination/page-iterator.ts +43 -0
- package/src/infrastructure/query/query-builder.ts +26 -0
- package/src/infrastructure/services/billing.client.ts +300 -0
- package/src/infrastructure/services/create-billing-client.ts +23 -0
- package/src/infrastructure/services/http/create-http-adapter.ts +123 -0
- package/src/infrastructure/services/memory/create-memory-adapter.ts +126 -0
- package/src/infrastructure/services/product.repository.ts +198 -0
- package/src/infrastructure/utils/checkout-metadata.ts +55 -0
- package/src/infrastructure/utils/date-renderer.ts +53 -0
- package/src/infrastructure/utils/money-formatter.ts +39 -0
- package/src/infrastructure/utils/normalization.util.ts +22 -0
- package/src/infrastructure/utils/pagination-clamp.ts +11 -0
- package/src/infrastructure/utils/receipt.util.ts +6 -0
- package/src/infrastructure/utils/security.util.ts +23 -0
- package/src/infrastructure/utils/type-guards.util.ts +23 -0
- package/src/infrastructure/utils/validation.util.ts +10 -0
- package/src/infrastructure/webhook/parse-event.ts +135 -0
- package/src/infrastructure/webhook/verify-webhook.ts +124 -0
- package/src/memory.ts +3 -0
- package/src/portal.ts +2 -0
- package/src/presentation/components/BillingHistoryList.tsx +172 -0
- package/src/presentation/components/BillingProvider.tsx +179 -0
- package/src/presentation/components/CancelSubscriptionButton.tsx +155 -0
- package/src/presentation/components/CheckoutButton.tsx +76 -0
- package/src/presentation/components/CustomerPortalLink.tsx +71 -0
- package/src/presentation/components/PricingTable.tsx +225 -0
- package/src/presentation/components/SubscriptionStatusBadge.tsx +102 -0
- package/src/presentation/context/billing-context.ts +30 -0
- package/src/presentation/hooks/useBilling.ts +3 -0
- package/src/presentation/hooks/useCancelSubscription.ts +47 -0
- package/src/presentation/hooks/useCheckoutButton.ts +49 -0
- package/src/presentation/hooks/useProducts.ts +76 -0
- package/src/presentation/navigation/navigation.strategy.ts +16 -0
- package/src/presentation/portal/TurnkeyBillingPage.tsx +144 -0
- package/src/presentation/portal/components/BillingLayout.tsx +163 -0
- package/src/presentation/portal/components/BillingPage.tsx +22 -0
- package/src/presentation/portal/components/BillingPortal.tsx +248 -0
- package/src/presentation/portal/components/InvoiceCard.tsx +275 -0
- package/src/presentation/portal/components/OverviewTab.tsx +137 -0
- package/src/presentation/portal/components/PaymentMethodsList.tsx +268 -0
- package/src/presentation/portal/components/PlanComparison.tsx +405 -0
- package/src/presentation/portal/components/PlanTab.tsx +197 -0
- package/src/presentation/portal/components/PortalButton.tsx +89 -0
- package/src/presentation/portal/components/PortalSpinner.tsx +30 -0
- package/src/presentation/portal/components/UsageCard.tsx +158 -0
- package/src/presentation/portal/components/index.ts +11 -0
- package/src/presentation/portal/constants.ts +44 -0
- package/src/presentation/portal/create-portal-client.ts +202 -0
- package/src/presentation/portal/hooks/index.ts +3 -0
- package/src/presentation/portal/hooks/useBilling.ts +240 -0
- package/src/presentation/portal/index.ts +97 -0
- package/src/presentation/portal/labels.tsx +218 -0
- package/src/presentation/portal/styles.ts +70 -0
- package/src/presentation/portal/types.ts +213 -0
- package/src/presentation/portal/utils/billing.ts +193 -0
- package/src/presentation/portal/utils/index.ts +27 -0
- package/src/presentation/portal/utils/plans.ts +71 -0
- package/src/presentation/theme/visual-tokens.ts +103 -0
- package/src/presentation/utils/cancellation-reason-catalog.ts +18 -0
- package/src/presentation/utils/checkout-button-visual.ts +50 -0
- package/src/presentation/utils/inline-error-visual.ts +21 -0
- package/src/presentation/utils/product-pricing.ts +33 -0
- package/src/presentation/utils/status-label.ts +23 -0
- package/src/presentation/utils/status-tone.ts +25 -0
- package/src/products.ts +20 -0
- package/src/react.tsx +63 -0
- package/src/server.ts +79 -0
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHECKOUT_ID_PARAM,
|
|
3
|
+
CHECKOUT_ID_PLACEHOLDER,
|
|
4
|
+
parseProductIds,
|
|
5
|
+
productIdOf,
|
|
6
|
+
successUrlWithCheckoutId
|
|
7
|
+
} from "./chunk-QFB7AOSQ.mjs";
|
|
8
|
+
import {
|
|
9
|
+
PAYMENT_PROVIDER,
|
|
10
|
+
PAYMENT_PROVIDER_VALUES,
|
|
11
|
+
buildCheckoutMetadata,
|
|
12
|
+
formatReceiptNumber,
|
|
13
|
+
isKnownPaymentProvider,
|
|
14
|
+
readCheckoutMetadata
|
|
15
|
+
} from "./chunk-2KV7JFYF.mjs";
|
|
16
|
+
import {
|
|
17
|
+
toMajorUnits
|
|
18
|
+
} from "./chunk-PIWNUAKW.mjs";
|
|
19
|
+
import {
|
|
20
|
+
API_BASE_URL,
|
|
21
|
+
BillingApiError,
|
|
22
|
+
FetchHttpClient,
|
|
23
|
+
isBillingApiError
|
|
24
|
+
} from "./chunk-XQ2HJTTA.mjs";
|
|
25
|
+
import "./chunk-U45FS4FF.mjs";
|
|
26
|
+
import "./chunk-RQTMBR74.mjs";
|
|
27
|
+
|
|
28
|
+
// src/infrastructure/services/billing.client.ts
|
|
29
|
+
var SANDBOX_API_BASE_URL = "https://sandbox-api.polar.sh/v1";
|
|
30
|
+
function resolveBaseUrl(config) {
|
|
31
|
+
if (config.baseUrl) return config.baseUrl;
|
|
32
|
+
return config.sandbox ? SANDBOX_API_BASE_URL : API_BASE_URL;
|
|
33
|
+
}
|
|
34
|
+
function mapCheckout(dto) {
|
|
35
|
+
return {
|
|
36
|
+
id: dto.id,
|
|
37
|
+
url: dto.url ?? "",
|
|
38
|
+
status: dto.status,
|
|
39
|
+
customerId: dto.customer_id ?? void 0,
|
|
40
|
+
customerEmail: dto.customer_email ?? void 0,
|
|
41
|
+
subscriptionId: dto.subscription_id ?? null,
|
|
42
|
+
metadata: dto.metadata ?? void 0
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function mapSubscription(dto) {
|
|
46
|
+
return {
|
|
47
|
+
id: dto.id,
|
|
48
|
+
status: dto.status,
|
|
49
|
+
productId: dto.product_id,
|
|
50
|
+
amountCents: dto.amount,
|
|
51
|
+
currency: dto.currency,
|
|
52
|
+
currentPeriodStart: dto.current_period_start ?? void 0,
|
|
53
|
+
currentPeriodEnd: dto.current_period_end ?? void 0,
|
|
54
|
+
cancelAtPeriodEnd: dto.cancel_at_period_end,
|
|
55
|
+
canceledAt: dto.canceled_at ?? null,
|
|
56
|
+
endsAt: dto.ends_at ?? null,
|
|
57
|
+
startedAt: dto.started_at ?? void 0,
|
|
58
|
+
customerId: dto.customer_id ?? "",
|
|
59
|
+
metadata: dto.metadata ?? {}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
var PolarBillingClient = class {
|
|
63
|
+
provider = "polar";
|
|
64
|
+
http;
|
|
65
|
+
baseUrl;
|
|
66
|
+
headers;
|
|
67
|
+
constructor(config) {
|
|
68
|
+
this.http = config.http ?? new FetchHttpClient();
|
|
69
|
+
this.baseUrl = resolveBaseUrl(config).replace(/\/$/, "");
|
|
70
|
+
this.headers = { Authorization: `Bearer ${config.apiToken}` };
|
|
71
|
+
}
|
|
72
|
+
async request(method, path, body) {
|
|
73
|
+
const response = await this.http.request({
|
|
74
|
+
method,
|
|
75
|
+
url: `${this.baseUrl}${path}`,
|
|
76
|
+
headers: this.headers,
|
|
77
|
+
body
|
|
78
|
+
});
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new BillingApiError(
|
|
81
|
+
`Billing API ${method} ${path} failed with ${response.status}`,
|
|
82
|
+
response.status,
|
|
83
|
+
await response.text()
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return response.data ?? {};
|
|
87
|
+
}
|
|
88
|
+
async createProduct(params) {
|
|
89
|
+
const dto = await this.request("POST", "/products", {
|
|
90
|
+
name: params.name,
|
|
91
|
+
recurring_interval: null,
|
|
92
|
+
...params.description ? { description: params.description } : {},
|
|
93
|
+
prices: [
|
|
94
|
+
{
|
|
95
|
+
amount_type: "fixed",
|
|
96
|
+
price_currency: params.currency ?? "usd",
|
|
97
|
+
price_amount: params.amountCents
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
...params.metadata ? { metadata: params.metadata } : {}
|
|
101
|
+
});
|
|
102
|
+
return { id: dto.id, name: dto.name ?? params.name };
|
|
103
|
+
}
|
|
104
|
+
async createCheckout(params) {
|
|
105
|
+
const dto = await this.request("POST", "/checkouts", {
|
|
106
|
+
products: [params.productId],
|
|
107
|
+
...params.amountCents !== void 0 ? {
|
|
108
|
+
prices: {
|
|
109
|
+
[params.productId]: [
|
|
110
|
+
{
|
|
111
|
+
amount_type: "fixed",
|
|
112
|
+
price_currency: params.currency ?? "usd",
|
|
113
|
+
price_amount: params.amountCents
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
} : {},
|
|
118
|
+
...params.successUrl ? { success_url: params.successUrl } : {},
|
|
119
|
+
...params.customerId ? { customer_id: params.customerId } : {},
|
|
120
|
+
...params.customerEmail ? { customer_email: params.customerEmail } : {},
|
|
121
|
+
...params.externalCustomerId ? { external_customer_id: params.externalCustomerId } : {},
|
|
122
|
+
...params.allowDiscountCodes !== void 0 ? { allow_discount_codes: params.allowDiscountCodes } : {},
|
|
123
|
+
...params.metadata ? { metadata: params.metadata } : {}
|
|
124
|
+
});
|
|
125
|
+
return mapCheckout(dto);
|
|
126
|
+
}
|
|
127
|
+
async getCheckout(id) {
|
|
128
|
+
const dto = await this.request("GET", `/checkouts/${encodeURIComponent(id)}`);
|
|
129
|
+
return mapCheckout(dto);
|
|
130
|
+
}
|
|
131
|
+
async createPortalSession(customerId, returnUrl) {
|
|
132
|
+
const dto = await this.request(
|
|
133
|
+
"POST",
|
|
134
|
+
"/customer-sessions",
|
|
135
|
+
{
|
|
136
|
+
customer_id: customerId,
|
|
137
|
+
...returnUrl ? { return_url: returnUrl } : {}
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
const url = dto.customer_portal_url;
|
|
141
|
+
if (!url) {
|
|
142
|
+
throw new BillingApiError("Billing customer portal session returned no URL", 500, JSON.stringify(dto));
|
|
143
|
+
}
|
|
144
|
+
return { url };
|
|
145
|
+
}
|
|
146
|
+
async getSubscription(id) {
|
|
147
|
+
const dto = await this.request("GET", `/subscriptions/${encodeURIComponent(id)}`);
|
|
148
|
+
return mapSubscription(dto);
|
|
149
|
+
}
|
|
150
|
+
async listSubscriptions(filter = {}) {
|
|
151
|
+
const query = new URLSearchParams();
|
|
152
|
+
if (filter.customerId) query.set("customer_id", filter.customerId);
|
|
153
|
+
if (filter.productId) query.set("product_id", filter.productId);
|
|
154
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : "";
|
|
155
|
+
const first = await this.request("GET", `/subscriptions${suffix}`);
|
|
156
|
+
const records = [...first.items ?? []];
|
|
157
|
+
const maxPage = typeof first.pagination?.max_page === "number" ? first.pagination.max_page : 1;
|
|
158
|
+
for (let page = 2; page <= maxPage; page += 1) {
|
|
159
|
+
const next = await this.request(
|
|
160
|
+
"GET",
|
|
161
|
+
`/subscriptions${suffix}${suffix ? "&" : "?"}page=${page}`
|
|
162
|
+
);
|
|
163
|
+
records.push(...next.items ?? []);
|
|
164
|
+
}
|
|
165
|
+
return records.map(mapSubscription);
|
|
166
|
+
}
|
|
167
|
+
async updateSubscription(id, patch) {
|
|
168
|
+
const body = patch.cancelAtPeriodEnd !== void 0 ? { cancel_at_period_end: patch.cancelAtPeriodEnd } : void 0;
|
|
169
|
+
const dto = await this.request(
|
|
170
|
+
"PATCH",
|
|
171
|
+
`/subscriptions/${encodeURIComponent(id)}`,
|
|
172
|
+
body
|
|
173
|
+
);
|
|
174
|
+
return mapSubscription(dto);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var BillingClient = PolarBillingClient;
|
|
178
|
+
function createPaymentClient(config) {
|
|
179
|
+
if (config.provider === "stripe") {
|
|
180
|
+
throw new Error('Stripe provider client is not yet supported. Use provider "polar" or custom adapter.');
|
|
181
|
+
}
|
|
182
|
+
return new PolarBillingClient(config);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/infrastructure/webhook/verify-webhook.ts
|
|
186
|
+
var KNOWN_SECRET_PREFIXES = ["whsec_"];
|
|
187
|
+
function headerValue(headers, name) {
|
|
188
|
+
if (typeof headers.get === "function") {
|
|
189
|
+
const value = headers.get(name);
|
|
190
|
+
return value ?? void 0;
|
|
191
|
+
}
|
|
192
|
+
const record = headers;
|
|
193
|
+
return record[name] ?? record[name.toLowerCase()];
|
|
194
|
+
}
|
|
195
|
+
function stripSecretPrefix(secret) {
|
|
196
|
+
for (const prefix of KNOWN_SECRET_PREFIXES) {
|
|
197
|
+
if (secret.startsWith(prefix)) return secret.slice(prefix.length);
|
|
198
|
+
}
|
|
199
|
+
return secret;
|
|
200
|
+
}
|
|
201
|
+
function base64ToBytes(value) {
|
|
202
|
+
try {
|
|
203
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
204
|
+
const binary = atob(normalized);
|
|
205
|
+
const bytes = new Uint8Array(binary.length);
|
|
206
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
207
|
+
return bytes;
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function encodeUtf8(value) {
|
|
213
|
+
return new TextEncoder().encode(value);
|
|
214
|
+
}
|
|
215
|
+
async function hmacSha256(keyBytes, payload) {
|
|
216
|
+
const cryptoRef = globalThis.crypto;
|
|
217
|
+
if (!cryptoRef?.subtle) {
|
|
218
|
+
throw new Error("WebCrypto (crypto.subtle) is not available in this runtime");
|
|
219
|
+
}
|
|
220
|
+
const key = await cryptoRef.subtle.importKey(
|
|
221
|
+
"raw",
|
|
222
|
+
keyBytes,
|
|
223
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
224
|
+
false,
|
|
225
|
+
["sign"]
|
|
226
|
+
);
|
|
227
|
+
const signature = await cryptoRef.subtle.sign("HMAC", key, encodeUtf8(payload));
|
|
228
|
+
const bytes = new Uint8Array(signature);
|
|
229
|
+
let binary = "";
|
|
230
|
+
for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
|
|
231
|
+
return btoa(binary);
|
|
232
|
+
}
|
|
233
|
+
function keyCandidates(secret) {
|
|
234
|
+
const stripped = stripSecretPrefix(secret);
|
|
235
|
+
const decoded = base64ToBytes(stripped);
|
|
236
|
+
const candidates = [];
|
|
237
|
+
if (decoded && decoded.length > 0) candidates.push(decoded);
|
|
238
|
+
const raw = encodeUtf8(secret);
|
|
239
|
+
if (raw.length > 0) candidates.push(raw);
|
|
240
|
+
const rawStripped = encodeUtf8(stripped);
|
|
241
|
+
if (rawStripped.length > 0 && rawStripped.length !== raw.length) candidates.push(rawStripped);
|
|
242
|
+
return candidates;
|
|
243
|
+
}
|
|
244
|
+
function signaturesFromHeader(header) {
|
|
245
|
+
return header.split(",").map((part) => part.trim()).filter((part) => part.length > 0).map((part) => part.startsWith("v1,") ? part.slice(3) : part);
|
|
246
|
+
}
|
|
247
|
+
var DEFAULT_WEBHOOK_TOLERANCE_MS = 5 * 60 * 1e3;
|
|
248
|
+
async function verifyWebhook(rawBody, headers, secret, options = {}) {
|
|
249
|
+
const id = headerValue(headers, "webhook-id");
|
|
250
|
+
const timestamp = headerValue(headers, "webhook-timestamp");
|
|
251
|
+
const signatureHeader = headerValue(headers, "webhook-signature");
|
|
252
|
+
if (!id || !timestamp || !signatureHeader || !secret) return false;
|
|
253
|
+
if (options.toleranceMs !== void 0) {
|
|
254
|
+
const age = Math.abs(Date.now() - Number(timestamp) * 1e3);
|
|
255
|
+
if (!Number.isFinite(age) || age > options.toleranceMs) return false;
|
|
256
|
+
}
|
|
257
|
+
const expectedSignatures = signaturesFromHeader(signatureHeader);
|
|
258
|
+
if (expectedSignatures.length === 0) return false;
|
|
259
|
+
const payload = `${id}.${timestamp}.${rawBody}`;
|
|
260
|
+
for (const key of keyCandidates(secret)) {
|
|
261
|
+
const computed = await hmacSha256(key, payload);
|
|
262
|
+
for (const expected of expectedSignatures) {
|
|
263
|
+
if (timingSafeEqual(computed, expected)) return true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
function timingSafeEqual(a, b) {
|
|
269
|
+
if (a.length !== b.length) return false;
|
|
270
|
+
let mismatch = 0;
|
|
271
|
+
for (let i = 0; i < a.length; i += 1) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
272
|
+
return mismatch === 0;
|
|
273
|
+
}
|
|
274
|
+
var verifyPolarWebhook = verifyWebhook;
|
|
275
|
+
|
|
276
|
+
// src/infrastructure/webhook/parse-event.ts
|
|
277
|
+
var SUBSCRIPTION_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
278
|
+
"subscription.active",
|
|
279
|
+
"subscription.canceled",
|
|
280
|
+
"subscription.updated",
|
|
281
|
+
"subscription.uncanceled",
|
|
282
|
+
"subscription.revoked"
|
|
283
|
+
]);
|
|
284
|
+
function readString(value) {
|
|
285
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
286
|
+
}
|
|
287
|
+
function readNumber(value) {
|
|
288
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
289
|
+
}
|
|
290
|
+
function readObject(value) {
|
|
291
|
+
return value !== null && typeof value === "object" ? value : null;
|
|
292
|
+
}
|
|
293
|
+
function readMetadata(value) {
|
|
294
|
+
const metadata = readObject(value);
|
|
295
|
+
if (!metadata)
|
|
296
|
+
return {};
|
|
297
|
+
return Object.fromEntries(Object.entries(metadata).filter((entry) => typeof entry[1] === "string"));
|
|
298
|
+
}
|
|
299
|
+
function parseWebhookEvent(rawBody) {
|
|
300
|
+
let parsed;
|
|
301
|
+
try {
|
|
302
|
+
parsed = JSON.parse(rawBody);
|
|
303
|
+
} catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
const envelope = readObject(parsed);
|
|
307
|
+
const type = envelope ? readString(envelope.type) : null;
|
|
308
|
+
const data = envelope ? readObject(envelope.data) : null;
|
|
309
|
+
if (!envelope || !type || !data)
|
|
310
|
+
return null;
|
|
311
|
+
return { type, data };
|
|
312
|
+
}
|
|
313
|
+
function asCheckoutCompleted(event) {
|
|
314
|
+
if (event.type !== "checkout.completed")
|
|
315
|
+
return null;
|
|
316
|
+
const data = event.data;
|
|
317
|
+
const product = readObject(data.product);
|
|
318
|
+
const customer = readObject(data.customer);
|
|
319
|
+
const minorUnits = readNumber(data.total_amount ?? data.amount);
|
|
320
|
+
const currency = readString(data.currency);
|
|
321
|
+
return {
|
|
322
|
+
checkoutId: readString(data.id),
|
|
323
|
+
productId: readString(data.product_id) ?? (product ? readString(product.id) : null),
|
|
324
|
+
customerEmail: readString(data.customer_email) ?? (customer ? readString(customer.email) : null),
|
|
325
|
+
customerName: readString(data.customer_name) ?? (customer ? readString(customer.name) : null),
|
|
326
|
+
amount: minorUnits !== null && currency ? toMajorUnits(minorUnits, currency) : null,
|
|
327
|
+
currency,
|
|
328
|
+
metadata: readMetadata(data.metadata),
|
|
329
|
+
createdAt: readString(data.created_at) ?? readString(data.createdAt)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function asSubscriptionChanged(event) {
|
|
333
|
+
if (!SUBSCRIPTION_EVENT_TYPES.has(event.type))
|
|
334
|
+
return null;
|
|
335
|
+
const data = event.data;
|
|
336
|
+
const minorUnits = readNumber(data.amount);
|
|
337
|
+
const currency = readString(data.currency);
|
|
338
|
+
return {
|
|
339
|
+
subscriptionId: readString(data.id),
|
|
340
|
+
status: readString(data.status),
|
|
341
|
+
productId: readString(data.product_id),
|
|
342
|
+
amount: minorUnits !== null && currency ? toMajorUnits(minorUnits, currency) : null,
|
|
343
|
+
currency,
|
|
344
|
+
currentPeriodEnd: readString(data.current_period_end),
|
|
345
|
+
cancelAtPeriodEnd: data.cancel_at_period_end === true,
|
|
346
|
+
canceledAt: readString(data.canceled_at),
|
|
347
|
+
endsAt: readString(data.ends_at),
|
|
348
|
+
customerId: readString(data.customer_id),
|
|
349
|
+
metadata: readMetadata(data.metadata)
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function asOrderPaid(event) {
|
|
353
|
+
if (event.type !== "order.paid")
|
|
354
|
+
return null;
|
|
355
|
+
const data = event.data;
|
|
356
|
+
const product = readObject(data.product);
|
|
357
|
+
const customer = readObject(data.customer);
|
|
358
|
+
const minorUnits = readNumber(data.amount);
|
|
359
|
+
const currency = readString(data.currency);
|
|
360
|
+
return {
|
|
361
|
+
orderId: readString(data.id),
|
|
362
|
+
productId: readString(data.product_id) ?? (product ? readString(product.id) : null),
|
|
363
|
+
productName: readString(data.product_name) ?? (product ? readString(product.name) : null),
|
|
364
|
+
amount: minorUnits !== null && currency ? toMajorUnits(minorUnits, currency) : null,
|
|
365
|
+
currency,
|
|
366
|
+
customerEmail: readString(data.customer_email) ?? (customer ? readString(customer.email) : null),
|
|
367
|
+
metadata: readMetadata(data.metadata),
|
|
368
|
+
createdAt: readString(data.created_at) ?? readString(data.paid_at)
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
var parsePolarWebhookEvent = parseWebhookEvent;
|
|
372
|
+
|
|
373
|
+
// src/infrastructure/services/create-billing-client.ts
|
|
374
|
+
function sandboxEnabled(raw) {
|
|
375
|
+
if (typeof raw === "boolean") return raw;
|
|
376
|
+
return raw === "true";
|
|
377
|
+
}
|
|
378
|
+
function createPolarClient(config) {
|
|
379
|
+
return new PolarBillingClient({
|
|
380
|
+
apiToken: config.apiToken ?? "",
|
|
381
|
+
...sandboxEnabled(config.sandbox) ? { sandbox: true } : {}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
function createBillingClient(config) {
|
|
385
|
+
return createPolarClient(config);
|
|
386
|
+
}
|
|
387
|
+
export {
|
|
388
|
+
API_BASE_URL,
|
|
389
|
+
BillingApiError,
|
|
390
|
+
BillingClient,
|
|
391
|
+
CHECKOUT_ID_PARAM,
|
|
392
|
+
CHECKOUT_ID_PLACEHOLDER,
|
|
393
|
+
DEFAULT_WEBHOOK_TOLERANCE_MS,
|
|
394
|
+
PAYMENT_PROVIDER,
|
|
395
|
+
PAYMENT_PROVIDER_VALUES,
|
|
396
|
+
PolarBillingClient,
|
|
397
|
+
SANDBOX_API_BASE_URL,
|
|
398
|
+
asCheckoutCompleted,
|
|
399
|
+
asOrderPaid,
|
|
400
|
+
asSubscriptionChanged,
|
|
401
|
+
buildCheckoutMetadata,
|
|
402
|
+
createBillingClient,
|
|
403
|
+
createPaymentClient,
|
|
404
|
+
createPolarClient,
|
|
405
|
+
formatReceiptNumber,
|
|
406
|
+
isBillingApiError,
|
|
407
|
+
isKnownPaymentProvider,
|
|
408
|
+
parsePolarWebhookEvent,
|
|
409
|
+
parseProductIds,
|
|
410
|
+
parseWebhookEvent,
|
|
411
|
+
productIdOf,
|
|
412
|
+
readCheckoutMetadata,
|
|
413
|
+
sandboxEnabled,
|
|
414
|
+
successUrlWithCheckoutId,
|
|
415
|
+
verifyPolarWebhook,
|
|
416
|
+
verifyWebhook
|
|
417
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
interface NavigationStrategy {
|
|
2
|
+
redirect(url: string): void;
|
|
3
|
+
}
|
|
4
|
+
declare class WindowNavigationStrategy implements NavigationStrategy {
|
|
5
|
+
redirect(url: string): void;
|
|
6
|
+
}
|
|
7
|
+
declare class NoopNavigationStrategy implements NavigationStrategy {
|
|
8
|
+
redirect(_url: string): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare const VisualTokens: Readonly<{
|
|
12
|
+
readonly spacing: Readonly<{
|
|
13
|
+
none: 0;
|
|
14
|
+
xs: 4;
|
|
15
|
+
sm: 6;
|
|
16
|
+
md: 8;
|
|
17
|
+
lg: 12;
|
|
18
|
+
xl: 16;
|
|
19
|
+
xxl: 20;
|
|
20
|
+
section: 24;
|
|
21
|
+
page: 32;
|
|
22
|
+
}>;
|
|
23
|
+
readonly radius: Readonly<{
|
|
24
|
+
sm: 6;
|
|
25
|
+
md: 8;
|
|
26
|
+
lg: 12;
|
|
27
|
+
pill: 999;
|
|
28
|
+
}>;
|
|
29
|
+
readonly fontSize: Readonly<{
|
|
30
|
+
caption: 12;
|
|
31
|
+
body: 14;
|
|
32
|
+
title: 18;
|
|
33
|
+
display: 24;
|
|
34
|
+
}>;
|
|
35
|
+
readonly fontWeight: Readonly<{
|
|
36
|
+
regular: 400;
|
|
37
|
+
medium: 500;
|
|
38
|
+
strong: 600;
|
|
39
|
+
}>;
|
|
40
|
+
readonly lineHeight: Readonly<{
|
|
41
|
+
tight: 1.25;
|
|
42
|
+
normal: 1.5;
|
|
43
|
+
}>;
|
|
44
|
+
readonly opacity: Readonly<{
|
|
45
|
+
muted: 0.7;
|
|
46
|
+
disabled: 0.6;
|
|
47
|
+
hover: 0.85;
|
|
48
|
+
}>;
|
|
49
|
+
readonly color: Readonly<{
|
|
50
|
+
textPrimary: "#111827";
|
|
51
|
+
textMuted: "#6b7280";
|
|
52
|
+
textOnPrimary: "#ffffff";
|
|
53
|
+
textInverse: "#ffffff";
|
|
54
|
+
textError: "#b91c1c";
|
|
55
|
+
textOnError: "#ffffff";
|
|
56
|
+
surfacePrimary: "#ffffff";
|
|
57
|
+
surfaceMuted: "#f9fafb";
|
|
58
|
+
surfaceSubtle: "#f3f4f6";
|
|
59
|
+
surfaceWarning: "#fef3c7";
|
|
60
|
+
surfaceDanger: "#fee2e2";
|
|
61
|
+
surfaceDangerMuted: "#fef2f2";
|
|
62
|
+
borderSubtle: "#e5e7eb";
|
|
63
|
+
borderStrong: "#d1d5db";
|
|
64
|
+
borderWarning: "#fecaca";
|
|
65
|
+
brandPrimary: "#2563eb";
|
|
66
|
+
brandPrimaryHover: "#1d4ed8";
|
|
67
|
+
brandDanger: "#dc2626";
|
|
68
|
+
brandDangerHover: "#b91c1c";
|
|
69
|
+
brandDangerText: "#991b1b";
|
|
70
|
+
badgeNeutralBg: "#e5e7eb";
|
|
71
|
+
badgeNeutralFg: "#111827";
|
|
72
|
+
badgeActiveBg: "#dcfce7";
|
|
73
|
+
badgeActiveFg: "#166534";
|
|
74
|
+
badgeWarningBg: "#fef3c7";
|
|
75
|
+
badgeWarningFg: "#92400e";
|
|
76
|
+
badgeDangerBg: "#fee2e2";
|
|
77
|
+
badgeDangerFg: "#991b1b";
|
|
78
|
+
}>;
|
|
79
|
+
readonly transition: Readonly<{
|
|
80
|
+
instant: "120ms ease";
|
|
81
|
+
}>;
|
|
82
|
+
readonly layout: Readonly<{
|
|
83
|
+
cardMinWidth: 260;
|
|
84
|
+
benefitListMax: 5;
|
|
85
|
+
}>;
|
|
86
|
+
}>;
|
|
87
|
+
type VisualTone = 'neutral' | 'active' | 'warning' | 'danger';
|
|
88
|
+
interface VisualTonePair {
|
|
89
|
+
readonly bg: string;
|
|
90
|
+
readonly fg: string;
|
|
91
|
+
}
|
|
92
|
+
declare const TONE_PAIRS: Readonly<Record<VisualTone, VisualTonePair>>;
|
|
93
|
+
|
|
94
|
+
export { type NavigationStrategy as N, TONE_PAIRS as T, VisualTokens as V, WindowNavigationStrategy as W, NoopNavigationStrategy as a, type VisualTone as b, type VisualTonePair as c };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
interface NavigationStrategy {
|
|
2
|
+
redirect(url: string): void;
|
|
3
|
+
}
|
|
4
|
+
declare class WindowNavigationStrategy implements NavigationStrategy {
|
|
5
|
+
redirect(url: string): void;
|
|
6
|
+
}
|
|
7
|
+
declare class NoopNavigationStrategy implements NavigationStrategy {
|
|
8
|
+
redirect(_url: string): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare const VisualTokens: Readonly<{
|
|
12
|
+
readonly spacing: Readonly<{
|
|
13
|
+
none: 0;
|
|
14
|
+
xs: 4;
|
|
15
|
+
sm: 6;
|
|
16
|
+
md: 8;
|
|
17
|
+
lg: 12;
|
|
18
|
+
xl: 16;
|
|
19
|
+
xxl: 20;
|
|
20
|
+
section: 24;
|
|
21
|
+
page: 32;
|
|
22
|
+
}>;
|
|
23
|
+
readonly radius: Readonly<{
|
|
24
|
+
sm: 6;
|
|
25
|
+
md: 8;
|
|
26
|
+
lg: 12;
|
|
27
|
+
pill: 999;
|
|
28
|
+
}>;
|
|
29
|
+
readonly fontSize: Readonly<{
|
|
30
|
+
caption: 12;
|
|
31
|
+
body: 14;
|
|
32
|
+
title: 18;
|
|
33
|
+
display: 24;
|
|
34
|
+
}>;
|
|
35
|
+
readonly fontWeight: Readonly<{
|
|
36
|
+
regular: 400;
|
|
37
|
+
medium: 500;
|
|
38
|
+
strong: 600;
|
|
39
|
+
}>;
|
|
40
|
+
readonly lineHeight: Readonly<{
|
|
41
|
+
tight: 1.25;
|
|
42
|
+
normal: 1.5;
|
|
43
|
+
}>;
|
|
44
|
+
readonly opacity: Readonly<{
|
|
45
|
+
muted: 0.7;
|
|
46
|
+
disabled: 0.6;
|
|
47
|
+
hover: 0.85;
|
|
48
|
+
}>;
|
|
49
|
+
readonly color: Readonly<{
|
|
50
|
+
textPrimary: "#111827";
|
|
51
|
+
textMuted: "#6b7280";
|
|
52
|
+
textOnPrimary: "#ffffff";
|
|
53
|
+
textInverse: "#ffffff";
|
|
54
|
+
textError: "#b91c1c";
|
|
55
|
+
textOnError: "#ffffff";
|
|
56
|
+
surfacePrimary: "#ffffff";
|
|
57
|
+
surfaceMuted: "#f9fafb";
|
|
58
|
+
surfaceSubtle: "#f3f4f6";
|
|
59
|
+
surfaceWarning: "#fef3c7";
|
|
60
|
+
surfaceDanger: "#fee2e2";
|
|
61
|
+
surfaceDangerMuted: "#fef2f2";
|
|
62
|
+
borderSubtle: "#e5e7eb";
|
|
63
|
+
borderStrong: "#d1d5db";
|
|
64
|
+
borderWarning: "#fecaca";
|
|
65
|
+
brandPrimary: "#2563eb";
|
|
66
|
+
brandPrimaryHover: "#1d4ed8";
|
|
67
|
+
brandDanger: "#dc2626";
|
|
68
|
+
brandDangerHover: "#b91c1c";
|
|
69
|
+
brandDangerText: "#991b1b";
|
|
70
|
+
badgeNeutralBg: "#e5e7eb";
|
|
71
|
+
badgeNeutralFg: "#111827";
|
|
72
|
+
badgeActiveBg: "#dcfce7";
|
|
73
|
+
badgeActiveFg: "#166534";
|
|
74
|
+
badgeWarningBg: "#fef3c7";
|
|
75
|
+
badgeWarningFg: "#92400e";
|
|
76
|
+
badgeDangerBg: "#fee2e2";
|
|
77
|
+
badgeDangerFg: "#991b1b";
|
|
78
|
+
}>;
|
|
79
|
+
readonly transition: Readonly<{
|
|
80
|
+
instant: "120ms ease";
|
|
81
|
+
}>;
|
|
82
|
+
readonly layout: Readonly<{
|
|
83
|
+
cardMinWidth: 260;
|
|
84
|
+
benefitListMax: 5;
|
|
85
|
+
}>;
|
|
86
|
+
}>;
|
|
87
|
+
type VisualTone = 'neutral' | 'active' | 'warning' | 'danger';
|
|
88
|
+
interface VisualTonePair {
|
|
89
|
+
readonly bg: string;
|
|
90
|
+
readonly fg: string;
|
|
91
|
+
}
|
|
92
|
+
declare const TONE_PAIRS: Readonly<Record<VisualTone, VisualTonePair>>;
|
|
93
|
+
|
|
94
|
+
export { type NavigationStrategy as N, TONE_PAIRS as T, VisualTokens as V, WindowNavigationStrategy as W, NoopNavigationStrategy as a, type VisualTone as b, type VisualTonePair as c };
|