@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.
@@ -0,0 +1,578 @@
1
+ import { PaymentProvider, PaymentProviderType, Subscription, Customer, WebhookEventType, WebhookHandler } from './types.js';
2
+ import { WebhookProcessResult } from './webhooks/index.js';
3
+
4
+ /**
5
+ * @parsrun/payments - Billing Types
6
+ * Types for multi-provider billing service
7
+ */
8
+
9
+ /**
10
+ * Supported regions for provider routing
11
+ */
12
+ type BillingRegion = "TR" | "EU" | "US" | "UK" | "APAC" | "LATAM" | "GLOBAL" | string;
13
+ /**
14
+ * Region detection result
15
+ */
16
+ interface RegionDetectionResult {
17
+ /** Detected region code */
18
+ region: BillingRegion;
19
+ /** Country code (ISO 3166-1 alpha-2) */
20
+ countryCode?: string;
21
+ /** Detection method used */
22
+ method: "ip" | "customer" | "explicit" | "default";
23
+ /** Confidence level */
24
+ confidence: "high" | "medium" | "low";
25
+ }
26
+ /**
27
+ * Region detector function type
28
+ */
29
+ type RegionDetector = (context: RegionDetectionContext) => BillingRegion | Promise<BillingRegion>;
30
+ /**
31
+ * Context for region detection
32
+ */
33
+ interface RegionDetectionContext {
34
+ /** Customer ID if available */
35
+ customerId?: string | undefined;
36
+ /** Customer email */
37
+ email?: string | undefined;
38
+ /** Explicit country code */
39
+ countryCode?: string | undefined;
40
+ /** IP address */
41
+ ipAddress?: string | undefined;
42
+ /** Request headers */
43
+ headers?: Record<string, string> | undefined;
44
+ /** Custom context data */
45
+ custom?: Record<string, unknown> | undefined;
46
+ }
47
+ /**
48
+ * Provider routing rule
49
+ */
50
+ interface ProviderRoutingRule {
51
+ /** Regions this rule applies to */
52
+ regions: BillingRegion[];
53
+ /** Provider to use */
54
+ provider: PaymentProvider;
55
+ /** Priority (lower = higher priority) */
56
+ priority?: number;
57
+ /** Rule condition (optional) */
58
+ condition?: (context: RegionDetectionContext) => boolean;
59
+ }
60
+ /**
61
+ * Provider strategy configuration
62
+ */
63
+ interface ProviderStrategyConfig {
64
+ /** Default provider (used when no region matches) */
65
+ default: PaymentProvider;
66
+ /**
67
+ * Region-based provider mapping
68
+ * @example { TR: iyzicoProvider, EU: stripeProvider }
69
+ */
70
+ regions?: Record<BillingRegion, PaymentProvider>;
71
+ /**
72
+ * Advanced routing rules (takes precedence over regions)
73
+ */
74
+ rules?: ProviderRoutingRule[];
75
+ /**
76
+ * Custom region detector
77
+ * Default: uses countryCode from context or "GLOBAL"
78
+ */
79
+ regionDetector?: RegionDetector;
80
+ }
81
+ /**
82
+ * Fallback configuration
83
+ */
84
+ interface FallbackConfig {
85
+ /**
86
+ * Enable fallback to alternative providers
87
+ * @default false
88
+ *
89
+ * WARNING: Enable with caution! Fallback may cause:
90
+ * - Double charges if not handled properly
91
+ * - Inconsistent customer records across providers
92
+ * - Webhook handling complexity
93
+ *
94
+ * Recommended only for:
95
+ * - One-time payments (not subscriptions)
96
+ * - Idempotent operations
97
+ * - When you have proper reconciliation in place
98
+ */
99
+ enabled: boolean;
100
+ /**
101
+ * Fallback providers in order of preference
102
+ * If not specified, uses all configured providers except the failed one
103
+ */
104
+ providers?: PaymentProvider[];
105
+ /**
106
+ * Operations that allow fallback
107
+ * @default ["createCheckout"] - Only checkout is safe by default
108
+ */
109
+ allowedOperations?: FallbackOperation[];
110
+ /**
111
+ * Maximum fallback attempts
112
+ * @default 1
113
+ */
114
+ maxAttempts?: number;
115
+ /**
116
+ * Errors that should trigger fallback
117
+ * @default ["API_ERROR", "RATE_LIMITED", "PROVIDER_UNAVAILABLE"]
118
+ */
119
+ retryableErrors?: string[];
120
+ /**
121
+ * Callback when fallback is triggered
122
+ */
123
+ onFallback?: (context: FallbackContext) => void | Promise<void>;
124
+ /**
125
+ * Callback when all providers fail
126
+ */
127
+ onAllFailed?: (context: FallbackContext) => void | Promise<void>;
128
+ }
129
+ /**
130
+ * Operations that can trigger fallback
131
+ */
132
+ type FallbackOperation = "createCheckout" | "createCustomer" | "createSubscription" | "createPayment";
133
+ /**
134
+ * Fallback context for callbacks
135
+ */
136
+ interface FallbackContext {
137
+ /** Operation that failed */
138
+ operation: FallbackOperation;
139
+ /** Original provider that failed */
140
+ originalProvider: PaymentProviderType;
141
+ /** Fallback provider being tried */
142
+ fallbackProvider?: PaymentProviderType;
143
+ /** Error that triggered fallback */
144
+ error: Error;
145
+ /** Attempt number */
146
+ attempt: number;
147
+ /** Total attempts made */
148
+ totalAttempts: number;
149
+ /** Whether all providers failed */
150
+ allFailed: boolean;
151
+ }
152
+ /**
153
+ * Billing service configuration
154
+ */
155
+ interface BillingServiceConfig {
156
+ /**
157
+ * Provider strategy configuration
158
+ */
159
+ providers: ProviderStrategyConfig;
160
+ /**
161
+ * Fallback configuration
162
+ * @default { enabled: false }
163
+ */
164
+ fallback?: FallbackConfig;
165
+ /**
166
+ * Tenant ID for multi-tenant setups
167
+ */
168
+ tenantId?: string;
169
+ /**
170
+ * Enable debug logging
171
+ */
172
+ debug?: boolean;
173
+ /**
174
+ * Custom logger
175
+ */
176
+ logger?: BillingLogger;
177
+ /**
178
+ * Webhook configuration
179
+ */
180
+ webhooks?: {
181
+ /**
182
+ * Normalize events from all providers to unified format
183
+ * @default true
184
+ */
185
+ normalize?: boolean;
186
+ /**
187
+ * Secret keys for each provider
188
+ */
189
+ secrets?: Partial<Record<PaymentProviderType, string>>;
190
+ };
191
+ }
192
+ /**
193
+ * Logger interface for billing service
194
+ */
195
+ interface BillingLogger {
196
+ debug(message: string, context?: Record<string, unknown>): void;
197
+ info(message: string, context?: Record<string, unknown>): void;
198
+ warn(message: string, context?: Record<string, unknown>): void;
199
+ error(message: string, context?: Record<string, unknown>): void;
200
+ }
201
+ /**
202
+ * Subscribe options (high-level)
203
+ */
204
+ interface SubscribeOptions {
205
+ /** Customer email */
206
+ email: string;
207
+ /** Customer name */
208
+ name?: string;
209
+ /** Plan/Price ID */
210
+ planId: string;
211
+ /** Success redirect URL */
212
+ successUrl: string;
213
+ /** Cancel redirect URL */
214
+ cancelUrl: string;
215
+ /** Trial days */
216
+ trialDays?: number;
217
+ /** Country code for region routing */
218
+ countryCode?: string;
219
+ /** Custom metadata */
220
+ metadata?: Record<string, string>;
221
+ /**
222
+ * Existing customer ID (skip customer creation)
223
+ */
224
+ customerId?: string;
225
+ /**
226
+ * Force specific provider (bypass region routing)
227
+ */
228
+ forceProvider?: PaymentProviderType;
229
+ }
230
+ /**
231
+ * Subscribe result
232
+ */
233
+ interface SubscribeResult {
234
+ /** Checkout URL to redirect user */
235
+ checkoutUrl: string;
236
+ /** Checkout session ID */
237
+ sessionId: string;
238
+ /** Customer ID (created or existing) */
239
+ customerId: string;
240
+ /** Provider used */
241
+ provider: PaymentProviderType;
242
+ /** Region detected */
243
+ region: BillingRegion;
244
+ }
245
+ /**
246
+ * Cancel subscription options
247
+ */
248
+ interface CancelOptions {
249
+ /** Subscription ID */
250
+ subscriptionId: string;
251
+ /** Cancel immediately or at period end */
252
+ immediate?: boolean;
253
+ /** Reason for cancellation */
254
+ reason?: string;
255
+ /** Provider (if known, for faster lookup) */
256
+ provider?: PaymentProviderType;
257
+ }
258
+ /**
259
+ * Cancel result
260
+ */
261
+ interface CancelResult {
262
+ /** Subscription ID */
263
+ subscriptionId: string;
264
+ /** New status */
265
+ status: string;
266
+ /** When subscription will end */
267
+ endsAt: Date;
268
+ /** Provider used */
269
+ provider: PaymentProviderType;
270
+ }
271
+ /**
272
+ * Get subscription options
273
+ */
274
+ interface GetSubscriptionOptions {
275
+ /** Customer ID */
276
+ customerId?: string;
277
+ /** Customer email (alternative to customerId) */
278
+ email?: string;
279
+ /** Specific subscription ID */
280
+ subscriptionId?: string;
281
+ /** Provider hint */
282
+ provider?: PaymentProviderType;
283
+ }
284
+ /**
285
+ * Subscription with provider info
286
+ */
287
+ interface BillingSubscription extends Subscription {
288
+ /** Provider that manages this subscription */
289
+ provider: PaymentProviderType;
290
+ }
291
+ /**
292
+ * Customer with provider info
293
+ */
294
+ interface BillingCustomer extends Customer {
295
+ /** Provider that manages this customer */
296
+ provider: PaymentProviderType;
297
+ /** Region */
298
+ region?: BillingRegion;
299
+ }
300
+ /**
301
+ * Provider selection result
302
+ */
303
+ interface ProviderSelection {
304
+ /** Selected provider */
305
+ provider: PaymentProvider;
306
+ /** Provider type */
307
+ type: PaymentProviderType;
308
+ /** Region used for selection */
309
+ region: BillingRegion;
310
+ /** Selection reason */
311
+ reason: "region" | "rule" | "default" | "forced" | "fallback";
312
+ }
313
+ /**
314
+ * Billing error codes
315
+ */
316
+ declare const BillingErrorCodes: {
317
+ readonly NO_PROVIDER_CONFIGURED: "NO_PROVIDER_CONFIGURED";
318
+ readonly PROVIDER_UNAVAILABLE: "PROVIDER_UNAVAILABLE";
319
+ readonly ALL_PROVIDERS_FAILED: "ALL_PROVIDERS_FAILED";
320
+ readonly REGION_NOT_SUPPORTED: "REGION_NOT_SUPPORTED";
321
+ readonly SUBSCRIPTION_NOT_FOUND: "SUBSCRIPTION_NOT_FOUND";
322
+ readonly CUSTOMER_NOT_FOUND: "CUSTOMER_NOT_FOUND";
323
+ readonly FALLBACK_DISABLED: "FALLBACK_DISABLED";
324
+ readonly OPERATION_NOT_ALLOWED: "OPERATION_NOT_ALLOWED";
325
+ };
326
+ type BillingErrorCode = keyof typeof BillingErrorCodes;
327
+ /**
328
+ * Billing error
329
+ */
330
+ declare class BillingError extends Error {
331
+ readonly code: BillingErrorCode;
332
+ readonly provider?: PaymentProviderType | undefined;
333
+ readonly cause?: Error | undefined;
334
+ constructor(message: string, code: BillingErrorCode, provider?: PaymentProviderType | undefined, cause?: Error | undefined);
335
+ }
336
+
337
+ /**
338
+ * @parsrun/payments - Billing Service
339
+ * High-level billing API with multi-provider support
340
+ */
341
+
342
+ /**
343
+ * Billing Service
344
+ *
345
+ * High-level billing API with:
346
+ * - Multi-provider support (Stripe, Paddle, iyzico)
347
+ * - Region-based provider routing
348
+ * - Optional fallback mechanism
349
+ * - Unified webhook handling
350
+ *
351
+ * @example Basic usage
352
+ * ```typescript
353
+ * const billing = createBillingService({
354
+ * providers: {
355
+ * default: stripeProvider,
356
+ * regions: {
357
+ * TR: iyzicoProvider,
358
+ * EU: stripeProvider,
359
+ * },
360
+ * },
361
+ * });
362
+ *
363
+ * // Subscribe a customer
364
+ * const result = await billing.subscribe({
365
+ * email: "user@example.com",
366
+ * planId: "price_xxx",
367
+ * successUrl: "https://app.com/success",
368
+ * cancelUrl: "https://app.com/cancel",
369
+ * countryCode: "TR", // Will use iyzico
370
+ * });
371
+ *
372
+ * // Redirect to checkout
373
+ * redirect(result.checkoutUrl);
374
+ * ```
375
+ *
376
+ * @example With fallback (opt-in)
377
+ * ```typescript
378
+ * const billing = createBillingService({
379
+ * providers: {
380
+ * default: stripeProvider,
381
+ * regions: { TR: iyzicoProvider },
382
+ * },
383
+ * fallback: {
384
+ * enabled: true, // WARNING: Enable with caution
385
+ * allowedOperations: ["createCheckout"],
386
+ * maxAttempts: 1,
387
+ * onFallback: (ctx) => {
388
+ * logger.warn("Payment fallback triggered", ctx);
389
+ * },
390
+ * },
391
+ * });
392
+ * ```
393
+ */
394
+ declare class BillingService {
395
+ private readonly strategy;
396
+ private readonly fallback;
397
+ private readonly webhookRegistry;
398
+ private readonly webhookProcessors;
399
+ private readonly logger;
400
+ private readonly tenantId;
401
+ private readonly debug;
402
+ constructor(config: BillingServiceConfig);
403
+ /**
404
+ * Subscribe a customer to a plan
405
+ *
406
+ * This is the recommended way to handle subscriptions:
407
+ * 1. Creates or retrieves customer
408
+ * 2. Selects provider based on region
409
+ * 3. Creates checkout session
410
+ * 4. Returns checkout URL for redirect
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const { checkoutUrl } = await billing.subscribe({
415
+ * email: "user@example.com",
416
+ * planId: "price_monthly",
417
+ * successUrl: "https://app.com/success",
418
+ * cancelUrl: "https://app.com/cancel",
419
+ * countryCode: "TR",
420
+ * });
421
+ * redirect(checkoutUrl);
422
+ * ```
423
+ */
424
+ subscribe(options: SubscribeOptions): Promise<SubscribeResult>;
425
+ /**
426
+ * Cancel a subscription
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const result = await billing.cancel({
431
+ * subscriptionId: "sub_xxx",
432
+ * immediate: false, // Cancel at period end
433
+ * });
434
+ * ```
435
+ */
436
+ cancel(options: CancelOptions): Promise<CancelResult>;
437
+ /**
438
+ * Get subscription(s) for a customer
439
+ *
440
+ * @example
441
+ * ```typescript
442
+ * // Get by customer ID
443
+ * const subs = await billing.getSubscriptions({ customerId: "cus_xxx" });
444
+ *
445
+ * // Get specific subscription
446
+ * const sub = await billing.getSubscription({ subscriptionId: "sub_xxx" });
447
+ * ```
448
+ */
449
+ getSubscriptions(options: GetSubscriptionOptions): Promise<BillingSubscription[]>;
450
+ /**
451
+ * Get a single subscription
452
+ */
453
+ getSubscription(options: GetSubscriptionOptions): Promise<BillingSubscription | null>;
454
+ /**
455
+ * Get customer by ID
456
+ */
457
+ getCustomer(customerId: string, provider?: PaymentProviderType): Promise<BillingCustomer | null>;
458
+ /**
459
+ * Create a customer portal session
460
+ */
461
+ createPortalSession(customerId: string, returnUrl: string, provider?: PaymentProviderType): Promise<{
462
+ url: string;
463
+ provider: PaymentProviderType;
464
+ }>;
465
+ /**
466
+ * Select provider for a region/context
467
+ */
468
+ selectProvider(context: RegionDetectionContext, forceProvider?: PaymentProviderType): Promise<ProviderSelection>;
469
+ /**
470
+ * Get all configured providers
471
+ */
472
+ getProviders(): PaymentProvider[];
473
+ /**
474
+ * Get provider by type
475
+ */
476
+ getProvider(type: PaymentProviderType): PaymentProvider | undefined;
477
+ /**
478
+ * Get supported regions
479
+ */
480
+ getSupportedRegions(): BillingRegion[];
481
+ /**
482
+ * Register webhook handler for all providers
483
+ *
484
+ * @example
485
+ * ```typescript
486
+ * billing.onWebhook("subscription.created", async (event) => {
487
+ * console.log(`New subscription from ${event.provider}:`, event.data);
488
+ * });
489
+ *
490
+ * // Handle all events
491
+ * billing.onWebhook("*", async (event) => {
492
+ * await saveToAuditLog(event);
493
+ * });
494
+ * ```
495
+ */
496
+ onWebhook(type: WebhookEventType | "*", handler: WebhookHandler): this;
497
+ /**
498
+ * Handle webhook request
499
+ *
500
+ * @example
501
+ * ```typescript
502
+ * app.post("/webhooks/:provider", async (c) => {
503
+ * const provider = c.req.param("provider") as PaymentProviderType;
504
+ * const result = await billing.handleWebhook(c.req.raw, provider);
505
+ * return c.json({ received: result.success });
506
+ * });
507
+ * ```
508
+ */
509
+ handleWebhook(request: Request, provider: PaymentProviderType): Promise<WebhookProcessResult>;
510
+ /**
511
+ * Handle raw webhook payload
512
+ */
513
+ handleWebhookRaw(payload: string | Uint8Array, signature: string, provider: PaymentProviderType): Promise<WebhookProcessResult>;
514
+ /**
515
+ * Execute operation on specific provider
516
+ *
517
+ * Use this for advanced operations not covered by high-level API
518
+ *
519
+ * @example
520
+ * ```typescript
521
+ * const prices = await billing.withProvider("stripe", async (provider) => {
522
+ * return provider.listPrices?.("prod_xxx") ?? [];
523
+ * });
524
+ * ```
525
+ */
526
+ withProvider<T>(type: PaymentProviderType, operation: (provider: PaymentProvider) => Promise<T>): Promise<T>;
527
+ /**
528
+ * Execute operation with fallback support
529
+ */
530
+ private executeWithFallback;
531
+ /**
532
+ * Find which provider owns a subscription
533
+ */
534
+ private findSubscriptionProvider;
535
+ /**
536
+ * Find which provider owns a customer
537
+ */
538
+ private findCustomerProvider;
539
+ }
540
+ /**
541
+ * Create billing service
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * import { createBillingService, createStripeProvider, createIyzicoProvider } from "@parsrun/payments";
546
+ *
547
+ * const stripeProvider = createStripeProvider({
548
+ * secretKey: process.env.STRIPE_SECRET_KEY!,
549
+ * webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
550
+ * });
551
+ *
552
+ * const iyzicoProvider = createIyzicoProvider({
553
+ * apiKey: process.env.IYZICO_API_KEY!,
554
+ * secretKey: process.env.IYZICO_SECRET_KEY!,
555
+ * baseUrl: "https://api.iyzipay.com",
556
+ * });
557
+ *
558
+ * const billing = createBillingService({
559
+ * providers: {
560
+ * default: stripeProvider,
561
+ * regions: {
562
+ * TR: iyzicoProvider,
563
+ * EU: stripeProvider,
564
+ * US: stripeProvider,
565
+ * },
566
+ * },
567
+ * fallback: {
568
+ * enabled: false, // Disabled by default - enable with caution!
569
+ * },
570
+ * debug: process.env.NODE_ENV === "development",
571
+ * });
572
+ *
573
+ * export { billing };
574
+ * ```
575
+ */
576
+ declare function createBillingService(config: BillingServiceConfig): BillingService;
577
+
578
+ export { BillingService as B, type CancelOptions as C, type FallbackConfig as F, type GetSubscriptionOptions as G, type ProviderRoutingRule as P, type RegionDetectionResult as R, type SubscribeOptions as S, BillingError as a, BillingErrorCodes as b, createBillingService as c, type BillingRegion as d, type RegionDetector as e, type RegionDetectionContext as f, type ProviderStrategyConfig as g, type FallbackOperation as h, type FallbackContext as i, type BillingServiceConfig as j, type BillingLogger as k, type SubscribeResult as l, type CancelResult as m, type BillingSubscription as n, type BillingCustomer as o, type ProviderSelection as p, type BillingErrorCode as q };