@revstackhq/providers-core 0.0.0-dev-20260209034148

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,38 @@
1
+ BUSINESS SOURCE LICENSE 1.1
2
+
3
+ The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make use of the Licensed Work for any purpose, except as limited by the Additional Use Grant below.
4
+
5
+ Redistribution is allowed only if this License is included with the Licensed Work and any derivative works.
6
+
7
+ ADDITIONAL USE GRANT:
8
+
9
+ You may make use of the Licensed Work, provided that you do not use the Licensed Work to provide a "Managed Payment Orchestration Service".
10
+
11
+ A "Managed Payment Orchestration Service" is defined as a commercial service, product, or offering where the Licensed Work is used to provide payment routing, checkout orchestration, or connection management to third parties as a service (SaaS, PaaS, or IaaS), whether for a fee or free of charge.
12
+
13
+ For clarity:
14
+ 1. You MAY use the Licensed Work internally to process payments for your own business or merchandise.
15
+ 2. You MAY NOT use the Licensed Work to build a competitor service to Revstack that allows your customers to orchestrate their own payments.
16
+
17
+ CHANGE DATE:
18
+
19
+ January 1, 2031
20
+
21
+ CHANGE LICENSE:
22
+
23
+ Apache License, Version 2.0
24
+
25
+ ERROR CORRECTIONS AND SECURITY UPDATES:
26
+
27
+ The Licensor may, at its option, provide error corrections and security updates to the Licensed Work. If the Licensor provides such error corrections and security updates, they are considered part of the Licensed Work and are subject to this License.
28
+
29
+ MANAGED SERVICE EXCEPTION:
30
+
31
+ If you have a separate written agreement with the Licensor (e.g., an Enterprise License) that specifically grants you the right to provide a Managed Payment Orchestration Service, that agreement supersedes the limitations of the Additional Use Grant.
32
+
33
+ *****************************************************************************
34
+
35
+ LICENSOR: Revstack Inc.
36
+ LICENSED WORK: The Revstack OS software and associated documentation.
37
+
38
+ *****************************************************************************
@@ -0,0 +1,621 @@
1
+ /**
2
+ * The execution context for any provider operation.
3
+ * Contains the decrypted configuration and environment info.
4
+ */
5
+ interface ProviderContext {
6
+ /**
7
+ * The decrypted credentials/settings for this specific merchant connection.
8
+ * e.g., { apiKey: "sk_live_...", webhookSecret: "whsec_..." }
9
+ */
10
+ config: Record<string, any>;
11
+ /**
12
+ * Optional: The webhook ID or relevant correlation ID for logging/tracing.
13
+ */
14
+ traceId?: string;
15
+ /**
16
+ * Indicates if the operation is running in Sandbox/Test mode.
17
+ */
18
+ isTestMode: boolean;
19
+ /**
20
+ * Optional: Idempotency key for duplicate request prevention.
21
+ */
22
+ idempotencyKey?: string;
23
+ }
24
+
25
+ /**
26
+ * * Normalized data models for the Revstack ecosystem.
27
+ * These types represent the "Internal Source of Truth" for the OS.
28
+ */
29
+ declare enum PaymentStatus {
30
+ Pending = "pending",
31
+ Authorized = "authorized",// Funds held but not captured yet
32
+ Succeeded = "succeeded",
33
+ Failed = "failed",
34
+ Refunded = "refunded",
35
+ PartiallyRefunded = "partially_refunded",
36
+ Disputed = "disputed",
37
+ Canceled = "canceled"
38
+ }
39
+ type PaymentMethodDetails = {
40
+ type: "card" | "bank_transfer" | "wallet" | "crypto" | "checkout";
41
+ brand?: string;
42
+ last4?: string;
43
+ email?: string;
44
+ expiryMonth?: number;
45
+ expiryYear?: number;
46
+ cardHolderName?: string;
47
+ };
48
+ type Payment = {
49
+ id: string;
50
+ providerId: string;
51
+ externalId: string;
52
+ amount: number;
53
+ /** * Financial breakdown, vital for invoicing and tax calculation.
54
+ */
55
+ amountDetails?: {
56
+ subtotal: number;
57
+ tax: number;
58
+ shipping: number;
59
+ discount: number;
60
+ fee?: number;
61
+ };
62
+ amountRefunded: number;
63
+ currency: string;
64
+ status: PaymentStatus;
65
+ method?: PaymentMethodDetails;
66
+ description?: string;
67
+ customerId?: string;
68
+ externalCustomerId?: string;
69
+ createdAt: string;
70
+ updatedAt?: string;
71
+ metadata?: Record<string, any>;
72
+ raw?: any;
73
+ };
74
+ declare enum SubscriptionStatus {
75
+ Trialing = "trialing",
76
+ Active = "active",
77
+ PastDue = "past_due",
78
+ Canceled = "canceled",
79
+ Unpaid = "unpaid",
80
+ Incomplete = "incomplete",
81
+ IncompleteExpired = "incomplete_expired",
82
+ Paused = "paused"
83
+ }
84
+ type Subscription = {
85
+ id: string;
86
+ providerId: string;
87
+ externalId: string;
88
+ status: SubscriptionStatus;
89
+ planId?: string;
90
+ externalPlanId?: string;
91
+ amount: number;
92
+ currency: string;
93
+ interval: "day" | "week" | "month" | "year";
94
+ customerId: string;
95
+ currentPeriodStart: string;
96
+ currentPeriodEnd: string;
97
+ cancelAtPeriodEnd: boolean;
98
+ canceledAt?: string;
99
+ startedAt: string;
100
+ endedAt?: string;
101
+ trialStart?: string;
102
+ trialEnd?: string;
103
+ metadata?: Record<string, any>;
104
+ };
105
+ type SubscriptionInterval = "day" | "week" | "month" | "year";
106
+ type CreateCustomerInput = {
107
+ email: string;
108
+ name?: string;
109
+ phone?: string;
110
+ description?: string;
111
+ address?: Address;
112
+ metadata?: Record<string, any>;
113
+ };
114
+ type UpdateCustomerInput = Partial<CreateCustomerInput>;
115
+ type CreatePaymentInput = {
116
+ amount: number;
117
+ currency: string;
118
+ customerId?: string;
119
+ paymentMethodId?: string;
120
+ description?: string;
121
+ capture?: boolean;
122
+ billingAddress?: Address;
123
+ shippingAddress?: Address;
124
+ metadata?: Record<string, any>;
125
+ providerOptions?: any;
126
+ };
127
+ type RefundPaymentInput = {
128
+ paymentId: string;
129
+ externalPaymentId?: string;
130
+ amount?: number;
131
+ reason?: "duplicate" | "fraudulent" | "requested_by_customer";
132
+ metadata?: Record<string, any>;
133
+ };
134
+ type SetupPaymentMethodInput = {
135
+ customerId: string;
136
+ returnUrl: string;
137
+ metadata?: Record<string, any>;
138
+ };
139
+ type CheckoutSessionInput = {
140
+ customerId?: string;
141
+ customerEmail?: string;
142
+ setupFutureUsage?: boolean;
143
+ lineItems: {
144
+ name: string;
145
+ amount: number;
146
+ quantity: number;
147
+ currency: string;
148
+ images?: string[];
149
+ taxRates?: string[];
150
+ }[];
151
+ successUrl: string;
152
+ cancelUrl: string;
153
+ billingAddressCollection?: "auto" | "required";
154
+ mode: "payment" | "subscription" | "setup";
155
+ metadata?: Record<string, any>;
156
+ };
157
+ type CreateSubscriptionInput = {
158
+ customerId: string;
159
+ planId?: string;
160
+ priceId?: string;
161
+ quantity?: number;
162
+ trialDays?: number;
163
+ metadata?: Record<string, any>;
164
+ };
165
+ type PaymentResult = {
166
+ payment: Payment;
167
+ /**
168
+ * Action required to complete the payment (e.g., 3DS Redirect).
169
+ */
170
+ nextAction?: {
171
+ type: "redirect" | "modal";
172
+ url: string;
173
+ };
174
+ };
175
+ type SubscriptionResult = {
176
+ subscription: Subscription;
177
+ };
178
+ type CheckoutSessionResult = {
179
+ id: string;
180
+ url: string;
181
+ expiresAt?: string;
182
+ };
183
+ type PaginationOptions = {
184
+ limit?: number;
185
+ cursor?: string;
186
+ startingAfter?: string;
187
+ page?: number;
188
+ };
189
+ type PaginatedResult<T> = {
190
+ data: T[];
191
+ hasMore: boolean;
192
+ nextCursor?: string;
193
+ };
194
+ type Address = {
195
+ line1: string;
196
+ line2?: string;
197
+ city: string;
198
+ state?: string;
199
+ postalCode: string;
200
+ country: string;
201
+ };
202
+ type Customer = {
203
+ id: string;
204
+ providerId: string;
205
+ externalId: string;
206
+ email: string;
207
+ name?: string;
208
+ phone?: string;
209
+ metadata?: Record<string, any>;
210
+ createdAt: string;
211
+ };
212
+ type PaymentMethod = {
213
+ id: string;
214
+ customerId: string;
215
+ externalId: string;
216
+ type: "card" | "bank_transfer" | "wallet";
217
+ details: PaymentMethodDetails;
218
+ isDefault: boolean;
219
+ metadata?: Record<string, any>;
220
+ };
221
+
222
+ interface IPaymentFeature {
223
+ createPayment(ctx: ProviderContext, input: CreatePaymentInput): Promise<PaymentResult>;
224
+ getPayment(ctx: ProviderContext, id: string): Promise<Payment>;
225
+ refundPayment(ctx: ProviderContext, input: RefundPaymentInput): Promise<Payment>;
226
+ listPayments?(ctx: ProviderContext, pagination: PaginationOptions): Promise<PaginatedResult<Payment>>;
227
+ }
228
+ interface ISubscriptionFeature {
229
+ createSubscription(ctx: ProviderContext, input: CreateSubscriptionInput): Promise<SubscriptionResult>;
230
+ cancelSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
231
+ pauseSubscription(ctx: ProviderContext, id: string): Promise<SubscriptionResult>;
232
+ resumeSubscription(ctx: ProviderContext, id: string): Promise<SubscriptionResult>;
233
+ getSubscription(ctx: ProviderContext, id: string): Promise<Subscription>;
234
+ }
235
+ interface ICheckoutFeature {
236
+ createCheckoutSession(ctx: ProviderContext, input: CheckoutSessionInput): Promise<CheckoutSessionResult>;
237
+ }
238
+ interface ICustomerFeature {
239
+ createCustomer(ctx: ProviderContext, input: CreateCustomerInput): Promise<Customer>;
240
+ updateCustomer(ctx: ProviderContext, id: string, input: UpdateCustomerInput): Promise<Customer>;
241
+ deleteCustomer(ctx: ProviderContext, id: string): Promise<boolean>;
242
+ getCustomer(ctx: ProviderContext, id: string): Promise<Customer>;
243
+ }
244
+ interface IPaymentMethodFeature {
245
+ listPaymentMethods(ctx: ProviderContext, customerId: string): Promise<PaymentMethod[]>;
246
+ deletePaymentMethod(ctx: ProviderContext, id: string): Promise<boolean>;
247
+ }
248
+ /**
249
+ * Unified interface that all providers must satisfy.
250
+ * (Even if they just throw 'Not Implemented' errors for unsupported features).
251
+ */
252
+ interface IProvider extends IPaymentFeature, ISubscriptionFeature, ICheckoutFeature, ICustomerFeature, IPaymentMethodFeature {
253
+ }
254
+
255
+ type EventType = "PAYMENT_SUCCEEDED" | "PAYMENT_FAILED" | "PAYMENT_AUTHORIZED" | "PAYMENT_CAPTURED" | "REFUND_PROCESSED" | "REFUND_FAILED" | "SUBSCRIPTION_CREATED" | "SUBSCRIPTION_UPDATED" | "SUBSCRIPTION_CANCELED" | "DISPUTE_CREATED" | "DISPUTE_RESOLVED" | "MANDATE_CREATED";
256
+ interface RevstackEvent {
257
+ type: EventType;
258
+ providerEventId: string;
259
+ createdAt: Date;
260
+ resourceId: string;
261
+ originalPayload: any;
262
+ metadata?: Record<string, any>;
263
+ }
264
+ interface WebhookResponse {
265
+ statusCode: number;
266
+ body: any;
267
+ }
268
+
269
+ type CheckoutStrategy = "redirect" | "native_sdk" | "sdui";
270
+ /**
271
+ * Defines who orchestrates the recurring billing logic.
272
+ */
273
+ type SubscriptionMode = "native" | "virtual";
274
+ interface ProviderCapabilities {
275
+ checkout: {
276
+ supported: boolean;
277
+ strategy: "redirect" | "native_sdk" | "sdui";
278
+ };
279
+ payments: {
280
+ supported: boolean;
281
+ features: {
282
+ refunds: boolean;
283
+ partialRefunds: boolean;
284
+ capture: boolean;
285
+ disputes: boolean;
286
+ };
287
+ };
288
+ subscriptions: {
289
+ supported: boolean;
290
+ mode: "native" | "virtual";
291
+ features: {
292
+ pause: boolean;
293
+ resume: boolean;
294
+ cancellation: boolean;
295
+ proration?: boolean;
296
+ };
297
+ };
298
+ customers: {
299
+ supported: boolean;
300
+ features: {
301
+ create: boolean;
302
+ update: boolean;
303
+ delete: boolean;
304
+ };
305
+ };
306
+ webhooks: {
307
+ supported: boolean;
308
+ verification: "signature" | "secret" | "none";
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Catálogo completo de errores estandarizados para Revstack.
314
+ * Agrupados por dominio para facilitar su manejo en el Frontend/API.
315
+ */
316
+ declare enum RevstackErrorCode {
317
+ UnknownError = "unknown_error",
318
+ InternalError = "internal_error",
319
+ NotImplemented = "not_implemented",// Para features opcionales no soportadas por un provider
320
+ Timeout = "timeout",
321
+ RateLimitExceeded = "rate_limit_exceeded",
322
+ InvalidCredentials = "invalid_credentials",// API Key incorrecta
323
+ Unauthorized = "unauthorized",// No tiene permisos
324
+ MisconfiguredProvider = "misconfigured_provider",// Faltan campos en el dashboard
325
+ AccountSuspended = "account_suspended",// La cuenta del merchant en Stripe/etc está bloqueada
326
+ InvalidInput = "invalid_input",
327
+ MissingRequiredField = "missing_required_field",
328
+ InvalidEmail = "invalid_email",
329
+ InvalidAmount = "invalid_amount",// Monto negativo o cero
330
+ InvalidCurrency = "invalid_currency",// Provider no soporta esa moneda
331
+ ResourceNotFound = "resource_not_found",
332
+ ResourceAlreadyExists = "resource_already_exists",
333
+ IdempotencyKeyConflict = "idempotency_key_conflict",
334
+ PaymentFailed = "payment_failed",// Fallo genérico
335
+ CardDeclined = "card_declined",// El banco rechazó la tarjeta
336
+ InsufficientFunds = "insufficient_funds",// No hay saldo
337
+ ExpiredCard = "expired_card",
338
+ IncorrectCvc = "incorrect_cvc",
339
+ AuthenticationRequired = "authentication_required",// Requiere 3D Secure / SCA
340
+ LimitExceeded = "limit_exceeded",// Límite de la tarjeta excedido
341
+ DuplicateTransaction = "duplicate_transaction",
342
+ SubscriptionNotFound = "subscription_not_found",
343
+ SubscriptionAlreadyActive = "subscription_already_active",
344
+ SubscriptionCancelled = "subscription_cancelled",// Intentar operar sobre una cancelada
345
+ PlanNotFound = "plan_not_found",
346
+ RefundFailed = "refund_failed",
347
+ DisputeLost = "dispute_lost",
348
+ ProviderUnavailable = "provider_unavailable",// Stripe está caído
349
+ ProviderRejected = "provider_rejected",// El provider rechazó la conexión (ej: riesgo alto)
350
+ WebhookSignatureVerificationFailed = "webhook_signature_verification_failed"
351
+ }
352
+ /**
353
+ * Estructura del Error.
354
+ * Extendemos la clase nativa 'Error' para mantener el stack trace
355
+ * y permitir 'instanceof RevstackError'.
356
+ */
357
+ declare class RevstackError extends Error {
358
+ readonly code: RevstackErrorCode;
359
+ readonly provider?: string;
360
+ readonly cause?: any;
361
+ readonly statusCode: number;
362
+ readonly documentationUrl?: string;
363
+ constructor(opts: {
364
+ code: RevstackErrorCode;
365
+ message: string;
366
+ provider?: string;
367
+ cause?: any;
368
+ documentationUrl?: string;
369
+ });
370
+ /**
371
+ * Mapea el código de error interno a un HTTP Status Code estándar.
372
+ * Útil para que la API responda correctamente sin lógica extra en el controller.
373
+ */
374
+ private mapToStatusCode;
375
+ }
376
+ /**
377
+ * Helper factory para compatibilidad con código funcional o uso rápido.
378
+ */
379
+ declare function createError(code: RevstackErrorCode, message: string, provider?: string, cause?: any): RevstackError;
380
+ /**
381
+ * Type Guard para verificar si un error es de Revstack.
382
+ */
383
+ declare function isRevstackError(error: unknown): error is RevstackError;
384
+
385
+ interface InstallInput {
386
+ config: Record<string, any>;
387
+ /**
388
+ * The absolute URL where Revstack expects to receive events for this merchant.
389
+ */
390
+ webhookUrl: string;
391
+ }
392
+ interface InstallResult {
393
+ success: boolean;
394
+ data?: Record<string, any>;
395
+ error?: RevstackError;
396
+ }
397
+ interface UninstallInput {
398
+ config: Record<string, any>;
399
+ data: Record<string, any>;
400
+ }
401
+ interface UninstallResult {
402
+ success: boolean;
403
+ error?: RevstackError;
404
+ }
405
+
406
+ declare enum ProviderCategory {
407
+ Card = "card",// Stripe, Adyen, dLocal
408
+ BankTransfer = "bank",// PSE, PIX, Wire, Spei
409
+ Wallet = "wallet",// PayPal, MercadoPago, ApplePay
410
+ Crypto = "crypto",// Coinbase, BitPay
411
+ Cash = "cash",// OXXO, Rapipago, PagoFácil
412
+ BuyNowPayLater = "bnpl"
413
+ }
414
+ declare const CATEGORY_LABELS: Record<ProviderCategory, string>;
415
+
416
+ /**
417
+ * Defines the input field type for the installation UI.
418
+ */
419
+ type ConfigFieldType = "text" | "password" | "switch" | "select" | "number" | "json";
420
+ /**
421
+ * Configuration schema for a provider setting.
422
+ * Used to generate the UI and handle encryption.
423
+ */
424
+ interface ConfigFieldDefinition {
425
+ /** Label to display in the UI */
426
+ label: string;
427
+ /** Input type */
428
+ type: ConfigFieldType;
429
+ /** If true, this field must be encrypted in the DB */
430
+ secure: boolean;
431
+ /** Is this field required? */
432
+ required: boolean;
433
+ /** Description or tooltip for the user */
434
+ description?: string;
435
+ /** Options for 'select' type */
436
+ options?: {
437
+ label: string;
438
+ value: string;
439
+ }[];
440
+ }
441
+ interface DataFieldDefinition {
442
+ /**
443
+ * If true, the value of this field will be encrypted in the DB.
444
+ */
445
+ secure: boolean;
446
+ /**
447
+ * Description for internal documentation (optional)
448
+ */
449
+ description?: string;
450
+ }
451
+ /**
452
+ * The Provider Manifest.
453
+ * Acts as the source of truth for the provider's metadata and capabilities.
454
+ */
455
+ interface ProviderManifest {
456
+ /** Unique identifier (e.g., 'stripe', 'polar') */
457
+ slug: string;
458
+ /** Display name (e.g., 'Stripe') */
459
+ name: string;
460
+ /** Provider category */
461
+ category: ProviderCategory;
462
+ /** * Semantic version of the provider package (e.g., '1.0.0').
463
+ * Vital for managing updates in the marketplace.
464
+ */
465
+ version: string;
466
+ /** Short description displayed in the marketplace card. */
467
+ description?: string;
468
+ /** URL to the provider's logo */
469
+ logoUrl?: string;
470
+ /** The organization or developer maintaining this provider. */
471
+ author?: string;
472
+ /** Link to the official documentation for this specific provider. */
473
+ documentationUrl?: string;
474
+ /** Link to the support page or repository issue tracker. */
475
+ supportUrl?: string;
476
+ /** * List of supported ISO 3166-1 alpha-2 country codes (e.g., ['US', 'GB', 'BR']).
477
+ * Use ['global'] if the provider works worldwide.
478
+ * Used to filter providers in the UI based on the merchant's location.
479
+ */
480
+ regions?: string[];
481
+ /**
482
+ * List of supported ISO 4217 currency codes (e.g., ['USD', 'EUR']).
483
+ * Use ['*'] if the provider supports all currencies.
484
+ */
485
+ currencies?: string[];
486
+ /** * Indicates if the provider supports a dedicated sandbox/test mode.
487
+ * If true, the UI should allow switching between Test and Live credentials.
488
+ */
489
+ sandboxAvailable?: boolean;
490
+ /** * Schema to generate the installation form.
491
+ * Key is the internal config key (e.g., 'apiKey').
492
+ */
493
+ configSchema: Record<string, ConfigFieldDefinition>;
494
+ /**
495
+ * Defines the fields that the provider generates and stores internally (Outputs).
496
+ * The Core uses this to know which fields to encrypt in the 'data' column.
497
+ */
498
+ dataSchema?: Record<string, DataFieldDefinition>;
499
+ /**
500
+ * What this provider can do. Used for feature flagging in the core.
501
+ */
502
+ capabilities: ProviderCapabilities;
503
+ }
504
+
505
+ /**
506
+ * src/base.ts
507
+ * * The Foundation of the Provider Development Kit (PDK).
508
+ */
509
+
510
+ /**
511
+ * The Abstract Base Class for all Revstack Providers.
512
+ * * It implements the IProvider interface with default behaviors (throwing errors).
513
+ * Specific providers (e.g., Stripe) will override only the methods they actually support.
514
+ */
515
+ declare abstract class BaseProvider implements IProvider {
516
+ getPayment(ctx: ProviderContext, id: string): Promise<Payment>;
517
+ refundPayment(ctx: ProviderContext, input: RefundPaymentInput): Promise<Payment>;
518
+ listPayments?(ctx: ProviderContext, pagination: PaginationOptions): Promise<PaginatedResult<Payment>>;
519
+ getSubscription(ctx: ProviderContext, id: string): Promise<Subscription>;
520
+ createCustomer(ctx: ProviderContext, input: CreateCustomerInput): Promise<Customer>;
521
+ updateCustomer(ctx: ProviderContext, id: string, input: UpdateCustomerInput): Promise<Customer>;
522
+ deleteCustomer(ctx: ProviderContext, id: string): Promise<boolean>;
523
+ getCustomer(ctx: ProviderContext, id: string): Promise<Customer>;
524
+ listPaymentMethods(ctx: ProviderContext, customerId: string): Promise<PaymentMethod[]>;
525
+ deletePaymentMethod(ctx: ProviderContext, id: string): Promise<boolean>;
526
+ /**
527
+ * The static manifest definition.
528
+ * Defines capabilities, metadata, and config schema (UI).
529
+ */
530
+ abstract readonly manifest: ProviderManifest;
531
+ /**
532
+ * Called when a merchant installs or updates this provider.
533
+ * * RESPONSIBILITY:
534
+ * 1. Instantiate the provider SDK with the input config.
535
+ * 2. Validate credentials (e.g., make a 'ping' or 'get balance' request).
536
+ * 3. Return the payload to be saved in the database.
537
+ * * @param ctx - The execution context (includes environment info).
538
+ * @param input - The input data provided by the user in the UI.
539
+ * @returns The success status and the data to be stored (encrypted by Core).
540
+ */
541
+ abstract onInstall(ctx: ProviderContext, input: InstallInput): Promise<InstallResult>;
542
+ /**
543
+ * Called when a merchant uninstalls this provider.
544
+ * * RESPONSIBILITY:
545
+ * 1. Instantiate the provider SDK with the input config.
546
+ * 2. Validate credentials (e.g., make a 'ping' or 'get balance' request).
547
+ * 3. Return the success status.
548
+ * * @param ctx - The execution context (includes environment info).
549
+ * @param input - The input data provided by the user in the UI.
550
+ * @returns The success status.
551
+ */
552
+ abstract onUninstall(ctx: ProviderContext, input: UninstallInput): Promise<boolean>;
553
+ /**
554
+ * Verifies the cryptographic signature of an incoming webhook.
555
+ * * SECURITY CRITICAL:
556
+ * This ensures that the request actually originated from the Payment Provider
557
+ * and hasn't been tampered with.
558
+ * * @param payload - The raw body of the request (string or buffer).
559
+ * @param headers - The request headers.
560
+ * @param secret - The webhook signing secret stored in the DB.
561
+ */
562
+ abstract verifyWebhookSignature(ctx: ProviderContext, payload: string | Buffer, headers: Record<string, string | string[] | undefined>, secret: string): Promise<boolean>;
563
+ /**
564
+ * Transforms a raw provider payload into a standardized Revstack Event.
565
+ * * This acts as the "Translation Layer" or "Adapter" for incoming events.
566
+ * * @param payload - The raw JSON body from the provider.
567
+ * @returns A normalized RevstackEvent or null if the event is irrelevant.
568
+ */
569
+ abstract parseWebhookEvent(payload: any): Promise<RevstackEvent | null>;
570
+ /**
571
+ * Returns the HTTP response that should be sent back to the Provider
572
+ * after receiving a webhook.
573
+ * * Default implementation returns 200 OK. Override if the provider expects
574
+ * a specific XML or JSON confirmation.
575
+ */
576
+ getWebhookResponse(): Promise<WebhookResponse>;
577
+ /**
578
+ * Process a one-time payment.
579
+ * Override this if manifest.capabilities.payments.oneTime is true.
580
+ * * @throws Error if not implemented by the specific provider.
581
+ */
582
+ createPayment(ctx: ProviderContext, input: CreatePaymentInput): Promise<PaymentResult>;
583
+ /**
584
+ * Create a recurring subscription.
585
+ * Override this if manifest.capabilities.subscriptions.native is true.
586
+ */
587
+ createSubscription(ctx: ProviderContext, input: CreateSubscriptionInput): Promise<SubscriptionResult>;
588
+ /**
589
+ * Cancel an active subscription immediately or at period end.
590
+ */
591
+ cancelSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
592
+ pauseSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
593
+ resumeSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
594
+ /**
595
+ * Generate a hosted checkout session URL (e.g., Stripe Checkout).
596
+ * Override this if manifest.capabilities.checkout.supported is true.
597
+ */
598
+ createCheckoutSession(ctx: ProviderContext, input: CheckoutSessionInput): Promise<CheckoutSessionResult>;
599
+ }
600
+
601
+ /**
602
+ * Validates and casts raw input (e.g. from a POST request) against the Provider's schema.
603
+ * Ensures that numbers are numbers, booleans are booleans, etc.
604
+ */
605
+ declare function validateAndCastConfig(rawConfig: Record<string, any>, schema: Record<string, ConfigFieldDefinition>): Record<string, any>;
606
+
607
+ /**
608
+ * Generic HMAC signature verification.
609
+ * usable by ~80% of providers (Shopify, MercadoPago, PayPal, etc.)
610
+ */
611
+ declare function verifyHmacSignature(payload: string, secret: string, signature: string, algorithm?: "sha256" | "sha1", encoding?: "hex" | "base64"): boolean;
612
+
613
+ interface SmokeConfig {
614
+ provider: IProvider;
615
+ ctx: ProviderContext;
616
+ scenarios: Record<string, (ctx: ProviderContext) => Promise<any>>;
617
+ manifest: ProviderManifest;
618
+ }
619
+ declare function runSmoke(config: SmokeConfig): Promise<void>;
620
+
621
+ export { type Address, BaseProvider, CATEGORY_LABELS, type CheckoutSessionInput, type CheckoutSessionResult, type CheckoutStrategy, type ConfigFieldDefinition, type ConfigFieldType, type CreateCustomerInput, type CreatePaymentInput, type CreateSubscriptionInput, type Customer, type DataFieldDefinition, type EventType, type ICheckoutFeature, type ICustomerFeature, type IPaymentFeature, type IPaymentMethodFeature, type IProvider, type ISubscriptionFeature, type InstallInput, type InstallResult, type PaginatedResult, type PaginationOptions, type Payment, type PaymentMethod, type PaymentMethodDetails, type PaymentResult, PaymentStatus, type ProviderCapabilities, ProviderCategory, type ProviderContext, type ProviderManifest, type RefundPaymentInput, RevstackError, RevstackErrorCode, type RevstackEvent, type SetupPaymentMethodInput, type SmokeConfig, type Subscription, type SubscriptionInterval, type SubscriptionMode, type SubscriptionResult, SubscriptionStatus, type UninstallInput, type UninstallResult, type UpdateCustomerInput, type WebhookResponse, createError, isRevstackError, runSmoke, validateAndCastConfig, verifyHmacSignature };
package/dist/index.js ADDED
@@ -0,0 +1,362 @@
1
+ // src/types/models.ts
2
+ var PaymentStatus = /* @__PURE__ */ ((PaymentStatus2) => {
3
+ PaymentStatus2["Pending"] = "pending";
4
+ PaymentStatus2["Authorized"] = "authorized";
5
+ PaymentStatus2["Succeeded"] = "succeeded";
6
+ PaymentStatus2["Failed"] = "failed";
7
+ PaymentStatus2["Refunded"] = "refunded";
8
+ PaymentStatus2["PartiallyRefunded"] = "partially_refunded";
9
+ PaymentStatus2["Disputed"] = "disputed";
10
+ PaymentStatus2["Canceled"] = "canceled";
11
+ return PaymentStatus2;
12
+ })(PaymentStatus || {});
13
+ var SubscriptionStatus = /* @__PURE__ */ ((SubscriptionStatus2) => {
14
+ SubscriptionStatus2["Trialing"] = "trialing";
15
+ SubscriptionStatus2["Active"] = "active";
16
+ SubscriptionStatus2["PastDue"] = "past_due";
17
+ SubscriptionStatus2["Canceled"] = "canceled";
18
+ SubscriptionStatus2["Unpaid"] = "unpaid";
19
+ SubscriptionStatus2["Incomplete"] = "incomplete";
20
+ SubscriptionStatus2["IncompleteExpired"] = "incomplete_expired";
21
+ SubscriptionStatus2["Paused"] = "paused";
22
+ return SubscriptionStatus2;
23
+ })(SubscriptionStatus || {});
24
+
25
+ // src/types/errors.ts
26
+ var RevstackErrorCode = /* @__PURE__ */ ((RevstackErrorCode2) => {
27
+ RevstackErrorCode2["UnknownError"] = "unknown_error";
28
+ RevstackErrorCode2["InternalError"] = "internal_error";
29
+ RevstackErrorCode2["NotImplemented"] = "not_implemented";
30
+ RevstackErrorCode2["Timeout"] = "timeout";
31
+ RevstackErrorCode2["RateLimitExceeded"] = "rate_limit_exceeded";
32
+ RevstackErrorCode2["InvalidCredentials"] = "invalid_credentials";
33
+ RevstackErrorCode2["Unauthorized"] = "unauthorized";
34
+ RevstackErrorCode2["MisconfiguredProvider"] = "misconfigured_provider";
35
+ RevstackErrorCode2["AccountSuspended"] = "account_suspended";
36
+ RevstackErrorCode2["InvalidInput"] = "invalid_input";
37
+ RevstackErrorCode2["MissingRequiredField"] = "missing_required_field";
38
+ RevstackErrorCode2["InvalidEmail"] = "invalid_email";
39
+ RevstackErrorCode2["InvalidAmount"] = "invalid_amount";
40
+ RevstackErrorCode2["InvalidCurrency"] = "invalid_currency";
41
+ RevstackErrorCode2["ResourceNotFound"] = "resource_not_found";
42
+ RevstackErrorCode2["ResourceAlreadyExists"] = "resource_already_exists";
43
+ RevstackErrorCode2["IdempotencyKeyConflict"] = "idempotency_key_conflict";
44
+ RevstackErrorCode2["PaymentFailed"] = "payment_failed";
45
+ RevstackErrorCode2["CardDeclined"] = "card_declined";
46
+ RevstackErrorCode2["InsufficientFunds"] = "insufficient_funds";
47
+ RevstackErrorCode2["ExpiredCard"] = "expired_card";
48
+ RevstackErrorCode2["IncorrectCvc"] = "incorrect_cvc";
49
+ RevstackErrorCode2["AuthenticationRequired"] = "authentication_required";
50
+ RevstackErrorCode2["LimitExceeded"] = "limit_exceeded";
51
+ RevstackErrorCode2["DuplicateTransaction"] = "duplicate_transaction";
52
+ RevstackErrorCode2["SubscriptionNotFound"] = "subscription_not_found";
53
+ RevstackErrorCode2["SubscriptionAlreadyActive"] = "subscription_already_active";
54
+ RevstackErrorCode2["SubscriptionCancelled"] = "subscription_cancelled";
55
+ RevstackErrorCode2["PlanNotFound"] = "plan_not_found";
56
+ RevstackErrorCode2["RefundFailed"] = "refund_failed";
57
+ RevstackErrorCode2["DisputeLost"] = "dispute_lost";
58
+ RevstackErrorCode2["ProviderUnavailable"] = "provider_unavailable";
59
+ RevstackErrorCode2["ProviderRejected"] = "provider_rejected";
60
+ RevstackErrorCode2["WebhookSignatureVerificationFailed"] = "webhook_signature_verification_failed";
61
+ return RevstackErrorCode2;
62
+ })(RevstackErrorCode || {});
63
+ var RevstackError = class _RevstackError extends Error {
64
+ code;
65
+ provider;
66
+ // Slug del provider (ej: 'stripe')
67
+ cause;
68
+ // El error original del SDK (raw)
69
+ statusCode;
70
+ // Sugerencia de HTTP Status Code
71
+ documentationUrl;
72
+ constructor(opts) {
73
+ super(opts.message);
74
+ Object.setPrototypeOf(this, _RevstackError.prototype);
75
+ this.name = "RevstackError";
76
+ this.code = opts.code;
77
+ this.provider = opts.provider;
78
+ this.cause = opts.cause;
79
+ this.documentationUrl = opts.documentationUrl;
80
+ this.statusCode = this.mapToStatusCode(opts.code);
81
+ }
82
+ /**
83
+ * Mapea el código de error interno a un HTTP Status Code estándar.
84
+ * Útil para que la API responda correctamente sin lógica extra en el controller.
85
+ */
86
+ mapToStatusCode(code) {
87
+ switch (code) {
88
+ // 400 Bad Request
89
+ case "invalid_input" /* InvalidInput */:
90
+ case "missing_required_field" /* MissingRequiredField */:
91
+ case "invalid_email" /* InvalidEmail */:
92
+ case "invalid_amount" /* InvalidAmount */:
93
+ case "invalid_currency" /* InvalidCurrency */:
94
+ case "payment_failed" /* PaymentFailed */:
95
+ // Fallo de negocio, no del server
96
+ case "card_declined" /* CardDeclined */:
97
+ case "insufficient_funds" /* InsufficientFunds */:
98
+ case "expired_card" /* ExpiredCard */:
99
+ case "incorrect_cvc" /* IncorrectCvc */:
100
+ return 400;
101
+ // 401 Unauthorized
102
+ case "invalid_credentials" /* InvalidCredentials */:
103
+ case "unauthorized" /* Unauthorized */:
104
+ case "webhook_signature_verification_failed" /* WebhookSignatureVerificationFailed */:
105
+ return 401;
106
+ // 402 Payment Required (Especialmente útil para SCA/3DS)
107
+ case "authentication_required" /* AuthenticationRequired */:
108
+ return 402;
109
+ // 403 Forbidden
110
+ case "account_suspended" /* AccountSuspended */:
111
+ case "provider_rejected" /* ProviderRejected */:
112
+ return 403;
113
+ // 404 Not Found
114
+ case "resource_not_found" /* ResourceNotFound */:
115
+ case "subscription_not_found" /* SubscriptionNotFound */:
116
+ case "plan_not_found" /* PlanNotFound */:
117
+ return 404;
118
+ // 409 Conflict
119
+ case "resource_already_exists" /* ResourceAlreadyExists */:
120
+ case "idempotency_key_conflict" /* IdempotencyKeyConflict */:
121
+ case "subscription_already_active" /* SubscriptionAlreadyActive */:
122
+ return 409;
123
+ // 429 Too Many Requests
124
+ case "rate_limit_exceeded" /* RateLimitExceeded */:
125
+ return 429;
126
+ // 501 Not Implemented
127
+ case "not_implemented" /* NotImplemented */:
128
+ return 501;
129
+ // 502 Bad Gateway (Culpa del provider)
130
+ case "provider_unavailable" /* ProviderUnavailable */:
131
+ case "timeout" /* Timeout */:
132
+ return 502;
133
+ // 500 Internal Server Error (Default)
134
+ default:
135
+ return 500;
136
+ }
137
+ }
138
+ };
139
+ function createError(code, message, provider, cause) {
140
+ return new RevstackError({ code, message, provider, cause });
141
+ }
142
+ function isRevstackError(error) {
143
+ return error instanceof RevstackError;
144
+ }
145
+
146
+ // src/types/categories.ts
147
+ var ProviderCategory = /* @__PURE__ */ ((ProviderCategory2) => {
148
+ ProviderCategory2["Card"] = "card";
149
+ ProviderCategory2["BankTransfer"] = "bank";
150
+ ProviderCategory2["Wallet"] = "wallet";
151
+ ProviderCategory2["Crypto"] = "crypto";
152
+ ProviderCategory2["Cash"] = "cash";
153
+ ProviderCategory2["BuyNowPayLater"] = "bnpl";
154
+ return ProviderCategory2;
155
+ })(ProviderCategory || {});
156
+ var CATEGORY_LABELS = {
157
+ ["card" /* Card */]: "Credit / Debit Card",
158
+ ["bank" /* BankTransfer */]: "Bank Transfer",
159
+ ["wallet" /* Wallet */]: "Digital Wallet",
160
+ ["crypto" /* Crypto */]: "Cryptocurrency",
161
+ ["cash" /* Cash */]: "Cash Payment",
162
+ ["bnpl" /* BuyNowPayLater */]: "Buy Now, Pay Later"
163
+ };
164
+
165
+ // src/base.ts
166
+ var BaseProvider = class {
167
+ getPayment(ctx, id) {
168
+ throw new Error("Method not implemented.");
169
+ }
170
+ refundPayment(ctx, input) {
171
+ throw new Error("Method not implemented.");
172
+ }
173
+ listPayments(ctx, pagination) {
174
+ throw new Error("Method not implemented.");
175
+ }
176
+ getSubscription(ctx, id) {
177
+ throw new Error("Method not implemented.");
178
+ }
179
+ createCustomer(ctx, input) {
180
+ throw new Error("Method not implemented.");
181
+ }
182
+ updateCustomer(ctx, id, input) {
183
+ throw new Error("Method not implemented.");
184
+ }
185
+ deleteCustomer(ctx, id) {
186
+ throw new Error("Method not implemented.");
187
+ }
188
+ getCustomer(ctx, id) {
189
+ throw new Error("Method not implemented.");
190
+ }
191
+ listPaymentMethods(ctx, customerId) {
192
+ throw new Error("Method not implemented.");
193
+ }
194
+ deletePaymentMethod(ctx, id) {
195
+ throw new Error("Method not implemented.");
196
+ }
197
+ /**
198
+ * Returns the HTTP response that should be sent back to the Provider
199
+ * after receiving a webhook.
200
+ * * Default implementation returns 200 OK. Override if the provider expects
201
+ * a specific XML or JSON confirmation.
202
+ */
203
+ async getWebhookResponse() {
204
+ return { statusCode: 200, body: { received: true } };
205
+ }
206
+ // ===========================================================================
207
+ // PAYMENT FEATURE IMPLEMENTATION (IProvider)
208
+ // ===========================================================================
209
+ /**
210
+ * Process a one-time payment.
211
+ * Override this if manifest.capabilities.payments.oneTime is true.
212
+ * * @throws Error if not implemented by the specific provider.
213
+ */
214
+ async createPayment(ctx, input) {
215
+ throw new Error(
216
+ `Provider '${this.manifest.slug}' does not support createPayment.`
217
+ );
218
+ }
219
+ // ===========================================================================
220
+ // SUBSCRIPTION FEATURE IMPLEMENTATION (IProvider)
221
+ // ===========================================================================
222
+ /**
223
+ * Create a recurring subscription.
224
+ * Override this if manifest.capabilities.subscriptions.native is true.
225
+ */
226
+ async createSubscription(ctx, input) {
227
+ throw new Error(
228
+ `Provider '${this.manifest.slug}' does not support createSubscription.`
229
+ );
230
+ }
231
+ /**
232
+ * Cancel an active subscription immediately or at period end.
233
+ */
234
+ async cancelSubscription(ctx, id, reason) {
235
+ throw new Error(
236
+ `Provider '${this.manifest.slug}' does not support cancelSubscription.`
237
+ );
238
+ }
239
+ pauseSubscription(ctx, id, reason) {
240
+ throw new Error("Method not implemented.");
241
+ }
242
+ resumeSubscription(ctx, id, reason) {
243
+ throw new Error("Method not implemented.");
244
+ }
245
+ // ===========================================================================
246
+ // CHECKOUT FEATURE IMPLEMENTATION (IProvider)
247
+ // ===========================================================================
248
+ /**
249
+ * Generate a hosted checkout session URL (e.g., Stripe Checkout).
250
+ * Override this if manifest.capabilities.checkout.supported is true.
251
+ */
252
+ async createCheckoutSession(ctx, input) {
253
+ throw new Error(
254
+ `Provider '${this.manifest.slug}' does not support createCheckoutSession.`
255
+ );
256
+ }
257
+ };
258
+
259
+ // src/lib/config-validator.ts
260
+ function validateAndCastConfig(rawConfig, schema) {
261
+ const processedConfig = {};
262
+ for (const [key, definition] of Object.entries(schema)) {
263
+ let value = rawConfig[key];
264
+ if (definition.required && (value === void 0 || value === null || value === "")) {
265
+ throw new RevstackError({
266
+ code: "missing_required_field" /* MissingRequiredField */,
267
+ message: `Field '${definition.label}' (${key}) is required.`
268
+ });
269
+ }
270
+ if (value === void 0 || value === null || value === "") {
271
+ continue;
272
+ }
273
+ switch (definition.type) {
274
+ case "text":
275
+ case "password":
276
+ case "select":
277
+ processedConfig[key] = String(value).trim();
278
+ break;
279
+ case "number":
280
+ const num = Number(value);
281
+ if (isNaN(num)) {
282
+ throw new RevstackError({
283
+ code: "invalid_input" /* InvalidInput */,
284
+ message: `Field '${definition.label}' must be a valid number.`
285
+ });
286
+ }
287
+ processedConfig[key] = num;
288
+ break;
289
+ case "switch":
290
+ processedConfig[key] = value === "true" || value === true || value === 1 || value === "1";
291
+ break;
292
+ case "json":
293
+ try {
294
+ processedConfig[key] = typeof value === "object" ? value : JSON.parse(value);
295
+ } catch (e) {
296
+ throw new RevstackError({
297
+ code: "invalid_input" /* InvalidInput */,
298
+ message: `Invalid JSON for ${key}`
299
+ });
300
+ }
301
+ break;
302
+ }
303
+ }
304
+ return processedConfig;
305
+ }
306
+
307
+ // src/utils/crypto.ts
308
+ import * as crypto from "crypto";
309
+ function verifyHmacSignature(payload, secret, signature, algorithm = "sha256", encoding = "hex") {
310
+ if (!payload || !secret || !signature) return false;
311
+ const hmac = crypto.createHmac(algorithm, secret);
312
+ const digest = hmac.update(payload).digest(encoding);
313
+ return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
314
+ }
315
+
316
+ // src/smoke-runner.ts
317
+ async function runSmoke(config) {
318
+ const method = process.argv[2] || "onInstall";
319
+ const providerName = config.manifest.name;
320
+ console.log(`
321
+ \u{1F6AC} \x1B[36mSmoking Provider:\x1B[0m ${providerName}`);
322
+ console.log(`\u{1F449} \x1B[33mMethod:\x1B[0m ${method}
323
+ `);
324
+ if (method === "list") {
325
+ console.log("\u{1F4CB} Available scenarios:");
326
+ console.log(
327
+ Object.keys(config.scenarios).map((k) => ` - ${k}`).join("\n")
328
+ );
329
+ return;
330
+ }
331
+ const scenario = config.scenarios[method];
332
+ if (!scenario) {
333
+ console.error(`\u274C \x1B[31mError:\x1B[0m Scenario '${method}' not found.`);
334
+ console.log(` Run 'list' to see available scenarios.`);
335
+ process.exit(1);
336
+ }
337
+ try {
338
+ console.time("\u23F1\uFE0F Execution time");
339
+ const result = await scenario(config.ctx);
340
+ console.timeEnd("\u23F1\uFE0F Execution time");
341
+ console.log("\n\u2705 \x1B[32mSUCCESS RESULT:\x1B[0m");
342
+ console.dir(result, { depth: null, colors: true });
343
+ } catch (e) {
344
+ console.error("\n\u{1F525} \x1B[31mFAILED:\x1B[0m");
345
+ console.error(e);
346
+ process.exit(1);
347
+ }
348
+ }
349
+ export {
350
+ BaseProvider,
351
+ CATEGORY_LABELS,
352
+ PaymentStatus,
353
+ ProviderCategory,
354
+ RevstackError,
355
+ RevstackErrorCode,
356
+ SubscriptionStatus,
357
+ createError,
358
+ isRevstackError,
359
+ runSmoke,
360
+ validateAndCastConfig,
361
+ verifyHmacSignature
362
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@revstackhq/providers-core",
3
+ "version": "0.0.0-dev-20260209034148",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "dependencies": {},
12
+ "devDependencies": {
13
+ "typescript": "5.9.2",
14
+ "tsup": "^8.1.0",
15
+ "eslint": "^9.39.1",
16
+ "@eslint/js": "^9.39.1",
17
+ "typescript-eslint": "^8.50.0",
18
+ "eslint-config-prettier": "^10.1.1",
19
+ "eslint-plugin-turbo": "^2.7.1",
20
+ "@types/node": "^20.11.30",
21
+ "@revstackhq/eslint-config": "0.0.0"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup src/index.ts --format esm --dts --clean",
28
+ "check-types": "tsc -p tsconfig.json --noEmit",
29
+ "lint": "eslint \"src/**/*.{ts,tsx}\""
30
+ }
31
+ }