@nokinc-flur/sdk 0.1.6 → 1.0.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/dist/index.cjs ADDED
@@ -0,0 +1,2294 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ADDITIONAL_DATA_SUBFIELD: () => ADDITIONAL_DATA_SUBFIELD,
24
+ FIELD: () => FIELD,
25
+ FlurApiError: () => FlurApiError,
26
+ FlurCapExceededError: () => FlurCapExceededError,
27
+ FlurClient: () => FlurClient,
28
+ FlurError: () => FlurError,
29
+ FlurExpiredError: () => FlurExpiredError,
30
+ FlurReplayError: () => FlurReplayError,
31
+ NGN_CURRENCY_CODE: () => NGN_CURRENCY_CODE,
32
+ NG_COUNTRY_CODE: () => NG_COUNTRY_CODE,
33
+ NQRParseError: () => NQRParseError,
34
+ OACSchema: () => OACSchema,
35
+ OAC_DEFAULT_CUMULATIVE_KOBO: () => OAC_DEFAULT_CUMULATIVE_KOBO,
36
+ OAC_DEFAULT_PER_TX_KOBO: () => OAC_DEFAULT_PER_TX_KOBO,
37
+ OAC_DEFAULT_VALIDITY_MS: () => OAC_DEFAULT_VALIDITY_MS,
38
+ OfflinePaymentAuthorizationSchema: () => OfflinePaymentAuthorizationSchema,
39
+ OfflinePaymentRequestSchema: () => OfflinePaymentRequestSchema,
40
+ PASS_KINDS: () => PASS_KINDS,
41
+ PASS_STATES: () => PASS_STATES,
42
+ PAYLOAD_FORMAT_INDICATOR_VALUE: () => PAYLOAD_FORMAT_INDICATOR_VALUE,
43
+ POINT_OF_INITIATION: () => POINT_OF_INITIATION,
44
+ PassMetadataSchema: () => PassMetadataSchema,
45
+ PassSchema: () => PassSchema,
46
+ RECEIPT_CHANNELS: () => RECEIPT_CHANNELS,
47
+ RECEIPT_KINDS: () => RECEIPT_KINDS,
48
+ REPLAY_WINDOW_MS: () => REPLAY_WINDOW_MS,
49
+ ReceiptPayloadSchema: () => ReceiptPayloadSchema,
50
+ ReceiptSchema: () => ReceiptSchema,
51
+ RedemptionSchema: () => RedemptionSchema,
52
+ bodySha256Hex: () => bodySha256Hex,
53
+ buildAuthorization: () => buildAuthorization,
54
+ buildOAC: () => buildOAC,
55
+ buildPass: () => buildPass,
56
+ buildPaymentRequest: () => buildPaymentRequest,
57
+ buildReceipt: () => buildReceipt,
58
+ buildRedemption: () => buildRedemption,
59
+ canonicalJSONBytes: () => canonicalJSONBytes,
60
+ canonicalJSONStringify: () => canonicalJSONStringify,
61
+ canonicalRequestString: () => canonicalRequestString,
62
+ constantTimeEqual: () => constantTimeEqual,
63
+ crc16ccitt: () => crc16ccitt,
64
+ crc16ccittHex: () => crc16ccittHex,
65
+ createHmacFetch: () => createHmacFetch,
66
+ createPassesClient: () => createPassesClient,
67
+ createReceiptsClient: () => createReceiptsClient,
68
+ decodeAuthorizationQR: () => decodeAuthorizationQR,
69
+ decodeBase45: () => decodeBase45,
70
+ decodePaymentRequestQR: () => decodePaymentRequestQR,
71
+ encodeAuthorizationQR: () => encodeAuthorizationQR,
72
+ encodeBase45: () => encodeBase45,
73
+ encodeNQR: () => encodeNQR,
74
+ encodePaymentRequestQR: () => encodePaymentRequestQR,
75
+ formatAmount: () => formatAmount,
76
+ generateDynamicQR: () => generateDynamicQR,
77
+ generateKeyPair: () => generateKeyPair,
78
+ generateStaticQR: () => generateStaticQR,
79
+ init: () => init,
80
+ isPassWithinValidity: () => isPassWithinValidity,
81
+ moneyMinorToNumber: () => moneyMinorToNumber,
82
+ normalizeE164: () => normalizeE164,
83
+ parseAmountInput: () => parseAmountInput,
84
+ parseNQR: () => parseNQR,
85
+ parseQR: () => parseQR,
86
+ publicKeyFromPrivate: () => publicKeyFromPrivate,
87
+ readTLV: () => readTLV,
88
+ routingHint: () => routingHint,
89
+ sign: () => sign,
90
+ signAuthorization: () => signAuthorization,
91
+ signCanonical: () => signCanonical,
92
+ signOAC: () => signOAC,
93
+ signPass: () => signPass,
94
+ signPaymentRequest: () => signPaymentRequest,
95
+ signReceipt: () => signReceipt,
96
+ signRedemption: () => signRedemption,
97
+ signRequestHMAC: () => signRequestHMAC,
98
+ verify: () => verify,
99
+ verifyAuthorization: () => verifyAuthorization,
100
+ verifyCanonical: () => verifyCanonical,
101
+ verifyOAC: () => verifyOAC,
102
+ verifyPass: () => verifyPass,
103
+ verifyPaymentRequest: () => verifyPaymentRequest,
104
+ verifyReceipt: () => verifyReceipt,
105
+ verifyRedemption: () => verifyRedemption,
106
+ verifyRequestHMAC: () => verifyRequestHMAC,
107
+ writeTLV: () => writeTLV
108
+ });
109
+ module.exports = __toCommonJS(index_exports);
110
+
111
+ // src/client.ts
112
+ var import_zod3 = require("zod");
113
+
114
+ // src/contracts.ts
115
+ var import_zod = require("zod");
116
+ var E164Regex = /^\+[1-9]\d{7,14}$/;
117
+ var UuidSchema = import_zod.z.string().uuid();
118
+ var IsoDateSchema = import_zod.z.string().datetime({ offset: true });
119
+ var CurrencySchema = import_zod.z.string().trim().length(3).transform((value) => value.toUpperCase());
120
+ var HealthResponseSchema = import_zod.z.object({
121
+ ok: import_zod.z.boolean()
122
+ });
123
+ var WelcomeResponseSchema = import_zod.z.object({
124
+ message: import_zod.z.string()
125
+ });
126
+ var OnboardingStartRequestSchema = import_zod.z.object({
127
+ phoneE164: import_zod.z.string().regex(E164Regex),
128
+ appInstanceId: import_zod.z.string().min(3),
129
+ platform: import_zod.z.enum(["android", "ios", "web"]),
130
+ turnstileToken: import_zod.z.string().min(20).optional(),
131
+ appAttestation: import_zod.z.object({
132
+ provider: import_zod.z.enum(["android", "ios", "web"]),
133
+ token: import_zod.z.string().min(24)
134
+ }).optional(),
135
+ firstName: import_zod.z.string().trim().min(1).max(80).optional(),
136
+ lastName: import_zod.z.string().trim().min(1).max(80).optional()
137
+ });
138
+ var OnboardingStartResponseSchema = import_zod.z.object({
139
+ requestId: import_zod.z.string().min(1),
140
+ checkUrl: import_zod.z.string().url().optional(),
141
+ expiresInSec: import_zod.z.number().int().positive(),
142
+ fallback: import_zod.z.enum(["SILENT_AUTH", "OTP"])
143
+ });
144
+ var OnboardingCompleteRequestSchema = import_zod.z.object({
145
+ requestId: import_zod.z.string().min(1),
146
+ code: import_zod.z.string().min(1).max(32),
147
+ appInstanceId: import_zod.z.string().min(3),
148
+ fingerprintHash: import_zod.z.string().min(3).optional()
149
+ });
150
+ var OnboardingCompleteResponseSchema = import_zod.z.object({
151
+ sessionToken: import_zod.z.string().min(1),
152
+ userId: UuidSchema,
153
+ restricted: import_zod.z.boolean(),
154
+ risk_reasons: import_zod.z.array(import_zod.z.enum(["SIM_SWAP_RECENT", "ROAMING", "CARRIER_CHANGED"])),
155
+ stepUpRequired: import_zod.z.boolean().optional(),
156
+ riskStatus: import_zod.z.enum(["ok", "unavailable"]).optional()
157
+ });
158
+ var RegisterDeviceRequestSchema = import_zod.z.object({
159
+ userId: UuidSchema,
160
+ appInstanceId: import_zod.z.string().min(3),
161
+ platform: import_zod.z.string().min(2),
162
+ model: import_zod.z.string().optional(),
163
+ networkSignals: import_zod.z.object({
164
+ ip: import_zod.z.string().min(3),
165
+ asn: import_zod.z.number().int().optional(),
166
+ country: import_zod.z.string().min(2).optional(),
167
+ carrier: import_zod.z.string().optional()
168
+ })
169
+ });
170
+ var RegisterDeviceResponseSchema = import_zod.z.object({
171
+ deviceId: import_zod.z.string().min(1),
172
+ fingerprintHash: import_zod.z.string().min(1),
173
+ driftScore: import_zod.z.number(),
174
+ trustState: import_zod.z.enum(["TRUSTED_PRIMARY", "TRUSTED_SECONDARY", "UNVERIFIED"]),
175
+ stepUpRequired: import_zod.z.boolean()
176
+ });
177
+ var AuthRefreshRequestSchema = import_zod.z.object({
178
+ userId: UuidSchema,
179
+ refreshToken: import_zod.z.string().min(8),
180
+ appInstanceId: import_zod.z.string().min(3),
181
+ fingerprintHash: import_zod.z.string().min(3)
182
+ });
183
+ var AuthRefreshResponseSchema = import_zod.z.object({
184
+ refreshToken: import_zod.z.string().min(8),
185
+ stepUpRequired: import_zod.z.boolean()
186
+ });
187
+ var AuthLogoutRequestSchema = import_zod.z.object({
188
+ userId: UuidSchema,
189
+ refreshToken: import_zod.z.string().min(8)
190
+ });
191
+ var PinSetRequestSchema = import_zod.z.object({
192
+ userId: UuidSchema,
193
+ pin: import_zod.z.string().regex(/^\d{6}$/)
194
+ });
195
+ var PinVerifyRequestSchema = import_zod.z.object({
196
+ userId: UuidSchema,
197
+ pin: import_zod.z.string().regex(/^\d{6}$/)
198
+ });
199
+ var OkResponseSchema = import_zod.z.object({
200
+ ok: import_zod.z.boolean()
201
+ });
202
+ var RegisterSendDeviceKeyRequestSchema = import_zod.z.object({
203
+ userId: UuidSchema,
204
+ deviceId: import_zod.z.string().min(3),
205
+ publicKey: import_zod.z.string().min(32)
206
+ });
207
+ var SendChallengeRequestSchema = import_zod.z.object({
208
+ userId: UuidSchema,
209
+ deviceId: import_zod.z.string().min(3)
210
+ });
211
+ var SendChallengeResponseSchema = import_zod.z.object({
212
+ challengeId: UuidSchema,
213
+ nonce: import_zod.z.string().min(1),
214
+ expiresAt: IsoDateSchema
215
+ });
216
+ var SendVerifyRequestSchema = import_zod.z.object({
217
+ userId: UuidSchema,
218
+ deviceId: import_zod.z.string().min(3),
219
+ challengeId: UuidSchema,
220
+ signature: import_zod.z.string().min(16)
221
+ });
222
+ var SendVerifyResponseSchema = import_zod.z.object({
223
+ sendAuthToken: import_zod.z.string().min(16)
224
+ });
225
+ var ResolveRecipientRequestSchema = import_zod.z.object({
226
+ identifier: import_zod.z.string().min(3)
227
+ });
228
+ var ResolveRecipientResponseSchema = import_zod.z.object({
229
+ recipientUserId: UuidSchema,
230
+ displayName: import_zod.z.string().min(1),
231
+ normalizedIdentifier: import_zod.z.string().regex(E164Regex),
232
+ isActive: import_zod.z.boolean()
233
+ });
234
+ var CreateTransferRequestSchema = import_zod.z.object({
235
+ recipientIdentifier: import_zod.z.string().min(3),
236
+ amountMinor: import_zod.z.number().int().positive(),
237
+ currency: CurrencySchema,
238
+ sendAuthToken: import_zod.z.string().min(16)
239
+ });
240
+ var TransferStatusSchema = import_zod.z.enum(["SETTLED", "PENDING_REVIEW", "DECLINED"]);
241
+ var TransferResponseSchema = import_zod.z.object({
242
+ transactionId: import_zod.z.string().min(1),
243
+ status: TransferStatusSchema,
244
+ userStatus: TransferStatusSchema,
245
+ recipientName: import_zod.z.string().min(1),
246
+ timestamp: IsoDateSchema
247
+ });
248
+ var DirectionSchema = import_zod.z.enum(["OUTGOING", "INCOMING"]);
249
+ var AccountActivityItemSchema = import_zod.z.object({
250
+ id: import_zod.z.string().min(1),
251
+ type: import_zod.z.string().min(1),
252
+ direction: DirectionSchema,
253
+ name: import_zod.z.string().min(1),
254
+ identifier: import_zod.z.string().min(1),
255
+ amountMinor: import_zod.z.number().int(),
256
+ currency: CurrencySchema,
257
+ status: import_zod.z.string().min(1),
258
+ timestamp: IsoDateSchema
259
+ });
260
+ var AccountSummaryResponseSchema = import_zod.z.object({
261
+ balance: import_zod.z.number().int(),
262
+ currency: CurrencySchema,
263
+ dailySendLimit: import_zod.z.number().int().nonnegative(),
264
+ dailySendRemaining: import_zod.z.number().int().nonnegative(),
265
+ kycTier: import_zod.z.string().min(1),
266
+ kycStatus: import_zod.z.string().min(1),
267
+ recentActivity: import_zod.z.array(AccountActivityItemSchema)
268
+ });
269
+ var TransactionsListResponseSchema = import_zod.z.object({
270
+ items: import_zod.z.array(AccountActivityItemSchema),
271
+ nextCursor: import_zod.z.string().nullable()
272
+ });
273
+ var TransactionDetailResponseSchema = import_zod.z.object({
274
+ transactionId: import_zod.z.string().min(1),
275
+ type: import_zod.z.string().min(1),
276
+ direction: DirectionSchema,
277
+ counterpartyName: import_zod.z.string().min(1),
278
+ counterpartyIdentifier: import_zod.z.string().min(1),
279
+ amountMinor: import_zod.z.number().int(),
280
+ currency: CurrencySchema,
281
+ status: import_zod.z.string().min(1),
282
+ timestamp: IsoDateSchema
283
+ });
284
+ var PushRegisterRequestSchema = import_zod.z.object({
285
+ deviceId: import_zod.z.string().min(3),
286
+ platform: import_zod.z.enum(["ios", "android", "web"]),
287
+ token: import_zod.z.string().min(16)
288
+ });
289
+ var CreatePayLinkResponseSchema = import_zod.z.object({
290
+ token: import_zod.z.string().min(1)
291
+ });
292
+ var ResolvePayLinkResponseSchema = import_zod.z.object({
293
+ recipientUserId: UuidSchema,
294
+ displayName: import_zod.z.string().min(1),
295
+ normalizedIdentifier: import_zod.z.string().regex(E164Regex),
296
+ isActive: import_zod.z.boolean()
297
+ });
298
+
299
+ // src/errors.ts
300
+ var backendErrorCodeSet = /* @__PURE__ */ new Set([
301
+ "UNAUTHORIZED",
302
+ "INVALID_REQUEST",
303
+ "RATE_LIMITED",
304
+ "NOT_FOUND",
305
+ "USER_NOT_FOUND",
306
+ "TOKEN_REPLAYED",
307
+ "SESSION_MISMATCH",
308
+ "PIN_INVALID",
309
+ "PIN_LOCKED",
310
+ "PIN_NOT_SET",
311
+ "STEP_UP_REQUIRED",
312
+ "DEVICE_KEY_NOT_REGISTERED",
313
+ "CHALLENGE_INVALID",
314
+ "CHALLENGE_EXPIRED",
315
+ "DEVICE_KEY_REVOKED",
316
+ "SIGNATURE_INVALID",
317
+ "INVALID_RECIPIENT",
318
+ "SEND_AUTH_INVALID",
319
+ "IDEMPOTENCY_KEY_CONFLICT",
320
+ "IDEMPOTENCY_IN_PROGRESS",
321
+ "CANNOT_SEND_TO_SELF",
322
+ "INSUFFICIENT_FUNDS"
323
+ ]);
324
+ var FlurError = class extends Error {
325
+ code;
326
+ status;
327
+ details;
328
+ reqId;
329
+ constructor(message, code, opts) {
330
+ super(message);
331
+ this.name = "FlurError";
332
+ this.code = code;
333
+ this.status = opts?.status;
334
+ this.details = opts?.details;
335
+ this.reqId = opts?.reqId;
336
+ }
337
+ };
338
+ var FlurApiError = class extends Error {
339
+ constructor(status, code, message, raw) {
340
+ super(message);
341
+ this.status = status;
342
+ this.code = code;
343
+ this.raw = raw;
344
+ this.name = "FlurApiError";
345
+ }
346
+ status;
347
+ code;
348
+ raw;
349
+ };
350
+ var FlurExpiredError = class extends Error {
351
+ code = "PASS_EXPIRED";
352
+ constructor(message = "pass is expired") {
353
+ super(message);
354
+ this.name = "FlurExpiredError";
355
+ }
356
+ };
357
+ var FlurReplayError = class extends Error {
358
+ code = "PASS_REPLAY";
359
+ constructor(message = "redemption counter is not strictly increasing") {
360
+ super(message);
361
+ this.name = "FlurReplayError";
362
+ }
363
+ };
364
+ var FlurCapExceededError = class extends Error {
365
+ code = "PASS_CAP_EXCEEDED";
366
+ constructor(message = "redemption exceeds cumulative cap") {
367
+ super(message);
368
+ this.name = "FlurCapExceededError";
369
+ }
370
+ };
371
+ async function mapToFlurError(res) {
372
+ const reqId = res.headers.get("x-request-id");
373
+ let details = void 0;
374
+ let mappedCode = "HTTP_ERROR";
375
+ let mappedMessage = `HTTP ${res.status}`;
376
+ try {
377
+ const ct = res.headers.get("content-type") ?? "";
378
+ details = ct.includes("application/json") ? await res.json() : await res.text();
379
+ if (details && typeof details === "object") {
380
+ const candidateCode = details.code;
381
+ const candidateMessage = details.message;
382
+ const fallbackMessage = details.error;
383
+ if (typeof candidateCode === "string" && backendErrorCodeSet.has(candidateCode)) {
384
+ mappedCode = candidateCode;
385
+ }
386
+ if (typeof candidateMessage === "string" && candidateMessage.length > 0) {
387
+ mappedMessage = candidateMessage;
388
+ } else if (typeof fallbackMessage === "string" && fallbackMessage.length > 0) {
389
+ mappedMessage = fallbackMessage;
390
+ }
391
+ }
392
+ } catch {
393
+ }
394
+ return new FlurError(mappedMessage, mappedCode, {
395
+ status: res.status,
396
+ details,
397
+ reqId
398
+ });
399
+ }
400
+
401
+ // src/primitives.ts
402
+ var import_zod2 = require("zod");
403
+ var CurrencyCodeSchema = import_zod2.z.string().trim().length(3).transform((value) => value.toUpperCase());
404
+ var currencyFractionDigits = {
405
+ NGN: 2,
406
+ USD: 2,
407
+ EUR: 2,
408
+ GBP: 2,
409
+ CAD: 2,
410
+ JPY: 0
411
+ };
412
+ function getFractionDigits(currency) {
413
+ return currencyFractionDigits[currency] ?? 2;
414
+ }
415
+ function parseAmountInput(value, currency) {
416
+ const normalizedCurrency = CurrencyCodeSchema.parse(currency);
417
+ const raw = value.trim();
418
+ if (!/^\d+(\.\d+)?$/.test(raw)) {
419
+ throw new FlurError("Invalid amount format", "INVALID_REQUEST");
420
+ }
421
+ const [whole, fraction = ""] = raw.split(".");
422
+ const fractionDigits = getFractionDigits(normalizedCurrency);
423
+ if (fraction.length > fractionDigits) {
424
+ throw new FlurError("Too many decimal places for currency", "INVALID_REQUEST");
425
+ }
426
+ const paddedFraction = fraction.padEnd(fractionDigits, "0");
427
+ const minorAsString = `${whole}${paddedFraction}`.replace(/^0+(\d)/, "$1") || "0";
428
+ const amountMinor = BigInt(minorAsString);
429
+ if (amountMinor < 0n) {
430
+ throw new FlurError("Amount cannot be negative", "INVALID_REQUEST");
431
+ }
432
+ return {
433
+ amountMinor,
434
+ currency: normalizedCurrency
435
+ };
436
+ }
437
+ function formatAmount(amountMinor, currency) {
438
+ const normalizedCurrency = CurrencyCodeSchema.parse(currency);
439
+ if (amountMinor < 0n) {
440
+ throw new FlurError("Amount cannot be negative", "INVALID_REQUEST");
441
+ }
442
+ const fractionDigits = getFractionDigits(normalizedCurrency);
443
+ if (fractionDigits === 0) {
444
+ return amountMinor.toString();
445
+ }
446
+ const divisor = 10n ** BigInt(fractionDigits);
447
+ const whole = amountMinor / divisor;
448
+ const fraction = (amountMinor % divisor).toString().padStart(fractionDigits, "0");
449
+ return `${whole.toString()}.${fraction}`;
450
+ }
451
+ var defaultCountryDialingCode = {
452
+ NG: "234",
453
+ US: "1",
454
+ CA: "1",
455
+ GB: "44"
456
+ };
457
+ function normalizeE164(input, defaultCountry) {
458
+ const trimmed = input.trim();
459
+ if (trimmed.startsWith("+")) {
460
+ if (!E164Regex.test(trimmed)) {
461
+ throw new FlurError("Invalid phone number", "INVALID_REQUEST");
462
+ }
463
+ return trimmed;
464
+ }
465
+ const digits = trimmed.replace(/\D/g, "");
466
+ if (digits.length === 0) {
467
+ throw new FlurError("Invalid phone number", "INVALID_REQUEST");
468
+ }
469
+ if (digits.startsWith("00")) {
470
+ const candidate2 = `+${digits.slice(2)}`;
471
+ if (!E164Regex.test(candidate2)) {
472
+ throw new FlurError("Invalid phone number", "INVALID_REQUEST");
473
+ }
474
+ return candidate2;
475
+ }
476
+ const normalizedCountry = defaultCountry?.trim().toUpperCase();
477
+ const countryCode = normalizedCountry ? defaultCountryDialingCode[normalizedCountry] : void 0;
478
+ if (!countryCode) {
479
+ throw new FlurError("Invalid phone number", "INVALID_REQUEST");
480
+ }
481
+ const localDigits = digits.startsWith("0") ? digits.slice(1) : digits;
482
+ const candidate = `+${countryCode}${localDigits}`;
483
+ if (!E164Regex.test(candidate)) {
484
+ throw new FlurError("Invalid phone number", "INVALID_REQUEST");
485
+ }
486
+ return candidate;
487
+ }
488
+ function moneyMinorToNumber(amountMinor) {
489
+ const asNumber = Number(amountMinor);
490
+ if (!Number.isSafeInteger(asNumber)) {
491
+ throw new FlurError("Amount exceeds safe integer range", "INVALID_REQUEST");
492
+ }
493
+ return asNumber;
494
+ }
495
+
496
+ // src/client.ts
497
+ var FlurClient = class {
498
+ baseUrl;
499
+ fetchImpl;
500
+ timeoutMs;
501
+ getExtraHeaders;
502
+ constructor(opts) {
503
+ this.baseUrl = opts.baseUrl.replace(/\/$/, "");
504
+ this.fetchImpl = opts.fetchImpl ?? fetch;
505
+ this.timeoutMs = opts.timeoutMs ?? 1e4;
506
+ this.getExtraHeaders = opts.getExtraHeaders;
507
+ }
508
+ async health() {
509
+ return this.requestJson("/health", { method: "GET" }, void 0, HealthResponseSchema);
510
+ }
511
+ async welcome() {
512
+ return this.requestJson("/welcome", { method: "GET" }, void 0, WelcomeResponseSchema);
513
+ }
514
+ async onboardingStart(input) {
515
+ return this.requestJson(
516
+ "/v1/onboarding/start",
517
+ {
518
+ method: "POST",
519
+ headers: { "content-type": "application/json" }
520
+ },
521
+ OnboardingStartRequestSchema,
522
+ OnboardingStartResponseSchema,
523
+ input
524
+ );
525
+ }
526
+ async onboardingComplete(input) {
527
+ return this.requestJson(
528
+ "/v1/onboarding/complete",
529
+ {
530
+ method: "POST",
531
+ headers: { "content-type": "application/json" }
532
+ },
533
+ OnboardingCompleteRequestSchema,
534
+ OnboardingCompleteResponseSchema,
535
+ input
536
+ );
537
+ }
538
+ async registerDevice(input, options) {
539
+ return this.requestJson(
540
+ "/v1/devices/register",
541
+ {
542
+ method: "POST",
543
+ headers: {
544
+ "content-type": "application/json",
545
+ authorization: `Bearer ${options.accessToken}`
546
+ }
547
+ },
548
+ RegisterDeviceRequestSchema,
549
+ RegisterDeviceResponseSchema,
550
+ input
551
+ );
552
+ }
553
+ async authRefresh(input) {
554
+ return this.requestJson(
555
+ "/v1/auth/refresh",
556
+ {
557
+ method: "POST",
558
+ headers: { "content-type": "application/json" }
559
+ },
560
+ AuthRefreshRequestSchema,
561
+ AuthRefreshResponseSchema,
562
+ input
563
+ );
564
+ }
565
+ async authLogout(input, options) {
566
+ return this.requestJson(
567
+ "/v1/auth/logout",
568
+ {
569
+ method: "POST",
570
+ headers: {
571
+ "content-type": "application/json",
572
+ authorization: `Bearer ${options.accessToken}`
573
+ }
574
+ },
575
+ AuthLogoutRequestSchema,
576
+ OkResponseSchema,
577
+ input
578
+ );
579
+ }
580
+ async pinSet(input, options) {
581
+ return this.requestJson(
582
+ "/v1/auth/pin/set",
583
+ {
584
+ method: "POST",
585
+ headers: {
586
+ "content-type": "application/json",
587
+ authorization: `Bearer ${options.accessToken}`
588
+ }
589
+ },
590
+ PinSetRequestSchema,
591
+ OkResponseSchema,
592
+ input
593
+ );
594
+ }
595
+ async pinVerify(input, options) {
596
+ return this.requestJson(
597
+ "/v1/auth/pin/verify",
598
+ {
599
+ method: "POST",
600
+ headers: {
601
+ "content-type": "application/json",
602
+ authorization: `Bearer ${options.accessToken}`
603
+ }
604
+ },
605
+ PinVerifyRequestSchema,
606
+ OkResponseSchema,
607
+ input
608
+ );
609
+ }
610
+ async registerSendDeviceKey(input, options) {
611
+ return this.requestJson(
612
+ "/api/v1/auth/send/device-key",
613
+ {
614
+ method: "POST",
615
+ headers: {
616
+ "content-type": "application/json",
617
+ authorization: `Bearer ${options.accessToken}`
618
+ }
619
+ },
620
+ RegisterSendDeviceKeyRequestSchema,
621
+ OkResponseSchema,
622
+ input
623
+ );
624
+ }
625
+ async createSendChallenge(input, options) {
626
+ return this.requestJson(
627
+ "/api/v1/auth/send/challenge",
628
+ {
629
+ method: "POST",
630
+ headers: {
631
+ "content-type": "application/json",
632
+ authorization: `Bearer ${options.accessToken}`
633
+ }
634
+ },
635
+ SendChallengeRequestSchema,
636
+ SendChallengeResponseSchema,
637
+ input
638
+ );
639
+ }
640
+ async verifySendChallenge(input, options) {
641
+ return this.requestJson(
642
+ "/api/v1/auth/send/verify",
643
+ {
644
+ method: "POST",
645
+ headers: {
646
+ "content-type": "application/json",
647
+ authorization: `Bearer ${options.accessToken}`
648
+ }
649
+ },
650
+ SendVerifyRequestSchema,
651
+ SendVerifyResponseSchema,
652
+ input
653
+ );
654
+ }
655
+ async registerBiometricDeviceKey(input) {
656
+ const publicKey = await input.signer.getOrCreateKeyPair(input.deviceId);
657
+ return this.registerSendDeviceKey({
658
+ userId: input.userId,
659
+ deviceId: input.deviceId,
660
+ publicKey
661
+ }, { accessToken: input.accessToken });
662
+ }
663
+ async authorizeSendWithBiometric(input) {
664
+ const challenge = await this.createSendChallenge({
665
+ userId: input.userId,
666
+ deviceId: input.deviceId
667
+ }, { accessToken: input.accessToken });
668
+ const signature = await input.signer.sign(challenge.nonce);
669
+ return this.verifySendChallenge({
670
+ userId: input.userId,
671
+ deviceId: input.deviceId,
672
+ challengeId: challenge.challengeId,
673
+ signature
674
+ }, { accessToken: input.accessToken });
675
+ }
676
+ async resolveRecipient(input, options) {
677
+ return this.requestJson(
678
+ "/api/v1/recipients/resolve",
679
+ {
680
+ method: "POST",
681
+ headers: {
682
+ "content-type": "application/json",
683
+ authorization: `Bearer ${options.accessToken}`
684
+ }
685
+ },
686
+ ResolveRecipientRequestSchema,
687
+ ResolveRecipientResponseSchema,
688
+ input
689
+ );
690
+ }
691
+ async createTransfer(input, options) {
692
+ return this.requestJson(
693
+ "/api/v1/transfers",
694
+ {
695
+ method: "POST",
696
+ headers: {
697
+ "content-type": "application/json",
698
+ authorization: `Bearer ${options.accessToken}`,
699
+ "x-device-id": options.deviceId,
700
+ "x-idempotency-key": options.idempotencyKey
701
+ }
702
+ },
703
+ CreateTransferRequestSchema,
704
+ TransferResponseSchema,
705
+ input
706
+ );
707
+ }
708
+ async sendMoney(input, options) {
709
+ const normalizedRecipient = E164Regex.test(input.recipientIdentifier) ? input.recipientIdentifier : normalizeE164(input.recipientIdentifier, input.defaultCountry);
710
+ const idempotencyKey = input.idempotencyKey ?? getSecureRandomUuid();
711
+ return this.createTransfer(
712
+ {
713
+ recipientIdentifier: normalizedRecipient,
714
+ amountMinor: moneyMinorToNumber(input.money.amountMinor),
715
+ currency: input.money.currency,
716
+ sendAuthToken: input.sendAuthToken
717
+ },
718
+ {
719
+ accessToken: options.accessToken,
720
+ deviceId: options.deviceId,
721
+ idempotencyKey
722
+ }
723
+ );
724
+ }
725
+ async accountSummary(options) {
726
+ return this.requestJson(
727
+ "/api/v1/account/summary",
728
+ {
729
+ method: "GET",
730
+ headers: {
731
+ authorization: `Bearer ${options.accessToken}`
732
+ }
733
+ },
734
+ void 0,
735
+ AccountSummaryResponseSchema
736
+ );
737
+ }
738
+ async listTransactions(options) {
739
+ const query = new URLSearchParams();
740
+ if (typeof options.cursor === "string" && options.cursor.trim().length > 0) {
741
+ query.set("cursor", options.cursor);
742
+ }
743
+ if (typeof options.limit === "number") {
744
+ query.set("limit", String(options.limit));
745
+ }
746
+ const path = query.size > 0 ? `/api/v1/transactions?${query.toString()}` : "/api/v1/transactions";
747
+ return this.requestJson(
748
+ path,
749
+ {
750
+ method: "GET",
751
+ headers: {
752
+ authorization: `Bearer ${options.accessToken}`
753
+ }
754
+ },
755
+ void 0,
756
+ TransactionsListResponseSchema
757
+ );
758
+ }
759
+ async transactionDetail(transactionId, options) {
760
+ const encodedId = encodeURIComponent(transactionId);
761
+ return this.requestJson(
762
+ `/api/v1/transactions/${encodedId}`,
763
+ {
764
+ method: "GET",
765
+ headers: {
766
+ authorization: `Bearer ${options.accessToken}`
767
+ }
768
+ },
769
+ void 0,
770
+ TransactionDetailResponseSchema
771
+ );
772
+ }
773
+ async registerPushToken(input, options) {
774
+ return this.requestJson(
775
+ "/api/v1/push/register",
776
+ {
777
+ method: "POST",
778
+ headers: {
779
+ "content-type": "application/json",
780
+ authorization: `Bearer ${options.accessToken}`
781
+ }
782
+ },
783
+ PushRegisterRequestSchema,
784
+ OkResponseSchema,
785
+ input
786
+ );
787
+ }
788
+ async createPayLink(options) {
789
+ return this.requestJson(
790
+ "/api/v1/pay-links",
791
+ {
792
+ method: "POST",
793
+ headers: {
794
+ authorization: `Bearer ${options.accessToken}`
795
+ }
796
+ },
797
+ void 0,
798
+ CreatePayLinkResponseSchema
799
+ );
800
+ }
801
+ async resolvePayLink(token, options) {
802
+ const encodedToken = encodeURIComponent(token);
803
+ return this.requestJson(
804
+ `/api/v1/pay-links/${encodedToken}`,
805
+ {
806
+ method: "GET",
807
+ headers: {
808
+ authorization: `Bearer ${options.accessToken}`
809
+ }
810
+ },
811
+ void 0,
812
+ ResolvePayLinkResponseSchema
813
+ );
814
+ }
815
+ async requestJson(path, init2, requestSchema, responseSchema, input) {
816
+ const url = `${this.baseUrl}${path}`;
817
+ const controller = new AbortController();
818
+ const t = setTimeout(() => controller.abort(), this.timeoutMs);
819
+ let body = init2.body;
820
+ try {
821
+ body = requestSchema ? JSON.stringify(requestSchema.parse(input)) : init2.body;
822
+ } catch (err) {
823
+ if (err instanceof import_zod3.z.ZodError) {
824
+ throw new FlurError("Invalid request payload", "INVALID_REQUEST", {
825
+ details: err.flatten()
826
+ });
827
+ }
828
+ throw err;
829
+ }
830
+ try {
831
+ let extraHeaders = {};
832
+ if (this.getExtraHeaders) {
833
+ extraHeaders = await this.getExtraHeaders();
834
+ }
835
+ const finalHeaders = {
836
+ ...init2.headers,
837
+ ...extraHeaders
838
+ };
839
+ const res = await this.fetchImpl(url, { ...init2, headers: finalHeaders, body, signal: controller.signal });
840
+ if (!res.ok) throw await mapToFlurError(res);
841
+ const payload = await res.json();
842
+ if (!responseSchema) return payload;
843
+ try {
844
+ return responseSchema.parse(payload);
845
+ } catch (err) {
846
+ if (err instanceof import_zod3.z.ZodError) {
847
+ throw new FlurError("SDK contract validation failed", "INVALID_REQUEST", {
848
+ details: err.flatten()
849
+ });
850
+ }
851
+ throw err;
852
+ }
853
+ } catch (err) {
854
+ if (err instanceof Error && err.name === "AbortError") {
855
+ throw new FlurError("Request timed out", "TIMEOUT");
856
+ }
857
+ if (err instanceof FlurError) throw err;
858
+ throw new FlurError("Network error", "NETWORK_ERROR", { details: String(err) });
859
+ } finally {
860
+ clearTimeout(t);
861
+ }
862
+ }
863
+ };
864
+ function getSecureRandomUuid() {
865
+ if (typeof globalThis.crypto?.randomUUID === "function") {
866
+ return globalThis.crypto.randomUUID();
867
+ }
868
+ throw new FlurError("Secure UUID generator unavailable; provide idempotencyKey", "INVALID_REQUEST");
869
+ }
870
+
871
+ // src/nqr/fields.ts
872
+ var FIELD = {
873
+ PAYLOAD_FORMAT_INDICATOR: "00",
874
+ POINT_OF_INITIATION: "01",
875
+ // 02..51 — Merchant Account Information templates (issuer-specific)
876
+ MERCHANT_CATEGORY_CODE: "52",
877
+ TRANSACTION_CURRENCY: "53",
878
+ TRANSACTION_AMOUNT: "54",
879
+ TIP_OR_CONVENIENCE_INDICATOR: "55",
880
+ VALUE_CONVENIENCE_FEE_FIXED: "56",
881
+ VALUE_CONVENIENCE_FEE_PERCENT: "57",
882
+ COUNTRY_CODE: "58",
883
+ MERCHANT_NAME: "59",
884
+ MERCHANT_CITY: "60",
885
+ POSTAL_CODE: "61",
886
+ ADDITIONAL_DATA_FIELD: "62",
887
+ CRC: "63",
888
+ MERCHANT_INFO_LANGUAGE: "64"
889
+ // 80..99 — Unreserved Templates
890
+ };
891
+ var ADDITIONAL_DATA_SUBFIELD = {
892
+ BILL_NUMBER: "01",
893
+ MOBILE_NUMBER: "02",
894
+ STORE_LABEL: "03",
895
+ LOYALTY_NUMBER: "04",
896
+ REFERENCE_LABEL: "05",
897
+ CUSTOMER_LABEL: "06",
898
+ TERMINAL_LABEL: "07",
899
+ PURPOSE_OF_TRANSACTION: "08"
900
+ };
901
+ var POINT_OF_INITIATION = {
902
+ STATIC: "11",
903
+ DYNAMIC: "12"
904
+ };
905
+ var PAYLOAD_FORMAT_INDICATOR_VALUE = "01";
906
+ var NGN_CURRENCY_CODE = "566";
907
+ var NG_COUNTRY_CODE = "NG";
908
+ var CRC_TAG_PREFIX = "6304";
909
+
910
+ // src/nqr/crc16.ts
911
+ function crc16ccitt(bytes) {
912
+ let crc = 65535;
913
+ for (let i = 0; i < bytes.length; i++) {
914
+ crc ^= bytes[i] << 8;
915
+ for (let j = 0; j < 8; j++) {
916
+ if (crc & 32768) {
917
+ crc = crc << 1 ^ 4129;
918
+ } else {
919
+ crc <<= 1;
920
+ }
921
+ crc &= 65535;
922
+ }
923
+ }
924
+ return crc;
925
+ }
926
+ function crc16ccittHex(bytes) {
927
+ return crc16ccitt(bytes).toString(16).toUpperCase().padStart(4, "0");
928
+ }
929
+
930
+ // src/nqr/tlv.ts
931
+ function writeTLV(tag, value) {
932
+ if (!/^\d{2}$/.test(tag)) {
933
+ throw new Error(`TLV: tag must be 2 digits, got "${tag}"`);
934
+ }
935
+ if (value.length > 99) {
936
+ throw new Error(
937
+ `TLV: value for tag ${tag} exceeds 99 chars (${value.length})`
938
+ );
939
+ }
940
+ const len = value.length.toString().padStart(2, "0");
941
+ return `${tag}${len}${value}`;
942
+ }
943
+ function readTLV(buf) {
944
+ const out = [];
945
+ let i = 0;
946
+ while (i < buf.length) {
947
+ if (i + 4 > buf.length) {
948
+ throw new Error(`TLV: truncated header at offset ${i}`);
949
+ }
950
+ const tag = buf.slice(i, i + 2);
951
+ const lenStr = buf.slice(i + 2, i + 4);
952
+ if (!/^\d{2}$/.test(tag)) {
953
+ throw new Error(`TLV: bad tag "${tag}" at offset ${i}`);
954
+ }
955
+ if (!/^\d{2}$/.test(lenStr)) {
956
+ throw new Error(`TLV: bad length "${lenStr}" at offset ${i + 2}`);
957
+ }
958
+ const len = parseInt(lenStr, 10);
959
+ const valStart = i + 4;
960
+ const valEnd = valStart + len;
961
+ if (valEnd > buf.length) {
962
+ throw new Error(
963
+ `TLV: truncated value for tag ${tag} at offset ${valStart}`
964
+ );
965
+ }
966
+ out.push({ tag, value: buf.slice(valStart, valEnd) });
967
+ i = valEnd;
968
+ }
969
+ return out;
970
+ }
971
+
972
+ // src/nqr/encoder.ts
973
+ var ASCII_PRINTABLE = /^[\x20-\x7E]*$/;
974
+ function buildMAIValue(mai) {
975
+ if (!/^(0[2-9]|[1-4]\d|5[0-1])$/.test(mai.tag)) {
976
+ throw new Error(
977
+ `encodeNQR: merchantAccountInfo.tag must be in 02..51, got "${mai.tag}"`
978
+ );
979
+ }
980
+ let out = "";
981
+ for (const c of mai.children) {
982
+ out += writeTLV(c.tag, c.value);
983
+ }
984
+ return out;
985
+ }
986
+ function buildAdditionalDataValue(ad) {
987
+ let out = "";
988
+ const map = [
989
+ ["billNumber", ADDITIONAL_DATA_SUBFIELD.BILL_NUMBER],
990
+ ["mobileNumber", ADDITIONAL_DATA_SUBFIELD.MOBILE_NUMBER],
991
+ ["storeLabel", ADDITIONAL_DATA_SUBFIELD.STORE_LABEL],
992
+ ["loyaltyNumber", ADDITIONAL_DATA_SUBFIELD.LOYALTY_NUMBER],
993
+ ["referenceLabel", ADDITIONAL_DATA_SUBFIELD.REFERENCE_LABEL],
994
+ ["customerLabel", ADDITIONAL_DATA_SUBFIELD.CUSTOMER_LABEL],
995
+ ["terminalLabel", ADDITIONAL_DATA_SUBFIELD.TERMINAL_LABEL],
996
+ ["purposeOfTransaction", ADDITIONAL_DATA_SUBFIELD.PURPOSE_OF_TRANSACTION]
997
+ ];
998
+ for (const [k, t] of map) {
999
+ const v = ad[k];
1000
+ if (v !== void 0) {
1001
+ out += writeTLV(t, v);
1002
+ }
1003
+ }
1004
+ return out;
1005
+ }
1006
+ function assertAscii(name, v) {
1007
+ if (!ASCII_PRINTABLE.test(v)) {
1008
+ throw new Error(
1009
+ `encodeNQR: ${name} contains non-printable-ASCII characters`
1010
+ );
1011
+ }
1012
+ }
1013
+ function assertMaxLen(name, v, max) {
1014
+ if (v.length > max) {
1015
+ throw new Error(`encodeNQR: ${name} length ${v.length} exceeds max ${max}`);
1016
+ }
1017
+ }
1018
+ function assertMCC(mcc) {
1019
+ if (!/^\d{4}$/.test(mcc)) {
1020
+ throw new Error(
1021
+ `encodeNQR: merchantCategoryCode must be 4 digits, got "${mcc}"`
1022
+ );
1023
+ }
1024
+ }
1025
+ function assertAmount(amt) {
1026
+ if (!/^\d{1,10}(\.\d{1,2})?$/.test(amt)) {
1027
+ throw new Error(
1028
+ `encodeNQR: transactionAmount must match /^\\d{1,10}(\\.\\d{1,2})?$/, got "${amt}"`
1029
+ );
1030
+ }
1031
+ if (amt.length > 13) {
1032
+ throw new Error(
1033
+ `encodeNQR: transactionAmount length ${amt.length} exceeds 13`
1034
+ );
1035
+ }
1036
+ }
1037
+ function encodeNQR(input) {
1038
+ assertMCC(input.merchantCategoryCode);
1039
+ assertAscii("merchantName", input.merchantName);
1040
+ assertMaxLen("merchantName", input.merchantName, 25);
1041
+ assertAscii("merchantCity", input.merchantCity);
1042
+ assertMaxLen("merchantCity", input.merchantCity, 15);
1043
+ if (input.postalCode !== void 0) {
1044
+ assertAscii("postalCode", input.postalCode);
1045
+ assertMaxLen("postalCode", input.postalCode, 10);
1046
+ }
1047
+ if (input.transactionAmount !== void 0) {
1048
+ assertAmount(input.transactionAmount);
1049
+ }
1050
+ if (input.pointOfInitiation === "dynamic" && input.transactionAmount === void 0) {
1051
+ }
1052
+ let out = "";
1053
+ out += writeTLV(
1054
+ FIELD.PAYLOAD_FORMAT_INDICATOR,
1055
+ PAYLOAD_FORMAT_INDICATOR_VALUE
1056
+ );
1057
+ out += writeTLV(
1058
+ FIELD.POINT_OF_INITIATION,
1059
+ input.pointOfInitiation === "dynamic" ? POINT_OF_INITIATION.DYNAMIC : POINT_OF_INITIATION.STATIC
1060
+ );
1061
+ out += writeTLV(
1062
+ input.merchantAccountInfo.tag,
1063
+ buildMAIValue(input.merchantAccountInfo)
1064
+ );
1065
+ out += writeTLV(FIELD.MERCHANT_CATEGORY_CODE, input.merchantCategoryCode);
1066
+ out += writeTLV(FIELD.TRANSACTION_CURRENCY, NGN_CURRENCY_CODE);
1067
+ if (input.transactionAmount !== void 0) {
1068
+ out += writeTLV(FIELD.TRANSACTION_AMOUNT, input.transactionAmount);
1069
+ }
1070
+ out += writeTLV(FIELD.COUNTRY_CODE, NG_COUNTRY_CODE);
1071
+ out += writeTLV(FIELD.MERCHANT_NAME, input.merchantName);
1072
+ out += writeTLV(FIELD.MERCHANT_CITY, input.merchantCity);
1073
+ if (input.postalCode !== void 0) {
1074
+ out += writeTLV(FIELD.POSTAL_CODE, input.postalCode);
1075
+ }
1076
+ if (input.additionalData) {
1077
+ const adfValue = buildAdditionalDataValue(input.additionalData);
1078
+ if (adfValue.length > 0) {
1079
+ out += writeTLV(FIELD.ADDITIONAL_DATA_FIELD, adfValue);
1080
+ }
1081
+ }
1082
+ out += CRC_TAG_PREFIX;
1083
+ const crc = crc16ccittHex(new TextEncoder().encode(out));
1084
+ out += crc;
1085
+ return out;
1086
+ }
1087
+
1088
+ // src/nqr/parser.ts
1089
+ var NQRParseError = class extends Error {
1090
+ path;
1091
+ constructor(message, path = "") {
1092
+ super(message);
1093
+ this.name = "NQRParseError";
1094
+ this.path = path;
1095
+ }
1096
+ };
1097
+ function parseNQR(payload) {
1098
+ if (payload.length < 8) {
1099
+ throw new NQRParseError(`payload too short (${payload.length} chars)`);
1100
+ }
1101
+ const crcTagAndLen = payload.slice(-8, -4);
1102
+ const crcValue = payload.slice(-4);
1103
+ if (crcTagAndLen !== CRC_TAG_PREFIX) {
1104
+ throw new NQRParseError(
1105
+ `CRC tag prefix not found (expected "6304" at end)`
1106
+ );
1107
+ }
1108
+ const beforeCrc = payload.slice(0, -4);
1109
+ const expectedCrc = crc16ccittHex(new TextEncoder().encode(beforeCrc));
1110
+ if (expectedCrc !== crcValue.toUpperCase()) {
1111
+ throw new NQRParseError(
1112
+ `CRC mismatch: payload says ${crcValue}, computed ${expectedCrc}`,
1113
+ "63"
1114
+ );
1115
+ }
1116
+ const topBuf = payload.slice(0, -8);
1117
+ const top = readTLV(topBuf);
1118
+ const get = (tag) => top.find((f) => f.tag === tag)?.value;
1119
+ const pfi = get(FIELD.PAYLOAD_FORMAT_INDICATOR);
1120
+ if (pfi !== "01")
1121
+ throw new NQRParseError(
1122
+ `payloadFormatIndicator must be "01", got "${pfi}"`,
1123
+ "00"
1124
+ );
1125
+ const poiRaw = get(FIELD.POINT_OF_INITIATION);
1126
+ const pointOfInitiation = poiRaw === "11" ? "static" : poiRaw === "12" ? "dynamic" : "unknown";
1127
+ const currency = get(FIELD.TRANSACTION_CURRENCY);
1128
+ if (currency !== NGN_CURRENCY_CODE) {
1129
+ throw new NQRParseError(
1130
+ `unsupported currency "${currency}" (expected ${NGN_CURRENCY_CODE})`,
1131
+ "53"
1132
+ );
1133
+ }
1134
+ const country = get(FIELD.COUNTRY_CODE);
1135
+ if (country !== NG_COUNTRY_CODE) {
1136
+ throw new NQRParseError(
1137
+ `unsupported country "${country}" (expected ${NG_COUNTRY_CODE})`,
1138
+ "58"
1139
+ );
1140
+ }
1141
+ const mcc = get(FIELD.MERCHANT_CATEGORY_CODE);
1142
+ if (!mcc) throw new NQRParseError("merchantCategoryCode missing", "52");
1143
+ const merchantName = get(FIELD.MERCHANT_NAME) ?? "";
1144
+ const merchantCity = get(FIELD.MERCHANT_CITY) ?? "";
1145
+ const transactionAmount = get(FIELD.TRANSACTION_AMOUNT);
1146
+ const postalCode = get(FIELD.POSTAL_CODE);
1147
+ const mais = [];
1148
+ for (const f of top) {
1149
+ const n = parseInt(f.tag, 10);
1150
+ if (n >= 2 && n <= 51) {
1151
+ const children = readTLV(f.value).map((c) => ({
1152
+ tag: c.tag,
1153
+ value: c.value
1154
+ }));
1155
+ mais.push({ tag: f.tag, children });
1156
+ }
1157
+ }
1158
+ let additionalData;
1159
+ const adfRaw = get(FIELD.ADDITIONAL_DATA_FIELD);
1160
+ if (adfRaw !== void 0) {
1161
+ additionalData = {};
1162
+ for (const c of readTLV(adfRaw)) {
1163
+ switch (c.tag) {
1164
+ case "01":
1165
+ additionalData.billNumber = c.value;
1166
+ break;
1167
+ case "02":
1168
+ additionalData.mobileNumber = c.value;
1169
+ break;
1170
+ case "03":
1171
+ additionalData.storeLabel = c.value;
1172
+ break;
1173
+ case "04":
1174
+ additionalData.loyaltyNumber = c.value;
1175
+ break;
1176
+ case "05":
1177
+ additionalData.referenceLabel = c.value;
1178
+ break;
1179
+ case "06":
1180
+ additionalData.customerLabel = c.value;
1181
+ break;
1182
+ case "07":
1183
+ additionalData.terminalLabel = c.value;
1184
+ break;
1185
+ case "08":
1186
+ additionalData.purposeOfTransaction = c.value;
1187
+ break;
1188
+ }
1189
+ }
1190
+ }
1191
+ return {
1192
+ payloadFormatIndicator: pfi,
1193
+ pointOfInitiation,
1194
+ merchantAccountInfo: mais,
1195
+ merchantCategoryCode: mcc,
1196
+ transactionCurrency: currency,
1197
+ transactionAmount,
1198
+ countryCode: country,
1199
+ merchantName,
1200
+ merchantCity,
1201
+ postalCode,
1202
+ additionalData,
1203
+ flurReference: additionalData?.referenceLabel?.startsWith("FL-") ? additionalData.referenceLabel : void 0,
1204
+ raw: payload
1205
+ };
1206
+ }
1207
+
1208
+ // src/nqr/routing.ts
1209
+ function routingHint(parsed) {
1210
+ if (parsed.flurReference && parsed.flurReference.startsWith("FL-")) {
1211
+ return "flur-onus";
1212
+ }
1213
+ if (parsed.merchantAccountInfo.length > 0) return "nibss";
1214
+ return "unknown";
1215
+ }
1216
+
1217
+ // src/crypto/canonical.ts
1218
+ function canonicalJSONStringify(value) {
1219
+ return stringify(value);
1220
+ }
1221
+ function canonicalJSONBytes(value) {
1222
+ return new TextEncoder().encode(canonicalJSONStringify(value));
1223
+ }
1224
+ function stringify(v) {
1225
+ if (v === null) return "null";
1226
+ const t = typeof v;
1227
+ if (t === "string") return JSON.stringify(v);
1228
+ if (t === "boolean") return v ? "true" : "false";
1229
+ if (t === "number") {
1230
+ if (!Number.isFinite(v)) {
1231
+ throw new Error("canonicalJSON: non-finite number not allowed");
1232
+ }
1233
+ return JSON.stringify(v);
1234
+ }
1235
+ if (t === "bigint" || t === "function" || t === "symbol" || t === "undefined") {
1236
+ throw new Error(`canonicalJSON: unsupported value type ${t}`);
1237
+ }
1238
+ if (Array.isArray(v)) {
1239
+ const parts = [];
1240
+ for (const item of v) parts.push(stringify(item));
1241
+ return "[" + parts.join(",") + "]";
1242
+ }
1243
+ if (t === "object") {
1244
+ const obj = v;
1245
+ const keys = Object.keys(obj).sort();
1246
+ const parts = [];
1247
+ for (const k of keys) {
1248
+ const val = obj[k];
1249
+ if (val === void 0) {
1250
+ throw new Error(`canonicalJSON: undefined value at key "${k}"`);
1251
+ }
1252
+ parts.push(JSON.stringify(k) + ":" + stringify(val));
1253
+ }
1254
+ return "{" + parts.join(",") + "}";
1255
+ }
1256
+ throw new Error(`canonicalJSON: unsupported value type ${t}`);
1257
+ }
1258
+
1259
+ // src/crypto/ct.ts
1260
+ function constantTimeEqual(a, b) {
1261
+ if (a.length !== b.length) return false;
1262
+ let diff = 0;
1263
+ for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
1264
+ return diff === 0;
1265
+ }
1266
+
1267
+ // src/crypto/ed25519.ts
1268
+ var import_ed25519 = require("@noble/curves/ed25519");
1269
+ function generateKeyPair() {
1270
+ const privateKey = import_ed25519.ed25519.utils.randomPrivateKey();
1271
+ const publicKey = import_ed25519.ed25519.getPublicKey(privateKey);
1272
+ return { privateKey, publicKey };
1273
+ }
1274
+ function publicKeyFromPrivate(privateKey) {
1275
+ return import_ed25519.ed25519.getPublicKey(privateKey);
1276
+ }
1277
+ function sign(message, privateKey) {
1278
+ return import_ed25519.ed25519.sign(message, privateKey);
1279
+ }
1280
+ function verify(message, signature, publicKey) {
1281
+ try {
1282
+ return import_ed25519.ed25519.verify(signature, message, publicKey);
1283
+ } catch {
1284
+ return false;
1285
+ }
1286
+ }
1287
+ function signCanonical(value, privateKey) {
1288
+ return sign(canonicalJSONBytes(value), privateKey);
1289
+ }
1290
+ function verifyCanonical(value, signature, publicKey) {
1291
+ return verify(canonicalJSONBytes(value), signature, publicKey);
1292
+ }
1293
+
1294
+ // src/offline/oac.ts
1295
+ var import_zod4 = require("zod");
1296
+ var OAC_DEFAULT_PER_TX_KOBO = 5e5;
1297
+ var OAC_DEFAULT_CUMULATIVE_KOBO = 2e6;
1298
+ var OAC_DEFAULT_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1299
+ var HexString = (length) => import_zod4.z.string().regex(
1300
+ new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1301
+ `expected ${length}-byte hex string`
1302
+ );
1303
+ var OACSchema = import_zod4.z.object({
1304
+ userId: import_zod4.z.string().min(1),
1305
+ deviceId: import_zod4.z.string().min(1),
1306
+ devicePublicKey: HexString(32),
1307
+ perTxCapKobo: import_zod4.z.number().int().nonnegative(),
1308
+ cumulativeCapKobo: import_zod4.z.number().int().nonnegative(),
1309
+ validFromMs: import_zod4.z.number().int().nonnegative(),
1310
+ validUntilMs: import_zod4.z.number().int().positive(),
1311
+ counterSeed: import_zod4.z.number().int().nonnegative(),
1312
+ nonce: import_zod4.z.string().min(1),
1313
+ issuerSig: HexString(64)
1314
+ }).refine((v) => v.validUntilMs > v.validFromMs, {
1315
+ message: "validUntilMs must be greater than validFromMs"
1316
+ }).refine((v) => v.perTxCapKobo <= v.cumulativeCapKobo, {
1317
+ message: "perTxCapKobo must not exceed cumulativeCapKobo"
1318
+ });
1319
+ function buildOAC(input) {
1320
+ const devicePublicKey = typeof input.devicePublicKey === "string" ? input.devicePublicKey : bytesToHex(input.devicePublicKey);
1321
+ return {
1322
+ userId: input.userId,
1323
+ deviceId: input.deviceId,
1324
+ devicePublicKey,
1325
+ perTxCapKobo: input.perTxCapKobo ?? OAC_DEFAULT_PER_TX_KOBO,
1326
+ cumulativeCapKobo: input.cumulativeCapKobo ?? OAC_DEFAULT_CUMULATIVE_KOBO,
1327
+ validFromMs: input.validFromMs,
1328
+ validUntilMs: input.validUntilMs,
1329
+ counterSeed: input.counterSeed ?? 0,
1330
+ nonce: input.nonce
1331
+ };
1332
+ }
1333
+ function signOAC(unsigned, issuerPrivateKey) {
1334
+ const issuerSig = bytesToHex(
1335
+ sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
1336
+ );
1337
+ return { ...unsigned, issuerSig };
1338
+ }
1339
+ function verifyOAC(oac, issuerPublicKey) {
1340
+ try {
1341
+ const parsed = OACSchema.parse(oac);
1342
+ const { issuerSig, ...unsigned } = parsed;
1343
+ return verify(
1344
+ canonicalJSONBytes(unsigned),
1345
+ hexToBytes(issuerSig),
1346
+ issuerPublicKey
1347
+ );
1348
+ } catch {
1349
+ return false;
1350
+ }
1351
+ }
1352
+ function bytesToHex(b) {
1353
+ let s = "";
1354
+ for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
1355
+ return s;
1356
+ }
1357
+ function hexToBytes(s) {
1358
+ if (s.length % 2 !== 0) throw new Error("hex: odd length");
1359
+ const out = new Uint8Array(s.length / 2);
1360
+ for (let i = 0; i < out.length; i++)
1361
+ out[i] = parseInt(s.slice(i * 2, i * 2 + 2), 16);
1362
+ return out;
1363
+ }
1364
+
1365
+ // src/offline/codec.ts
1366
+ var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
1367
+ var REVERSE = {};
1368
+ for (let i = 0; i < ALPHABET.length; i++) REVERSE[ALPHABET[i]] = i;
1369
+ function encodeBase45(bytes) {
1370
+ let out = "";
1371
+ let i = 0;
1372
+ for (; i + 1 < bytes.length; i += 2) {
1373
+ const x = bytes[i] << 8 | bytes[i + 1];
1374
+ const e = Math.floor(x / (45 * 45));
1375
+ const d = Math.floor(x % (45 * 45) / 45);
1376
+ const c = x % 45;
1377
+ out += ALPHABET[c] + ALPHABET[d] + ALPHABET[e];
1378
+ }
1379
+ if (i < bytes.length) {
1380
+ const x = bytes[i];
1381
+ const d = Math.floor(x / 45);
1382
+ const c = x % 45;
1383
+ out += ALPHABET[c] + ALPHABET[d];
1384
+ }
1385
+ return out;
1386
+ }
1387
+ function decodeBase45(s) {
1388
+ if (s.length === 0) return new Uint8Array(0);
1389
+ const fullChunks = Math.floor(s.length / 3);
1390
+ const tail = s.length - fullChunks * 3;
1391
+ if (tail === 1) throw new Error("base45: invalid length");
1392
+ const out = new Uint8Array(fullChunks * 2 + (tail === 2 ? 1 : 0));
1393
+ let oi = 0;
1394
+ for (let i = 0; i < fullChunks; i++) {
1395
+ const c = REVERSE[s[i * 3]];
1396
+ const d = REVERSE[s[i * 3 + 1]];
1397
+ const e = REVERSE[s[i * 3 + 2]];
1398
+ if (c === void 0 || d === void 0 || e === void 0) {
1399
+ throw new Error("base45: invalid character");
1400
+ }
1401
+ const x = c + 45 * d + 45 * 45 * e;
1402
+ if (x > 65535) throw new Error("base45: chunk overflow");
1403
+ out[oi++] = x >> 8 & 255;
1404
+ out[oi++] = x & 255;
1405
+ }
1406
+ if (tail === 2) {
1407
+ const c = REVERSE[s[fullChunks * 3]];
1408
+ const d = REVERSE[s[fullChunks * 3 + 1]];
1409
+ if (c === void 0 || d === void 0)
1410
+ throw new Error("base45: invalid character");
1411
+ const x = c + 45 * d;
1412
+ if (x > 255) throw new Error("base45: tail overflow");
1413
+ out[oi++] = x & 255;
1414
+ }
1415
+ return out;
1416
+ }
1417
+
1418
+ // src/offline/messages.ts
1419
+ var import_zod5 = require("zod");
1420
+ var HexSig = import_zod5.z.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
1421
+ var OfflinePaymentRequestSchema = import_zod5.z.object({
1422
+ reference: import_zod5.z.string().min(1),
1423
+ amountKobo: import_zod5.z.number().int().positive(),
1424
+ merchantOAC: OACSchema,
1425
+ expiresAtMs: import_zod5.z.number().int().positive(),
1426
+ merchantSig: HexSig
1427
+ });
1428
+ var OfflinePaymentAuthorizationSchema = import_zod5.z.object({
1429
+ request: OfflinePaymentRequestSchema,
1430
+ payerOAC: OACSchema,
1431
+ payerCounter: import_zod5.z.number().int().positive(),
1432
+ payerSig: HexSig
1433
+ });
1434
+ function buildPaymentRequest(input) {
1435
+ if (!Number.isInteger(input.amountKobo) || input.amountKobo <= 0) {
1436
+ throw new Error("amountKobo must be a positive integer");
1437
+ }
1438
+ if (input.amountKobo > input.merchantOAC.perTxCapKobo) {
1439
+ throw new Error("amountKobo exceeds merchant OAC perTxCapKobo");
1440
+ }
1441
+ return {
1442
+ reference: input.reference,
1443
+ amountKobo: input.amountKobo,
1444
+ merchantOAC: input.merchantOAC,
1445
+ expiresAtMs: input.expiresAtMs
1446
+ };
1447
+ }
1448
+ function signPaymentRequest(unsigned, merchantDevicePrivateKey) {
1449
+ const merchantSig = bytesToHex(
1450
+ sign(canonicalJSONBytes(unsigned), merchantDevicePrivateKey)
1451
+ );
1452
+ return { ...unsigned, merchantSig };
1453
+ }
1454
+ function verifyPaymentRequest(req, issuerPublicKey) {
1455
+ try {
1456
+ const parsed = OfflinePaymentRequestSchema.parse(req);
1457
+ const { issuerSig: merchantOacSig, ...merchantOacUnsigned } = parsed.merchantOAC;
1458
+ if (!verify(
1459
+ canonicalJSONBytes(merchantOacUnsigned),
1460
+ hexToBytes(merchantOacSig),
1461
+ issuerPublicKey
1462
+ )) {
1463
+ return false;
1464
+ }
1465
+ const { merchantSig, ...unsigned } = parsed;
1466
+ return verify(
1467
+ canonicalJSONBytes(unsigned),
1468
+ hexToBytes(merchantSig),
1469
+ hexToBytes(parsed.merchantOAC.devicePublicKey)
1470
+ );
1471
+ } catch {
1472
+ return false;
1473
+ }
1474
+ }
1475
+ function buildAuthorization(input) {
1476
+ if (!Number.isInteger(input.payerCounter) || input.payerCounter <= 0) {
1477
+ throw new Error("payerCounter must be a positive integer");
1478
+ }
1479
+ if (input.request.amountKobo > input.payerOAC.perTxCapKobo) {
1480
+ throw new Error("amountKobo exceeds payer OAC perTxCapKobo");
1481
+ }
1482
+ return {
1483
+ request: input.request,
1484
+ payerOAC: input.payerOAC,
1485
+ payerCounter: input.payerCounter
1486
+ };
1487
+ }
1488
+ function signAuthorization(unsigned, payerDevicePrivateKey) {
1489
+ const payerSig = bytesToHex(
1490
+ sign(canonicalJSONBytes(unsigned), payerDevicePrivateKey)
1491
+ );
1492
+ return { ...unsigned, payerSig };
1493
+ }
1494
+ function verifyAuthorization(auth, issuerPublicKey) {
1495
+ try {
1496
+ const parsed = OfflinePaymentAuthorizationSchema.parse(auth);
1497
+ if (!verifyPaymentRequest(parsed.request, issuerPublicKey)) return false;
1498
+ const { issuerSig: payerOacSig, ...payerOacUnsigned } = parsed.payerOAC;
1499
+ if (!verify(
1500
+ canonicalJSONBytes(payerOacUnsigned),
1501
+ hexToBytes(payerOacSig),
1502
+ issuerPublicKey
1503
+ )) {
1504
+ return false;
1505
+ }
1506
+ const { payerSig, ...unsigned } = parsed;
1507
+ return verify(
1508
+ canonicalJSONBytes(unsigned),
1509
+ hexToBytes(payerSig),
1510
+ hexToBytes(parsed.payerOAC.devicePublicKey)
1511
+ );
1512
+ } catch {
1513
+ return false;
1514
+ }
1515
+ }
1516
+ function encodePaymentRequestQR(req) {
1517
+ const json = JSON.stringify(req);
1518
+ return "FLUR1:" + encodeBase45(new TextEncoder().encode(json));
1519
+ }
1520
+ function decodePaymentRequestQR(s) {
1521
+ if (!s.startsWith("FLUR1:"))
1522
+ throw new Error("not a Flur offline payment request QR");
1523
+ const bytes = decodeBase45(s.slice("FLUR1:".length));
1524
+ const json = new TextDecoder().decode(bytes);
1525
+ return OfflinePaymentRequestSchema.parse(JSON.parse(json));
1526
+ }
1527
+ function encodeAuthorizationQR(auth) {
1528
+ const json = JSON.stringify(auth);
1529
+ return "FLUR2:" + encodeBase45(new TextEncoder().encode(json));
1530
+ }
1531
+ function decodeAuthorizationQR(s) {
1532
+ if (!s.startsWith("FLUR2:"))
1533
+ throw new Error("not a Flur offline authorization QR");
1534
+ const bytes = decodeBase45(s.slice("FLUR2:".length));
1535
+ const json = new TextDecoder().decode(bytes);
1536
+ return OfflinePaymentAuthorizationSchema.parse(JSON.parse(json));
1537
+ }
1538
+
1539
+ // src/auth/hmac.ts
1540
+ var import_hmac = require("@noble/hashes/hmac");
1541
+ var import_sha256 = require("@noble/hashes/sha256");
1542
+ function bytesToHex2(b) {
1543
+ let s = "";
1544
+ for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
1545
+ return s;
1546
+ }
1547
+ function constantTimeStringEqual(a, b) {
1548
+ if (a.length !== b.length) return false;
1549
+ let diff = 0;
1550
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
1551
+ return diff === 0;
1552
+ }
1553
+ function bodySha256Hex(body) {
1554
+ return bytesToHex2((0, import_sha256.sha256)(new TextEncoder().encode(body)));
1555
+ }
1556
+ function canonicalRequestString(input) {
1557
+ return [
1558
+ input.method.toUpperCase(),
1559
+ input.path,
1560
+ input.timestamp,
1561
+ input.nonce,
1562
+ bodySha256Hex(input.body)
1563
+ ].join("\n");
1564
+ }
1565
+ function signRequestHMAC(input) {
1566
+ return bytesToHex2(
1567
+ (0, import_hmac.hmac)(import_sha256.sha256, input.apiSecret, canonicalRequestString(input))
1568
+ );
1569
+ }
1570
+ function verifyRequestHMAC(input) {
1571
+ const expected = signRequestHMAC(input);
1572
+ return constantTimeStringEqual(expected, input.signature.toLowerCase());
1573
+ }
1574
+
1575
+ // src/auth/middleware.ts
1576
+ var REPLAY_WINDOW_MS = 5 * 60 * 1e3;
1577
+ var SERVER_TIME_HEADER = "x-flur-server-time";
1578
+ function defaultNonce() {
1579
+ const c = globalThis.crypto;
1580
+ if (typeof c?.randomUUID === "function") return c.randomUUID();
1581
+ if (typeof c?.getRandomValues !== "function") {
1582
+ throw new Error(
1583
+ "Flur SDK: no CSPRNG available (globalThis.crypto.getRandomValues missing). Refusing to fall back to Math.random for HMAC nonce generation."
1584
+ );
1585
+ }
1586
+ const arr = new Uint8Array(16);
1587
+ c.getRandomValues(arr);
1588
+ let s = "";
1589
+ for (let i = 0; i < arr.length; i++)
1590
+ s += arr[i].toString(16).padStart(2, "0");
1591
+ return s;
1592
+ }
1593
+ function assertCSPRNG() {
1594
+ const c = globalThis.crypto;
1595
+ if (typeof c?.getRandomValues !== "function" && typeof c?.randomUUID !== "function") {
1596
+ throw new Error(
1597
+ "Flur SDK: no CSPRNG available (globalThis.crypto missing). Initialize on a runtime that provides Web Crypto (Node 19+, modern browsers, RN 0.74+)."
1598
+ );
1599
+ }
1600
+ }
1601
+ function parseServerTimeMs(value) {
1602
+ if (!value) return null;
1603
+ const trimmed = value.trim();
1604
+ if (/^\d+$/.test(trimmed)) {
1605
+ const n = Number(trimmed);
1606
+ return n < 1e12 ? n * 1e3 : n;
1607
+ }
1608
+ const t = Date.parse(trimmed);
1609
+ return Number.isFinite(t) ? t : null;
1610
+ }
1611
+ function createHmacFetch(opts) {
1612
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
1613
+ if (!fetchImpl)
1614
+ throw new Error("createHmacFetch: no fetch implementation available");
1615
+ if (!opts.nonceFn) assertCSPRNG();
1616
+ const nowMs = opts.nowMs ?? (() => Date.now());
1617
+ const nonceFn = opts.nonceFn ?? defaultNonce;
1618
+ const scopeHeader = opts.scope && opts.scope.length > 0 ? opts.scope.join(",") : void 0;
1619
+ async function signAndSend(req, skewOffsetMs) {
1620
+ const cloned = req.clone();
1621
+ const url = new URL(cloned.url);
1622
+ const path = url.pathname + url.search;
1623
+ const method = cloned.method.toUpperCase();
1624
+ const bodyText = method === "GET" || method === "HEAD" ? "" : await cloned.clone().text();
1625
+ const timestamp = Math.floor((nowMs() + skewOffsetMs) / 1e3).toString();
1626
+ const nonce = nonceFn();
1627
+ const signature = signRequestHMAC({
1628
+ method,
1629
+ path,
1630
+ timestamp,
1631
+ nonce,
1632
+ body: bodyText,
1633
+ apiSecret: opts.apiSecret
1634
+ });
1635
+ const headers = new Headers(cloned.headers);
1636
+ headers.set("x-flur-key", opts.apiKey);
1637
+ headers.set("x-flur-timestamp", timestamp);
1638
+ headers.set("x-flur-nonce", nonce);
1639
+ headers.set("x-flur-signature", signature);
1640
+ if (scopeHeader) headers.set("x-flur-scope", scopeHeader);
1641
+ const signed = new Request(cloned, { headers });
1642
+ return fetchImpl(signed);
1643
+ }
1644
+ return (async (input, init2) => {
1645
+ const req = input instanceof Request ? input : new Request(input, init2);
1646
+ let resp = await signAndSend(req, 0);
1647
+ if (resp.status === 401) {
1648
+ const serverTimeMs = parseServerTimeMs(
1649
+ resp.headers.get(SERVER_TIME_HEADER)
1650
+ );
1651
+ if (serverTimeMs !== null) {
1652
+ const skew = serverTimeMs - nowMs();
1653
+ if (Math.abs(skew) > 0) {
1654
+ resp = await signAndSend(req, skew);
1655
+ }
1656
+ }
1657
+ }
1658
+ return resp;
1659
+ });
1660
+ }
1661
+
1662
+ // src/passes/pass.ts
1663
+ var import_zod6 = require("zod");
1664
+ var PASS_KINDS = [
1665
+ "ride-ticket",
1666
+ "transit-pass",
1667
+ "event-ticket",
1668
+ "voucher",
1669
+ "loyalty",
1670
+ "receipt-link"
1671
+ ];
1672
+ var PASS_STATES = [
1673
+ "issued",
1674
+ "active",
1675
+ "redeemed",
1676
+ "expired",
1677
+ "revoked"
1678
+ ];
1679
+ var HexString2 = (length) => import_zod6.z.string().regex(
1680
+ new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1681
+ `expected ${length}-byte hex string`
1682
+ );
1683
+ var PassMetadataSchema = import_zod6.z.record(
1684
+ import_zod6.z.union([import_zod6.z.string(), import_zod6.z.number(), import_zod6.z.boolean(), import_zod6.z.null()])
1685
+ );
1686
+ var PassSchema = import_zod6.z.object({
1687
+ passId: import_zod6.z.string().min(1),
1688
+ /** Optional client/template grouping id (server may omit). */
1689
+ templateId: import_zod6.z.string().min(1).optional(),
1690
+ /** Optional human-facing holder identity (server may omit). The cryptographic binding
1691
+ * is `holderDevicePubkey` below. */
1692
+ holderUserId: import_zod6.z.string().min(1).optional(),
1693
+ kind: import_zod6.z.enum(PASS_KINDS),
1694
+ issuerId: import_zod6.z.string().min(1),
1695
+ issuedAtMs: import_zod6.z.number().int().nonnegative(),
1696
+ validFromMs: import_zod6.z.number().int().nonnegative(),
1697
+ validUntilMs: import_zod6.z.number().int().positive(),
1698
+ state: import_zod6.z.enum(PASS_STATES),
1699
+ metadata: PassMetadataSchema,
1700
+ nonce: import_zod6.z.string().min(1),
1701
+ /** Device id this pass is bound to (FK to backend `device_keys`). */
1702
+ holderDeviceId: import_zod6.z.string().min(1),
1703
+ /** 32-byte hex Ed25519 public key of the bound device. The redemption signature
1704
+ * is verified against this key — it is the security-critical binding. */
1705
+ holderDevicePubkey: HexString2(32),
1706
+ /** Optional fixed amount for monetary passes (vouchers, gift cards) in kobo. */
1707
+ amountKobo: import_zod6.z.number().int().nonnegative().optional(),
1708
+ /** ISO-4217-ish currency code; required on the wire. SDK builders default to NGN. */
1709
+ currency: import_zod6.z.string().min(3).max(8),
1710
+ /** Monotonic redemption counter floor. Redemption.counter MUST be > counterSeed. */
1711
+ counterSeed: import_zod6.z.number().int().nonnegative(),
1712
+ /** Optional cumulative spend cap in kobo across all redemptions of this pass. */
1713
+ cumulativeCapKobo: import_zod6.z.number().int().nonnegative().optional(),
1714
+ issuerSig: HexString2(64)
1715
+ }).refine((v) => v.validUntilMs > v.validFromMs, {
1716
+ message: "validUntilMs must be greater than validFromMs"
1717
+ });
1718
+ function buildPass(input) {
1719
+ if (input.validUntilMs <= input.validFromMs) {
1720
+ throw new Error("validUntilMs must be greater than validFromMs");
1721
+ }
1722
+ const out = {
1723
+ passId: input.passId,
1724
+ kind: input.kind,
1725
+ issuerId: input.issuerId,
1726
+ issuedAtMs: input.issuedAtMs,
1727
+ validFromMs: input.validFromMs,
1728
+ validUntilMs: input.validUntilMs,
1729
+ state: input.state,
1730
+ metadata: input.metadata,
1731
+ nonce: input.nonce,
1732
+ holderDeviceId: input.holderDeviceId,
1733
+ holderDevicePubkey: input.holderDevicePubkey,
1734
+ currency: input.currency ?? "NGN",
1735
+ counterSeed: input.counterSeed
1736
+ };
1737
+ if (typeof input.templateId === "string") out.templateId = input.templateId;
1738
+ if (typeof input.holderUserId === "string")
1739
+ out.holderUserId = input.holderUserId;
1740
+ if (typeof input.amountKobo === "number") out.amountKobo = input.amountKobo;
1741
+ if (typeof input.cumulativeCapKobo === "number") {
1742
+ out.cumulativeCapKobo = input.cumulativeCapKobo;
1743
+ }
1744
+ return out;
1745
+ }
1746
+ function signPass(unsigned, issuerPrivateKey) {
1747
+ const issuerSig = bytesToHex(
1748
+ sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
1749
+ );
1750
+ return { ...unsigned, issuerSig };
1751
+ }
1752
+ function verifyPass(pass, issuerPublicKey) {
1753
+ try {
1754
+ const parsed = PassSchema.parse(pass);
1755
+ const { issuerSig, ...unsigned } = parsed;
1756
+ return verify(
1757
+ canonicalJSONBytes(unsigned),
1758
+ hexToBytes(issuerSig),
1759
+ issuerPublicKey
1760
+ );
1761
+ } catch {
1762
+ return false;
1763
+ }
1764
+ }
1765
+ function isPassWithinValidity(pass, nowMs) {
1766
+ return nowMs >= pass.validFromMs && nowMs < pass.validUntilMs;
1767
+ }
1768
+
1769
+ // src/passes/redemption.ts
1770
+ var import_zod7 = require("zod");
1771
+ var HexSig2 = import_zod7.z.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
1772
+ var RedemptionSchema = import_zod7.z.object({
1773
+ pass: PassSchema,
1774
+ redeemerId: import_zod7.z.string().min(1),
1775
+ redeemedAtMs: import_zod7.z.number().int().nonnegative(),
1776
+ /** Strictly monotonic counter scoped to a single pass. Must be > pass.counterSeed
1777
+ * and > the redeemer's lastSeenCounter for this pass. */
1778
+ counter: import_zod7.z.number().int().positive(),
1779
+ /** Amount being redeemed in kobo (0 for non-monetary passes like ride tickets). */
1780
+ amountKobo: import_zod7.z.number().int().nonnegative(),
1781
+ nonce: import_zod7.z.string().min(1),
1782
+ holderSig: HexSig2
1783
+ });
1784
+ var REDEEMABLE_STATES = /* @__PURE__ */ new Set(["issued", "active"]);
1785
+ function buildRedemption(input) {
1786
+ if (!REDEEMABLE_STATES.has(input.pass.state)) {
1787
+ throw new Error(`pass not in redeemable state: ${input.pass.state}`);
1788
+ }
1789
+ if (input.redeemedAtMs < input.pass.validFromMs) {
1790
+ throw new Error("redeemedAtMs is before pass validFromMs");
1791
+ }
1792
+ if (input.redeemedAtMs >= input.pass.validUntilMs) {
1793
+ throw new FlurExpiredError(
1794
+ `pass ${input.pass.passId} expired at ${input.pass.validUntilMs}`
1795
+ );
1796
+ }
1797
+ const now = typeof input.nowMs === "number" ? input.nowMs : input.redeemedAtMs;
1798
+ if (now >= input.pass.validUntilMs) {
1799
+ throw new FlurExpiredError(
1800
+ `pass ${input.pass.passId} expired at ${input.pass.validUntilMs}`
1801
+ );
1802
+ }
1803
+ if (!input.pass.holderDevicePubkey) {
1804
+ throw new Error(
1805
+ "pass.holderDevicePubkey is required to build a redemption"
1806
+ );
1807
+ }
1808
+ const lastSeen = Math.max(
1809
+ input.pass.counterSeed,
1810
+ typeof input.lastSeenCounter === "number" ? input.lastSeenCounter : 0
1811
+ );
1812
+ if (input.counter <= lastSeen) {
1813
+ throw new FlurReplayError(
1814
+ `redemption counter ${input.counter} must be > lastSeenCounter ${lastSeen}`
1815
+ );
1816
+ }
1817
+ const amount = input.amountKobo ?? 0;
1818
+ if (typeof input.pass.cumulativeCapKobo === "number") {
1819
+ const used = input.cumulativeUsedKobo ?? 0;
1820
+ if (used + amount > input.pass.cumulativeCapKobo) {
1821
+ throw new FlurCapExceededError(
1822
+ `pass ${input.pass.passId} cap ${input.pass.cumulativeCapKobo} would be exceeded (used ${used} + ${amount})`
1823
+ );
1824
+ }
1825
+ }
1826
+ return {
1827
+ pass: input.pass,
1828
+ redeemerId: input.redeemerId,
1829
+ redeemedAtMs: input.redeemedAtMs,
1830
+ counter: input.counter,
1831
+ amountKobo: amount,
1832
+ nonce: input.nonce
1833
+ };
1834
+ }
1835
+ function signRedemption(unsigned, holderDevicePrivateKey) {
1836
+ const holderSig = bytesToHex(
1837
+ sign(canonicalJSONBytes(unsigned), holderDevicePrivateKey)
1838
+ );
1839
+ return { ...unsigned, holderSig };
1840
+ }
1841
+ function verifyRedemption(r, issuerPublicKey) {
1842
+ try {
1843
+ const parsed = RedemptionSchema.parse(r);
1844
+ if (parsed.counter <= parsed.pass.counterSeed) return false;
1845
+ const { issuerSig, ...passUnsigned } = parsed.pass;
1846
+ if (!verify(
1847
+ canonicalJSONBytes(passUnsigned),
1848
+ hexToBytes(issuerSig),
1849
+ issuerPublicKey
1850
+ )) {
1851
+ return false;
1852
+ }
1853
+ const holderHex = parsed.pass.holderDevicePubkey;
1854
+ if (typeof holderHex !== "string") return false;
1855
+ const { holderSig, ...unsigned } = parsed;
1856
+ return verify(
1857
+ canonicalJSONBytes(unsigned),
1858
+ hexToBytes(holderSig),
1859
+ hexToBytes(holderHex)
1860
+ );
1861
+ } catch {
1862
+ return false;
1863
+ }
1864
+ }
1865
+
1866
+ // src/passes/client.ts
1867
+ function createPassesClient(opts) {
1868
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
1869
+ if (!fetchImpl)
1870
+ throw new Error("createPassesClient: no fetch implementation available");
1871
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
1872
+ async function call(method, path, body, parser) {
1873
+ const init2 = {
1874
+ method,
1875
+ headers: {
1876
+ "content-type": "application/json",
1877
+ accept: "application/json"
1878
+ }
1879
+ };
1880
+ if (body !== void 0) init2.body = JSON.stringify(body);
1881
+ const resp = await fetchImpl(`${baseUrl}${path}`, init2);
1882
+ const text = await resp.text();
1883
+ let raw = void 0;
1884
+ if (text) {
1885
+ try {
1886
+ raw = JSON.parse(text);
1887
+ } catch {
1888
+ }
1889
+ }
1890
+ if (!resp.ok) {
1891
+ const code = (raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : void 0) ?? `http_${resp.status}`;
1892
+ const message = (raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : void 0) ?? `request failed with status ${resp.status}`;
1893
+ throw new FlurApiError(resp.status, code, message, raw);
1894
+ }
1895
+ return parser(raw);
1896
+ }
1897
+ return {
1898
+ issuePass: (input) => call("POST", "/v1/passes", input, (raw) => PassSchema.parse(raw)),
1899
+ listPasses: (input) => {
1900
+ const qs = new URLSearchParams();
1901
+ if (input.holderDeviceId) qs.set("holderDeviceId", input.holderDeviceId);
1902
+ if (input.holderUserId) qs.set("holderUserId", input.holderUserId);
1903
+ if (input.state) qs.set("state", input.state);
1904
+ if (input.kind) qs.set("kind", input.kind);
1905
+ if (input.templateId) qs.set("templateId", input.templateId);
1906
+ if (typeof input.limit === "number") qs.set("limit", String(input.limit));
1907
+ if (input.cursor) qs.set("cursor", input.cursor);
1908
+ const path = `/v1/passes${qs.size > 0 ? `?${qs.toString()}` : ""}`;
1909
+ return call("GET", path, void 0, (raw) => {
1910
+ const obj = raw;
1911
+ const items = obj.items.map(
1912
+ (it) => PassSchema.parse(it)
1913
+ );
1914
+ const nextCursor = typeof obj.nextCursor === "string" ? obj.nextCursor : null;
1915
+ return { items, nextCursor };
1916
+ });
1917
+ },
1918
+ getPass: (passId) => call(
1919
+ "GET",
1920
+ `/v1/passes/${encodeURIComponent(passId)}`,
1921
+ void 0,
1922
+ (raw) => PassSchema.parse(raw)
1923
+ ),
1924
+ redeemPass: (passId, redemption) => call(
1925
+ "POST",
1926
+ `/v1/passes/${encodeURIComponent(passId)}/redeem`,
1927
+ RedemptionSchema.parse(redemption),
1928
+ (raw) => PassSchema.parse(raw)
1929
+ ),
1930
+ revokePass: (passId, input) => call(
1931
+ "POST",
1932
+ `/v1/passes/${encodeURIComponent(passId)}/revoke`,
1933
+ input,
1934
+ (raw) => PassSchema.parse(raw)
1935
+ ),
1936
+ verifyPass: (pass, issuerPublicKey) => verifyPass(pass, issuerPublicKey)
1937
+ };
1938
+ }
1939
+
1940
+ // src/receipts/receipt.ts
1941
+ var import_zod8 = require("zod");
1942
+ var RECEIPT_CHANNELS = ["cash", "pass"];
1943
+ var RECEIPT_KINDS = RECEIPT_CHANNELS;
1944
+ var HexString3 = (length) => import_zod8.z.string().regex(
1945
+ new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1946
+ `expected ${length}-byte hex string`
1947
+ );
1948
+ var ReceiptPayloadSchema = import_zod8.z.record(
1949
+ import_zod8.z.union([import_zod8.z.string(), import_zod8.z.number(), import_zod8.z.boolean(), import_zod8.z.null()])
1950
+ );
1951
+ var ReceiptSchema = import_zod8.z.object({
1952
+ receiptId: import_zod8.z.string().min(1),
1953
+ channel: import_zod8.z.enum(RECEIPT_CHANNELS),
1954
+ /** Cash-channel: send_intents.id. Required when channel === 'cash'. */
1955
+ intentId: import_zod8.z.string().min(1).optional(),
1956
+ /** Pass-channel: pass_redemptions.id. Required when channel === 'pass'. */
1957
+ passRedemptionId: import_zod8.z.string().min(1).optional(),
1958
+ payerUserId: import_zod8.z.string().min(1),
1959
+ payeeUserId: import_zod8.z.string().min(1),
1960
+ amountKobo: import_zod8.z.number().int().nonnegative(),
1961
+ currency: import_zod8.z.string().min(3).max(8),
1962
+ issuedAtMs: import_zod8.z.number().int().nonnegative(),
1963
+ issuerId: import_zod8.z.string().min(1),
1964
+ payload: ReceiptPayloadSchema,
1965
+ issuerSig: HexString3(64)
1966
+ }).superRefine((v, ctx) => {
1967
+ if (v.channel === "cash") {
1968
+ if (!v.intentId) {
1969
+ ctx.addIssue({
1970
+ code: import_zod8.z.ZodIssueCode.custom,
1971
+ message: "cash receipts require intentId",
1972
+ path: ["intentId"]
1973
+ });
1974
+ }
1975
+ if (v.passRedemptionId) {
1976
+ ctx.addIssue({
1977
+ code: import_zod8.z.ZodIssueCode.custom,
1978
+ message: "cash receipts must not carry passRedemptionId",
1979
+ path: ["passRedemptionId"]
1980
+ });
1981
+ }
1982
+ } else if (v.channel === "pass") {
1983
+ if (!v.passRedemptionId) {
1984
+ ctx.addIssue({
1985
+ code: import_zod8.z.ZodIssueCode.custom,
1986
+ message: "pass receipts require passRedemptionId",
1987
+ path: ["passRedemptionId"]
1988
+ });
1989
+ }
1990
+ if (v.intentId) {
1991
+ ctx.addIssue({
1992
+ code: import_zod8.z.ZodIssueCode.custom,
1993
+ message: "pass receipts must not carry intentId",
1994
+ path: ["intentId"]
1995
+ });
1996
+ }
1997
+ }
1998
+ });
1999
+ function buildReceipt(input) {
2000
+ if (input.channel === "cash") {
2001
+ if (!input.intentId) {
2002
+ throw new Error("cash receipts require intentId");
2003
+ }
2004
+ if (input.passRedemptionId) {
2005
+ throw new Error("cash receipts must not carry passRedemptionId");
2006
+ }
2007
+ } else {
2008
+ if (!input.passRedemptionId) {
2009
+ throw new Error("pass receipts require passRedemptionId");
2010
+ }
2011
+ if (input.intentId) {
2012
+ throw new Error("pass receipts must not carry intentId");
2013
+ }
2014
+ }
2015
+ const out = {
2016
+ receiptId: input.receiptId,
2017
+ channel: input.channel,
2018
+ payerUserId: input.payerUserId,
2019
+ payeeUserId: input.payeeUserId,
2020
+ amountKobo: input.amountKobo,
2021
+ currency: input.currency,
2022
+ issuedAtMs: input.issuedAtMs,
2023
+ issuerId: input.issuerId,
2024
+ payload: input.payload ?? {}
2025
+ };
2026
+ if (input.intentId) out.intentId = input.intentId;
2027
+ if (input.passRedemptionId) out.passRedemptionId = input.passRedemptionId;
2028
+ return out;
2029
+ }
2030
+ function signReceipt(unsigned, issuerPrivateKey) {
2031
+ const issuerSig = bytesToHex(
2032
+ sign(canonicalJSONBytes(unsigned), issuerPrivateKey)
2033
+ );
2034
+ return { ...unsigned, issuerSig };
2035
+ }
2036
+ function verifyReceipt(r, issuerPublicKey) {
2037
+ try {
2038
+ const parsed = ReceiptSchema.parse(r);
2039
+ const { issuerSig, ...unsigned } = parsed;
2040
+ return verify(
2041
+ canonicalJSONBytes(unsigned),
2042
+ hexToBytes(issuerSig),
2043
+ issuerPublicKey
2044
+ );
2045
+ } catch {
2046
+ return false;
2047
+ }
2048
+ }
2049
+
2050
+ // src/receipts/client.ts
2051
+ function createReceiptsClient(opts) {
2052
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
2053
+ if (!fetchImpl)
2054
+ throw new Error("createReceiptsClient: no fetch implementation available");
2055
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
2056
+ async function call(method, path, body, parser) {
2057
+ const init2 = {
2058
+ method,
2059
+ headers: {
2060
+ "content-type": "application/json",
2061
+ accept: "application/json"
2062
+ }
2063
+ };
2064
+ if (body !== void 0) init2.body = JSON.stringify(body);
2065
+ const resp = await fetchImpl(`${baseUrl}${path}`, init2);
2066
+ const text = await resp.text();
2067
+ let raw = void 0;
2068
+ if (text) {
2069
+ try {
2070
+ raw = JSON.parse(text);
2071
+ } catch {
2072
+ }
2073
+ }
2074
+ if (!resp.ok) {
2075
+ const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
2076
+ const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
2077
+ throw new FlurApiError(resp.status, code, message, raw);
2078
+ }
2079
+ return parser(raw);
2080
+ }
2081
+ const getById = (receiptId) => call(
2082
+ "GET",
2083
+ `/v1/receipts/${encodeURIComponent(receiptId)}`,
2084
+ void 0,
2085
+ (raw) => ReceiptSchema.parse(raw)
2086
+ );
2087
+ return {
2088
+ issueReceipt: (input) => call("POST", "/v1/receipts", input, (raw) => ReceiptSchema.parse(raw)),
2089
+ getReceipt: getById,
2090
+ getById,
2091
+ getByIntentId: (intentId) => call(
2092
+ "GET",
2093
+ `/v1/receipts/by-intent/${encodeURIComponent(intentId)}`,
2094
+ void 0,
2095
+ (raw) => ReceiptSchema.parse(raw)
2096
+ ),
2097
+ getByPassRedemptionId: (passRedemptionId) => call(
2098
+ "GET",
2099
+ `/v1/receipts/by-pass-redemption/${encodeURIComponent(passRedemptionId)}`,
2100
+ void 0,
2101
+ (raw) => ReceiptSchema.parse(raw)
2102
+ ),
2103
+ listForUser: (input) => {
2104
+ const qs = new URLSearchParams();
2105
+ if (input.payerUserId) qs.set("payerUserId", input.payerUserId);
2106
+ if (input.payeeUserId) qs.set("payeeUserId", input.payeeUserId);
2107
+ if (input.channel) qs.set("channel", input.channel);
2108
+ if (typeof input.limit === "number") qs.set("limit", String(input.limit));
2109
+ if (input.cursor) qs.set("cursor", input.cursor);
2110
+ const path = `/v1/receipts${qs.size > 0 ? `?${qs.toString()}` : ""}`;
2111
+ return call("GET", path, void 0, (raw) => {
2112
+ const obj = raw;
2113
+ const items = obj.items.map(
2114
+ (it) => ReceiptSchema.parse(it)
2115
+ );
2116
+ const nextCursor = typeof obj.nextCursor === "string" ? obj.nextCursor : null;
2117
+ return { items, nextCursor };
2118
+ });
2119
+ },
2120
+ verifyReceipt: (receipt, issuerPublicKey) => verifyReceipt(receipt, issuerPublicKey)
2121
+ };
2122
+ }
2123
+
2124
+ // src/client/flur.ts
2125
+ function generateStaticQR(input) {
2126
+ return encodeNQR({ ...input, pointOfInitiation: "static" });
2127
+ }
2128
+ function generateDynamicQR(input) {
2129
+ return encodeNQR({ ...input, pointOfInitiation: "dynamic" });
2130
+ }
2131
+ function parseQR(payload) {
2132
+ return parseNQR(payload);
2133
+ }
2134
+ function init(opts) {
2135
+ const signedFetch = createHmacFetch({
2136
+ apiKey: opts.apiKey,
2137
+ apiSecret: opts.apiSecret,
2138
+ fetchImpl: opts.fetchImpl,
2139
+ scope: opts.scope
2140
+ });
2141
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
2142
+ function subscribeToPayments(s) {
2143
+ const controller = new AbortController();
2144
+ let cancelled = false;
2145
+ (async () => {
2146
+ try {
2147
+ const url = `${baseUrl}/v1/payments/subscribe?reference=${encodeURIComponent(s.reference)}`;
2148
+ const resp = await signedFetch(url, {
2149
+ method: "GET",
2150
+ headers: { accept: "text/event-stream" },
2151
+ signal: controller.signal
2152
+ });
2153
+ if (!resp.body) return;
2154
+ const reader = resp.body.getReader();
2155
+ const decoder = new TextDecoder();
2156
+ let buffer = "";
2157
+ while (!cancelled) {
2158
+ const { value, done } = await reader.read();
2159
+ if (done) return;
2160
+ buffer += decoder.decode(value, { stream: true });
2161
+ let idx;
2162
+ while ((idx = buffer.indexOf("\n\n")) >= 0) {
2163
+ const chunk = buffer.slice(0, idx);
2164
+ buffer = buffer.slice(idx + 2);
2165
+ for (const line of chunk.split("\n")) {
2166
+ if (!line.startsWith("data:")) continue;
2167
+ const json = line.slice(5).trim();
2168
+ if (!json) continue;
2169
+ try {
2170
+ s.onEvent(JSON.parse(json));
2171
+ } catch (err) {
2172
+ s.onError?.(err);
2173
+ }
2174
+ }
2175
+ }
2176
+ }
2177
+ } catch (err) {
2178
+ if (!cancelled) s.onError?.(err);
2179
+ }
2180
+ })();
2181
+ return () => {
2182
+ cancelled = true;
2183
+ controller.abort();
2184
+ };
2185
+ }
2186
+ const cash = {
2187
+ generateStaticQR,
2188
+ generateDynamicQR,
2189
+ parseQR,
2190
+ subscribeToPayments
2191
+ };
2192
+ const passes = createPassesClient({ baseUrl, fetchImpl: signedFetch });
2193
+ const receipts = createReceiptsClient({ baseUrl, fetchImpl: signedFetch });
2194
+ return {
2195
+ // top-level back-compat surface
2196
+ generateStaticQR,
2197
+ generateDynamicQR,
2198
+ parseQR,
2199
+ subscribeToPayments,
2200
+ // namespaces
2201
+ cash,
2202
+ passes,
2203
+ receipts
2204
+ };
2205
+ }
2206
+ // Annotate the CommonJS export names for ESM import in node:
2207
+ 0 && (module.exports = {
2208
+ ADDITIONAL_DATA_SUBFIELD,
2209
+ FIELD,
2210
+ FlurApiError,
2211
+ FlurCapExceededError,
2212
+ FlurClient,
2213
+ FlurError,
2214
+ FlurExpiredError,
2215
+ FlurReplayError,
2216
+ NGN_CURRENCY_CODE,
2217
+ NG_COUNTRY_CODE,
2218
+ NQRParseError,
2219
+ OACSchema,
2220
+ OAC_DEFAULT_CUMULATIVE_KOBO,
2221
+ OAC_DEFAULT_PER_TX_KOBO,
2222
+ OAC_DEFAULT_VALIDITY_MS,
2223
+ OfflinePaymentAuthorizationSchema,
2224
+ OfflinePaymentRequestSchema,
2225
+ PASS_KINDS,
2226
+ PASS_STATES,
2227
+ PAYLOAD_FORMAT_INDICATOR_VALUE,
2228
+ POINT_OF_INITIATION,
2229
+ PassMetadataSchema,
2230
+ PassSchema,
2231
+ RECEIPT_CHANNELS,
2232
+ RECEIPT_KINDS,
2233
+ REPLAY_WINDOW_MS,
2234
+ ReceiptPayloadSchema,
2235
+ ReceiptSchema,
2236
+ RedemptionSchema,
2237
+ bodySha256Hex,
2238
+ buildAuthorization,
2239
+ buildOAC,
2240
+ buildPass,
2241
+ buildPaymentRequest,
2242
+ buildReceipt,
2243
+ buildRedemption,
2244
+ canonicalJSONBytes,
2245
+ canonicalJSONStringify,
2246
+ canonicalRequestString,
2247
+ constantTimeEqual,
2248
+ crc16ccitt,
2249
+ crc16ccittHex,
2250
+ createHmacFetch,
2251
+ createPassesClient,
2252
+ createReceiptsClient,
2253
+ decodeAuthorizationQR,
2254
+ decodeBase45,
2255
+ decodePaymentRequestQR,
2256
+ encodeAuthorizationQR,
2257
+ encodeBase45,
2258
+ encodeNQR,
2259
+ encodePaymentRequestQR,
2260
+ formatAmount,
2261
+ generateDynamicQR,
2262
+ generateKeyPair,
2263
+ generateStaticQR,
2264
+ init,
2265
+ isPassWithinValidity,
2266
+ moneyMinorToNumber,
2267
+ normalizeE164,
2268
+ parseAmountInput,
2269
+ parseNQR,
2270
+ parseQR,
2271
+ publicKeyFromPrivate,
2272
+ readTLV,
2273
+ routingHint,
2274
+ sign,
2275
+ signAuthorization,
2276
+ signCanonical,
2277
+ signOAC,
2278
+ signPass,
2279
+ signPaymentRequest,
2280
+ signReceipt,
2281
+ signRedemption,
2282
+ signRequestHMAC,
2283
+ verify,
2284
+ verifyAuthorization,
2285
+ verifyCanonical,
2286
+ verifyOAC,
2287
+ verifyPass,
2288
+ verifyPaymentRequest,
2289
+ verifyReceipt,
2290
+ verifyRedemption,
2291
+ verifyRequestHMAC,
2292
+ writeTLV
2293
+ });
2294
+ //# sourceMappingURL=index.cjs.map