@ozura/elements 1.2.4-next.48 → 1.2.4-next.50

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.
@@ -174,6 +174,93 @@ export interface CardSaleInput {
174
174
  */
175
175
  transactionChannel?: 'cardPresent' | 'ecommerce' | 'moto' | 'recurring';
176
176
  }
177
+ /** Billing cadence for a recurring subscription plan. */
178
+ export type RecurringInterval = 'daily' | 'weekly' | 'monthly' | 'yearly';
179
+ export interface CreateRecurringPlanInput {
180
+ /** From TokenResponse.token (frontend SDK). */
181
+ token: string;
182
+ /** From TokenResponse.cvcSession (frontend SDK). */
183
+ cvcSession: string;
184
+ /** Decimal string — the base recurring charge amount, e.g. "19.99". */
185
+ amount: string;
186
+ /** ISO 4217, e.g. "USD". Default: "USD". */
187
+ currency?: string;
188
+ /**
189
+ * Customer billing details. All address fields are required by the Pay API
190
+ * for recurring plans (stricter than one-time card sales).
191
+ */
192
+ billing: BillingDetails;
193
+ /** Client IP address — always obtain server-side, never from the browser. */
194
+ clientIpAddress: string;
195
+ /** Display name for the subscription plan (shown on statements and receipts). */
196
+ planName: string;
197
+ /** Billing cadence. */
198
+ interval: RecurringInterval;
199
+ /** Optional plan description shown to customers (max 100 chars). */
200
+ planDescription?: string;
201
+ /**
202
+ * Amount for the initial trial period cycles. Requires `initialCycles`.
203
+ * Pass `"0.00"` for a free trial.
204
+ */
205
+ initialAmount?: string;
206
+ /** Number of cycles to charge `initialAmount` before switching to `amount`. */
207
+ initialCycles?: number;
208
+ /** One-time setup fee charged alongside the first cycle (additive). */
209
+ setupFee?: string;
210
+ /**
211
+ * Multiplier for the interval period. Default: 1.
212
+ * e.g. `interval: "monthly"` + `intervalCount: 3` → billed every 3 months.
213
+ */
214
+ intervalCount?: number;
215
+ /** ISO 8601 end date. Must be at least one full cycle after the enrollment date. */
216
+ endDate?: string;
217
+ /** Maximum number of billing cycles. Omit for indefinite. */
218
+ maxCycles?: number;
219
+ /** Payment retry attempts on failure. Default: 3. */
220
+ maxAttempts?: number;
221
+ /** Hours between retry attempts. Default: 24. */
222
+ retryIntervalHours?: number;
223
+ /** Your own internal subscription reference ID (max 50 chars). */
224
+ merchantRecurringReference?: string;
225
+ salesTaxExempt?: boolean;
226
+ /** Defaults to "0.00". */
227
+ surchargePercent?: string;
228
+ /** Processor to use. If omitted, the Pay API auto-selects. */
229
+ processor?: 'elavon' | 'nuvei' | 'worldpay';
230
+ /**
231
+ * Transaction channel. Default: `"ecommerce"`.
232
+ * Use `"moto"` for mail/telephone-order enrollment flows.
233
+ *
234
+ * Note: `transactionInitiationType` is always `"cit"` for recurring
235
+ * enrollment and is not configurable — the Pay API enforces this.
236
+ * Subsequent MIT charges are handled by Ozura's recurring queue worker.
237
+ */
238
+ transactionChannel?: 'ecommerce' | 'moto';
239
+ }
240
+ export interface CreateRecurringPlanResponseData {
241
+ /** Ozura's internal plan identifier. Store this to manage the subscription. */
242
+ planId: string;
243
+ planName: string;
244
+ planDescription?: string;
245
+ merchantRecurringReference?: string;
246
+ /** Base recurring charge amount as a decimal string. */
247
+ amount: string;
248
+ currency: string;
249
+ /** Transaction ID of the initial enrollment charge (Pay API's `citTransactionId`). */
250
+ transactionId: string;
251
+ /** Actual amount charged on enrollment (includes setup fee, initial amount, tax, surcharge). */
252
+ transactionAmount: string;
253
+ transactionSurchargeAmount?: string;
254
+ transactionSalesTaxAmount?: string;
255
+ /** ISO 8601 timestamp of the next scheduled billing cycle. */
256
+ nextCycleAt: string;
257
+ endDate?: string;
258
+ cardLastFour?: string;
259
+ cardBrand?: string;
260
+ cardExpMonth?: string;
261
+ cardExpYear?: string;
262
+ transDate?: string;
263
+ }
177
264
  export interface ListTransactionsInput {
178
265
  /** Look up a single transaction by ID. When set, dateFrom/dateTo are not required. */
179
266
  transactionId?: string;
@@ -214,6 +301,32 @@ export declare class Ozura {
214
301
  * Rate limit: 100 requests/minute per merchant.
215
302
  */
216
303
  cardSale(input: CardSaleInput): Promise<CardSaleResponseData>;
304
+ /**
305
+ * Create a recurring subscription plan and execute the enrollment charge.
306
+ *
307
+ * The customer's card is charged immediately for the first billing cycle
308
+ * (at `initialAmount` if set, otherwise `amount`) and a subscription record
309
+ * is created in Ozura's billing engine for all future cycles.
310
+ *
311
+ * `transactionInitiationType` is always `"cit"` for enrollment — the Pay API
312
+ * enforces this. Subsequent MIT charges are processed automatically by Ozura's
313
+ * recurring queue worker; merchants do not need to trigger them.
314
+ *
315
+ * Rate limit: 100 requests/minute per merchant.
316
+ *
317
+ * @example
318
+ * const plan = await ozura.createRecurringPlan({
319
+ * token: tokenResponse.token,
320
+ * cvcSession: tokenResponse.cvcSession,
321
+ * amount: '19.99',
322
+ * billing: tokenResponse.billing,
323
+ * clientIpAddress: req.ip,
324
+ * planName: 'Pro Monthly',
325
+ * interval: 'monthly',
326
+ * });
327
+ * console.log(plan.planId, plan.transactionId);
328
+ */
329
+ createRecurringPlan(input: CreateRecurringPlanInput): Promise<CreateRecurringPlanResponseData>;
217
330
  /**
218
331
  * List transactions by date range with pagination.
219
332
  *
@@ -478,5 +591,116 @@ export declare function createCardSaleMiddleware(ozura: Ozura, options: CardSale
478
591
  body?: unknown;
479
592
  headers?: unknown;
480
593
  }, res: NodeLikeResponse) => Promise<void>;
594
+ /**
595
+ * Server-side recurring plan configuration resolved per-request.
596
+ * Source all plan details from your own database — never trust client-supplied values.
597
+ */
598
+ export interface RecurringPlanConfig {
599
+ /** Display name for the subscription. */
600
+ planName: string;
601
+ /** Billing cadence. */
602
+ interval: RecurringInterval;
603
+ planDescription?: string;
604
+ initialAmount?: string;
605
+ initialCycles?: number;
606
+ setupFee?: string;
607
+ intervalCount?: number;
608
+ endDate?: string;
609
+ maxCycles?: number;
610
+ maxAttempts?: number;
611
+ retryIntervalHours?: number;
612
+ merchantRecurringReference?: string;
613
+ salesTaxExempt?: boolean;
614
+ surchargePercent?: string;
615
+ processor?: 'elavon' | 'nuvei' | 'worldpay';
616
+ /**
617
+ * Transaction channel. Default: `"ecommerce"`.
618
+ * Use `"moto"` for mail/telephone-order enrollment flows.
619
+ * Source from your database via `getPlanConfig` — do not read from the request body.
620
+ */
621
+ transactionChannel?: 'ecommerce' | 'moto';
622
+ }
623
+ /**
624
+ * Options for {@link createRecurringPlanHandler} and {@link createRecurringPlanMiddleware}.
625
+ *
626
+ * Both `getAmount` and `getPlanConfig` must source their values from your own
627
+ * records — never trust the request body for plan price or configuration.
628
+ */
629
+ export interface RecurringPlanHandlerOptions {
630
+ /**
631
+ * Return the base recurring charge amount as a decimal string (e.g. `"19.99"`).
632
+ * Source from your database; never trust the value the client sends.
633
+ */
634
+ getAmount: (body: Record<string, unknown>) => string | Promise<string>;
635
+ /**
636
+ * Return the recurring plan configuration for this subscription.
637
+ * At minimum you must return `planName` and `interval`.
638
+ *
639
+ * @example
640
+ * getPlanConfig: async (body) => {
641
+ * const plan = await db.plans.findById(body.planId as string);
642
+ * return { planName: plan.name, interval: plan.interval };
643
+ * }
644
+ */
645
+ getPlanConfig: (body: Record<string, unknown>) => RecurringPlanConfig | Promise<RecurringPlanConfig>;
646
+ /** Return the ISO 4217 currency code. Default: `"USD"`. */
647
+ getCurrency?: (body: Record<string, unknown>) => string | Promise<string>;
648
+ }
649
+ /**
650
+ * Creates a ready-to-use Fetch API route handler for recurring subscription enrollment.
651
+ *
652
+ * Drop-in for Next.js App Router, Cloudflare Workers, Vercel Edge, and any runtime
653
+ * built on the standard Web API `Request` / `Response`.
654
+ *
655
+ * The handler reads `{ token, cvcSession, billing }` from the JSON request body,
656
+ * resolves the amount and plan configuration via your `options` callbacks (which
657
+ * must source data from your own database — never from the request body), calls
658
+ * `ozura.createRecurringPlan()`, and returns
659
+ * `{ planId, transactionId, transactionAmount, nextCycleAt }` on success.
660
+ *
661
+ * @example
662
+ * // app/api/subscribe/route.ts (Next.js App Router)
663
+ * import { Ozura, createRecurringPlanHandler } from '@ozura/elements/server';
664
+ *
665
+ * const ozura = new Ozura({ merchantId: '...', apiKey: '...', vaultKey: '...' });
666
+ *
667
+ * export const POST = createRecurringPlanHandler(ozura, {
668
+ * getAmount: async (body) => {
669
+ * const plan = await db.plans.findById(body.planId as string);
670
+ * return plan.price;
671
+ * },
672
+ * getPlanConfig: async (body) => {
673
+ * const plan = await db.plans.findById(body.planId as string);
674
+ * return { planName: plan.name, interval: plan.interval };
675
+ * },
676
+ * });
677
+ */
678
+ export declare function createRecurringPlanHandler(ozura: Ozura, options: RecurringPlanHandlerOptions): (req: Request) => Promise<Response>;
679
+ /**
680
+ * Creates a ready-to-use Express / Connect middleware for recurring subscription enrollment.
681
+ *
682
+ * Requires `express.json()` (or equivalent body-parser) to be registered before
683
+ * this middleware so `req.body` is available.
684
+ *
685
+ * @example
686
+ * // Express
687
+ * import express from 'express';
688
+ * import { Ozura, createRecurringPlanMiddleware } from '@ozura/elements/server';
689
+ *
690
+ * const app = express();
691
+ * const ozura = new Ozura({ merchantId: '...', apiKey: '...', vaultKey: '...' });
692
+ *
693
+ * app.use(express.json());
694
+ * app.post('/api/subscribe', createRecurringPlanMiddleware(ozura, {
695
+ * getAmount: async (body) => db.plans.findById(body.planId).then(p => p.price),
696
+ * getPlanConfig: async (body) => db.plans.findById(body.planId).then(p => ({
697
+ * planName: p.name, interval: p.interval,
698
+ * })),
699
+ * }));
700
+ */
701
+ export declare function createRecurringPlanMiddleware(ozura: Ozura, options: RecurringPlanHandlerOptions): (req: {
702
+ body?: unknown;
703
+ headers?: unknown;
704
+ }, res: NodeLikeResponse) => Promise<void>;
481
705
  export type { BillingDetails, CardSaleResponseData, TransactionQueryPagination, TransactionType, TransactionBase, CardTransactionData, AchTransactionData, CryptoTransactionData, TransactionData, } from '../types';
482
706
  export { normalizeCardSaleError } from '../sdk/errors';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ozura/elements",
3
- "version": "1.2.4-next.48",
3
+ "version": "1.2.4-next.50",
4
4
  "description": "PCI-compliant card tokenization SDK for the Ozura Vault — collect card data in iframe-isolated fields and tokenize without PCI scope",
5
5
  "main": "dist/oz-elements.umd.js",
6
6
  "module": "dist/oz-elements.esm.js",