@revstackhq/providers-core 0.2.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/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,592 @@
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
+
21
+ /**
22
+ * src/types/models.ts
23
+ * * Normalized data models for the Revstack ecosystem.
24
+ * These types represent the "Internal Source of Truth" for the OS.
25
+ */
26
+ declare enum PaymentStatus {
27
+ Pending = "pending",
28
+ Authorized = "authorized",// Funds held but not captured
29
+ Succeeded = "succeeded",
30
+ Failed = "failed",
31
+ Refunded = "refunded",
32
+ PartiallyRefunded = "partially_refunded",
33
+ Disputed = "disputed",
34
+ Canceled = "canceled"
35
+ }
36
+ type PaymentMethodDetails = {
37
+ type: "card" | "bank_transfer" | "wallet" | "crypto" | "checkout";
38
+ brand?: string;
39
+ last4?: string;
40
+ email?: string;
41
+ };
42
+ type Payment = {
43
+ /** Unique ID in Revstack (UUID) */
44
+ id: string;
45
+ /** The slug of the provider (e.g., 'stripe') */
46
+ providerId: string;
47
+ /** The ID generated by the provider (e.g., 'pi_12345') */
48
+ externalId: string;
49
+ /** Total amount in lowest currency unit (e.g., cents) */
50
+ amount: number;
51
+ /** Amount refunded so far */
52
+ amountRefunded: number;
53
+ /** ISO 4217 Currency Code (e.g., 'USD') */
54
+ currency: string;
55
+ status: PaymentStatus;
56
+ method?: PaymentMethodDetails;
57
+ description?: string;
58
+ /** Revstack internal Customer ID */
59
+ customerId?: string;
60
+ /** Provider-specific Customer ID (e.g., 'cus_999') */
61
+ externalCustomerId?: string;
62
+ /** * The fee charged by the provider for this transaction.
63
+ * Crucial for Net Revenue calculations.
64
+ */
65
+ fee?: number;
66
+ /** ISO Date String */
67
+ createdAt: string;
68
+ /** ISO Date String */
69
+ updatedAt?: string;
70
+ metadata?: Record<string, any>;
71
+ /** Optional: The raw object from the provider for debugging/audit */
72
+ raw?: any;
73
+ };
74
+ declare enum SubscriptionStatus {
75
+ Trialing = "trialing",
76
+ Active = "active",
77
+ PastDue = "past_due",// Payment failed, in grace period
78
+ Canceled = "canceled",
79
+ Unpaid = "unpaid",// Grace period ended, service cut
80
+ Incomplete = "incomplete",// Created but waiting for first payment (3DS)
81
+ IncompleteExpired = "incomplete_expired",
82
+ Paused = "paused"
83
+ }
84
+ type SubscriptionInterval = "day" | "week" | "month" | "year";
85
+ type Subscription = {
86
+ id: string;
87
+ providerId: string;
88
+ externalId: string;
89
+ status: SubscriptionStatus;
90
+ /** Internal Plan ID */
91
+ planId?: string;
92
+ /** Provider Plan/Price ID (e.g., 'price_Hk123') */
93
+ externalPlanId?: string;
94
+ amount: number;
95
+ currency: string;
96
+ interval: SubscriptionInterval;
97
+ customerId: string;
98
+ /** Start of the current billing period (ISO Date) */
99
+ currentPeriodStart: string;
100
+ /** End of the current billing period (ISO Date) */
101
+ currentPeriodEnd: string;
102
+ /** If true, the subscription will cancel at the end of the current period */
103
+ cancelAtPeriodEnd: boolean;
104
+ canceledAt?: string;
105
+ startedAt: string;
106
+ endedAt?: string;
107
+ trialStart?: string;
108
+ trialEnd?: string;
109
+ metadata?: Record<string, any>;
110
+ };
111
+ type CreatePaymentInput = {
112
+ amount: number;
113
+ currency: string;
114
+ customerId?: string;
115
+ paymentMethodId?: string;
116
+ description?: string;
117
+ metadata?: Record<string, any>;
118
+ /** Pass-through options for the specific provider */
119
+ providerOptions?: any;
120
+ };
121
+ type PaymentResult = {
122
+ payment: Payment;
123
+ /** If the payment requires 3DS or redirect */
124
+ nextAction?: {
125
+ type: "redirect" | "modal";
126
+ url: string;
127
+ };
128
+ };
129
+ type CreateSubscriptionInput = {
130
+ customerId: string;
131
+ planId?: string;
132
+ priceId?: string;
133
+ amount?: number;
134
+ currency?: string;
135
+ trialDays?: number;
136
+ metadata?: Record<string, any>;
137
+ };
138
+ type SubscriptionResult = {
139
+ subscription: Subscription;
140
+ };
141
+ type CheckoutSessionInput = {
142
+ customerId?: string;
143
+ /** Items to purchase */
144
+ lineItems: {
145
+ name: string;
146
+ amount: number;
147
+ quantity: number;
148
+ currency: string;
149
+ images?: string[];
150
+ }[];
151
+ successUrl: string;
152
+ cancelUrl: string;
153
+ mode: "payment" | "subscription";
154
+ metadata?: Record<string, any>;
155
+ };
156
+ type CheckoutSessionResult = {
157
+ id: string;
158
+ url: string;
159
+ expiresAt?: string;
160
+ };
161
+
162
+ /**
163
+ * src/interfaces/features.ts
164
+ * * Defines the specific capabilities a provider can implement.
165
+ * This adheres to the Interface Segregation Principle.
166
+ */
167
+
168
+ /**
169
+ * Contract for providers that support one-time payments.
170
+ */
171
+ interface IPaymentFeature {
172
+ createPayment(ctx: ProviderContext, input: CreatePaymentInput): Promise<PaymentResult>;
173
+ getPayment(ctx: ProviderContext, id: string): Promise<PaymentResult>;
174
+ }
175
+ /**
176
+ * Contract for providers that support native recurring billing.
177
+ * (e.g., Stripe, Paddle, but NOT simple bank transfers).
178
+ */
179
+ interface ISubscriptionFeature {
180
+ createSubscription(ctx: ProviderContext, input: CreateSubscriptionInput): Promise<SubscriptionResult>;
181
+ cancelSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
182
+ pauseSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
183
+ resumeSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
184
+ }
185
+ /**
186
+ * Contract for providers that offer a hosted checkout page.
187
+ */
188
+ interface ICheckoutFeature {
189
+ createCheckoutSession(ctx: ProviderContext, input: CheckoutSessionInput): Promise<CheckoutSessionResult>;
190
+ }
191
+ /**
192
+ * The unified contract that all Revstack Providers must technically satisfy.
193
+ * * Even if a provider doesn't support a feature, it must implement the interface
194
+ * (usually by throwing a 'Not Implemented' error via the BaseProvider).
195
+ */
196
+ interface IProvider extends IPaymentFeature, ISubscriptionFeature, ICheckoutFeature {
197
+ }
198
+
199
+ type EventType = "PAYMENT_SUCCEEDED" | "PAYMENT_FAILED" | "REFUND_PROCESSED" | "SUBSCRIPTION_CREATED" | "SUBSCRIPTION_UPDATED" | "SUBSCRIPTION_CANCELED" | "DISPUTE_CREATED" | "DISPUTE_RESOLVED";
200
+ /**
201
+ * A normalized event ready to be consumed by the Revstack Core.
202
+ */
203
+ interface RevstackEvent {
204
+ /** The standardized event type */
205
+ type: EventType;
206
+ /** The provider's original event ID */
207
+ providerEventId: string;
208
+ /** ISO timestamp of when the event happened */
209
+ createdAt: Date;
210
+ /** The raw original payload (for debugging) */
211
+ originalPayload: any;
212
+ /** * The primary resource affected (Payment ID, Subscription ID).
213
+ * Used to link the event to internal records.
214
+ */
215
+ resourceId: string;
216
+ /** Additional context */
217
+ metadata?: Record<string, any>;
218
+ }
219
+ interface WebhookResponse {
220
+ /** HTTP Status code to return to the provider (e.g., 200) */
221
+ statusCode: number;
222
+ /** Body to return (e.g., { received: true }) */
223
+ body: any;
224
+ }
225
+
226
+ /**
227
+ * Defines how the checkout flow is presented to the user.
228
+ */
229
+ type CheckoutStrategy = "redirect" | "native_sdk" | "sdui";
230
+ /**
231
+ * Defines who orchestrates the recurring billing logic.
232
+ */
233
+ type SubscriptionMode = "native" | "virtual";
234
+ interface CheckoutCapabilities {
235
+ /**
236
+ * If true, this provider can be used in the Hosted Checkout flow.
237
+ */
238
+ supported: boolean;
239
+ /**
240
+ * The primary UX strategy used to collect payment details.
241
+ * The frontend uses this to determine which adapter to load.
242
+ */
243
+ strategy: CheckoutStrategy;
244
+ }
245
+ interface PaymentCapabilities {
246
+ /**
247
+ * If true, the provider supports processing standard One-Time Payments.
248
+ * This is the fundamental capability of any payment provider.
249
+ */
250
+ supported: boolean;
251
+ /**
252
+ * Advanced transactional features supported by the provider.
253
+ */
254
+ features: {
255
+ /**
256
+ * Can the provider process full refunds via API?
257
+ */
258
+ refunds: boolean;
259
+ /**
260
+ * Can the provider process partial refunds (returning a specific amount)?
261
+ */
262
+ partialRefunds: boolean;
263
+ /**
264
+ * Supports "Authorization & Capture" flow.
265
+ * Useful for e-commerce (shipping) or rentals, where funds are held first
266
+ * and charged later.
267
+ */
268
+ capture: boolean;
269
+ /**
270
+ * Can the provider fetch or list disputes/chargebacks via API?
271
+ */
272
+ disputes: boolean;
273
+ };
274
+ }
275
+ interface SubscriptionCapabilities {
276
+ /**
277
+ * If true, the provider supports recurring billing flows.
278
+ */
279
+ supported: boolean;
280
+ /**
281
+ * The engine responsible for recurrence logic.
282
+ * - 'native': We delegate logic to the provider.
283
+ * - 'virtual': We emulate logic using one-time payments.
284
+ */
285
+ mode: SubscriptionMode;
286
+ /**
287
+ * Specific lifecycle actions supported by the provider.
288
+ */
289
+ features: {
290
+ /**
291
+ * Can a subscription be paused temporarily without canceling it?
292
+ */
293
+ pause: boolean;
294
+ /**
295
+ * Can a paused subscription be resumed?
296
+ */
297
+ resume: boolean;
298
+ /**
299
+ * Can a subscription be canceled via API?
300
+ */
301
+ cancellation: boolean;
302
+ /**
303
+ * Does the provider support native proration for upgrades/downgrades?
304
+ * (Only relevant if mode is 'native').
305
+ */
306
+ proration?: boolean;
307
+ };
308
+ }
309
+ interface WebhookCapabilities {
310
+ /**
311
+ * Does the provider send asynchronous events (webhooks)?
312
+ */
313
+ supported: boolean;
314
+ /**
315
+ * How the webhook payload is verified for security.
316
+ * - 'signature': HMAC/RSA signature header (Best).
317
+ * - 'secret': Simple shared secret in header/body.
318
+ * - 'none': No verification (Insecure).
319
+ */
320
+ verification: "signature" | "secret" | "none";
321
+ }
322
+ interface ProviderCapabilities {
323
+ checkout: CheckoutCapabilities;
324
+ payments: PaymentCapabilities;
325
+ subscriptions: SubscriptionCapabilities;
326
+ webhooks: WebhookCapabilities;
327
+ }
328
+
329
+ /**
330
+ * Catálogo completo de errores estandarizados para Revstack.
331
+ * Agrupados por dominio para facilitar su manejo en el Frontend/API.
332
+ */
333
+ declare enum RevstackErrorCode {
334
+ UnknownError = "unknown_error",
335
+ InternalError = "internal_error",
336
+ NotImplemented = "not_implemented",// Para features opcionales no soportadas por un provider
337
+ Timeout = "timeout",
338
+ RateLimitExceeded = "rate_limit_exceeded",
339
+ InvalidCredentials = "invalid_credentials",// API Key incorrecta
340
+ Unauthorized = "unauthorized",// No tiene permisos
341
+ MisconfiguredProvider = "misconfigured_provider",// Faltan campos en el dashboard
342
+ AccountSuspended = "account_suspended",// La cuenta del merchant en Stripe/etc está bloqueada
343
+ InvalidInput = "invalid_input",
344
+ MissingRequiredField = "missing_required_field",
345
+ InvalidEmail = "invalid_email",
346
+ InvalidAmount = "invalid_amount",// Monto negativo o cero
347
+ InvalidCurrency = "invalid_currency",// Provider no soporta esa moneda
348
+ ResourceNotFound = "resource_not_found",
349
+ ResourceAlreadyExists = "resource_already_exists",
350
+ IdempotencyKeyConflict = "idempotency_key_conflict",
351
+ PaymentFailed = "payment_failed",// Fallo genérico
352
+ CardDeclined = "card_declined",// El banco rechazó la tarjeta
353
+ InsufficientFunds = "insufficient_funds",// No hay saldo
354
+ ExpiredCard = "expired_card",
355
+ IncorrectCvc = "incorrect_cvc",
356
+ AuthenticationRequired = "authentication_required",// Requiere 3D Secure / SCA
357
+ LimitExceeded = "limit_exceeded",// Límite de la tarjeta excedido
358
+ DuplicateTransaction = "duplicate_transaction",
359
+ SubscriptionNotFound = "subscription_not_found",
360
+ SubscriptionAlreadyActive = "subscription_already_active",
361
+ SubscriptionCancelled = "subscription_cancelled",// Intentar operar sobre una cancelada
362
+ PlanNotFound = "plan_not_found",
363
+ RefundFailed = "refund_failed",
364
+ DisputeLost = "dispute_lost",
365
+ ProviderUnavailable = "provider_unavailable",// Stripe está caído
366
+ ProviderRejected = "provider_rejected",// El provider rechazó la conexión (ej: riesgo alto)
367
+ WebhookSignatureVerificationFailed = "webhook_signature_verification_failed"
368
+ }
369
+ /**
370
+ * Estructura del Error.
371
+ * Extendemos la clase nativa 'Error' para mantener el stack trace
372
+ * y permitir 'instanceof RevstackError'.
373
+ */
374
+ declare class RevstackError extends Error {
375
+ readonly code: RevstackErrorCode;
376
+ readonly provider?: string;
377
+ readonly cause?: any;
378
+ readonly statusCode: number;
379
+ readonly documentationUrl?: string;
380
+ constructor(opts: {
381
+ code: RevstackErrorCode;
382
+ message: string;
383
+ provider?: string;
384
+ cause?: any;
385
+ documentationUrl?: string;
386
+ });
387
+ /**
388
+ * Mapea el código de error interno a un HTTP Status Code estándar.
389
+ * Útil para que la API responda correctamente sin lógica extra en el controller.
390
+ */
391
+ private mapToStatusCode;
392
+ }
393
+ /**
394
+ * Helper factory para compatibilidad con código funcional o uso rápido.
395
+ */
396
+ declare function createError(code: RevstackErrorCode, message: string, provider?: string, cause?: any): RevstackError;
397
+ /**
398
+ * Type Guard para verificar si un error es de Revstack.
399
+ */
400
+ declare function isRevstackError(error: unknown): error is RevstackError;
401
+
402
+ interface InstallInput {
403
+ /**
404
+ * La configuración ingresada por el usuario en el formulario (API Keys, etc).
405
+ */
406
+ config: Record<string, any>;
407
+ /**
408
+ * La URL absoluta donde Revstack espera recibir los eventos para este merchant.
409
+ * El Core la genera: https://api.revstack.os/webhooks/{provider}/{merchantId}
410
+ */
411
+ webhookUrl: string;
412
+ }
413
+ interface InstallResult {
414
+ success: boolean;
415
+ /**
416
+ * Datos finales a guardar en la DB.
417
+ * Aquí el provider puede inyectar datos generados (como el webhookSecret).
418
+ */
419
+ data?: Record<string, any>;
420
+ error?: RevstackError;
421
+ }
422
+
423
+ declare enum ProviderCategory {
424
+ Card = "card",// Stripe, Adyen, dLocal
425
+ BankTransfer = "bank",// PSE, PIX, Wire, Spei
426
+ Wallet = "wallet",// PayPal, MercadoPago, ApplePay
427
+ Crypto = "crypto",// Coinbase, BitPay
428
+ Cash = "cash",// OXXO, Rapipago, PagoFácil
429
+ BuyNowPayLater = "bnpl"
430
+ }
431
+ declare const CATEGORY_LABELS: Record<ProviderCategory, string>;
432
+
433
+ /**
434
+ * Defines the input field type for the installation UI.
435
+ */
436
+ type ConfigFieldType = "text" | "password" | "switch" | "select" | "number" | "json";
437
+ /**
438
+ * Configuration schema for a provider setting.
439
+ * Used to generate the UI and handle encryption.
440
+ */
441
+ interface ConfigFieldDefinition {
442
+ /** Label to display in the UI */
443
+ label: string;
444
+ /** Input type */
445
+ type: ConfigFieldType;
446
+ /** If true, this field must be encrypted in the DB */
447
+ secure: boolean;
448
+ /** Is this field required? */
449
+ required: boolean;
450
+ /** Description or tooltip for the user */
451
+ description?: string;
452
+ /** Options for 'select' type */
453
+ options?: {
454
+ label: string;
455
+ value: string;
456
+ }[];
457
+ }
458
+ /**
459
+ * The Provider Manifest.
460
+ * Acts as the source of truth for the provider's metadata and capabilities.
461
+ */
462
+ interface ProviderManifest {
463
+ /** Unique identifier (e.g., 'stripe', 'polar') */
464
+ slug: string;
465
+ /** Display name (e.g., 'Stripe') */
466
+ name: string;
467
+ /** Provider category */
468
+ category: ProviderCategory;
469
+ /** * Semantic version of the provider package (e.g., '1.0.0').
470
+ * Vital for managing updates in the marketplace.
471
+ */
472
+ version: string;
473
+ /** Short description displayed in the marketplace card. */
474
+ description?: string;
475
+ /** URL to the provider's logo */
476
+ logoUrl?: string;
477
+ /** The organization or developer maintaining this provider. */
478
+ author?: string;
479
+ /** Link to the official documentation for this specific provider. */
480
+ documentationUrl?: string;
481
+ /** Link to the support page or repository issue tracker. */
482
+ supportUrl?: string;
483
+ /** * List of supported ISO 3166-1 alpha-2 country codes (e.g., ['US', 'GB', 'BR']).
484
+ * Use ['global'] if the provider works worldwide.
485
+ * Used to filter providers in the UI based on the merchant's location.
486
+ */
487
+ regions?: string[];
488
+ /**
489
+ * List of supported ISO 4217 currency codes (e.g., ['USD', 'EUR']).
490
+ * Use ['*'] if the provider supports all currencies.
491
+ */
492
+ currencies?: string[];
493
+ /** * Indicates if the provider supports a dedicated sandbox/test mode.
494
+ * If true, the UI should allow switching between Test and Live credentials.
495
+ */
496
+ sandboxAvailable?: boolean;
497
+ /** * Schema to generate the installation form.
498
+ * Key is the internal config key (e.g., 'apiKey').
499
+ */
500
+ configSchema: Record<string, ConfigFieldDefinition>;
501
+ /**
502
+ * What this provider can do. Used for feature flagging in the core.
503
+ */
504
+ capabilities: ProviderCapabilities;
505
+ }
506
+
507
+ /**
508
+ * src/base.ts
509
+ * * The Foundation of the Provider Development Kit (PDK).
510
+ */
511
+
512
+ /**
513
+ * The Abstract Base Class for all Revstack Providers.
514
+ * * It implements the IProvider interface with default behaviors (throwing errors).
515
+ * Specific providers (e.g., Stripe) will override only the methods they actually support.
516
+ */
517
+ declare abstract class BaseProvider implements IProvider {
518
+ /**
519
+ * The static manifest definition.
520
+ * Defines capabilities, metadata, and config schema (UI).
521
+ */
522
+ abstract readonly manifest: ProviderManifest;
523
+ /**
524
+ * Called when a merchant installs or updates this provider.
525
+ * * RESPONSIBILITY:
526
+ * 1. Instantiate the provider SDK with the input config.
527
+ * 2. Validate credentials (e.g., make a 'ping' or 'get balance' request).
528
+ * 3. Return the payload to be saved in the database.
529
+ * * @param ctx - The execution context (includes environment info).
530
+ * @param input - The input data provided by the user in the UI.
531
+ * @returns The success status and the data to be stored (encrypted by Core).
532
+ */
533
+ abstract onInstall(ctx: ProviderContext, input: InstallInput): Promise<InstallResult>;
534
+ /**
535
+ * Verifies the cryptographic signature of an incoming webhook.
536
+ * * SECURITY CRITICAL:
537
+ * This ensures that the request actually originated from the Payment Provider
538
+ * and hasn't been tampered with.
539
+ * * @param payload - The raw body of the request (string or buffer).
540
+ * @param headers - The request headers.
541
+ * @param secret - The webhook signing secret stored in the DB.
542
+ */
543
+ abstract verifyWebhookSignature(payload: string | Buffer, headers: Record<string, string | string[] | undefined>, secret: string): Promise<boolean>;
544
+ /**
545
+ * Transforms a raw provider payload into a standardized Revstack Event.
546
+ * * This acts as the "Translation Layer" or "Adapter" for incoming events.
547
+ * * @param payload - The raw JSON body from the provider.
548
+ * @returns A normalized RevstackEvent or null if the event is irrelevant.
549
+ */
550
+ abstract parseWebhookEvent(payload: any): Promise<RevstackEvent | null>;
551
+ /**
552
+ * Returns the HTTP response that should be sent back to the Provider
553
+ * after receiving a webhook.
554
+ * * Default implementation returns 200 OK. Override if the provider expects
555
+ * a specific XML or JSON confirmation.
556
+ */
557
+ getWebhookResponse(): Promise<WebhookResponse>;
558
+ /**
559
+ * Process a one-time payment.
560
+ * Override this if manifest.capabilities.payments.oneTime is true.
561
+ * * @throws Error if not implemented by the specific provider.
562
+ */
563
+ createPayment(ctx: ProviderContext, input: CreatePaymentInput): Promise<PaymentResult>;
564
+ /**
565
+ * Retrieve details of a payment by its ID.
566
+ */
567
+ getPayment(ctx: ProviderContext, id: string): Promise<PaymentResult>;
568
+ /**
569
+ * Create a recurring subscription.
570
+ * Override this if manifest.capabilities.subscriptions.native is true.
571
+ */
572
+ createSubscription(ctx: ProviderContext, input: CreateSubscriptionInput): Promise<SubscriptionResult>;
573
+ /**
574
+ * Cancel an active subscription immediately or at period end.
575
+ */
576
+ cancelSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
577
+ pauseSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
578
+ resumeSubscription(ctx: ProviderContext, id: string, reason?: string): Promise<SubscriptionResult>;
579
+ /**
580
+ * Generate a hosted checkout session URL (e.g., Stripe Checkout).
581
+ * Override this if manifest.capabilities.checkout.supported is true.
582
+ */
583
+ createCheckoutSession(ctx: ProviderContext, input: CheckoutSessionInput): Promise<CheckoutSessionResult>;
584
+ }
585
+
586
+ /**
587
+ * Validates and casts raw input (e.g. from a POST request) against the Provider's schema.
588
+ * Ensures that numbers are numbers, booleans are booleans, etc.
589
+ */
590
+ declare function validateAndCastConfig(rawConfig: Record<string, any>, schema: Record<string, ConfigFieldDefinition>): Record<string, any>;
591
+
592
+ export { BaseProvider, CATEGORY_LABELS, type CheckoutCapabilities, type CheckoutSessionInput, type CheckoutSessionResult, type CheckoutStrategy, type ConfigFieldDefinition, type ConfigFieldType, type CreatePaymentInput, type CreateSubscriptionInput, type EventType, type ICheckoutFeature, type IPaymentFeature, type IProvider, type ISubscriptionFeature, type InstallInput, type InstallResult, type Payment, type PaymentCapabilities, type PaymentMethodDetails, type PaymentResult, PaymentStatus, type ProviderCapabilities, ProviderCategory, type ProviderContext, type ProviderManifest, RevstackError, RevstackErrorCode, type RevstackEvent, type Subscription, type SubscriptionCapabilities, type SubscriptionInterval, type SubscriptionMode, type SubscriptionResult, SubscriptionStatus, type WebhookCapabilities, type WebhookResponse, createError, isRevstackError, validateAndCastConfig };
package/dist/index.js ADDED
@@ -0,0 +1,295 @@
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
+ /**
168
+ * Returns the HTTP response that should be sent back to the Provider
169
+ * after receiving a webhook.
170
+ * * Default implementation returns 200 OK. Override if the provider expects
171
+ * a specific XML or JSON confirmation.
172
+ */
173
+ async getWebhookResponse() {
174
+ return { statusCode: 200, body: { received: true } };
175
+ }
176
+ // ===========================================================================
177
+ // PAYMENT FEATURE IMPLEMENTATION (IProvider)
178
+ // ===========================================================================
179
+ /**
180
+ * Process a one-time payment.
181
+ * Override this if manifest.capabilities.payments.oneTime is true.
182
+ * * @throws Error if not implemented by the specific provider.
183
+ */
184
+ async createPayment(ctx, input) {
185
+ throw new Error(
186
+ `Provider '${this.manifest.slug}' does not support createPayment.`
187
+ );
188
+ }
189
+ /**
190
+ * Retrieve details of a payment by its ID.
191
+ */
192
+ async getPayment(ctx, id) {
193
+ throw new Error(
194
+ `Provider '${this.manifest.slug}' does not support getPayment.`
195
+ );
196
+ }
197
+ // ===========================================================================
198
+ // SUBSCRIPTION FEATURE IMPLEMENTATION (IProvider)
199
+ // ===========================================================================
200
+ /**
201
+ * Create a recurring subscription.
202
+ * Override this if manifest.capabilities.subscriptions.native is true.
203
+ */
204
+ async createSubscription(ctx, input) {
205
+ throw new Error(
206
+ `Provider '${this.manifest.slug}' does not support createSubscription.`
207
+ );
208
+ }
209
+ /**
210
+ * Cancel an active subscription immediately or at period end.
211
+ */
212
+ async cancelSubscription(ctx, id, reason) {
213
+ throw new Error(
214
+ `Provider '${this.manifest.slug}' does not support cancelSubscription.`
215
+ );
216
+ }
217
+ pauseSubscription(ctx, id, reason) {
218
+ throw new Error("Method not implemented.");
219
+ }
220
+ resumeSubscription(ctx, id, reason) {
221
+ throw new Error("Method not implemented.");
222
+ }
223
+ // ===========================================================================
224
+ // CHECKOUT FEATURE IMPLEMENTATION (IProvider)
225
+ // ===========================================================================
226
+ /**
227
+ * Generate a hosted checkout session URL (e.g., Stripe Checkout).
228
+ * Override this if manifest.capabilities.checkout.supported is true.
229
+ */
230
+ async createCheckoutSession(ctx, input) {
231
+ throw new Error(
232
+ `Provider '${this.manifest.slug}' does not support createCheckoutSession.`
233
+ );
234
+ }
235
+ };
236
+
237
+ // src/lib/config-validator.ts
238
+ function validateAndCastConfig(rawConfig, schema) {
239
+ const processedConfig = {};
240
+ for (const [key, definition] of Object.entries(schema)) {
241
+ let value = rawConfig[key];
242
+ if (definition.required && (value === void 0 || value === null || value === "")) {
243
+ throw new RevstackError({
244
+ code: "missing_required_field" /* MissingRequiredField */,
245
+ message: `Field '${definition.label}' (${key}) is required.`
246
+ });
247
+ }
248
+ if (value === void 0 || value === null || value === "") {
249
+ continue;
250
+ }
251
+ switch (definition.type) {
252
+ case "text":
253
+ case "password":
254
+ case "select":
255
+ processedConfig[key] = String(value).trim();
256
+ break;
257
+ case "number":
258
+ const num = Number(value);
259
+ if (isNaN(num)) {
260
+ throw new RevstackError({
261
+ code: "invalid_input" /* InvalidInput */,
262
+ message: `Field '${definition.label}' must be a valid number.`
263
+ });
264
+ }
265
+ processedConfig[key] = num;
266
+ break;
267
+ case "switch":
268
+ processedConfig[key] = value === "true" || value === true || value === 1 || value === "1";
269
+ break;
270
+ case "json":
271
+ try {
272
+ processedConfig[key] = typeof value === "object" ? value : JSON.parse(value);
273
+ } catch (e) {
274
+ throw new RevstackError({
275
+ code: "invalid_input" /* InvalidInput */,
276
+ message: `Invalid JSON for ${key}`
277
+ });
278
+ }
279
+ break;
280
+ }
281
+ }
282
+ return processedConfig;
283
+ }
284
+ export {
285
+ BaseProvider,
286
+ CATEGORY_LABELS,
287
+ PaymentStatus,
288
+ ProviderCategory,
289
+ RevstackError,
290
+ RevstackErrorCode,
291
+ SubscriptionStatus,
292
+ createError,
293
+ isRevstackError,
294
+ validateAndCastConfig
295
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@revstackhq/providers-core",
3
+ "version": "0.2.0",
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
+ }