@ozura/elements 0.1.0-beta.7 → 1.0.1

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.
Files changed (50) hide show
  1. package/README.md +906 -663
  2. package/dist/frame/element-frame.js +77 -57
  3. package/dist/frame/element-frame.js.map +1 -1
  4. package/dist/frame/tokenizer-frame.html +1 -1
  5. package/dist/frame/tokenizer-frame.js +211 -94
  6. package/dist/frame/tokenizer-frame.js.map +1 -1
  7. package/dist/oz-elements.esm.js +817 -230
  8. package/dist/oz-elements.esm.js.map +1 -1
  9. package/dist/oz-elements.umd.js +817 -229
  10. package/dist/oz-elements.umd.js.map +1 -1
  11. package/dist/react/frame/tokenizerFrame.d.ts +32 -0
  12. package/dist/react/index.cjs.js +968 -218
  13. package/dist/react/index.cjs.js.map +1 -1
  14. package/dist/react/index.esm.js +965 -219
  15. package/dist/react/index.esm.js.map +1 -1
  16. package/dist/react/react/index.d.ts +148 -6
  17. package/dist/react/sdk/OzElement.d.ts +34 -3
  18. package/dist/react/sdk/OzVault.d.ts +68 -4
  19. package/dist/react/sdk/errors.d.ts +9 -0
  20. package/dist/react/sdk/index.d.ts +29 -0
  21. package/dist/react/server/index.d.ts +181 -17
  22. package/dist/react/types/index.d.ts +69 -19
  23. package/dist/react/utils/appearance.d.ts +9 -0
  24. package/dist/react/utils/cardUtils.d.ts +14 -0
  25. package/dist/react/utils/uuid.d.ts +12 -0
  26. package/dist/server/frame/tokenizerFrame.d.ts +32 -0
  27. package/dist/server/index.cjs.js +608 -71
  28. package/dist/server/index.cjs.js.map +1 -1
  29. package/dist/server/index.esm.js +606 -72
  30. package/dist/server/index.esm.js.map +1 -1
  31. package/dist/server/sdk/OzElement.d.ts +34 -3
  32. package/dist/server/sdk/OzVault.d.ts +68 -4
  33. package/dist/server/sdk/errors.d.ts +9 -0
  34. package/dist/server/sdk/index.d.ts +29 -0
  35. package/dist/server/server/index.d.ts +181 -17
  36. package/dist/server/types/index.d.ts +69 -19
  37. package/dist/server/utils/appearance.d.ts +9 -0
  38. package/dist/server/utils/cardUtils.d.ts +14 -0
  39. package/dist/server/utils/uuid.d.ts +12 -0
  40. package/dist/types/frame/tokenizerFrame.d.ts +32 -0
  41. package/dist/types/sdk/OzElement.d.ts +34 -3
  42. package/dist/types/sdk/OzVault.d.ts +68 -4
  43. package/dist/types/sdk/errors.d.ts +9 -0
  44. package/dist/types/sdk/index.d.ts +29 -0
  45. package/dist/types/server/index.d.ts +181 -17
  46. package/dist/types/types/index.d.ts +69 -19
  47. package/dist/types/utils/appearance.d.ts +9 -0
  48. package/dist/types/utils/cardUtils.d.ts +14 -0
  49. package/dist/types/utils/uuid.d.ts +12 -0
  50. package/package.json +7 -4
