@nokinc-flur/sdk 0.1.7 → 1.0.2

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