@parsrun/payments 0.1.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/README.md +184 -0
- package/dist/billing/index.d.ts +121 -0
- package/dist/billing/index.js +1082 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing-service-LsAFesou.d.ts +578 -0
- package/dist/dunning/index.d.ts +310 -0
- package/dist/dunning/index.js +2677 -0
- package/dist/dunning/index.js.map +1 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +7698 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js +1396 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/iyzico.d.ts +250 -0
- package/dist/providers/iyzico.js +469 -0
- package/dist/providers/iyzico.js.map +1 -0
- package/dist/providers/paddle.d.ts +66 -0
- package/dist/providers/paddle.js +437 -0
- package/dist/providers/paddle.js.map +1 -0
- package/dist/providers/stripe.d.ts +122 -0
- package/dist/providers/stripe.js +586 -0
- package/dist/providers/stripe.js.map +1 -0
- package/dist/schema-C5Zcju_j.d.ts +4191 -0
- package/dist/types.d.ts +388 -0
- package/dist/types.js +74 -0
- package/dist/types.js.map +1 -0
- package/dist/usage/index.d.ts +2674 -0
- package/dist/usage/index.js +2916 -0
- package/dist/usage/index.js.map +1 -0
- package/dist/webhooks/index.d.ts +89 -0
- package/dist/webhooks/index.js +188 -0
- package/dist/webhooks/index.js.map +1 -0
- package/package.json +91 -0
|
@@ -0,0 +1,1396 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
import {
|
|
3
|
+
type,
|
|
4
|
+
currencyCode,
|
|
5
|
+
money,
|
|
6
|
+
paymentCustomer,
|
|
7
|
+
createCustomerRequest,
|
|
8
|
+
cardDetails,
|
|
9
|
+
paymentMethod,
|
|
10
|
+
paymentIntentStatus,
|
|
11
|
+
paymentIntent,
|
|
12
|
+
createPaymentIntentRequest,
|
|
13
|
+
subscriptionStatus,
|
|
14
|
+
priceInterval,
|
|
15
|
+
price,
|
|
16
|
+
subscription,
|
|
17
|
+
createSubscriptionRequest,
|
|
18
|
+
refundStatus,
|
|
19
|
+
refund,
|
|
20
|
+
createRefundRequest,
|
|
21
|
+
webhookEventType,
|
|
22
|
+
webhookEvent,
|
|
23
|
+
stripeConfig,
|
|
24
|
+
paddleConfig,
|
|
25
|
+
iyzicoConfig,
|
|
26
|
+
paymentsConfig
|
|
27
|
+
} from "@parsrun/types";
|
|
28
|
+
var PaymentError = class extends Error {
|
|
29
|
+
constructor(message, code, cause) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.code = code;
|
|
32
|
+
this.cause = cause;
|
|
33
|
+
this.name = "PaymentError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var PaymentErrorCodes = {
|
|
37
|
+
INVALID_CONFIG: "INVALID_CONFIG",
|
|
38
|
+
CUSTOMER_NOT_FOUND: "CUSTOMER_NOT_FOUND",
|
|
39
|
+
SUBSCRIPTION_NOT_FOUND: "SUBSCRIPTION_NOT_FOUND",
|
|
40
|
+
CHECKOUT_FAILED: "CHECKOUT_FAILED",
|
|
41
|
+
PAYMENT_FAILED: "PAYMENT_FAILED",
|
|
42
|
+
WEBHOOK_VERIFICATION_FAILED: "WEBHOOK_VERIFICATION_FAILED",
|
|
43
|
+
API_ERROR: "API_ERROR",
|
|
44
|
+
RATE_LIMITED: "RATE_LIMITED"
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/providers/stripe.ts
|
|
48
|
+
var StripeProvider = class {
|
|
49
|
+
type = "stripe";
|
|
50
|
+
secretKey;
|
|
51
|
+
webhookSecret;
|
|
52
|
+
baseUrl = "https://api.stripe.com/v1";
|
|
53
|
+
apiVersion;
|
|
54
|
+
constructor(config) {
|
|
55
|
+
this.secretKey = config.secretKey;
|
|
56
|
+
this.webhookSecret = config.webhookSecret;
|
|
57
|
+
this.apiVersion = config.apiVersion ?? "2024-12-18.acacia";
|
|
58
|
+
}
|
|
59
|
+
async request(endpoint, options = {}) {
|
|
60
|
+
const { method = "GET", body } = options;
|
|
61
|
+
const headers = {
|
|
62
|
+
Authorization: `Bearer ${this.secretKey}`,
|
|
63
|
+
"Stripe-Version": this.apiVersion
|
|
64
|
+
};
|
|
65
|
+
const fetchOptions = {
|
|
66
|
+
method,
|
|
67
|
+
headers
|
|
68
|
+
};
|
|
69
|
+
if (body) {
|
|
70
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
71
|
+
fetchOptions.body = this.encodeFormData(body);
|
|
72
|
+
}
|
|
73
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, fetchOptions);
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
if (!response.ok || data.error) {
|
|
76
|
+
const errorMessage = data.error?.message ?? `HTTP ${response.status}`;
|
|
77
|
+
throw new PaymentError(
|
|
78
|
+
`Stripe API error: ${errorMessage}`,
|
|
79
|
+
data.error?.code ?? PaymentErrorCodes.API_ERROR,
|
|
80
|
+
data.error
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
encodeFormData(obj, prefix = "") {
|
|
86
|
+
const parts = [];
|
|
87
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
88
|
+
if (value === void 0 || value === null) continue;
|
|
89
|
+
const fullKey = prefix ? `${prefix}[${key}]` : key;
|
|
90
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
91
|
+
parts.push(this.encodeFormData(value, fullKey));
|
|
92
|
+
} else if (Array.isArray(value)) {
|
|
93
|
+
value.forEach((item, index) => {
|
|
94
|
+
if (typeof item === "object") {
|
|
95
|
+
parts.push(this.encodeFormData(item, `${fullKey}[${index}]`));
|
|
96
|
+
} else {
|
|
97
|
+
parts.push(`${encodeURIComponent(`${fullKey}[${index}]`)}=${encodeURIComponent(String(item))}`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
parts.push(`${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return parts.filter(Boolean).join("&");
|
|
105
|
+
}
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Customer
|
|
108
|
+
// ============================================================================
|
|
109
|
+
async createCustomer(options) {
|
|
110
|
+
const body = {
|
|
111
|
+
email: options.email
|
|
112
|
+
};
|
|
113
|
+
if (options.name) body["name"] = options.name;
|
|
114
|
+
if (options.phone) body["phone"] = options.phone;
|
|
115
|
+
if (options.metadata) body["metadata"] = options.metadata;
|
|
116
|
+
if (options.address) {
|
|
117
|
+
body["address"] = {
|
|
118
|
+
line1: options.address.line1,
|
|
119
|
+
line2: options.address.line2,
|
|
120
|
+
city: options.address.city,
|
|
121
|
+
state: options.address.state,
|
|
122
|
+
postal_code: options.address.postalCode,
|
|
123
|
+
country: options.address.country
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const result = await this.request("/customers", {
|
|
127
|
+
method: "POST",
|
|
128
|
+
body
|
|
129
|
+
});
|
|
130
|
+
return this.mapCustomer(result);
|
|
131
|
+
}
|
|
132
|
+
async getCustomer(customerId) {
|
|
133
|
+
try {
|
|
134
|
+
const result = await this.request(`/customers/${customerId}`);
|
|
135
|
+
return this.mapCustomer(result);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (err instanceof PaymentError && err.code === "resource_missing") {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async updateCustomer(customerId, options) {
|
|
144
|
+
const body = {};
|
|
145
|
+
if (options.email) body["email"] = options.email;
|
|
146
|
+
if (options.name) body["name"] = options.name;
|
|
147
|
+
if (options.phone) body["phone"] = options.phone;
|
|
148
|
+
if (options.metadata) body["metadata"] = options.metadata;
|
|
149
|
+
if (options.address) {
|
|
150
|
+
body["address"] = {
|
|
151
|
+
line1: options.address.line1,
|
|
152
|
+
line2: options.address.line2,
|
|
153
|
+
city: options.address.city,
|
|
154
|
+
state: options.address.state,
|
|
155
|
+
postal_code: options.address.postalCode,
|
|
156
|
+
country: options.address.country
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const result = await this.request(`/customers/${customerId}`, {
|
|
160
|
+
method: "POST",
|
|
161
|
+
body
|
|
162
|
+
});
|
|
163
|
+
return this.mapCustomer(result);
|
|
164
|
+
}
|
|
165
|
+
async deleteCustomer(customerId) {
|
|
166
|
+
await this.request(`/customers/${customerId}`, { method: "DELETE" });
|
|
167
|
+
}
|
|
168
|
+
mapCustomer(stripe) {
|
|
169
|
+
return {
|
|
170
|
+
id: stripe.id,
|
|
171
|
+
email: stripe.email ?? "",
|
|
172
|
+
name: stripe.name ?? void 0,
|
|
173
|
+
phone: stripe.phone ?? void 0,
|
|
174
|
+
address: stripe.address ? {
|
|
175
|
+
line1: stripe.address.line1 ?? void 0,
|
|
176
|
+
line2: stripe.address.line2 ?? void 0,
|
|
177
|
+
city: stripe.address.city ?? void 0,
|
|
178
|
+
state: stripe.address.state ?? void 0,
|
|
179
|
+
postalCode: stripe.address.postal_code ?? void 0,
|
|
180
|
+
country: stripe.address.country ?? void 0
|
|
181
|
+
} : void 0,
|
|
182
|
+
metadata: stripe.metadata ?? void 0,
|
|
183
|
+
providerData: stripe
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// Checkout
|
|
188
|
+
// ============================================================================
|
|
189
|
+
async createCheckout(options) {
|
|
190
|
+
const body = {
|
|
191
|
+
mode: options.mode,
|
|
192
|
+
success_url: options.successUrl,
|
|
193
|
+
cancel_url: options.cancelUrl,
|
|
194
|
+
line_items: options.lineItems.map((item) => ({
|
|
195
|
+
price: item.priceId,
|
|
196
|
+
quantity: item.quantity
|
|
197
|
+
}))
|
|
198
|
+
};
|
|
199
|
+
if (options.customerId) body["customer"] = options.customerId;
|
|
200
|
+
if (options.customerEmail) body["customer_email"] = options.customerEmail;
|
|
201
|
+
if (options.allowPromotionCodes) body["allow_promotion_codes"] = true;
|
|
202
|
+
if (options.metadata) body["metadata"] = options.metadata;
|
|
203
|
+
if (options.mode === "subscription" && options.trialDays) {
|
|
204
|
+
body["subscription_data"] = {
|
|
205
|
+
trial_period_days: options.trialDays
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const result = await this.request("/checkout/sessions", {
|
|
209
|
+
method: "POST",
|
|
210
|
+
body
|
|
211
|
+
});
|
|
212
|
+
return this.mapCheckoutSession(result);
|
|
213
|
+
}
|
|
214
|
+
async getCheckout(sessionId) {
|
|
215
|
+
try {
|
|
216
|
+
const result = await this.request(
|
|
217
|
+
`/checkout/sessions/${sessionId}`
|
|
218
|
+
);
|
|
219
|
+
return this.mapCheckoutSession(result);
|
|
220
|
+
} catch (err) {
|
|
221
|
+
if (err instanceof PaymentError && err.code === "resource_missing") {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
mapCheckoutSession(stripe) {
|
|
228
|
+
return {
|
|
229
|
+
id: stripe.id,
|
|
230
|
+
url: stripe.url ?? "",
|
|
231
|
+
customerId: stripe.customer ?? void 0,
|
|
232
|
+
status: stripe.status,
|
|
233
|
+
mode: stripe.mode,
|
|
234
|
+
amountTotal: stripe.amount_total ?? void 0,
|
|
235
|
+
currency: stripe.currency ?? void 0,
|
|
236
|
+
providerData: stripe
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// Subscriptions
|
|
241
|
+
// ============================================================================
|
|
242
|
+
async createSubscription(options) {
|
|
243
|
+
const body = {
|
|
244
|
+
customer: options.customerId,
|
|
245
|
+
items: [{ price: options.priceId }]
|
|
246
|
+
};
|
|
247
|
+
if (options.trialDays) body["trial_period_days"] = options.trialDays;
|
|
248
|
+
if (options.metadata) body["metadata"] = options.metadata;
|
|
249
|
+
if (options.paymentBehavior) body["payment_behavior"] = options.paymentBehavior;
|
|
250
|
+
const result = await this.request("/subscriptions", {
|
|
251
|
+
method: "POST",
|
|
252
|
+
body
|
|
253
|
+
});
|
|
254
|
+
return this.mapSubscription(result);
|
|
255
|
+
}
|
|
256
|
+
async getSubscription(subscriptionId) {
|
|
257
|
+
try {
|
|
258
|
+
const result = await this.request(
|
|
259
|
+
`/subscriptions/${subscriptionId}`
|
|
260
|
+
);
|
|
261
|
+
return this.mapSubscription(result);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (err instanceof PaymentError && err.code === "resource_missing") {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async updateSubscription(subscriptionId, options) {
|
|
270
|
+
const body = {};
|
|
271
|
+
if (options.cancelAtPeriodEnd !== void 0) {
|
|
272
|
+
body["cancel_at_period_end"] = options.cancelAtPeriodEnd;
|
|
273
|
+
}
|
|
274
|
+
if (options.metadata) body["metadata"] = options.metadata;
|
|
275
|
+
if (options.prorationBehavior) body["proration_behavior"] = options.prorationBehavior;
|
|
276
|
+
if (options.priceId) {
|
|
277
|
+
const current = await this.request(
|
|
278
|
+
`/subscriptions/${subscriptionId}`
|
|
279
|
+
);
|
|
280
|
+
const itemId = current.items.data[0]?.id;
|
|
281
|
+
if (itemId) {
|
|
282
|
+
body["items"] = [{ id: itemId, price: options.priceId }];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const result = await this.request(
|
|
286
|
+
`/subscriptions/${subscriptionId}`,
|
|
287
|
+
{ method: "POST", body }
|
|
288
|
+
);
|
|
289
|
+
return this.mapSubscription(result);
|
|
290
|
+
}
|
|
291
|
+
async cancelSubscription(subscriptionId, cancelAtPeriodEnd = true) {
|
|
292
|
+
if (cancelAtPeriodEnd) {
|
|
293
|
+
return this.updateSubscription(subscriptionId, { cancelAtPeriodEnd: true });
|
|
294
|
+
}
|
|
295
|
+
const result = await this.request(
|
|
296
|
+
`/subscriptions/${subscriptionId}`,
|
|
297
|
+
{ method: "DELETE" }
|
|
298
|
+
);
|
|
299
|
+
return this.mapSubscription(result);
|
|
300
|
+
}
|
|
301
|
+
async listSubscriptions(customerId) {
|
|
302
|
+
const result = await this.request(
|
|
303
|
+
`/subscriptions?customer=${customerId}`
|
|
304
|
+
);
|
|
305
|
+
return result.data.map((sub) => this.mapSubscription(sub));
|
|
306
|
+
}
|
|
307
|
+
mapSubscription(stripe) {
|
|
308
|
+
const item = stripe.items.data[0];
|
|
309
|
+
return {
|
|
310
|
+
id: stripe.id,
|
|
311
|
+
customerId: typeof stripe.customer === "string" ? stripe.customer : stripe.customer.id,
|
|
312
|
+
status: stripe.status,
|
|
313
|
+
priceId: item?.price.id ?? "",
|
|
314
|
+
productId: typeof item?.price.product === "string" ? item.price.product : item?.price.product?.id,
|
|
315
|
+
currentPeriodStart: new Date(stripe.current_period_start * 1e3),
|
|
316
|
+
currentPeriodEnd: new Date(stripe.current_period_end * 1e3),
|
|
317
|
+
cancelAtPeriodEnd: stripe.cancel_at_period_end,
|
|
318
|
+
canceledAt: stripe.canceled_at ? new Date(stripe.canceled_at * 1e3) : void 0,
|
|
319
|
+
trialStart: stripe.trial_start ? new Date(stripe.trial_start * 1e3) : void 0,
|
|
320
|
+
trialEnd: stripe.trial_end ? new Date(stripe.trial_end * 1e3) : void 0,
|
|
321
|
+
metadata: stripe.metadata ?? void 0,
|
|
322
|
+
providerData: stripe
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// Portal
|
|
327
|
+
// ============================================================================
|
|
328
|
+
async createPortalSession(options) {
|
|
329
|
+
const result = await this.request("/billing_portal/sessions", {
|
|
330
|
+
method: "POST",
|
|
331
|
+
body: {
|
|
332
|
+
customer: options.customerId,
|
|
333
|
+
return_url: options.returnUrl
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
return {
|
|
337
|
+
url: result.url,
|
|
338
|
+
returnUrl: options.returnUrl
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Products & Prices
|
|
343
|
+
// ============================================================================
|
|
344
|
+
async getProduct(productId) {
|
|
345
|
+
try {
|
|
346
|
+
const result = await this.request(`/products/${productId}`);
|
|
347
|
+
return this.mapProduct(result);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
if (err instanceof PaymentError && err.code === "resource_missing") {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
throw err;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async getPrice(priceId) {
|
|
356
|
+
try {
|
|
357
|
+
const result = await this.request(`/prices/${priceId}`);
|
|
358
|
+
return this.mapPrice(result);
|
|
359
|
+
} catch (err) {
|
|
360
|
+
if (err instanceof PaymentError && err.code === "resource_missing") {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
throw err;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async listPrices(productId) {
|
|
367
|
+
let endpoint = "/prices?active=true&limit=100";
|
|
368
|
+
if (productId) {
|
|
369
|
+
endpoint += `&product=${productId}`;
|
|
370
|
+
}
|
|
371
|
+
const result = await this.request(endpoint);
|
|
372
|
+
return result.data.map((price2) => this.mapPrice(price2));
|
|
373
|
+
}
|
|
374
|
+
mapProduct(stripe) {
|
|
375
|
+
return {
|
|
376
|
+
id: stripe.id,
|
|
377
|
+
name: stripe.name,
|
|
378
|
+
description: stripe.description ?? void 0,
|
|
379
|
+
active: stripe.active,
|
|
380
|
+
metadata: stripe.metadata ?? void 0,
|
|
381
|
+
providerData: stripe
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
mapPrice(stripe) {
|
|
385
|
+
return {
|
|
386
|
+
id: stripe.id,
|
|
387
|
+
productId: typeof stripe.product === "string" ? stripe.product : stripe.product.id,
|
|
388
|
+
unitAmount: stripe.unit_amount ?? 0,
|
|
389
|
+
currency: stripe.currency.toUpperCase(),
|
|
390
|
+
recurring: stripe.recurring ? {
|
|
391
|
+
interval: stripe.recurring.interval,
|
|
392
|
+
intervalCount: stripe.recurring.interval_count
|
|
393
|
+
} : void 0,
|
|
394
|
+
active: stripe.active,
|
|
395
|
+
metadata: stripe.metadata ?? void 0,
|
|
396
|
+
providerData: stripe
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// Webhooks
|
|
401
|
+
// ============================================================================
|
|
402
|
+
async verifyWebhook(payload, signature) {
|
|
403
|
+
if (!this.webhookSecret) {
|
|
404
|
+
throw new PaymentError(
|
|
405
|
+
"Webhook secret not configured",
|
|
406
|
+
PaymentErrorCodes.INVALID_CONFIG
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
const payloadString = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
|
|
410
|
+
const signatureParts = signature.split(",").reduce((acc, part) => {
|
|
411
|
+
const [key, value] = part.split("=");
|
|
412
|
+
if (key && value) {
|
|
413
|
+
acc[key] = value;
|
|
414
|
+
}
|
|
415
|
+
return acc;
|
|
416
|
+
}, {});
|
|
417
|
+
const timestamp = signatureParts["t"];
|
|
418
|
+
const expectedSignature = signatureParts["v1"];
|
|
419
|
+
if (!timestamp || !expectedSignature) {
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
const timestampSeconds = parseInt(timestamp, 10);
|
|
423
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
424
|
+
if (Math.abs(now - timestampSeconds) > 300) {
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const signedPayload = `${timestamp}.${payloadString}`;
|
|
428
|
+
const computedSignature = await this.computeHmacSignature(
|
|
429
|
+
signedPayload,
|
|
430
|
+
this.webhookSecret
|
|
431
|
+
);
|
|
432
|
+
if (!this.secureCompare(computedSignature, expectedSignature)) {
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
const event = JSON.parse(payloadString);
|
|
436
|
+
return {
|
|
437
|
+
id: event.id,
|
|
438
|
+
type: this.mapEventType(event.type),
|
|
439
|
+
data: event.data.object,
|
|
440
|
+
created: new Date(event.created * 1e3),
|
|
441
|
+
provider: "stripe",
|
|
442
|
+
raw: event
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
async computeHmacSignature(payload, secret) {
|
|
446
|
+
const encoder = new TextEncoder();
|
|
447
|
+
const keyData = encoder.encode(secret);
|
|
448
|
+
const messageData = encoder.encode(payload);
|
|
449
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
450
|
+
"raw",
|
|
451
|
+
keyData,
|
|
452
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
453
|
+
false,
|
|
454
|
+
["sign"]
|
|
455
|
+
);
|
|
456
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
|
|
457
|
+
const signatureArray = new Uint8Array(signature);
|
|
458
|
+
return Array.from(signatureArray).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
459
|
+
}
|
|
460
|
+
secureCompare(a, b) {
|
|
461
|
+
if (a.length !== b.length) return false;
|
|
462
|
+
let result = 0;
|
|
463
|
+
for (let i = 0; i < a.length; i++) {
|
|
464
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
465
|
+
}
|
|
466
|
+
return result === 0;
|
|
467
|
+
}
|
|
468
|
+
mapEventType(stripeType) {
|
|
469
|
+
const mapping = {
|
|
470
|
+
"checkout.session.completed": "checkout.session.completed",
|
|
471
|
+
"checkout.session.expired": "checkout.session.expired",
|
|
472
|
+
"customer.created": "customer.created",
|
|
473
|
+
"customer.updated": "customer.updated",
|
|
474
|
+
"customer.deleted": "customer.deleted",
|
|
475
|
+
"customer.subscription.created": "subscription.created",
|
|
476
|
+
"customer.subscription.updated": "subscription.updated",
|
|
477
|
+
"customer.subscription.deleted": "subscription.deleted",
|
|
478
|
+
"customer.subscription.trial_will_end": "subscription.trial_will_end",
|
|
479
|
+
"payment_intent.succeeded": "payment.succeeded",
|
|
480
|
+
"payment_intent.payment_failed": "payment.failed",
|
|
481
|
+
"invoice.created": "invoice.created",
|
|
482
|
+
"invoice.paid": "invoice.paid",
|
|
483
|
+
"invoice.payment_failed": "invoice.payment_failed",
|
|
484
|
+
"invoice.upcoming": "invoice.upcoming",
|
|
485
|
+
"charge.refunded": "refund.created",
|
|
486
|
+
"refund.created": "refund.created",
|
|
487
|
+
"refund.updated": "refund.updated"
|
|
488
|
+
};
|
|
489
|
+
return mapping[stripeType] ?? "unknown";
|
|
490
|
+
}
|
|
491
|
+
// ============================================================================
|
|
492
|
+
// Usage Reporting (Metered Billing)
|
|
493
|
+
// ============================================================================
|
|
494
|
+
/**
|
|
495
|
+
* Report usage for metered billing
|
|
496
|
+
*
|
|
497
|
+
* @example
|
|
498
|
+
* ```typescript
|
|
499
|
+
* // Report 100 API calls for a subscription item
|
|
500
|
+
* await stripe.reportUsage({
|
|
501
|
+
* subscriptionItemId: "si_xxx",
|
|
502
|
+
* quantity: 100,
|
|
503
|
+
* action: "increment", // or "set" to replace
|
|
504
|
+
* });
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
async reportUsage(record) {
|
|
508
|
+
const body = {
|
|
509
|
+
quantity: record.quantity,
|
|
510
|
+
action: record.action ?? "increment"
|
|
511
|
+
};
|
|
512
|
+
if (record.timestamp) {
|
|
513
|
+
body["timestamp"] = Math.floor(record.timestamp.getTime() / 1e3);
|
|
514
|
+
}
|
|
515
|
+
const headers = {};
|
|
516
|
+
if (record.idempotencyKey) {
|
|
517
|
+
headers["Idempotency-Key"] = record.idempotencyKey;
|
|
518
|
+
}
|
|
519
|
+
await this.request(
|
|
520
|
+
`/subscription_items/${record.subscriptionItemId}/usage_records`,
|
|
521
|
+
{
|
|
522
|
+
method: "POST",
|
|
523
|
+
body
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Report multiple usage records (batch)
|
|
529
|
+
* Note: Stripe doesn't have a batch API, so this is sequential
|
|
530
|
+
*/
|
|
531
|
+
async reportUsageBatch(records) {
|
|
532
|
+
for (const record of records) {
|
|
533
|
+
await this.reportUsage(record);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Get subscription item ID for a subscription and price
|
|
538
|
+
*/
|
|
539
|
+
async getSubscriptionItemId(subscriptionId, priceId) {
|
|
540
|
+
const subscription2 = await this.request(
|
|
541
|
+
`/subscriptions/${subscriptionId}`
|
|
542
|
+
);
|
|
543
|
+
const item = subscription2.items.data.find((i) => i.price.id === priceId);
|
|
544
|
+
return item?.id ?? null;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Get usage records for a subscription item
|
|
548
|
+
*/
|
|
549
|
+
async getUsageRecords(subscriptionItemId, options) {
|
|
550
|
+
let endpoint = `/subscription_items/${subscriptionItemId}/usage_record_summaries?`;
|
|
551
|
+
if (options?.limit) {
|
|
552
|
+
endpoint += `limit=${options.limit}&`;
|
|
553
|
+
}
|
|
554
|
+
if (options?.startingAfter) {
|
|
555
|
+
endpoint += `starting_after=${options.startingAfter}&`;
|
|
556
|
+
}
|
|
557
|
+
if (options?.endingBefore) {
|
|
558
|
+
endpoint += `ending_before=${options.endingBefore}&`;
|
|
559
|
+
}
|
|
560
|
+
const result = await this.request(endpoint);
|
|
561
|
+
return {
|
|
562
|
+
data: result.data.map((r) => ({
|
|
563
|
+
id: r.id,
|
|
564
|
+
quantity: r.total_usage,
|
|
565
|
+
timestamp: new Date(r.period.start * 1e3),
|
|
566
|
+
subscriptionItem: r.subscription_item
|
|
567
|
+
})),
|
|
568
|
+
hasMore: result.has_more
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Get current period usage total for a subscription item
|
|
573
|
+
*/
|
|
574
|
+
async getCurrentUsage(subscriptionItemId) {
|
|
575
|
+
const result = await this.getUsageRecords(subscriptionItemId, { limit: 1 });
|
|
576
|
+
return result.data[0]?.quantity ?? 0;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
function createStripeProvider(config) {
|
|
580
|
+
return new StripeProvider(config);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/providers/paddle.ts
|
|
584
|
+
var PaddleProvider = class {
|
|
585
|
+
type = "paddle";
|
|
586
|
+
apiKey;
|
|
587
|
+
webhookSecret;
|
|
588
|
+
baseUrl;
|
|
589
|
+
constructor(config) {
|
|
590
|
+
this.apiKey = config.apiKey;
|
|
591
|
+
this.webhookSecret = config.webhookSecret;
|
|
592
|
+
this.baseUrl = config.environment === "production" ? "https://api.paddle.com" : "https://sandbox-api.paddle.com";
|
|
593
|
+
}
|
|
594
|
+
async request(endpoint, options = {}) {
|
|
595
|
+
const { method = "GET", body } = options;
|
|
596
|
+
const headers = {
|
|
597
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
598
|
+
"Content-Type": "application/json"
|
|
599
|
+
};
|
|
600
|
+
const fetchOptions = {
|
|
601
|
+
method,
|
|
602
|
+
headers
|
|
603
|
+
};
|
|
604
|
+
if (body) {
|
|
605
|
+
fetchOptions.body = JSON.stringify(body);
|
|
606
|
+
}
|
|
607
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, fetchOptions);
|
|
608
|
+
const data = await response.json();
|
|
609
|
+
if (!response.ok || data.error) {
|
|
610
|
+
const errorMessage = data.error?.detail ?? `HTTP ${response.status}`;
|
|
611
|
+
throw new PaymentError(
|
|
612
|
+
`Paddle API error: ${errorMessage}`,
|
|
613
|
+
data.error?.code ?? PaymentErrorCodes.API_ERROR,
|
|
614
|
+
data.error
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
return data.data;
|
|
618
|
+
}
|
|
619
|
+
// ============================================================================
|
|
620
|
+
// Customer
|
|
621
|
+
// ============================================================================
|
|
622
|
+
async createCustomer(options) {
|
|
623
|
+
const body = {
|
|
624
|
+
email: options.email
|
|
625
|
+
};
|
|
626
|
+
if (options.name) body["name"] = options.name;
|
|
627
|
+
if (options.metadata) body["custom_data"] = options.metadata;
|
|
628
|
+
const result = await this.request("/customers", {
|
|
629
|
+
method: "POST",
|
|
630
|
+
body
|
|
631
|
+
});
|
|
632
|
+
return this.mapCustomer(result);
|
|
633
|
+
}
|
|
634
|
+
async getCustomer(customerId) {
|
|
635
|
+
try {
|
|
636
|
+
const result = await this.request(`/customers/${customerId}`);
|
|
637
|
+
return this.mapCustomer(result);
|
|
638
|
+
} catch (err) {
|
|
639
|
+
if (err instanceof PaymentError && err.code === "not_found") {
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
throw err;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
async updateCustomer(customerId, options) {
|
|
646
|
+
const body = {};
|
|
647
|
+
if (options.email) body["email"] = options.email;
|
|
648
|
+
if (options.name) body["name"] = options.name;
|
|
649
|
+
if (options.metadata) body["custom_data"] = options.metadata;
|
|
650
|
+
const result = await this.request(`/customers/${customerId}`, {
|
|
651
|
+
method: "PATCH",
|
|
652
|
+
body
|
|
653
|
+
});
|
|
654
|
+
return this.mapCustomer(result);
|
|
655
|
+
}
|
|
656
|
+
async deleteCustomer(_customerId) {
|
|
657
|
+
throw new PaymentError(
|
|
658
|
+
"Paddle does not support customer deletion",
|
|
659
|
+
PaymentErrorCodes.API_ERROR
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
mapCustomer(paddle) {
|
|
663
|
+
return {
|
|
664
|
+
id: paddle.id,
|
|
665
|
+
email: paddle.email,
|
|
666
|
+
name: paddle.name ?? void 0,
|
|
667
|
+
metadata: paddle.custom_data ?? void 0,
|
|
668
|
+
providerData: paddle
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
// ============================================================================
|
|
672
|
+
// Checkout
|
|
673
|
+
// ============================================================================
|
|
674
|
+
async createCheckout(options) {
|
|
675
|
+
const items = options.lineItems.map((item) => ({
|
|
676
|
+
price_id: item.priceId,
|
|
677
|
+
quantity: item.quantity
|
|
678
|
+
}));
|
|
679
|
+
const body = {
|
|
680
|
+
items
|
|
681
|
+
};
|
|
682
|
+
if (options.customerId) body["customer_id"] = options.customerId;
|
|
683
|
+
if (options.customerEmail) {
|
|
684
|
+
body["customer"] = { email: options.customerEmail };
|
|
685
|
+
}
|
|
686
|
+
if (options.metadata) body["custom_data"] = options.metadata;
|
|
687
|
+
body["settings"] = {
|
|
688
|
+
success_url: options.successUrl
|
|
689
|
+
};
|
|
690
|
+
const result = await this.request("/transactions", {
|
|
691
|
+
method: "POST",
|
|
692
|
+
body
|
|
693
|
+
});
|
|
694
|
+
return {
|
|
695
|
+
id: result.id,
|
|
696
|
+
url: result.checkout?.url ?? "",
|
|
697
|
+
customerId: result.customer_id ?? void 0,
|
|
698
|
+
status: result.status === "completed" ? "complete" : "open",
|
|
699
|
+
mode: result.subscription_id ? "subscription" : "payment",
|
|
700
|
+
amountTotal: this.parsePaddleAmount(result.details?.totals?.total),
|
|
701
|
+
currency: result.currency_code,
|
|
702
|
+
providerData: result
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
async getCheckout(sessionId) {
|
|
706
|
+
try {
|
|
707
|
+
const result = await this.request(`/transactions/${sessionId}`);
|
|
708
|
+
return {
|
|
709
|
+
id: result.id,
|
|
710
|
+
url: result.checkout?.url ?? "",
|
|
711
|
+
customerId: result.customer_id ?? void 0,
|
|
712
|
+
status: result.status === "completed" ? "complete" : "open",
|
|
713
|
+
mode: result.subscription_id ? "subscription" : "payment",
|
|
714
|
+
amountTotal: this.parsePaddleAmount(result.details?.totals?.total),
|
|
715
|
+
currency: result.currency_code,
|
|
716
|
+
providerData: result
|
|
717
|
+
};
|
|
718
|
+
} catch (err) {
|
|
719
|
+
if (err instanceof PaymentError && err.code === "not_found") {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
throw err;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
// ============================================================================
|
|
726
|
+
// Subscriptions
|
|
727
|
+
// ============================================================================
|
|
728
|
+
async createSubscription(_options) {
|
|
729
|
+
throw new PaymentError(
|
|
730
|
+
"Paddle subscriptions must be created through checkout",
|
|
731
|
+
PaymentErrorCodes.API_ERROR
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
async getSubscription(subscriptionId) {
|
|
735
|
+
try {
|
|
736
|
+
const result = await this.request(
|
|
737
|
+
`/subscriptions/${subscriptionId}`
|
|
738
|
+
);
|
|
739
|
+
return this.mapSubscription(result);
|
|
740
|
+
} catch (err) {
|
|
741
|
+
if (err instanceof PaymentError && err.code === "not_found") {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
throw err;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async updateSubscription(subscriptionId, options) {
|
|
748
|
+
const body = {};
|
|
749
|
+
if (options.priceId) {
|
|
750
|
+
body["items"] = [{ price_id: options.priceId, quantity: 1 }];
|
|
751
|
+
}
|
|
752
|
+
if (options.metadata) body["custom_data"] = options.metadata;
|
|
753
|
+
if (options.prorationBehavior) {
|
|
754
|
+
body["proration_billing_mode"] = options.prorationBehavior === "none" ? "do_not_bill" : "prorated_immediately";
|
|
755
|
+
}
|
|
756
|
+
const result = await this.request(
|
|
757
|
+
`/subscriptions/${subscriptionId}`,
|
|
758
|
+
{ method: "PATCH", body }
|
|
759
|
+
);
|
|
760
|
+
return this.mapSubscription(result);
|
|
761
|
+
}
|
|
762
|
+
async cancelSubscription(subscriptionId, cancelAtPeriodEnd = true) {
|
|
763
|
+
const body = {
|
|
764
|
+
effective_from: cancelAtPeriodEnd ? "next_billing_period" : "immediately"
|
|
765
|
+
};
|
|
766
|
+
const result = await this.request(
|
|
767
|
+
`/subscriptions/${subscriptionId}/cancel`,
|
|
768
|
+
{ method: "POST", body }
|
|
769
|
+
);
|
|
770
|
+
return this.mapSubscription(result);
|
|
771
|
+
}
|
|
772
|
+
async listSubscriptions(customerId) {
|
|
773
|
+
const result = await this.request(
|
|
774
|
+
`/subscriptions?customer_id=${customerId}`
|
|
775
|
+
);
|
|
776
|
+
return result.map((sub) => this.mapSubscription(sub));
|
|
777
|
+
}
|
|
778
|
+
mapSubscription(paddle) {
|
|
779
|
+
const item = paddle.items?.[0];
|
|
780
|
+
const statusMap = {
|
|
781
|
+
active: "active",
|
|
782
|
+
canceled: "canceled",
|
|
783
|
+
past_due: "past_due",
|
|
784
|
+
paused: "paused",
|
|
785
|
+
trialing: "trialing"
|
|
786
|
+
};
|
|
787
|
+
return {
|
|
788
|
+
id: paddle.id,
|
|
789
|
+
customerId: paddle.customer_id,
|
|
790
|
+
status: statusMap[paddle.status] ?? "active",
|
|
791
|
+
priceId: item?.price?.id ?? "",
|
|
792
|
+
productId: item?.price?.product_id,
|
|
793
|
+
currentPeriodStart: new Date(paddle.current_billing_period?.starts_at ?? Date.now()),
|
|
794
|
+
currentPeriodEnd: new Date(paddle.current_billing_period?.ends_at ?? Date.now()),
|
|
795
|
+
cancelAtPeriodEnd: paddle.scheduled_change?.action === "cancel",
|
|
796
|
+
canceledAt: paddle.canceled_at ? new Date(paddle.canceled_at) : void 0,
|
|
797
|
+
trialStart: paddle.started_at ? new Date(paddle.started_at) : void 0,
|
|
798
|
+
trialEnd: paddle.first_billed_at ? new Date(paddle.first_billed_at) : void 0,
|
|
799
|
+
metadata: paddle.custom_data ?? void 0,
|
|
800
|
+
providerData: paddle
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
// ============================================================================
|
|
804
|
+
// Portal
|
|
805
|
+
// ============================================================================
|
|
806
|
+
async createPortalSession(options) {
|
|
807
|
+
const customer = await this.getCustomer(options.customerId);
|
|
808
|
+
if (!customer) {
|
|
809
|
+
throw new PaymentError(
|
|
810
|
+
"Customer not found",
|
|
811
|
+
PaymentErrorCodes.CUSTOMER_NOT_FOUND
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
const result = await this.request(
|
|
815
|
+
`/customers/${options.customerId}/portal-sessions`,
|
|
816
|
+
{ method: "POST" }
|
|
817
|
+
);
|
|
818
|
+
return {
|
|
819
|
+
url: result.urls.general.overview,
|
|
820
|
+
returnUrl: options.returnUrl
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
// ============================================================================
|
|
824
|
+
// Products & Prices
|
|
825
|
+
// ============================================================================
|
|
826
|
+
async getProduct(productId) {
|
|
827
|
+
try {
|
|
828
|
+
const result = await this.request(`/products/${productId}`);
|
|
829
|
+
return this.mapProduct(result);
|
|
830
|
+
} catch (err) {
|
|
831
|
+
if (err instanceof PaymentError && err.code === "not_found") {
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
throw err;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
async getPrice(priceId) {
|
|
838
|
+
try {
|
|
839
|
+
const result = await this.request(`/prices/${priceId}`);
|
|
840
|
+
return this.mapPrice(result);
|
|
841
|
+
} catch (err) {
|
|
842
|
+
if (err instanceof PaymentError && err.code === "not_found") {
|
|
843
|
+
return null;
|
|
844
|
+
}
|
|
845
|
+
throw err;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
async listPrices(productId) {
|
|
849
|
+
let endpoint = "/prices?status=active";
|
|
850
|
+
if (productId) {
|
|
851
|
+
endpoint += `&product_id=${productId}`;
|
|
852
|
+
}
|
|
853
|
+
const result = await this.request(endpoint);
|
|
854
|
+
return result.map((price2) => this.mapPrice(price2));
|
|
855
|
+
}
|
|
856
|
+
mapProduct(paddle) {
|
|
857
|
+
return {
|
|
858
|
+
id: paddle.id,
|
|
859
|
+
name: paddle.name,
|
|
860
|
+
description: paddle.description ?? void 0,
|
|
861
|
+
active: paddle.status === "active",
|
|
862
|
+
metadata: paddle.custom_data ?? void 0,
|
|
863
|
+
providerData: paddle
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
mapPrice(paddle) {
|
|
867
|
+
const amount = paddle.unit_price?.amount ? parseInt(paddle.unit_price.amount, 10) : 0;
|
|
868
|
+
return {
|
|
869
|
+
id: paddle.id,
|
|
870
|
+
productId: paddle.product_id,
|
|
871
|
+
unitAmount: amount,
|
|
872
|
+
currency: paddle.unit_price?.currency_code ?? "USD",
|
|
873
|
+
recurring: paddle.billing_cycle ? {
|
|
874
|
+
interval: paddle.billing_cycle.interval,
|
|
875
|
+
intervalCount: paddle.billing_cycle.frequency
|
|
876
|
+
} : void 0,
|
|
877
|
+
active: paddle.status === "active",
|
|
878
|
+
metadata: paddle.custom_data ?? void 0,
|
|
879
|
+
providerData: paddle
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
parsePaddleAmount(amount) {
|
|
883
|
+
if (!amount) return void 0;
|
|
884
|
+
return parseInt(amount, 10);
|
|
885
|
+
}
|
|
886
|
+
// ============================================================================
|
|
887
|
+
// Webhooks
|
|
888
|
+
// ============================================================================
|
|
889
|
+
async verifyWebhook(payload, signature) {
|
|
890
|
+
if (!this.webhookSecret) {
|
|
891
|
+
throw new PaymentError(
|
|
892
|
+
"Webhook secret not configured",
|
|
893
|
+
PaymentErrorCodes.INVALID_CONFIG
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
const payloadString = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
|
|
897
|
+
const signatureParts = signature.split(";").reduce((acc, part) => {
|
|
898
|
+
const [key, value] = part.split("=");
|
|
899
|
+
if (key && value) {
|
|
900
|
+
acc[key] = value;
|
|
901
|
+
}
|
|
902
|
+
return acc;
|
|
903
|
+
}, {});
|
|
904
|
+
const timestamp = signatureParts["ts"];
|
|
905
|
+
const expectedSignature = signatureParts["h1"];
|
|
906
|
+
if (!timestamp || !expectedSignature) {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
const signedPayload = `${timestamp}:${payloadString}`;
|
|
910
|
+
const computedSignature = await this.computeHmacSignature(
|
|
911
|
+
signedPayload,
|
|
912
|
+
this.webhookSecret
|
|
913
|
+
);
|
|
914
|
+
if (!this.secureCompare(computedSignature, expectedSignature)) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
const event = JSON.parse(payloadString);
|
|
918
|
+
return {
|
|
919
|
+
id: event.event_id,
|
|
920
|
+
type: this.mapEventType(event.event_type),
|
|
921
|
+
data: event.data,
|
|
922
|
+
created: new Date(event.occurred_at),
|
|
923
|
+
provider: "paddle",
|
|
924
|
+
raw: event
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
async computeHmacSignature(payload, secret) {
|
|
928
|
+
const encoder = new TextEncoder();
|
|
929
|
+
const keyData = encoder.encode(secret);
|
|
930
|
+
const messageData = encoder.encode(payload);
|
|
931
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
932
|
+
"raw",
|
|
933
|
+
keyData,
|
|
934
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
935
|
+
false,
|
|
936
|
+
["sign"]
|
|
937
|
+
);
|
|
938
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
|
|
939
|
+
const signatureArray = new Uint8Array(signature);
|
|
940
|
+
return Array.from(signatureArray).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
941
|
+
}
|
|
942
|
+
secureCompare(a, b) {
|
|
943
|
+
if (a.length !== b.length) return false;
|
|
944
|
+
let result = 0;
|
|
945
|
+
for (let i = 0; i < a.length; i++) {
|
|
946
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
947
|
+
}
|
|
948
|
+
return result === 0;
|
|
949
|
+
}
|
|
950
|
+
mapEventType(paddleType) {
|
|
951
|
+
const mapping = {
|
|
952
|
+
"transaction.completed": "checkout.session.completed",
|
|
953
|
+
"customer.created": "customer.created",
|
|
954
|
+
"customer.updated": "customer.updated",
|
|
955
|
+
"subscription.created": "subscription.created",
|
|
956
|
+
"subscription.updated": "subscription.updated",
|
|
957
|
+
"subscription.canceled": "subscription.deleted",
|
|
958
|
+
"subscription.past_due": "subscription.updated",
|
|
959
|
+
"subscription.activated": "subscription.created",
|
|
960
|
+
"transaction.payment_failed": "payment.failed",
|
|
961
|
+
"adjustment.created": "refund.created"
|
|
962
|
+
};
|
|
963
|
+
return mapping[paddleType] ?? "unknown";
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
function createPaddleProvider(config) {
|
|
967
|
+
return new PaddleProvider(config);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/providers/iyzico.ts
|
|
971
|
+
var IyzicoProvider = class {
|
|
972
|
+
type = "iyzico";
|
|
973
|
+
apiKey;
|
|
974
|
+
secretKey;
|
|
975
|
+
baseUrl;
|
|
976
|
+
constructor(config) {
|
|
977
|
+
this.apiKey = config.apiKey;
|
|
978
|
+
this.secretKey = config.secretKey;
|
|
979
|
+
this.baseUrl = config.baseUrl ?? (config.environment === "production" ? "https://api.iyzipay.com" : "https://sandbox-api.iyzipay.com");
|
|
980
|
+
}
|
|
981
|
+
async request(endpoint, body) {
|
|
982
|
+
const randomString = this.generateRandomString(8);
|
|
983
|
+
const authorizationString = await this.generateAuthorizationString(
|
|
984
|
+
body,
|
|
985
|
+
randomString
|
|
986
|
+
);
|
|
987
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
988
|
+
method: "POST",
|
|
989
|
+
headers: {
|
|
990
|
+
Accept: "application/json",
|
|
991
|
+
"Content-Type": "application/json",
|
|
992
|
+
Authorization: authorizationString,
|
|
993
|
+
"x-iyzi-rnd": randomString
|
|
994
|
+
},
|
|
995
|
+
body: JSON.stringify(body)
|
|
996
|
+
});
|
|
997
|
+
const data = await response.json();
|
|
998
|
+
if (data.status !== "success") {
|
|
999
|
+
throw new PaymentError(
|
|
1000
|
+
`iyzico API error: ${data.errorMessage ?? "Unknown error"}`,
|
|
1001
|
+
data.errorCode ?? PaymentErrorCodes.API_ERROR,
|
|
1002
|
+
data
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
return data;
|
|
1006
|
+
}
|
|
1007
|
+
generateRandomString(length) {
|
|
1008
|
+
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
1009
|
+
let result = "";
|
|
1010
|
+
const randomValues = new Uint8Array(length);
|
|
1011
|
+
crypto.getRandomValues(randomValues);
|
|
1012
|
+
for (let i = 0; i < length; i++) {
|
|
1013
|
+
const randomValue = randomValues[i];
|
|
1014
|
+
if (randomValue !== void 0) {
|
|
1015
|
+
result += chars[randomValue % chars.length];
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return result;
|
|
1019
|
+
}
|
|
1020
|
+
async generateAuthorizationString(body, randomString) {
|
|
1021
|
+
const pkiString = this.generatePkiString(body);
|
|
1022
|
+
const hashString = `${this.apiKey}${randomString}${this.secretKey}${pkiString}`;
|
|
1023
|
+
const encoder = new TextEncoder();
|
|
1024
|
+
const data = encoder.encode(hashString);
|
|
1025
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
1026
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
1027
|
+
const hashBase64 = btoa(String.fromCharCode(...hashArray));
|
|
1028
|
+
const authorizationString = `${this.apiKey}:${hashBase64}`;
|
|
1029
|
+
const authorizationBase64 = btoa(authorizationString);
|
|
1030
|
+
return `IYZWS ${authorizationBase64}`;
|
|
1031
|
+
}
|
|
1032
|
+
generatePkiString(obj) {
|
|
1033
|
+
const parts = [];
|
|
1034
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1035
|
+
if (value === void 0 || value === null) continue;
|
|
1036
|
+
if (Array.isArray(value)) {
|
|
1037
|
+
const arrayParts = value.map((item) => {
|
|
1038
|
+
if (typeof item === "object" && item !== null) {
|
|
1039
|
+
return this.generatePkiString(item);
|
|
1040
|
+
}
|
|
1041
|
+
return String(item);
|
|
1042
|
+
});
|
|
1043
|
+
parts.push(`${key}=[${arrayParts.join(", ")}]`);
|
|
1044
|
+
} else if (typeof value === "object") {
|
|
1045
|
+
parts.push(
|
|
1046
|
+
`${key}=[${this.generatePkiString(value)}]`
|
|
1047
|
+
);
|
|
1048
|
+
} else {
|
|
1049
|
+
parts.push(`${key}=${value}`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return `[${parts.join(",")}]`;
|
|
1053
|
+
}
|
|
1054
|
+
// ============================================================================
|
|
1055
|
+
// Customer - iyzico uses buyer info per transaction, not stored customers
|
|
1056
|
+
// ============================================================================
|
|
1057
|
+
async createCustomer(_options) {
|
|
1058
|
+
throw new PaymentError(
|
|
1059
|
+
"iyzico does not support stored customers. Use buyer info in checkout.",
|
|
1060
|
+
PaymentErrorCodes.API_ERROR
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
async getCustomer(_customerId) {
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
async updateCustomer(_customerId, _options) {
|
|
1067
|
+
throw new PaymentError(
|
|
1068
|
+
"iyzico does not support stored customers",
|
|
1069
|
+
PaymentErrorCodes.API_ERROR
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
async deleteCustomer(_customerId) {
|
|
1073
|
+
throw new PaymentError(
|
|
1074
|
+
"iyzico does not support stored customers",
|
|
1075
|
+
PaymentErrorCodes.API_ERROR
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
// ============================================================================
|
|
1079
|
+
// Checkout
|
|
1080
|
+
// ============================================================================
|
|
1081
|
+
async createCheckout(_options) {
|
|
1082
|
+
throw new PaymentError(
|
|
1083
|
+
"Use createCheckoutForm() with IyzicoCheckoutOptions for iyzico",
|
|
1084
|
+
PaymentErrorCodes.INVALID_CONFIG
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Create iyzico checkout form (iframe/popup)
|
|
1089
|
+
*/
|
|
1090
|
+
async createCheckoutForm(options) {
|
|
1091
|
+
const body = {
|
|
1092
|
+
locale: "tr",
|
|
1093
|
+
conversationId: options.conversationId ?? this.generateRandomString(16),
|
|
1094
|
+
price: options.price,
|
|
1095
|
+
paidPrice: options.paidPrice,
|
|
1096
|
+
currency: options.currency,
|
|
1097
|
+
basketId: options.metadata?.["basketId"] ?? this.generateRandomString(16),
|
|
1098
|
+
paymentGroup: "PRODUCT",
|
|
1099
|
+
callbackUrl: options.successUrl,
|
|
1100
|
+
buyer: {
|
|
1101
|
+
id: options.buyer.id,
|
|
1102
|
+
name: options.buyer.name,
|
|
1103
|
+
surname: options.buyer.surname,
|
|
1104
|
+
gsmNumber: options.buyer.gsmNumber,
|
|
1105
|
+
email: options.buyer.email,
|
|
1106
|
+
identityNumber: options.buyer.identityNumber,
|
|
1107
|
+
registrationAddress: options.buyer.registrationAddress,
|
|
1108
|
+
ip: options.buyer.ip,
|
|
1109
|
+
city: options.buyer.city,
|
|
1110
|
+
country: options.buyer.country
|
|
1111
|
+
},
|
|
1112
|
+
shippingAddress: {
|
|
1113
|
+
contactName: options.shippingAddress.contactName,
|
|
1114
|
+
city: options.shippingAddress.city,
|
|
1115
|
+
country: options.shippingAddress.country,
|
|
1116
|
+
address: options.shippingAddress.address
|
|
1117
|
+
},
|
|
1118
|
+
billingAddress: {
|
|
1119
|
+
contactName: options.billingAddress.contactName,
|
|
1120
|
+
city: options.billingAddress.city,
|
|
1121
|
+
country: options.billingAddress.country,
|
|
1122
|
+
address: options.billingAddress.address
|
|
1123
|
+
},
|
|
1124
|
+
basketItems: options.basketItems.map((item) => ({
|
|
1125
|
+
id: item.id,
|
|
1126
|
+
name: item.name,
|
|
1127
|
+
category1: item.category1,
|
|
1128
|
+
category2: item.category2,
|
|
1129
|
+
itemType: item.itemType,
|
|
1130
|
+
price: item.price
|
|
1131
|
+
}))
|
|
1132
|
+
};
|
|
1133
|
+
if (options.enabledInstallments) {
|
|
1134
|
+
body["enabledInstallments"] = options.enabledInstallments;
|
|
1135
|
+
}
|
|
1136
|
+
if (options.force3ds) {
|
|
1137
|
+
body["forceThreeDS"] = 1;
|
|
1138
|
+
}
|
|
1139
|
+
const result = await this.request(
|
|
1140
|
+
"/payment/iyzi-pos/checkoutform/initialize/auth/ecom",
|
|
1141
|
+
body
|
|
1142
|
+
);
|
|
1143
|
+
return {
|
|
1144
|
+
token: result.token,
|
|
1145
|
+
checkoutFormContent: result.checkoutFormContent,
|
|
1146
|
+
tokenExpireTime: result.tokenExpireTime,
|
|
1147
|
+
paymentPageUrl: result.paymentPageUrl
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Retrieve checkout form result
|
|
1152
|
+
*/
|
|
1153
|
+
async retrieveCheckoutForm(token) {
|
|
1154
|
+
const body = {
|
|
1155
|
+
locale: "tr",
|
|
1156
|
+
conversationId: this.generateRandomString(16),
|
|
1157
|
+
token
|
|
1158
|
+
};
|
|
1159
|
+
const result = await this.request(
|
|
1160
|
+
"/payment/iyzi-pos/checkoutform/auth/ecom/detail",
|
|
1161
|
+
body
|
|
1162
|
+
);
|
|
1163
|
+
return {
|
|
1164
|
+
paymentId: result.paymentId,
|
|
1165
|
+
status: result.status,
|
|
1166
|
+
paymentStatus: result.paymentStatus,
|
|
1167
|
+
price: result.price,
|
|
1168
|
+
paidPrice: result.paidPrice,
|
|
1169
|
+
currency: result.currency,
|
|
1170
|
+
installment: result.installment,
|
|
1171
|
+
basketId: result.basketId,
|
|
1172
|
+
binNumber: result.binNumber,
|
|
1173
|
+
lastFourDigits: result.lastFourDigits,
|
|
1174
|
+
cardAssociation: result.cardAssociation,
|
|
1175
|
+
cardFamily: result.cardFamily,
|
|
1176
|
+
cardType: result.cardType,
|
|
1177
|
+
fraudStatus: result.fraudStatus,
|
|
1178
|
+
raw: result
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
async getCheckout(_sessionId) {
|
|
1182
|
+
return null;
|
|
1183
|
+
}
|
|
1184
|
+
// ============================================================================
|
|
1185
|
+
// 3D Secure Payment
|
|
1186
|
+
// ============================================================================
|
|
1187
|
+
/**
|
|
1188
|
+
* Initialize 3D Secure payment
|
|
1189
|
+
*/
|
|
1190
|
+
async initialize3DSPayment(options) {
|
|
1191
|
+
const body = {
|
|
1192
|
+
locale: "tr",
|
|
1193
|
+
conversationId: options.conversationId ?? this.generateRandomString(16),
|
|
1194
|
+
price: options.price,
|
|
1195
|
+
paidPrice: options.paidPrice,
|
|
1196
|
+
currency: options.currency,
|
|
1197
|
+
installment: options.installment,
|
|
1198
|
+
basketId: this.generateRandomString(16),
|
|
1199
|
+
paymentChannel: "WEB",
|
|
1200
|
+
paymentGroup: "PRODUCT",
|
|
1201
|
+
paymentCard: options.paymentCard,
|
|
1202
|
+
buyer: options.buyer,
|
|
1203
|
+
shippingAddress: options.shippingAddress,
|
|
1204
|
+
billingAddress: options.billingAddress,
|
|
1205
|
+
basketItems: options.basketItems,
|
|
1206
|
+
callbackUrl: options.callbackUrl
|
|
1207
|
+
};
|
|
1208
|
+
const result = await this.request(
|
|
1209
|
+
"/payment/3dsecure/initialize",
|
|
1210
|
+
body
|
|
1211
|
+
);
|
|
1212
|
+
return {
|
|
1213
|
+
threeDSHtmlContent: result.threeDSHtmlContent,
|
|
1214
|
+
status: result.status
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Complete 3D Secure payment after callback
|
|
1219
|
+
*/
|
|
1220
|
+
async complete3DSPayment(paymentId, conversationId) {
|
|
1221
|
+
const body = {
|
|
1222
|
+
locale: "tr",
|
|
1223
|
+
conversationId: conversationId ?? this.generateRandomString(16),
|
|
1224
|
+
paymentId
|
|
1225
|
+
};
|
|
1226
|
+
const result = await this.request(
|
|
1227
|
+
"/payment/3dsecure/auth",
|
|
1228
|
+
body
|
|
1229
|
+
);
|
|
1230
|
+
return {
|
|
1231
|
+
paymentId: result.paymentId,
|
|
1232
|
+
status: result.status,
|
|
1233
|
+
paymentStatus: result.paymentStatus,
|
|
1234
|
+
price: result.price,
|
|
1235
|
+
paidPrice: result.paidPrice,
|
|
1236
|
+
currency: result.currency,
|
|
1237
|
+
installment: result.installment,
|
|
1238
|
+
basketId: result.basketId,
|
|
1239
|
+
binNumber: result.binNumber,
|
|
1240
|
+
lastFourDigits: result.lastFourDigits,
|
|
1241
|
+
cardAssociation: result.cardAssociation,
|
|
1242
|
+
cardFamily: result.cardFamily,
|
|
1243
|
+
cardType: result.cardType,
|
|
1244
|
+
fraudStatus: result.fraudStatus,
|
|
1245
|
+
raw: result
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
// ============================================================================
|
|
1249
|
+
// Refund
|
|
1250
|
+
// ============================================================================
|
|
1251
|
+
/**
|
|
1252
|
+
* Create a refund
|
|
1253
|
+
*/
|
|
1254
|
+
async createRefund(options) {
|
|
1255
|
+
const body = {
|
|
1256
|
+
locale: "tr",
|
|
1257
|
+
conversationId: options.conversationId ?? this.generateRandomString(16),
|
|
1258
|
+
paymentTransactionId: options.paymentTransactionId,
|
|
1259
|
+
price: options.price,
|
|
1260
|
+
currency: options.currency,
|
|
1261
|
+
ip: options.ip
|
|
1262
|
+
};
|
|
1263
|
+
const result = await this.request(
|
|
1264
|
+
"/payment/refund",
|
|
1265
|
+
body
|
|
1266
|
+
);
|
|
1267
|
+
return {
|
|
1268
|
+
paymentId: result.paymentId,
|
|
1269
|
+
paymentTransactionId: result.paymentTransactionId,
|
|
1270
|
+
price: result.price,
|
|
1271
|
+
status: result.status
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Cancel a payment (full refund before settlement)
|
|
1276
|
+
*/
|
|
1277
|
+
async cancelPayment(options) {
|
|
1278
|
+
const body = {
|
|
1279
|
+
locale: "tr",
|
|
1280
|
+
conversationId: options.conversationId ?? this.generateRandomString(16),
|
|
1281
|
+
paymentId: options.paymentId,
|
|
1282
|
+
ip: options.ip
|
|
1283
|
+
};
|
|
1284
|
+
const result = await this.request(
|
|
1285
|
+
"/payment/cancel",
|
|
1286
|
+
body
|
|
1287
|
+
);
|
|
1288
|
+
return {
|
|
1289
|
+
paymentId: result.paymentId,
|
|
1290
|
+
price: result.price,
|
|
1291
|
+
currency: result.currency,
|
|
1292
|
+
status: result.status
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
// ============================================================================
|
|
1296
|
+
// Installment
|
|
1297
|
+
// ============================================================================
|
|
1298
|
+
/**
|
|
1299
|
+
* Get installment info for a BIN number
|
|
1300
|
+
*/
|
|
1301
|
+
async getInstallmentInfo(binNumber, price2) {
|
|
1302
|
+
const body = {
|
|
1303
|
+
locale: "tr",
|
|
1304
|
+
conversationId: this.generateRandomString(16),
|
|
1305
|
+
binNumber: binNumber.substring(0, 6),
|
|
1306
|
+
price: price2
|
|
1307
|
+
};
|
|
1308
|
+
const result = await this.request(
|
|
1309
|
+
"/payment/iyzi-pos/installment",
|
|
1310
|
+
body
|
|
1311
|
+
);
|
|
1312
|
+
return {
|
|
1313
|
+
installmentDetails: result.installmentDetails ?? []
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
// ============================================================================
|
|
1317
|
+
// Subscriptions - iyzico has separate subscription API
|
|
1318
|
+
// ============================================================================
|
|
1319
|
+
async createSubscription(_options) {
|
|
1320
|
+
throw new PaymentError(
|
|
1321
|
+
"Use iyzico subscription API methods directly",
|
|
1322
|
+
PaymentErrorCodes.API_ERROR
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
async getSubscription(_subscriptionId) {
|
|
1326
|
+
return null;
|
|
1327
|
+
}
|
|
1328
|
+
async updateSubscription(_subscriptionId, _options) {
|
|
1329
|
+
throw new PaymentError(
|
|
1330
|
+
"Use iyzico subscription API methods directly",
|
|
1331
|
+
PaymentErrorCodes.API_ERROR
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
async cancelSubscription(_subscriptionId, _cancelAtPeriodEnd) {
|
|
1335
|
+
throw new PaymentError(
|
|
1336
|
+
"Use iyzico subscription API methods directly",
|
|
1337
|
+
PaymentErrorCodes.API_ERROR
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
async listSubscriptions(_customerId) {
|
|
1341
|
+
return [];
|
|
1342
|
+
}
|
|
1343
|
+
// ============================================================================
|
|
1344
|
+
// Portal - not supported
|
|
1345
|
+
// ============================================================================
|
|
1346
|
+
async createPortalSession(_options) {
|
|
1347
|
+
throw new PaymentError(
|
|
1348
|
+
"iyzico does not support customer portal",
|
|
1349
|
+
PaymentErrorCodes.API_ERROR
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
// ============================================================================
|
|
1353
|
+
// Webhooks
|
|
1354
|
+
// ============================================================================
|
|
1355
|
+
async verifyWebhook(payload, _signature) {
|
|
1356
|
+
const payloadString = typeof payload === "string" ? payload : new TextDecoder().decode(payload);
|
|
1357
|
+
try {
|
|
1358
|
+
const data = JSON.parse(payloadString);
|
|
1359
|
+
if (data.token) {
|
|
1360
|
+
const result = await this.retrieveCheckoutForm(data.token);
|
|
1361
|
+
return {
|
|
1362
|
+
id: result.paymentId ?? data.token,
|
|
1363
|
+
type: this.mapEventType(data.status ?? result.status),
|
|
1364
|
+
data: result,
|
|
1365
|
+
created: /* @__PURE__ */ new Date(),
|
|
1366
|
+
provider: "iyzico",
|
|
1367
|
+
raw: data
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
return null;
|
|
1371
|
+
} catch {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
mapEventType(status) {
|
|
1376
|
+
const mapping = {
|
|
1377
|
+
success: "payment.succeeded",
|
|
1378
|
+
failure: "payment.failed",
|
|
1379
|
+
INIT_THREEDS: "payment.succeeded",
|
|
1380
|
+
CALLBACK_THREEDS: "payment.succeeded"
|
|
1381
|
+
};
|
|
1382
|
+
return mapping[status] ?? "payment.succeeded";
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
function createIyzicoProvider(config) {
|
|
1386
|
+
return new IyzicoProvider(config);
|
|
1387
|
+
}
|
|
1388
|
+
export {
|
|
1389
|
+
IyzicoProvider,
|
|
1390
|
+
PaddleProvider,
|
|
1391
|
+
StripeProvider,
|
|
1392
|
+
createIyzicoProvider,
|
|
1393
|
+
createPaddleProvider,
|
|
1394
|
+
createStripeProvider
|
|
1395
|
+
};
|
|
1396
|
+
//# sourceMappingURL=index.js.map
|