@@ -1,3 +1,267 @@
1
+ /**
2
+ * errors.ts — error types and normalisation for OzElements.
3
+ *
4
+ * Three normalisation functions:
5
+ * - normalizeVaultError — maps raw vault /tokenize errors to user-facing messages (card flows)
6
+ * - normalizeBankVaultError — maps raw vault /tokenize errors to user-facing messages (bank/ACH flows)
7
+ * - normalizeCardSaleError — maps raw cardSale API errors to user-facing messages
8
+ *
9
+ * Error keys in normalizeCardSaleError are taken directly from checkout's
10
+ * errorMapping.ts so the same error strings produce the same user-facing copy.
11
+ */
12
+ // ─── cardSale error map (mirrors checkout/src/utils/errorMapping.ts exactly) ─
13
+ const CARD_SALE_ERROR_MAP = [
14
+ ['Insufficient Funds', 'Your card has insufficient funds. Please use a different payment method.'],
15
+ ['Invalid card number', 'The card number you entered is invalid. Please check and try again.'],
16
+ ['Card expired', 'Your card has expired. Please use a different card.'],
17
+ ['CVV Verification Failed', 'The CVV code you entered is incorrect. Please check and try again.'],
18
+ ['Address Verification Failed', 'The billing address does not match your card. Please verify your address.'],
19
+ ['Do Not Honor', 'Your card was declined. Please contact your bank or use a different payment method.'],
20
+ ['Declined', 'Your card was declined. Please contact your bank or use a different payment method.'],
21
+ ['Surcharge is currently not supported', 'Surcharge fees are not supported at this time.'],
22
+ ['Surcharge percent must be between', 'Surcharge fees are not supported at this time.'],
23
+ ['Forbidden - API key', 'Authentication failed. Please refresh the page.'],
24
+ ['Api Key is invalid', 'Authentication failed. Please refresh the page.'],
25
+ ['API key not found', 'Authentication failed. Please refresh the page.'],
26
+ ['Access token expired', 'Your session has expired. Please refresh the page.'],
27
+ ['Access token is invalid', 'Authentication failed. Please refresh the page.'],
28
+ ['Unauthorized', 'Authentication failed. Please refresh the page.'],
29
+ ['Too Many Requests', 'Too many requests. Please wait a moment and try again.'],
30
+ ['Rate limit exceeded', 'System is busy. Please wait a moment and try again.'],
31
+ ['No processor integrations found', 'Payment processing is not configured for this merchant. Please contact the merchant for assistance.'],
32
+ ['processor integration', 'Payment processing is temporarily unavailable. Please try again later or contact the merchant.'],
33
+ ['Invalid zipcode', 'The ZIP code you entered is invalid. Please check and try again.'],
34
+ ['failed to save to database', 'Your payment was processed but we encountered an issue. Please contact support.'],
35
+ ['CASHBACK UNAVAIL', 'This transaction type is not supported. Please try again or use a different payment method.'],
36
+ ];
37
+ /**
38
+ * Maps a raw Ozura Pay API cardSale error string to a user-facing message.
39
+ *
40
+ * Uses the exact same error key table as checkout's `getUserFriendlyError()` in
41
+ * errorMapping.ts so both surfaces produce identical copy for the same errors.
42
+ *
43
+ * Falls back to the original string when it's under 100 characters, or to a
44
+ * generic message for long/opaque server errors — matching checkout's fallback
45
+ * behaviour exactly.
46
+ *
47
+ * **Trade-off:** Short unrecognised strings (e.g. processor codes like
48
+ * `"PROC_TIMEOUT"`) are passed through verbatim. This intentionally mirrors
49
+ * checkout so the same raw Pay API errors produce the same user-facing text on
50
+ * both surfaces. If the Pay API ever returns internal codes that should never
51
+ * reach the UI, the fix belongs in the Pay API error normalisation layer rather
52
+ * than here.
53
+ */
54
+ function normalizeCardSaleError(raw) {
55
+ if (!raw)
56
+ return 'Payment processing failed. Please try again.';
57
+ for (const [key, message] of CARD_SALE_ERROR_MAP) {
58
+ if (raw.toLowerCase().includes(key.toLowerCase())) {
59
+ return message;
60
+ }
61
+ }
62
+ // Checkout fallback: pass through short errors, genericise long ones
63
+ if (raw.length < 100)
64
+ return raw;
65
+ return 'Payment processing failed. Please try again or contact support.';
66
+ }
67
+
68
+ /**
69
+ * billingUtils.ts — billing detail validation and normalization.
70
+ *
71
+ * Mirrors the validation in checkout/page.tsx (pre-flight checks before cardSale)
72
+ * so that billing data passed to createToken() is guaranteed schema-compliant and
73
+ * ready to forward directly to the Ozura Pay API cardSale endpoint.
74
+ *
75
+ * All string fields enforced to 1–50 characters (cardSale schema constraint).
76
+ * State is normalized to 2-letter abbreviation for US and CA.
77
+ * Phone must be E.164 format (matches checkout's formatPhoneForAPI output).
78
+ */
79
+ // ─── Email ────────────────────────────────────────────────────────────────────
80
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
81
+ /** Returns true when the email is syntactically valid and ≤50 characters. */
82
+ function validateEmail(email) {
83
+ return EMAIL_RE.test(email) && email.length <= 50;
84
+ }
85
+ // ─── Phone ───────────────────────────────────────────────────────────────────
86
+ /**
87
+ * Validates E.164 phone format: starts with +, 1–3 digit country code,
88
+ * followed by 7–12 digits, total ≤50 characters.
89
+ *
90
+ * Matches the output of checkout's formatPhoneForAPI() function.
91
+ * Examples: "+15551234567", "+447911123456", "+61412345678"
92
+ */
93
+ function validateE164Phone(phone) {
94
+ return /^\+[1-9]\d{6,49}$/.test(phone) && phone.length <= 50;
95
+ }
96
+ // ─── Field length ─────────────────────────────────────────────────────────────
97
+ /** Returns true when the string is non-empty and ≤50 characters (cardSale schema). */
98
+ function isValidBillingField(value) {
99
+ return value.length > 0 && value.length <= 50;
100
+ }
101
+ // ─── US state normalization ───────────────────────────────────────────────────
102
+ // Mirrors checkout's convertStateToAbbreviation() so the same input variants work.
103
+ const US_STATES = {
104
+ alabama: 'AL', alaska: 'AK', arizona: 'AZ', arkansas: 'AR',
105
+ california: 'CA', colorado: 'CO', connecticut: 'CT', delaware: 'DE',
106
+ 'district of columbia': 'DC', florida: 'FL', georgia: 'GA', hawaii: 'HI',
107
+ idaho: 'ID', illinois: 'IL', indiana: 'IN', iowa: 'IA', kansas: 'KS',
108
+ kentucky: 'KY', louisiana: 'LA', maine: 'ME', maryland: 'MD',
109
+ massachusetts: 'MA', michigan: 'MI', minnesota: 'MN', mississippi: 'MS',
110
+ missouri: 'MO', montana: 'MT', nebraska: 'NE', nevada: 'NV',
111
+ 'new hampshire': 'NH', 'new jersey': 'NJ', 'new mexico': 'NM', 'new york': 'NY',
112
+ 'north carolina': 'NC', 'north dakota': 'ND', ohio: 'OH', oklahoma: 'OK',
113
+ oregon: 'OR', pennsylvania: 'PA', 'rhode island': 'RI', 'south carolina': 'SC',
114
+ 'south dakota': 'SD', tennessee: 'TN', texas: 'TX', utah: 'UT',
115
+ vermont: 'VT', virginia: 'VA', washington: 'WA', 'west virginia': 'WV',
116
+ wisconsin: 'WI', wyoming: 'WY',
117
+ // US territories
118
+ 'puerto rico': 'PR', guam: 'GU', 'virgin islands': 'VI',
119
+ 'us virgin islands': 'VI', 'u.s. virgin islands': 'VI',
120
+ 'american samoa': 'AS', 'northern mariana islands': 'MP',
121
+ 'commonwealth of the northern mariana islands': 'MP',
122
+ // Military / diplomatic addresses
123
+ 'armed forces europe': 'AE', 'armed forces pacific': 'AP',
124
+ 'armed forces americas': 'AA',
125
+ };
126
+ const US_ABBREVS = new Set(Object.values(US_STATES));
127
+ const CA_PROVINCES = {
128
+ alberta: 'AB', 'british columbia': 'BC', manitoba: 'MB', 'new brunswick': 'NB',
129
+ 'newfoundland and labrador': 'NL', 'nova scotia': 'NS', ontario: 'ON',
130
+ 'prince edward island': 'PE', quebec: 'QC', saskatchewan: 'SK',
131
+ 'northwest territories': 'NT', nunavut: 'NU', yukon: 'YT',
132
+ };
133
+ const CA_ABBREVS = new Set(Object.values(CA_PROVINCES));
134
+ /**
135
+ * Converts a full US state or Canadian province name to its 2-letter abbreviation.
136
+ * If already a valid abbreviation (case-insensitive), returns it uppercased.
137
+ * For non-US/CA countries, returns the input uppercased unchanged.
138
+ *
139
+ * Matches checkout's convertStateToAbbreviation() behaviour exactly.
140
+ */
141
+ function normalizeState(state, country) {
142
+ var _a, _b;
143
+ const upper = state.trim().toUpperCase();
144
+ const lower = state.trim().toLowerCase();
145
+ if (country === 'US') {
146
+ if (US_ABBREVS.has(upper))
147
+ return upper;
148
+ return (_a = US_STATES[lower]) !== null && _a !== void 0 ? _a : upper;
149
+ }
150
+ if (country === 'CA') {
151
+ if (CA_ABBREVS.has(upper))
152
+ return upper;
153
+ return (_b = CA_PROVINCES[lower]) !== null && _b !== void 0 ? _b : upper;
154
+ }
155
+ return upper;
156
+ }
157
+ // ─── Postal code validation ───────────────────────────────────────────────────
158
+ const POSTAL_PATTERNS = {
159
+ US: /^\d{5}(-?\d{4})?$/, // 5-digit or ZIP+4 (with or without hyphen)
160
+ CA: /^[A-Za-z]\d[A-Za-z]\s?\d[A-Za-z]\d$/, // A1A 1A1
161
+ GB: /^[A-Za-z]{1,2}\d[A-Za-z\d]?\s?\d[A-Za-z]{2}$/,
162
+ DE: /^\d{5}$/,
163
+ FR: /^\d{5}$/,
164
+ ES: /^\d{5}$/,
165
+ IT: /^\d{5}$/,
166
+ AU: /^\d{4}$/,
167
+ NL: /^\d{4}\s?[A-Za-z]{2}$/,
168
+ BR: /^\d{5}-?\d{3}$/,
169
+ JP: /^\d{3}-?\d{4}$/,
170
+ IN: /^\d{6}$/,
171
+ };
172
+ /**
173
+ * Validates a postal/ZIP code against country-specific format rules.
174
+ * For countries without a specific pattern, falls back to generic 1–50 char check.
175
+ */
176
+ function validatePostalCode(zip, country) {
177
+ if (!zip || zip.length === 0)
178
+ return { valid: false, error: 'Postal code is required' };
179
+ if (zip.length > 50)
180
+ return { valid: false, error: 'Postal code must be 50 characters or fewer' };
181
+ const pattern = POSTAL_PATTERNS[country.toUpperCase()];
182
+ if (pattern && !pattern.test(zip)) {
183
+ return { valid: false, error: `Invalid postal code format for ${country.toUpperCase()}` };
184
+ }
185
+ return { valid: true };
186
+ }
187
+ /**
188
+ * Validates and normalizes billing details against the Ozura cardSale API schema.
189
+ *
190
+ * Rules applied (same as checkout's pre-flight validation in page.tsx):
191
+ * - firstName, lastName: required, 1–50 chars
192
+ * - email: optional; if provided, must be valid format and ≤50 chars
193
+ * - phone: optional; if provided, must be E.164 and ≤50 chars
194
+ * - address fields: if address is provided, line1/city/state/zip/country are
195
+ * required (1–50 chars each); line2 is optional and omitted from normalized
196
+ * output if blank (cardSale schema: minLength 1 if present)
197
+ * - state: normalized to 2-letter abbreviation for US and CA
198
+ */
199
+ function validateBilling(billing) {
200
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
201
+ const errors = [];
202
+ const firstName = (_b = (_a = billing.firstName) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '';
203
+ const lastName = (_d = (_c = billing.lastName) === null || _c === void 0 ? void 0 : _c.trim()) !== null && _d !== void 0 ? _d : '';
204
+ const email = (_f = (_e = billing.email) === null || _e === void 0 ? void 0 : _e.trim()) !== null && _f !== void 0 ? _f : '';
205
+ const phone = (_h = (_g = billing.phone) === null || _g === void 0 ? void 0 : _g.trim()) !== null && _h !== void 0 ? _h : '';
206
+ if (!isValidBillingField(firstName)) {
207
+ errors.push('billing.firstName must be 1–50 characters');
208
+ }
209
+ if (!isValidBillingField(lastName)) {
210
+ errors.push('billing.lastName must be 1–50 characters');
211
+ }
212
+ if (email && !validateEmail(email)) {
213
+ errors.push('billing.email must be a valid address (max 50 characters)');
214
+ }
215
+ if (phone && !validateE164Phone(phone)) {
216
+ errors.push('billing.phone must be E.164 format, e.g. "+15551234567" (max 50 characters)');
217
+ }
218
+ let normalizedAddress;
219
+ if (billing.address) {
220
+ const a = billing.address;
221
+ const country = (_k = (_j = a.country) === null || _j === void 0 ? void 0 : _j.trim().toUpperCase()) !== null && _k !== void 0 ? _k : '';
222
+ const line1 = (_m = (_l = a.line1) === null || _l === void 0 ? void 0 : _l.trim()) !== null && _m !== void 0 ? _m : '';
223
+ const line2 = (_p = (_o = a.line2) === null || _o === void 0 ? void 0 : _o.trim()) !== null && _p !== void 0 ? _p : '';
224
+ const city = (_r = (_q = a.city) === null || _q === void 0 ? void 0 : _q.trim()) !== null && _r !== void 0 ? _r : '';
225
+ const zip = (_t = (_s = a.zip) === null || _s === void 0 ? void 0 : _s.trim()) !== null && _t !== void 0 ? _t : '';
226
+ const state = normalizeState((_v = (_u = a.state) === null || _u === void 0 ? void 0 : _u.trim()) !== null && _v !== void 0 ? _v : '', country);
227
+ if (!isValidBillingField(line1))
228
+ errors.push('billing.address.line1 must be 1–50 characters');
229
+ if (line2 && !isValidBillingField(line2))
230
+ errors.push('billing.address.line2 must be 1–50 characters if provided');
231
+ if (!isValidBillingField(city))
232
+ errors.push('billing.address.city must be 1–50 characters');
233
+ if (!isValidBillingField(state)) {
234
+ errors.push('billing.address.state must be 1–50 characters');
235
+ }
236
+ else if (country === 'US' && !US_ABBREVS.has(state)) {
237
+ errors.push(`billing.address.state "${state}" is not a recognized US state or territory abbreviation (e.g. "CA", "NY", "PR")`);
238
+ }
239
+ else if (country === 'CA' && !CA_ABBREVS.has(state)) {
240
+ errors.push(`billing.address.state "${state}" is not a recognized Canadian province or territory abbreviation (e.g. "ON", "BC", "QC")`);
241
+ }
242
+ // cardSale backend uses strict enum validation on country — must be exactly 2 uppercase letters
243
+ if (!/^[A-Z]{2}$/.test(country)) {
244
+ errors.push('billing.address.country must be a 2-letter ISO 3166-1 alpha-2 code (e.g. "US", "CA", "GB")');
245
+ }
246
+ if (!isValidBillingField(zip)) {
247
+ errors.push('billing.address.zip must be 1–50 characters');
248
+ }
249
+ else if (/^[A-Z]{2}$/.test(country)) {
250
+ const postalResult = validatePostalCode(zip, country);
251
+ if (!postalResult.valid) {
252
+ errors.push(`billing.address.zip: ${postalResult.error}`);
253
+ }
254
+ }
255
+ normalizedAddress = Object.assign(Object.assign({ line1 }, (line2 ? { line2 } : {})), { city,
256
+ state,
257
+ zip,
258
+ country });
259
+ }
260
+ const normalized = Object.assign(Object.assign(Object.assign({ firstName,
261
+ lastName }, (email ? { email } : {})), (phone ? { phone } : {})), (normalizedAddress ? { address: normalizedAddress } : {}));
262
+ return { valid: errors.length === 0, errors, normalized };
263
+ }
264
+
1
265
  /**
2
266
  * @ozura/server — Server-side SDK for the Ozura Pay API.
3
267
  *
@@ -6,7 +270,7 @@
6
270
  *
7
271
  * @example
8
272
  * ```ts
9
- * import { Ozura } from 'oz-elements/server';
273
+ * import { Ozura } from '@ozura/elements/server';
10
274
  *
11
275
  * const ozura = new Ozura({
12
276
  * merchantId: process.env.MERCHANT_ID!,
@@ -27,8 +291,8 @@
27
291
  * ```
28
292
  */
29
293
  // ─── Configuration ───────────────────────────────────────────────────────────
30
- const DEFAULT_API_URL = "https://ozurapay-pay-api-v2-staging-c8abbdfhfbd3c5em.eastus-01.azurewebsites.net";
31
- const DEFAULT_VAULT_URL = "https://pci-vault-staging-drc0duhcakf4g4fr.eastus-01.azurewebsites.net";
294
+ const DEFAULT_API_URL = "https://payapi.v2.ozurapay.com";
295
+ const DEFAULT_VAULT_URL = "https://api.ozuravault.com";
32
296
  const DEFAULT_TIMEOUT = 30000;
33
297
  // ─── Error ───────────────────────────────────────────────────────────────────
34
298
  class OzuraError extends Error {
@@ -48,14 +312,27 @@ function isRetryable(status) {
48
312
  async function sleep(ms) {
49
313
  return new Promise(resolve => setTimeout(resolve, ms));
50
314
  }
315
+ /**
316
+ * Returns an AbortSignal that aborts after `ms` milliseconds.
317
+ *
318
+ * Uses `AbortSignal.timeout()` when available (Node 17.3+ / 18.0+) and
319
+ * falls back to a manual `AbortController` + `setTimeout` for Node 16 and
320
+ * other older runtimes. Both paths produce an `AbortError`-family signal;
321
+ * the catch sites in this file only use `err.message` so the difference in
322
+ * `err.name` (`'AbortError'` vs `'TimeoutError'`) is inconsequential.
323
+ */
324
+ function timeoutSignal(ms) {
325
+ if (typeof AbortSignal.timeout === 'function') {
326
+ return AbortSignal.timeout(ms);
327
+ }
328
+ const controller = new AbortController();
329
+ setTimeout(() => controller.abort(), ms);
330
+ return controller.signal;
331
+ }
51
332
  // ─── Main class ──────────────────────────────────────────────────────────────
52
333
  class Ozura {
53
334
  constructor(config) {
54
335
  var _a, _b;
55
- if (!config.merchantId)
56
- throw new OzuraError('merchantId is required', 0);
57
- if (!config.apiKey)
58
- throw new OzuraError('apiKey is required', 0);
59
336
  if (!config.vaultKey)
60
337
  throw new OzuraError('vaultKey is required', 0);
61
338
  this.merchantId = config.merchantId;
@@ -74,6 +351,10 @@ class Ozura {
74
351
  */
75
352
  async cardSale(input) {
76
353
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
354
+ if (!this.merchantId)
355
+ throw new OzuraError('merchantId is required for cardSale(). Add it to the Ozura constructor config.', 0);
356
+ if (!this.apiKey)
357
+ throw new OzuraError('apiKey is required for cardSale(). Add it to the Ozura constructor config.', 0);
77
358
  const billing = input.billing;
78
359
  const body = {
79
360
  merchantId: this.merchantId,
@@ -109,6 +390,10 @@ class Ozura {
109
390
  * Rate limit: 200 requests/minute per merchant.
110
391
  */
111
392
  async listTransactions(input = {}) {
393
+ if (!this.merchantId)
394
+ throw new OzuraError('merchantId is required for listTransactions(). Add it to the Ozura constructor config.', 0);
395
+ if (!this.apiKey)
396
+ throw new OzuraError('apiKey is required for listTransactions(). Add it to the Ozura constructor config.', 0);
112
397
  const params = { merchantId: this.merchantId };
113
398
  if (input.transactionId)
114
399
  params.transactionId = input.transactionId;
@@ -134,56 +419,62 @@ class Ozura {
134
419
  }
135
420
  // ─── Wax key helpers ─────────────────────────────────────────────────────
136
421
  /**
137
- * Mint a short-lived wax key from the vault.
422
+ * Mint a short-lived, use-limited wax key from the vault.
138
423
  *
139
424
  * Call this server-side to implement the `fetchWaxKey` callback required by
140
425
  * `OzVault.create()` on the frontend. The wax key replaces the vault secret
141
426
  * on every browser tokenize call — the secret never leaves your server.
142
427
  *
143
- * The `tokenizationSessionId` passed to `fetchWaxKey` by the SDK should be
144
- * forwarded here for correlation; it is stored by the vault alongside the
145
- * wax key but is not used for authentication.
428
+ * **Use limits:** by default each wax key accepts up to 3 tokenize calls
429
+ * (`maxTokenizeCalls: 3`). After that the vault marks the key as consumed and
430
+ * the client SDK transparently re-mints. Keep `maxTokenizeCalls` in sync with
431
+ * `VaultOptions.maxTokenizeCalls` so the SDK can proactively refresh before
432
+ * hitting the limit rather than waiting for a rejection.
433
+ *
434
+ * **Session correlation:** the `tokenizationSessionId` forwarded from the SDK's
435
+ * `fetchWaxKey` callback should be passed here so the vault can correlate the
436
+ * key with the checkout session in its audit log.
146
437
  *
147
438
  * @example
148
439
  * // Next.js API route
149
440
  * export async function POST(req: Request) {
150
441
  * const { sessionId } = await req.json();
151
- * const { waxKey } = await ozura.mintWaxKey({ tokenizationSessionId: sessionId });
442
+ * const { waxKey } = await ozura.mintWaxKey({
443
+ * tokenizationSessionId: sessionId,
444
+ * maxTokenizeCalls: 3,
445
+ * });
152
446
  * return Response.json({ waxKey });
153
447
  * }
154
448
  */
155
449
  async mintWaxKey(options) {
156
- var _a, _b;
450
+ var _a, _b, _c;
157
451
  const body = {};
158
452
  if (options === null || options === void 0 ? void 0 : options.tokenizationSessionId) {
159
453
  body.checkout_session_id = options.tokenizationSessionId;
160
454
  }
161
- let res;
162
- try {
163
- res = await fetch(`${this.vaultUrl}/internal/wax-session`, {
164
- method: 'POST',
165
- headers: {
166
- 'Content-Type': 'application/json',
167
- 'X-API-Key': this.vaultKey,
168
- },
169
- body: JSON.stringify(body),
170
- signal: AbortSignal.timeout(this.timeoutMs),
171
- });
172
- }
173
- catch (err) {
174
- const msg = err instanceof Error ? err.message : 'Network error';
175
- throw new OzuraError(`Wax key mint failed: ${msg}`, 0, msg);
455
+ // Always send max_tokenize_calls — default 3 if not overridden.
456
+ body.max_tokenize_calls = (_a = options === null || options === void 0 ? void 0 : options.maxTokenizeCalls) !== null && _a !== void 0 ? _a : 3;
457
+ if ((options === null || options === void 0 ? void 0 : options.maxProxyCalls) !== undefined) {
458
+ body.max_proxy_calls = options.maxProxyCalls;
176
459
  }
460
+ const res = await this.fetchWithRetry(`${this.vaultUrl}/internal/wax-session`, {
461
+ method: 'POST',
462
+ headers: {
463
+ 'Content-Type': 'application/json',
464
+ 'X-API-Key': this.vaultKey,
465
+ },
466
+ body: JSON.stringify(body),
467
+ });
177
468
  const json = await res.json().catch(() => ({}));
178
469
  if (res.status === 201) {
179
470
  const data = json.data;
180
- const waxKey = String((_a = data === null || data === void 0 ? void 0 : data.wax_key) !== null && _a !== void 0 ? _a : '');
471
+ const waxKey = String((_b = data === null || data === void 0 ? void 0 : data.wax_key) !== null && _b !== void 0 ? _b : '');
181
472
  if (!waxKey) {
182
473
  throw new OzuraError('Vault mint response missing data.wax_key', res.status, JSON.stringify(json));
183
474
  }
184
475
  return {
185
476
  waxKey,
186
- expiresInSeconds: Number((_b = data === null || data === void 0 ? void 0 : data.expires_in_seconds) !== null && _b !== void 0 ? _b : 1800),
477
+ expiresInSeconds: Number((_c = data === null || data === void 0 ? void 0 : data.expires_in_seconds) !== null && _c !== void 0 ? _c : 1800),
187
478
  };
188
479
  }
189
480
  const errorCode = json.error_code || '';
@@ -217,7 +508,7 @@ class Ozura {
217
508
  'X-API-Key': this.vaultKey,
218
509
  },
219
510
  body: JSON.stringify({ wax_key: waxKey }),
220
- signal: AbortSignal.timeout(3000),
511
+ signal: timeoutSignal(3000),
221
512
  });
222
513
  if (res.status === 200 || res.status === 404)
223
514
  return;
@@ -233,12 +524,12 @@ class Ozura {
233
524
  * Execute a fetch with retry on 5xx / network errors.
234
525
  * 4xx errors (including 429) are never retried — they require caller action.
235
526
  */
236
- async fetchWithRetry(url, init) {
527
+ async fetchWithRetry(url, init, maxRetries = this.retries) {
237
528
  let lastError;
238
- for (let attempt = 0; attempt <= this.retries; attempt++) {
529
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
239
530
  try {
240
- const res = await fetch(url, Object.assign(Object.assign({}, init), { signal: AbortSignal.timeout(this.timeoutMs) }));
241
- if (isRetryable(res.status) && attempt < this.retries) {
531
+ const res = await fetch(url, Object.assign(Object.assign({}, init), { signal: timeoutSignal(this.timeoutMs) }));
532
+ if (isRetryable(res.status) && attempt < maxRetries) {
242
533
  // Consume/cancel body so the connection is released (undici holds socket until body is drained)
243
534
  if (res.body) {
244
535
  try {
@@ -256,7 +547,7 @@ class Ozura {
256
547
  }
257
548
  catch (err) {
258
549
  lastError = err;
259
- if (attempt < this.retries) {
550
+ if (attempt < maxRetries) {
260
551
  const backoff = Math.min(1000 * 2 ** attempt, 8000);
261
552
  await sleep(backoff);
262
553
  continue;
@@ -272,7 +563,7 @@ class Ozura {
272
563
  }
273
564
  throw new OzuraError('Network error', 0);
274
565
  }
275
- async post(path, body, includeVaultKey) {
566
+ async post(path, body, includeVaultKey, maxRetries) {
276
567
  // Enforce: the server SDK sends x-api-key on every request, so it must
277
568
  // never be used to call the vault tokenize endpoint. Tokenization is
278
569
  // client-side only (OzVault + tokenizerFrame). Surface misuse immediately.
@@ -282,6 +573,7 @@ class Ozura {
282
573
  }
283
574
  const headers = {
284
575
  'Content-Type': 'application/json',
576
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
285
577
  'x-api-key': this.apiKey,
286
578
  };
287
579
  if (includeVaultKey) {
@@ -291,7 +583,7 @@ class Ozura {
291
583
  method: 'POST',
292
584
  headers,
293
585
  body: JSON.stringify(body),
294
- });
586
+ }, maxRetries);
295
587
  return this.handleResponse(res);
296
588
  }
297
589
  async getRaw(path) {
@@ -302,33 +594,33 @@ class Ozura {
302
594
  }
303
595
  const res = await this.fetchWithRetry(`${this.apiUrl}${path}`, {
304
596
  method: 'GET',
597
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
305
598
  headers: { 'x-api-key': this.apiKey },
306
599
  });
307
- const json = await res.json().catch(() => ({ error: res.statusText }));
308
- if (!res.ok) {
309
- const retryAfter = res.status === 429
310
- ? Number(res.headers.get('retry-after') || json.retryAfter) || undefined
311
- : undefined;
312
- throw new OzuraError(json.error || `HTTP ${res.status}`, res.status, json.error, retryAfter);
313
- }
314
- if (json.success === false) {
315
- throw new OzuraError(json.error || 'Request was not successful', res.status, json.error);
316
- }
317
- return json;
600
+ return (await this.parseApiJson(res));
318
601
  }
319
602
  async handleResponse(res) {
320
603
  var _a;
604
+ const json = await this.parseApiJson(res);
605
+ return ((_a = json.data) !== null && _a !== void 0 ? _a : json);
606
+ }
607
+ /**
608
+ * Parses a Pay API response JSON and throws `OzuraError` on HTTP errors or
609
+ * `success: false` payloads. Used by both `getRaw` and `handleResponse` to
610
+ * avoid duplicating the error-mapping logic.
611
+ */
612
+ async parseApiJson(res) {
321
613
  const json = await res.json().catch(() => ({ error: res.statusText }));
322
614
  if (!res.ok) {
323
615
  const retryAfter = res.status === 429
324
616
  ? Number(res.headers.get('retry-after') || json.retryAfter) || undefined
325
617
  : undefined;
326
- throw new OzuraError(json.error || `HTTP ${res.status}`, res.status, json.error, retryAfter);
618
+ throw new OzuraError(json.error || `HTTP ${res.status}`, res.status, typeof json.error === 'string' ? json.error : undefined, retryAfter);
327
619
  }
328
620
  if (json.success === false) {
329
- throw new OzuraError(json.error || 'Request was not successful', res.status, json.error);
621
+ throw new OzuraError(json.error || 'Request was not successful', res.status, typeof json.error === 'string' ? json.error : undefined);
330
622
  }
331
- return ((_a = json.data) !== null && _a !== void 0 ? _a : json);
623
+ return json;
332
624
  }
333
625
  }
334
626
  /**
@@ -355,17 +647,22 @@ class Ozura {
355
647
  */
356
648
  function createMintWaxHandler(ozura) {
357
649
  return async (req) => {
358
- var _a;
359
- let sessionId;
360
- try {
361
- const body = await req.json().catch(() => ({}));
362
- const raw = (_a = body.sessionId) !== null && _a !== void 0 ? _a : body.tokenizationSessionId;
363
- if (typeof raw === 'string' && raw)
364
- sessionId = raw;
650
+ var _a, _b;
651
+ if (req.method !== 'POST') {
652
+ return Response.json({ error: 'Method Not Allowed' }, { status: 405 });
365
653
  }
366
- catch (_b) {
367
- // unreadable body proceed without sessionId
654
+ // Reject non-JSON bodies — blocks simple-form CSRF requests which send
655
+ // application/x-www-form-urlencoded and cannot set Content-Type to
656
+ // application/json without triggering a CORS preflight.
657
+ const contentType = (_a = req.headers.get('content-type')) !== null && _a !== void 0 ? _a : '';
658
+ if (!contentType.includes('application/json')) {
659
+ return Response.json({ error: 'Content-Type must be application/json' }, { status: 415 });
368
660
  }
661
+ let sessionId;
662
+ const body = await req.json().catch(() => ({}));
663
+ const raw = (_b = body.sessionId) !== null && _b !== void 0 ? _b : body.tokenizationSessionId;
664
+ if (typeof raw === 'string' && raw)
665
+ sessionId = raw;
369
666
  try {
370
667
  const { waxKey } = await ozura.mintWaxKey({ tokenizationSessionId: sessionId });
371
668
  return Response.json({ waxKey });
@@ -404,9 +701,22 @@ function createMintWaxHandler(ozura) {
404
701
  */
405
702
  function createMintWaxMiddleware(ozura) {
406
703
  return async (req, res) => {
407
- var _a, _b;
408
- const body = ((_a = req.body) !== null && _a !== void 0 ? _a : {});
409
- const raw = (_b = body.sessionId) !== null && _b !== void 0 ? _b : body.tokenizationSessionId;
704
+ var _a, _b, _c;
705
+ const method = req.method;
706
+ if (method && method !== 'POST') {
707
+ res.status(405).json({ error: 'Method Not Allowed' });
708
+ return;
709
+ }
710
+ // Reject non-JSON content types — blocks simple-form CSRF requests.
711
+ // Express sets req.headers as a plain object; check it directly.
712
+ const headers = req.headers;
713
+ const ct = (_a = (typeof (headers === null || headers === void 0 ? void 0 : headers['content-type']) === 'string' ? headers['content-type'] : '')) !== null && _a !== void 0 ? _a : '';
714
+ if (!ct.includes('application/json')) {
715
+ res.status(415).json({ error: 'Content-Type must be application/json' });
716
+ return;
717
+ }
718
+ const body = ((_b = req.body) !== null && _b !== void 0 ? _b : {});
719
+ const raw = (_c = body.sessionId) !== null && _c !== void 0 ? _c : body.tokenizationSessionId;
410
720
  const sessionId = typeof raw === 'string' && raw ? raw : undefined;
411
721
  try {
412
722
  const { waxKey } = await ozura.mintWaxKey({ tokenizationSessionId: sessionId });
@@ -429,8 +739,27 @@ function createMintWaxMiddleware(ozura) {
429
739
  * (Cloudflare) → `x-forwarded-for` (reverse proxy) → `x-real-ip` →
430
740
  * `socket.remoteAddress` → `"0.0.0.0"`.
431
741
  *
742
+ * **Proxy trust requirements — read before deploying:**
743
+ *
744
+ * - **`x-forwarded-for` / `x-real-ip`** are HTTP headers that any client can
745
+ * set arbitrarily. They are only trustworthy when your server sits behind a
746
+ * reverse proxy (nginx, AWS ALB, Cloudflare, etc.) that strips and rewrites
747
+ * those headers. If your Node.js process is directly internet-accessible,
748
+ * an attacker can spoof any IP value and bypass payment-processor
749
+ * IP-based fraud checks.
750
+ * - **Express `req.ip`** resolves through `X-Forwarded-For` only when
751
+ * `app.set('trust proxy', true)` (or a specific proxy count/subnet) is
752
+ * configured. Without `trust proxy`, `req.ip` returns the direct socket
753
+ * address (your load-balancer's IP, not the client's).
754
+ * - **`cf-connecting-ip`** is only trustworthy when Cloudflare is genuinely
755
+ * in front of your server. Without Cloudflare, any client can send this
756
+ * header with a fabricated value.
757
+ *
758
+ * In all cases, ensure your infrastructure strips untrusted forwarding headers
759
+ * before they reach your application.
760
+ *
432
761
  * @example
433
- * // Express / Fastify
762
+ * // Express requires app.set('trust proxy', true) behind a proxy
434
763
  * clientIpAddress: getClientIp(req)
435
764
  *
436
765
  * // Next.js App Router
@@ -448,8 +777,11 @@ function getClientIp(req) {
448
777
  if (cfIp)
449
778
  return cfIp;
450
779
  const xff = get('x-forwarded-for');
451
- if (xff)
452
- return xff.split(',')[0].trim();
780
+ if (xff) {
781
+ const candidate = xff.split(',')[0].trim();
782
+ if (candidate)
783
+ return candidate;
784
+ }
453
785
  const realIp = get('x-real-ip');
454
786
  if (realIp)
455
787
  return realIp;
@@ -461,10 +793,16 @@ function getClientIp(req) {
461
793
  if (typeof cfIp === 'string' && cfIp)
462
794
  return cfIp;
463
795
  const xff = h['x-forwarded-for'];
464
- if (typeof xff === 'string' && xff)
465
- return xff.split(',')[0].trim();
466
- if (Array.isArray(xff) && xff[0])
467
- return xff[0].split(',')[0].trim();
796
+ if (typeof xff === 'string' && xff) {
797
+ const candidate = xff.split(',')[0].trim();
798
+ if (candidate)
799
+ return candidate;
800
+ }
801
+ if (Array.isArray(xff) && xff[0]) {
802
+ const candidate = xff[0].split(',')[0].trim();
803
+ if (candidate)
804
+ return candidate;
805
+ }
468
806
  const realIp = h['x-real-ip'];
469
807
  if (typeof realIp === 'string' && realIp)
470
808
  return realIp;
@@ -475,6 +813,202 @@ function getClientIp(req) {
475
813
  return socket.remoteAddress;
476
814
  return '0.0.0.0';
477
815
  }
816
+ /**
817
+ * Validates the token/cvcSession/billing fields from a parsed request body.
818
+ * Returns the normalized billing or a 400 error descriptor.
819
+ */
820
+ function parseCardSaleBody(body) {
821
+ const { token, cvcSession, billing } = body;
822
+ if (typeof token !== 'string' || !token) {
823
+ return { ok: false, error: 'token is required' };
824
+ }
825
+ if (typeof cvcSession !== 'string' || !cvcSession) {
826
+ return { ok: false, error: 'cvcSession is required' };
827
+ }
828
+ if (!billing || typeof billing.firstName !== 'string' || typeof billing.lastName !== 'string') {
829
+ return { ok: false, error: 'billing with firstName and lastName is required' };
830
+ }
831
+ const billingValidation = validateBilling(billing);
832
+ if (!billingValidation.valid) {
833
+ return { ok: false, error: `Invalid billing details: ${billingValidation.errors.join('; ')}` };
834
+ }
835
+ return { ok: true, token, cvcSession, billing: billingValidation.normalized };
836
+ }
837
+ /**
838
+ * Runs the card sale after body validation: resolves amount + currency,
839
+ * calls ozura.cardSale(), and returns a typed outcome. Both handler and
840
+ * middleware factories delegate to this shared implementation.
841
+ */
842
+ async function executeCardSale(ozura, options, token, cvcSession, billing, rawBody, clientIpAddress) {
843
+ let amount;
844
+ try {
845
+ amount = await options.getAmount(rawBody);
846
+ }
847
+ catch (err) {
848
+ return { ok: false, error: err instanceof Error ? err.message : 'Failed to resolve amount', status: 500 };
849
+ }
850
+ if (typeof amount !== 'string' || !amount.trim()) {
851
+ return { ok: false, error: 'getAmount must return a non-empty decimal string', status: 500 };
852
+ }
853
+ if (!/^\d+(\.\d{1,2})?$/.test(amount.trim())) {
854
+ return {
855
+ ok: false,
856
+ error: `getAmount returned an invalid amount: "${amount}". Expected a positive decimal string, e.g. "49.00".`,
857
+ status: 500,
858
+ };
859
+ }
860
+ amount = amount.trim();
861
+ let currency = 'USD';
862
+ if (options.getCurrency) {
863
+ try {
864
+ currency = await options.getCurrency(rawBody);
865
+ }
866
+ catch (err) {
867
+ return { ok: false, error: err instanceof Error ? err.message : 'Failed to resolve currency', status: 500 };
868
+ }
869
+ }
870
+ try {
871
+ const result = await ozura.cardSale({ token, cvcSession, amount, currency, billing, clientIpAddress });
872
+ return {
873
+ ok: true,
874
+ data: {
875
+ transactionId: result.transactionId,
876
+ amount: result.amount,
877
+ cardLastFour: result.cardLastFour,
878
+ cardBrand: result.cardBrand,
879
+ },
880
+ };
881
+ }
882
+ catch (err) {
883
+ if (err instanceof OzuraError) {
884
+ if (err.statusCode === 429) {
885
+ return { ok: false, error: normalizeCardSaleError(err.message), status: 429, retryAfter: err.retryAfter };
886
+ }
887
+ const status = err.statusCode >= 400 && err.statusCode < 600 ? err.statusCode : 502;
888
+ return { ok: false, error: normalizeCardSaleError(err.message), status };
889
+ }
890
+ return { ok: false, error: 'Payment failed', status: 500 };
891
+ }
892
+ }
893
+ // ─── Public factories ─────────────────────────────────────────────────────────
894
+ /**
895
+ * Creates a ready-to-use Fetch API route handler for charging a tokenized card.
896
+ *
897
+ * Drop-in for Next.js App Router, Cloudflare Workers, Vercel Edge, and any
898
+ * runtime built on the standard Web API `Request` / `Response`.
899
+ *
900
+ * The handler reads `{ token, cvcSession, billing }` from the JSON request body,
901
+ * resolves the amount via `options.getAmount()`, calls `ozura.cardSale()`, and
902
+ * returns `{ transactionId, amount, cardLastFour, cardBrand }` on success.
903
+ * On error it returns `{ error }` with a normalized, user-facing message and
904
+ * an appropriate HTTP status.
905
+ *
906
+ * `clientIpAddress` is extracted automatically from the request headers.
907
+ *
908
+ * @example
909
+ * // app/api/charge/route.ts (Next.js App Router)
910
+ * import { Ozura, createCardSaleHandler } from '@ozura/elements/server';
911
+ *
912
+ * const ozura = new Ozura({ merchantId: '...', apiKey: '...', vaultKey: '...' });
913
+ *
914
+ * export const POST = createCardSaleHandler(ozura, {
915
+ * getAmount: async (body) => {
916
+ * const order = await db.orders.findById(body.orderId as string);
917
+ * return order.total;
918
+ * },
919
+ * });
920
+ */
921
+ function createCardSaleHandler(ozura, options) {
922
+ return async (req) => {
923
+ var _a;
924
+ if (req.method !== 'POST') {
925
+ return Response.json({ error: 'Method Not Allowed' }, { status: 405 });
926
+ }
927
+ const contentType = (_a = req.headers.get('content-type')) !== null && _a !== void 0 ? _a : '';
928
+ if (!contentType.includes('application/json')) {
929
+ return Response.json({ error: 'Content-Type must be application/json' }, { status: 415 });
930
+ }
931
+ let body;
932
+ try {
933
+ body = await req.json();
934
+ }
935
+ catch (_b) {
936
+ return Response.json({ error: 'Invalid JSON body' }, { status: 400 });
937
+ }
938
+ const parsed = parseCardSaleBody(body);
939
+ if (!parsed.ok) {
940
+ return Response.json({ error: parsed.error }, { status: 400 });
941
+ }
942
+ const outcome = await executeCardSale(ozura, options, parsed.token, parsed.cvcSession, parsed.billing, body, getClientIp(req));
943
+ if (!outcome.ok) {
944
+ const headers = outcome.retryAfter
945
+ ? { 'Retry-After': String(outcome.retryAfter) }
946
+ : {};
947
+ return Response.json({ error: outcome.error }, Object.assign({ status: outcome.status }, (Object.keys(headers).length > 0 ? { headers } : {})));
948
+ }
949
+ return Response.json(outcome.data);
950
+ };
951
+ }
952
+ /**
953
+ * Creates a ready-to-use Express / Connect middleware for charging a tokenized card.
954
+ *
955
+ * Requires `express.json()` (or equivalent body-parser) to be registered before
956
+ * this middleware so `req.body` is available.
957
+ *
958
+ * The middleware reads `{ token, cvcSession, billing }` from `req.body`, resolves
959
+ * the amount via `options.getAmount()`, calls `ozura.cardSale()`, and sends
960
+ * `{ transactionId, amount, cardLastFour, cardBrand }` on success.
961
+ * On error it sends `{ error }` with a normalized, user-facing message and an
962
+ * appropriate HTTP status.
963
+ *
964
+ * `clientIpAddress` is extracted automatically from the request object.
965
+ *
966
+ * @example
967
+ * // Express
968
+ * import express from 'express';
969
+ * import { Ozura, createCardSaleMiddleware } from '@ozura/elements/server';
970
+ *
971
+ * const app = express();
972
+ * const ozura = new Ozura({ merchantId: '...', apiKey: '...', vaultKey: '...' });
973
+ *
974
+ * app.use(express.json());
975
+ * app.post('/api/charge', createCardSaleMiddleware(ozura, {
976
+ * getAmount: async (body) => {
977
+ * const order = await db.orders.findById(body.orderId as string);
978
+ * return order.total;
979
+ * },
980
+ * }));
981
+ */
982
+ function createCardSaleMiddleware(ozura, options) {
983
+ return async (req, res) => {
984
+ var _a, _b, _c;
985
+ const method = req.method;
986
+ if (method && method !== 'POST') {
987
+ res.status(405).json({ error: 'Method Not Allowed' });
988
+ return;
989
+ }
990
+ const headers = req.headers;
991
+ const ct = (_a = (typeof (headers === null || headers === void 0 ? void 0 : headers['content-type']) === 'string' ? headers['content-type'] : '')) !== null && _a !== void 0 ? _a : '';
992
+ if (!ct.includes('application/json')) {
993
+ res.status(415).json({ error: 'Content-Type must be application/json' });
994
+ return;
995
+ }
996
+ const body = ((_b = req.body) !== null && _b !== void 0 ? _b : {});
997
+ const parsed = parseCardSaleBody(body);
998
+ if (!parsed.ok) {
999
+ res.status(400).json({ error: parsed.error });
1000
+ return;
1001
+ }
1002
+ const outcome = await executeCardSale(ozura, options, parsed.token, parsed.cvcSession, parsed.billing, body, getClientIp(req));
1003
+ if (!outcome.ok) {
1004
+ if (outcome.retryAfter)
1005
+ (_c = res.setHeader) === null || _c === void 0 ? void 0 : _c.call(res, 'Retry-After', String(outcome.retryAfter));
1006
+ res.status(outcome.status).json({ error: outcome.error });
1007
+ return;
1008
+ }
1009
+ res.json(outcome.data);
1010
+ };
1011
+ }
478
1012
 
479
- export { Ozura, OzuraError, createMintWaxHandler, createMintWaxMiddleware, getClientIp };
1013
+ export { Ozura, OzuraError, createCardSaleHandler, createCardSaleMiddleware, createMintWaxHandler, createMintWaxMiddleware, getClientIp, normalizeCardSaleError };
480
1014
  //# sourceMappingURL=index.esm.js.map