@nokinc-flur/sdk 0.1.6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1593 @@
1
+ import { z } from 'zod';
2
+
3
+ type Money = {
4
+ amountMinor: bigint;
5
+ currency: string;
6
+ };
7
+ declare function parseAmountInput(value: string, currency: string): Money;
8
+ declare function formatAmount(amountMinor: bigint, currency: string): string;
9
+ declare function normalizeE164(input: string, defaultCountry?: string): string;
10
+ declare function moneyMinorToNumber(amountMinor: bigint): number;
11
+
12
+ type FlurClientOptions = {
13
+ baseUrl: string;
14
+ fetchImpl?: typeof fetch;
15
+ timeoutMs?: number;
16
+ getExtraHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
17
+ };
18
+ type OnboardingFallback = "SILENT_AUTH" | "OTP";
19
+ type OnboardingStartInput = {
20
+ phoneE164: string;
21
+ appInstanceId: string;
22
+ platform: "android" | "ios" | "web";
23
+ turnstileToken?: string;
24
+ appAttestation?: {
25
+ provider: "android" | "ios" | "web";
26
+ token: string;
27
+ };
28
+ firstName?: string;
29
+ lastName?: string;
30
+ };
31
+ type OnboardingStartResponse = {
32
+ requestId: string;
33
+ checkUrl?: string;
34
+ expiresInSec: number;
35
+ fallback: OnboardingFallback;
36
+ };
37
+ type OnboardingRiskReason = "SIM_SWAP_RECENT" | "ROAMING" | "CARRIER_CHANGED";
38
+ type OnboardingCompleteInput = {
39
+ requestId: string;
40
+ code: string;
41
+ appInstanceId: string;
42
+ fingerprintHash?: string;
43
+ };
44
+ type OnboardingCompleteResponse = {
45
+ sessionToken: string;
46
+ userId: string;
47
+ restricted: boolean;
48
+ risk_reasons: OnboardingRiskReason[];
49
+ stepUpRequired?: boolean;
50
+ riskStatus?: "ok" | "unavailable";
51
+ };
52
+ type RegisterDeviceInput = {
53
+ userId: string;
54
+ appInstanceId: string;
55
+ platform: string;
56
+ model?: string;
57
+ networkSignals: {
58
+ ip: string;
59
+ asn?: number;
60
+ country?: string;
61
+ carrier?: string;
62
+ };
63
+ };
64
+ type DeviceTrustState = "TRUSTED_PRIMARY" | "TRUSTED_SECONDARY" | "UNVERIFIED";
65
+ type RegisterDeviceResponse = {
66
+ deviceId: string;
67
+ fingerprintHash: string;
68
+ driftScore: number;
69
+ trustState: DeviceTrustState;
70
+ stepUpRequired: boolean;
71
+ };
72
+ type AuthRefreshInput = {
73
+ userId: string;
74
+ refreshToken: string;
75
+ appInstanceId: string;
76
+ fingerprintHash: string;
77
+ };
78
+ type AuthRefreshResponse = {
79
+ refreshToken: string;
80
+ stepUpRequired: boolean;
81
+ };
82
+ type AuthLogoutInput = {
83
+ userId: string;
84
+ refreshToken: string;
85
+ };
86
+ type PinSetInput = {
87
+ userId: string;
88
+ pin: string;
89
+ };
90
+ type PinVerifyInput = {
91
+ userId: string;
92
+ pin: string;
93
+ };
94
+ type RegisterSendDeviceKeyInput = {
95
+ userId: string;
96
+ deviceId: string;
97
+ publicKey: string;
98
+ };
99
+ type SendChallengeInput = {
100
+ userId: string;
101
+ deviceId: string;
102
+ };
103
+ type SendChallengeResponse = {
104
+ challengeId: string;
105
+ nonce: string;
106
+ expiresAt: string;
107
+ };
108
+ type SendVerifyInput = {
109
+ userId: string;
110
+ deviceId: string;
111
+ challengeId: string;
112
+ signature: string;
113
+ };
114
+ type SendVerifyResponse = {
115
+ sendAuthToken: string;
116
+ };
117
+ type RecipientResolveInput = {
118
+ identifier: string;
119
+ };
120
+ type RecipientResolveResponse = {
121
+ recipientUserId: string;
122
+ displayName: string;
123
+ normalizedIdentifier: string;
124
+ isActive: boolean;
125
+ };
126
+ type TransferInput = {
127
+ recipientIdentifier: string;
128
+ amountMinor: number;
129
+ currency: string;
130
+ sendAuthToken: string;
131
+ };
132
+ type TransferStatus = "SETTLED" | "PENDING_REVIEW" | "DECLINED";
133
+ type TransferResponse = {
134
+ transactionId: string;
135
+ status: TransferStatus;
136
+ userStatus: TransferStatus;
137
+ recipientName: string;
138
+ timestamp: string;
139
+ };
140
+ type TransactionDirection = "OUTGOING" | "INCOMING";
141
+ type AccountActivityItem = {
142
+ id: string;
143
+ type: string;
144
+ direction: TransactionDirection;
145
+ name: string;
146
+ identifier: string;
147
+ amountMinor: number;
148
+ currency: string;
149
+ status: string;
150
+ timestamp: string;
151
+ };
152
+ type AccountSummaryResponse = {
153
+ balance: number;
154
+ currency: string;
155
+ dailySendLimit: number;
156
+ dailySendRemaining: number;
157
+ kycTier: string;
158
+ kycStatus: string;
159
+ recentActivity: AccountActivityItem[];
160
+ };
161
+ type TransactionsListResponse = {
162
+ items: AccountActivityItem[];
163
+ nextCursor: string | null;
164
+ };
165
+ type TransactionDetailResponse = {
166
+ transactionId: string;
167
+ type: string;
168
+ direction: TransactionDirection;
169
+ counterpartyName: string;
170
+ counterpartyIdentifier: string;
171
+ amountMinor: number;
172
+ currency: string;
173
+ status: string;
174
+ timestamp: string;
175
+ };
176
+ type PushPlatform = "ios" | "android" | "web";
177
+ type PushRegisterInput = {
178
+ deviceId: string;
179
+ platform: PushPlatform;
180
+ token: string;
181
+ };
182
+ type CreatePayLinkResponse = {
183
+ token: string;
184
+ };
185
+ type ResolvePayLinkResponse = {
186
+ recipientUserId: string;
187
+ displayName: string;
188
+ normalizedIdentifier: string;
189
+ isActive: boolean;
190
+ };
191
+ type AuthorizedOptions = {
192
+ accessToken: string;
193
+ };
194
+ type CreateTransferOptions = AuthorizedOptions & {
195
+ deviceId: string;
196
+ idempotencyKey: string;
197
+ };
198
+ type ListTransactionsOptions = AuthorizedOptions & {
199
+ cursor?: string;
200
+ limit?: number;
201
+ };
202
+ type BiometricSigner = {
203
+ getOrCreateKeyPair(deviceId: string): Promise<string>;
204
+ sign(payload: string): Promise<string>;
205
+ };
206
+ type SendMoneyInput = {
207
+ recipientIdentifier: string;
208
+ money: Money;
209
+ sendAuthToken: string;
210
+ idempotencyKey?: string;
211
+ defaultCountry?: string;
212
+ };
213
+ type SendMoneyOptions = AuthorizedOptions & {
214
+ deviceId: string;
215
+ };
216
+ type AuthorizeSendWithBiometricInput = {
217
+ userId: string;
218
+ deviceId: string;
219
+ accessToken: string;
220
+ signer: BiometricSigner;
221
+ };
222
+ declare class FlurClient {
223
+ private readonly baseUrl;
224
+ private readonly fetchImpl;
225
+ private readonly timeoutMs;
226
+ private readonly getExtraHeaders?;
227
+ constructor(opts: FlurClientOptions);
228
+ health(): Promise<{
229
+ ok: boolean;
230
+ }>;
231
+ welcome(): Promise<{
232
+ message: string;
233
+ }>;
234
+ onboardingStart(input: OnboardingStartInput): Promise<OnboardingStartResponse>;
235
+ onboardingComplete(input: OnboardingCompleteInput): Promise<OnboardingCompleteResponse>;
236
+ registerDevice(input: RegisterDeviceInput, options: AuthorizedOptions): Promise<RegisterDeviceResponse>;
237
+ authRefresh(input: AuthRefreshInput): Promise<AuthRefreshResponse>;
238
+ authLogout(input: AuthLogoutInput, options: AuthorizedOptions): Promise<{
239
+ ok: boolean;
240
+ }>;
241
+ pinSet(input: PinSetInput, options: AuthorizedOptions): Promise<{
242
+ ok: boolean;
243
+ }>;
244
+ pinVerify(input: PinVerifyInput, options: AuthorizedOptions): Promise<{
245
+ ok: boolean;
246
+ }>;
247
+ registerSendDeviceKey(input: RegisterSendDeviceKeyInput, options: AuthorizedOptions): Promise<{
248
+ ok: boolean;
249
+ }>;
250
+ createSendChallenge(input: SendChallengeInput, options: AuthorizedOptions): Promise<SendChallengeResponse>;
251
+ verifySendChallenge(input: SendVerifyInput, options: AuthorizedOptions): Promise<SendVerifyResponse>;
252
+ registerBiometricDeviceKey(input: {
253
+ userId: string;
254
+ deviceId: string;
255
+ accessToken: string;
256
+ signer: BiometricSigner;
257
+ }): Promise<{
258
+ ok: boolean;
259
+ }>;
260
+ authorizeSendWithBiometric(input: AuthorizeSendWithBiometricInput): Promise<SendVerifyResponse>;
261
+ resolveRecipient(input: RecipientResolveInput, options: AuthorizedOptions): Promise<RecipientResolveResponse>;
262
+ createTransfer(input: TransferInput, options: CreateTransferOptions): Promise<TransferResponse>;
263
+ sendMoney(input: SendMoneyInput, options: SendMoneyOptions): Promise<TransferResponse>;
264
+ accountSummary(options: AuthorizedOptions): Promise<AccountSummaryResponse>;
265
+ listTransactions(options: ListTransactionsOptions): Promise<TransactionsListResponse>;
266
+ transactionDetail(transactionId: string, options: AuthorizedOptions): Promise<TransactionDetailResponse>;
267
+ registerPushToken(input: PushRegisterInput, options: AuthorizedOptions): Promise<{
268
+ ok: boolean;
269
+ }>;
270
+ createPayLink(options: AuthorizedOptions): Promise<CreatePayLinkResponse>;
271
+ resolvePayLink(token: string, options: AuthorizedOptions): Promise<ResolvePayLinkResponse>;
272
+ private requestJson;
273
+ }
274
+
275
+ type FlurErrorCode = 'NETWORK_ERROR' | 'HTTP_ERROR' | 'TIMEOUT' | 'UNKNOWN' | 'UNAUTHORIZED' | 'INVALID_REQUEST' | 'RATE_LIMITED' | 'NOT_FOUND' | 'USER_NOT_FOUND' | 'TOKEN_REPLAYED' | 'SESSION_MISMATCH' | 'PIN_INVALID' | 'PIN_LOCKED' | 'PIN_NOT_SET' | 'STEP_UP_REQUIRED' | 'DEVICE_KEY_NOT_REGISTERED' | 'CHALLENGE_INVALID' | 'CHALLENGE_EXPIRED' | 'DEVICE_KEY_REVOKED' | 'SIGNATURE_INVALID' | 'INVALID_RECIPIENT' | 'SEND_AUTH_INVALID' | 'IDEMPOTENCY_KEY_CONFLICT' | 'IDEMPOTENCY_IN_PROGRESS' | 'CANNOT_SEND_TO_SELF' | 'INSUFFICIENT_FUNDS';
276
+ declare class FlurError extends Error {
277
+ readonly code: FlurErrorCode;
278
+ readonly status?: number;
279
+ readonly details?: unknown;
280
+ readonly reqId?: string | null;
281
+ constructor(message: string, code: FlurErrorCode, opts?: {
282
+ status?: number;
283
+ details?: unknown;
284
+ reqId?: string | null;
285
+ });
286
+ }
287
+ /**
288
+ * Generic API error thrown by HTTP clients in `flur.passes` / `flur.receipts`
289
+ * for non-2xx responses. Lives at the SDK root so bounded-context modules
290
+ * (passes, receipts) never cross-import each other.
291
+ */
292
+ declare class FlurApiError extends Error {
293
+ readonly status: number;
294
+ readonly code: string;
295
+ readonly raw?: unknown;
296
+ constructor(status: number, code: string, message: string, raw?: unknown);
297
+ }
298
+ /** Thrown by `buildRedemption` when the pass is past its `validUntilMs`. */
299
+ declare class FlurExpiredError extends Error {
300
+ readonly code: "PASS_EXPIRED";
301
+ constructor(message?: string);
302
+ }
303
+ /** Thrown by `buildRedemption` when `counter` is not strictly greater than the
304
+ * caller-supplied `lastSeenCounter` (replay / out-of-order redemption attempt). */
305
+ declare class FlurReplayError extends Error {
306
+ readonly code: "PASS_REPLAY";
307
+ constructor(message?: string);
308
+ }
309
+ /** Thrown by `buildRedemption` when this redemption would breach the pass's
310
+ * `cumulativeCapKobo` given the caller-supplied `cumulativeUsedKobo`. */
311
+ declare class FlurCapExceededError extends Error {
312
+ readonly code: "PASS_CAP_EXCEEDED";
313
+ constructor(message?: string);
314
+ }
315
+
316
+ /**
317
+ * EMV / NQR Merchant-Presented top-level field IDs.
318
+ * See EMVCo MPM v1.1 §4 and NIBSS NQR specification.
319
+ */
320
+ declare const FIELD: {
321
+ readonly PAYLOAD_FORMAT_INDICATOR: "00";
322
+ readonly POINT_OF_INITIATION: "01";
323
+ readonly MERCHANT_CATEGORY_CODE: "52";
324
+ readonly TRANSACTION_CURRENCY: "53";
325
+ readonly TRANSACTION_AMOUNT: "54";
326
+ readonly TIP_OR_CONVENIENCE_INDICATOR: "55";
327
+ readonly VALUE_CONVENIENCE_FEE_FIXED: "56";
328
+ readonly VALUE_CONVENIENCE_FEE_PERCENT: "57";
329
+ readonly COUNTRY_CODE: "58";
330
+ readonly MERCHANT_NAME: "59";
331
+ readonly MERCHANT_CITY: "60";
332
+ readonly POSTAL_CODE: "61";
333
+ readonly ADDITIONAL_DATA_FIELD: "62";
334
+ readonly CRC: "63";
335
+ readonly MERCHANT_INFO_LANGUAGE: "64";
336
+ };
337
+ declare const ADDITIONAL_DATA_SUBFIELD: {
338
+ readonly BILL_NUMBER: "01";
339
+ readonly MOBILE_NUMBER: "02";
340
+ readonly STORE_LABEL: "03";
341
+ readonly LOYALTY_NUMBER: "04";
342
+ readonly REFERENCE_LABEL: "05";
343
+ readonly CUSTOMER_LABEL: "06";
344
+ readonly TERMINAL_LABEL: "07";
345
+ readonly PURPOSE_OF_TRANSACTION: "08";
346
+ };
347
+ declare const POINT_OF_INITIATION: {
348
+ readonly STATIC: "11";
349
+ readonly DYNAMIC: "12";
350
+ };
351
+ declare const PAYLOAD_FORMAT_INDICATOR_VALUE = "01";
352
+ declare const NGN_CURRENCY_CODE = "566";
353
+ declare const NG_COUNTRY_CODE = "NG";
354
+
355
+ /**
356
+ * CRC-16/CCITT-FALSE — polynomial 0x1021, initial 0xFFFF, no reflection,
357
+ * no XOR-out. Specified by EMVCo MPM for tag 63 (CRC).
358
+ */
359
+ declare function crc16ccitt(bytes: Uint8Array): number;
360
+ declare function crc16ccittHex(bytes: Uint8Array): string;
361
+
362
+ /**
363
+ * EMV TLV (Tag-Length-Value) primitives.
364
+ * - Tag is 2 ASCII digits.
365
+ * - Length is 2 ASCII digits (00..99).
366
+ * - Value is up to 99 ASCII characters.
367
+ */
368
+ type TLVField = {
369
+ tag: string;
370
+ value: string;
371
+ };
372
+ declare function writeTLV(tag: string, value: string): string;
373
+ declare function readTLV(buf: string): TLVField[];
374
+
375
+ /**
376
+ * High-level EMV/NQR payload types.
377
+ *
378
+ * Merchant Account Information (tags 02..51) is issuer-specific. NQR uses
379
+ * one of these templates with a globally unique identifier (GUID) and a
380
+ * merchant identifier issued by the sponsor bank or NIBSS.
381
+ */
382
+ type MerchantAccountInfo = {
383
+ /** Tag in 02..51 (typically "26" for NQR). */
384
+ tag: string;
385
+ children: Array<{
386
+ tag: string;
387
+ value: string;
388
+ }>;
389
+ };
390
+ type AdditionalData = {
391
+ billNumber?: string;
392
+ mobileNumber?: string;
393
+ storeLabel?: string;
394
+ loyaltyNumber?: string;
395
+ referenceLabel?: string;
396
+ customerLabel?: string;
397
+ terminalLabel?: string;
398
+ purposeOfTransaction?: string;
399
+ };
400
+ type NQRPayloadInput = {
401
+ /** "static" → POI 11, "dynamic" → POI 12. Dynamic typically has amount. */
402
+ pointOfInitiation: 'static' | 'dynamic';
403
+ merchantAccountInfo: MerchantAccountInfo;
404
+ /** ISO 18245 Merchant Category Code. 4 digits. */
405
+ merchantCategoryCode: string;
406
+ /** Up to 25 chars per EMV spec. */
407
+ merchantName: string;
408
+ /** Up to 15 chars per EMV spec. */
409
+ merchantCity: string;
410
+ /** Decimal string (e.g. "100.00"). Required when dynamic. */
411
+ transactionAmount?: string;
412
+ postalCode?: string;
413
+ additionalData?: AdditionalData;
414
+ };
415
+ type ParsedNQR = {
416
+ payloadFormatIndicator: string;
417
+ pointOfInitiation: 'static' | 'dynamic' | 'unknown';
418
+ merchantAccountInfo: MerchantAccountInfo[];
419
+ merchantCategoryCode: string;
420
+ transactionCurrency: string;
421
+ transactionAmount?: string;
422
+ countryCode: string;
423
+ merchantName: string;
424
+ merchantCity: string;
425
+ postalCode?: string;
426
+ additionalData?: AdditionalData;
427
+ /** Convenience: extracted referenceLabel (ADF tag 05). */
428
+ flurReference?: string;
429
+ raw: string;
430
+ };
431
+
432
+ declare function encodeNQR(input: NQRPayloadInput): string;
433
+
434
+ declare class NQRParseError extends Error {
435
+ readonly path: string;
436
+ constructor(message: string, path?: string);
437
+ }
438
+ declare function parseNQR(payload: string): ParsedNQR;
439
+
440
+ type RoutingHint = 'flur-onus' | 'nibss' | 'unknown';
441
+ /**
442
+ * Decide whether a parsed NQR payload should be routed on-us through Flur
443
+ * (because it carries a Flur reference label) or sent through standard NIBSS.
444
+ */
445
+ declare function routingHint(parsed: ParsedNQR): RoutingHint;
446
+
447
+ /**
448
+ * Deterministic JSON serialization for cryptographic signing.
449
+ *
450
+ * Rules:
451
+ * - Object keys sorted lexicographically (recursively).
452
+ * - Arrays preserve order.
453
+ * - undefined / NaN / Infinity / bigint / function / symbol are rejected.
454
+ * Signatures must be unambiguous; ambiguous values are bugs.
455
+ */
456
+ declare function canonicalJSONStringify(value: unknown): string;
457
+ declare function canonicalJSONBytes(value: unknown): Uint8Array;
458
+
459
+ /**
460
+ * Constant-time equality for byte arrays. Returns false fast on length
461
+ * mismatch (length is not secret); compares byte-wise without short-circuit
462
+ * for equal-length inputs.
463
+ */
464
+ declare function constantTimeEqual(a: Uint8Array, b: Uint8Array): boolean;
465
+
466
+ type Ed25519KeyPair = {
467
+ privateKey: Uint8Array;
468
+ publicKey: Uint8Array;
469
+ };
470
+ declare function generateKeyPair(): Ed25519KeyPair;
471
+ declare function publicKeyFromPrivate(privateKey: Uint8Array): Uint8Array;
472
+ declare function sign(message: Uint8Array, privateKey: Uint8Array): Uint8Array;
473
+ declare function verify(message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean;
474
+ /**
475
+ * Sign a value by canonicalizing it to JSON bytes first.
476
+ * Use this for any object payload that must round-trip across devices.
477
+ */
478
+ declare function signCanonical(value: unknown, privateKey: Uint8Array): Uint8Array;
479
+ declare function verifyCanonical(value: unknown, signature: Uint8Array, publicKey: Uint8Array): boolean;
480
+
481
+ declare const OAC_DEFAULT_PER_TX_KOBO = 500000;
482
+ declare const OAC_DEFAULT_CUMULATIVE_KOBO = 2000000;
483
+ declare const OAC_DEFAULT_VALIDITY_MS: number;
484
+ declare const OACSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
485
+ userId: z.ZodString;
486
+ deviceId: z.ZodString;
487
+ devicePublicKey: z.ZodString;
488
+ perTxCapKobo: z.ZodNumber;
489
+ cumulativeCapKobo: z.ZodNumber;
490
+ validFromMs: z.ZodNumber;
491
+ validUntilMs: z.ZodNumber;
492
+ counterSeed: z.ZodNumber;
493
+ nonce: z.ZodString;
494
+ issuerSig: z.ZodString;
495
+ }, "strip", z.ZodTypeAny, {
496
+ userId: string;
497
+ deviceId: string;
498
+ nonce: string;
499
+ devicePublicKey: string;
500
+ perTxCapKobo: number;
501
+ cumulativeCapKobo: number;
502
+ validFromMs: number;
503
+ validUntilMs: number;
504
+ counterSeed: number;
505
+ issuerSig: string;
506
+ }, {
507
+ userId: string;
508
+ deviceId: string;
509
+ nonce: string;
510
+ devicePublicKey: string;
511
+ perTxCapKobo: number;
512
+ cumulativeCapKobo: number;
513
+ validFromMs: number;
514
+ validUntilMs: number;
515
+ counterSeed: number;
516
+ issuerSig: string;
517
+ }>, {
518
+ userId: string;
519
+ deviceId: string;
520
+ nonce: string;
521
+ devicePublicKey: string;
522
+ perTxCapKobo: number;
523
+ cumulativeCapKobo: number;
524
+ validFromMs: number;
525
+ validUntilMs: number;
526
+ counterSeed: number;
527
+ issuerSig: string;
528
+ }, {
529
+ userId: string;
530
+ deviceId: string;
531
+ nonce: string;
532
+ devicePublicKey: string;
533
+ perTxCapKobo: number;
534
+ cumulativeCapKobo: number;
535
+ validFromMs: number;
536
+ validUntilMs: number;
537
+ counterSeed: number;
538
+ issuerSig: string;
539
+ }>, {
540
+ userId: string;
541
+ deviceId: string;
542
+ nonce: string;
543
+ devicePublicKey: string;
544
+ perTxCapKobo: number;
545
+ cumulativeCapKobo: number;
546
+ validFromMs: number;
547
+ validUntilMs: number;
548
+ counterSeed: number;
549
+ issuerSig: string;
550
+ }, {
551
+ userId: string;
552
+ deviceId: string;
553
+ nonce: string;
554
+ devicePublicKey: string;
555
+ perTxCapKobo: number;
556
+ cumulativeCapKobo: number;
557
+ validFromMs: number;
558
+ validUntilMs: number;
559
+ counterSeed: number;
560
+ issuerSig: string;
561
+ }>;
562
+ type OAC = z.infer<typeof OACSchema>;
563
+ type UnsignedOAC = Omit<OAC, 'issuerSig'>;
564
+ declare function buildOAC(input: {
565
+ userId: string;
566
+ deviceId: string;
567
+ devicePublicKey: Uint8Array | string;
568
+ perTxCapKobo?: number;
569
+ cumulativeCapKobo?: number;
570
+ validFromMs: number;
571
+ validUntilMs: number;
572
+ counterSeed?: number;
573
+ nonce: string;
574
+ }): UnsignedOAC;
575
+ declare function signOAC(unsigned: UnsignedOAC, issuerPrivateKey: Uint8Array): OAC;
576
+ declare function verifyOAC(oac: OAC, issuerPublicKey: Uint8Array): boolean;
577
+
578
+ declare function encodeBase45(bytes: Uint8Array): string;
579
+ declare function decodeBase45(s: string): Uint8Array;
580
+
581
+ declare const OfflinePaymentRequestSchema: z.ZodObject<{
582
+ reference: z.ZodString;
583
+ amountKobo: z.ZodNumber;
584
+ merchantOAC: z.ZodEffects<z.ZodEffects<z.ZodObject<{
585
+ userId: z.ZodString;
586
+ deviceId: z.ZodString;
587
+ devicePublicKey: z.ZodString;
588
+ perTxCapKobo: z.ZodNumber;
589
+ cumulativeCapKobo: z.ZodNumber;
590
+ validFromMs: z.ZodNumber;
591
+ validUntilMs: z.ZodNumber;
592
+ counterSeed: z.ZodNumber;
593
+ nonce: z.ZodString;
594
+ issuerSig: z.ZodString;
595
+ }, "strip", z.ZodTypeAny, {
596
+ userId: string;
597
+ deviceId: string;
598
+ nonce: string;
599
+ devicePublicKey: string;
600
+ perTxCapKobo: number;
601
+ cumulativeCapKobo: number;
602
+ validFromMs: number;
603
+ validUntilMs: number;
604
+ counterSeed: number;
605
+ issuerSig: string;
606
+ }, {
607
+ userId: string;
608
+ deviceId: string;
609
+ nonce: string;
610
+ devicePublicKey: string;
611
+ perTxCapKobo: number;
612
+ cumulativeCapKobo: number;
613
+ validFromMs: number;
614
+ validUntilMs: number;
615
+ counterSeed: number;
616
+ issuerSig: string;
617
+ }>, {
618
+ userId: string;
619
+ deviceId: string;
620
+ nonce: string;
621
+ devicePublicKey: string;
622
+ perTxCapKobo: number;
623
+ cumulativeCapKobo: number;
624
+ validFromMs: number;
625
+ validUntilMs: number;
626
+ counterSeed: number;
627
+ issuerSig: string;
628
+ }, {
629
+ userId: string;
630
+ deviceId: string;
631
+ nonce: string;
632
+ devicePublicKey: string;
633
+ perTxCapKobo: number;
634
+ cumulativeCapKobo: number;
635
+ validFromMs: number;
636
+ validUntilMs: number;
637
+ counterSeed: number;
638
+ issuerSig: string;
639
+ }>, {
640
+ userId: string;
641
+ deviceId: string;
642
+ nonce: string;
643
+ devicePublicKey: string;
644
+ perTxCapKobo: number;
645
+ cumulativeCapKobo: number;
646
+ validFromMs: number;
647
+ validUntilMs: number;
648
+ counterSeed: number;
649
+ issuerSig: string;
650
+ }, {
651
+ userId: string;
652
+ deviceId: string;
653
+ nonce: string;
654
+ devicePublicKey: string;
655
+ perTxCapKobo: number;
656
+ cumulativeCapKobo: number;
657
+ validFromMs: number;
658
+ validUntilMs: number;
659
+ counterSeed: number;
660
+ issuerSig: string;
661
+ }>;
662
+ expiresAtMs: z.ZodNumber;
663
+ merchantSig: z.ZodString;
664
+ }, "strip", z.ZodTypeAny, {
665
+ reference: string;
666
+ amountKobo: number;
667
+ merchantOAC: {
668
+ userId: string;
669
+ deviceId: string;
670
+ nonce: string;
671
+ devicePublicKey: string;
672
+ perTxCapKobo: number;
673
+ cumulativeCapKobo: number;
674
+ validFromMs: number;
675
+ validUntilMs: number;
676
+ counterSeed: number;
677
+ issuerSig: string;
678
+ };
679
+ expiresAtMs: number;
680
+ merchantSig: string;
681
+ }, {
682
+ reference: string;
683
+ amountKobo: number;
684
+ merchantOAC: {
685
+ userId: string;
686
+ deviceId: string;
687
+ nonce: string;
688
+ devicePublicKey: string;
689
+ perTxCapKobo: number;
690
+ cumulativeCapKobo: number;
691
+ validFromMs: number;
692
+ validUntilMs: number;
693
+ counterSeed: number;
694
+ issuerSig: string;
695
+ };
696
+ expiresAtMs: number;
697
+ merchantSig: string;
698
+ }>;
699
+ type OfflinePaymentRequest = z.infer<typeof OfflinePaymentRequestSchema>;
700
+ type UnsignedOfflinePaymentRequest = Omit<OfflinePaymentRequest, 'merchantSig'>;
701
+ declare const OfflinePaymentAuthorizationSchema: z.ZodObject<{
702
+ request: z.ZodObject<{
703
+ reference: z.ZodString;
704
+ amountKobo: z.ZodNumber;
705
+ merchantOAC: z.ZodEffects<z.ZodEffects<z.ZodObject<{
706
+ userId: z.ZodString;
707
+ deviceId: z.ZodString;
708
+ devicePublicKey: z.ZodString;
709
+ perTxCapKobo: z.ZodNumber;
710
+ cumulativeCapKobo: z.ZodNumber;
711
+ validFromMs: z.ZodNumber;
712
+ validUntilMs: z.ZodNumber;
713
+ counterSeed: z.ZodNumber;
714
+ nonce: z.ZodString;
715
+ issuerSig: z.ZodString;
716
+ }, "strip", z.ZodTypeAny, {
717
+ userId: string;
718
+ deviceId: string;
719
+ nonce: string;
720
+ devicePublicKey: string;
721
+ perTxCapKobo: number;
722
+ cumulativeCapKobo: number;
723
+ validFromMs: number;
724
+ validUntilMs: number;
725
+ counterSeed: number;
726
+ issuerSig: string;
727
+ }, {
728
+ userId: string;
729
+ deviceId: string;
730
+ nonce: string;
731
+ devicePublicKey: string;
732
+ perTxCapKobo: number;
733
+ cumulativeCapKobo: number;
734
+ validFromMs: number;
735
+ validUntilMs: number;
736
+ counterSeed: number;
737
+ issuerSig: string;
738
+ }>, {
739
+ userId: string;
740
+ deviceId: string;
741
+ nonce: string;
742
+ devicePublicKey: string;
743
+ perTxCapKobo: number;
744
+ cumulativeCapKobo: number;
745
+ validFromMs: number;
746
+ validUntilMs: number;
747
+ counterSeed: number;
748
+ issuerSig: string;
749
+ }, {
750
+ userId: string;
751
+ deviceId: string;
752
+ nonce: string;
753
+ devicePublicKey: string;
754
+ perTxCapKobo: number;
755
+ cumulativeCapKobo: number;
756
+ validFromMs: number;
757
+ validUntilMs: number;
758
+ counterSeed: number;
759
+ issuerSig: string;
760
+ }>, {
761
+ userId: string;
762
+ deviceId: string;
763
+ nonce: string;
764
+ devicePublicKey: string;
765
+ perTxCapKobo: number;
766
+ cumulativeCapKobo: number;
767
+ validFromMs: number;
768
+ validUntilMs: number;
769
+ counterSeed: number;
770
+ issuerSig: string;
771
+ }, {
772
+ userId: string;
773
+ deviceId: string;
774
+ nonce: string;
775
+ devicePublicKey: string;
776
+ perTxCapKobo: number;
777
+ cumulativeCapKobo: number;
778
+ validFromMs: number;
779
+ validUntilMs: number;
780
+ counterSeed: number;
781
+ issuerSig: string;
782
+ }>;
783
+ expiresAtMs: z.ZodNumber;
784
+ merchantSig: z.ZodString;
785
+ }, "strip", z.ZodTypeAny, {
786
+ reference: string;
787
+ amountKobo: number;
788
+ merchantOAC: {
789
+ userId: string;
790
+ deviceId: string;
791
+ nonce: string;
792
+ devicePublicKey: string;
793
+ perTxCapKobo: number;
794
+ cumulativeCapKobo: number;
795
+ validFromMs: number;
796
+ validUntilMs: number;
797
+ counterSeed: number;
798
+ issuerSig: string;
799
+ };
800
+ expiresAtMs: number;
801
+ merchantSig: string;
802
+ }, {
803
+ reference: string;
804
+ amountKobo: number;
805
+ merchantOAC: {
806
+ userId: string;
807
+ deviceId: string;
808
+ nonce: string;
809
+ devicePublicKey: string;
810
+ perTxCapKobo: number;
811
+ cumulativeCapKobo: number;
812
+ validFromMs: number;
813
+ validUntilMs: number;
814
+ counterSeed: number;
815
+ issuerSig: string;
816
+ };
817
+ expiresAtMs: number;
818
+ merchantSig: string;
819
+ }>;
820
+ payerOAC: z.ZodEffects<z.ZodEffects<z.ZodObject<{
821
+ userId: z.ZodString;
822
+ deviceId: z.ZodString;
823
+ devicePublicKey: z.ZodString;
824
+ perTxCapKobo: z.ZodNumber;
825
+ cumulativeCapKobo: z.ZodNumber;
826
+ validFromMs: z.ZodNumber;
827
+ validUntilMs: z.ZodNumber;
828
+ counterSeed: z.ZodNumber;
829
+ nonce: z.ZodString;
830
+ issuerSig: z.ZodString;
831
+ }, "strip", z.ZodTypeAny, {
832
+ userId: string;
833
+ deviceId: string;
834
+ nonce: string;
835
+ devicePublicKey: string;
836
+ perTxCapKobo: number;
837
+ cumulativeCapKobo: number;
838
+ validFromMs: number;
839
+ validUntilMs: number;
840
+ counterSeed: number;
841
+ issuerSig: string;
842
+ }, {
843
+ userId: string;
844
+ deviceId: string;
845
+ nonce: string;
846
+ devicePublicKey: string;
847
+ perTxCapKobo: number;
848
+ cumulativeCapKobo: number;
849
+ validFromMs: number;
850
+ validUntilMs: number;
851
+ counterSeed: number;
852
+ issuerSig: string;
853
+ }>, {
854
+ userId: string;
855
+ deviceId: string;
856
+ nonce: string;
857
+ devicePublicKey: string;
858
+ perTxCapKobo: number;
859
+ cumulativeCapKobo: number;
860
+ validFromMs: number;
861
+ validUntilMs: number;
862
+ counterSeed: number;
863
+ issuerSig: string;
864
+ }, {
865
+ userId: string;
866
+ deviceId: string;
867
+ nonce: string;
868
+ devicePublicKey: string;
869
+ perTxCapKobo: number;
870
+ cumulativeCapKobo: number;
871
+ validFromMs: number;
872
+ validUntilMs: number;
873
+ counterSeed: number;
874
+ issuerSig: string;
875
+ }>, {
876
+ userId: string;
877
+ deviceId: string;
878
+ nonce: string;
879
+ devicePublicKey: string;
880
+ perTxCapKobo: number;
881
+ cumulativeCapKobo: number;
882
+ validFromMs: number;
883
+ validUntilMs: number;
884
+ counterSeed: number;
885
+ issuerSig: string;
886
+ }, {
887
+ userId: string;
888
+ deviceId: string;
889
+ nonce: string;
890
+ devicePublicKey: string;
891
+ perTxCapKobo: number;
892
+ cumulativeCapKobo: number;
893
+ validFromMs: number;
894
+ validUntilMs: number;
895
+ counterSeed: number;
896
+ issuerSig: string;
897
+ }>;
898
+ payerCounter: z.ZodNumber;
899
+ payerSig: z.ZodString;
900
+ }, "strip", z.ZodTypeAny, {
901
+ request: {
902
+ reference: string;
903
+ amountKobo: number;
904
+ merchantOAC: {
905
+ userId: string;
906
+ deviceId: string;
907
+ nonce: string;
908
+ devicePublicKey: string;
909
+ perTxCapKobo: number;
910
+ cumulativeCapKobo: number;
911
+ validFromMs: number;
912
+ validUntilMs: number;
913
+ counterSeed: number;
914
+ issuerSig: string;
915
+ };
916
+ expiresAtMs: number;
917
+ merchantSig: string;
918
+ };
919
+ payerOAC: {
920
+ userId: string;
921
+ deviceId: string;
922
+ nonce: string;
923
+ devicePublicKey: string;
924
+ perTxCapKobo: number;
925
+ cumulativeCapKobo: number;
926
+ validFromMs: number;
927
+ validUntilMs: number;
928
+ counterSeed: number;
929
+ issuerSig: string;
930
+ };
931
+ payerCounter: number;
932
+ payerSig: string;
933
+ }, {
934
+ request: {
935
+ reference: string;
936
+ amountKobo: number;
937
+ merchantOAC: {
938
+ userId: string;
939
+ deviceId: string;
940
+ nonce: string;
941
+ devicePublicKey: string;
942
+ perTxCapKobo: number;
943
+ cumulativeCapKobo: number;
944
+ validFromMs: number;
945
+ validUntilMs: number;
946
+ counterSeed: number;
947
+ issuerSig: string;
948
+ };
949
+ expiresAtMs: number;
950
+ merchantSig: string;
951
+ };
952
+ payerOAC: {
953
+ userId: string;
954
+ deviceId: string;
955
+ nonce: string;
956
+ devicePublicKey: string;
957
+ perTxCapKobo: number;
958
+ cumulativeCapKobo: number;
959
+ validFromMs: number;
960
+ validUntilMs: number;
961
+ counterSeed: number;
962
+ issuerSig: string;
963
+ };
964
+ payerCounter: number;
965
+ payerSig: string;
966
+ }>;
967
+ type OfflinePaymentAuthorization = z.infer<typeof OfflinePaymentAuthorizationSchema>;
968
+ type UnsignedOfflinePaymentAuthorization = Omit<OfflinePaymentAuthorization, 'payerSig'>;
969
+ declare function buildPaymentRequest(input: {
970
+ reference: string;
971
+ amountKobo: number;
972
+ merchantOAC: OAC;
973
+ expiresAtMs: number;
974
+ }): UnsignedOfflinePaymentRequest;
975
+ declare function signPaymentRequest(unsigned: UnsignedOfflinePaymentRequest, merchantDevicePrivateKey: Uint8Array): OfflinePaymentRequest;
976
+ declare function verifyPaymentRequest(req: OfflinePaymentRequest, issuerPublicKey: Uint8Array): boolean;
977
+ declare function buildAuthorization(input: {
978
+ request: OfflinePaymentRequest;
979
+ payerOAC: OAC;
980
+ payerCounter: number;
981
+ }): UnsignedOfflinePaymentAuthorization;
982
+ declare function signAuthorization(unsigned: UnsignedOfflinePaymentAuthorization, payerDevicePrivateKey: Uint8Array): OfflinePaymentAuthorization;
983
+ declare function verifyAuthorization(auth: OfflinePaymentAuthorization, issuerPublicKey: Uint8Array): boolean;
984
+ declare function encodePaymentRequestQR(req: OfflinePaymentRequest): string;
985
+ declare function decodePaymentRequestQR(s: string): OfflinePaymentRequest;
986
+ declare function encodeAuthorizationQR(auth: OfflinePaymentAuthorization): string;
987
+ declare function decodeAuthorizationQR(s: string): OfflinePaymentAuthorization;
988
+
989
+ declare function bodySha256Hex(body: string): string;
990
+ declare function canonicalRequestString(input: {
991
+ method: string;
992
+ path: string;
993
+ timestamp: string;
994
+ nonce: string;
995
+ body: string;
996
+ }): string;
997
+ declare function signRequestHMAC(input: {
998
+ method: string;
999
+ path: string;
1000
+ timestamp: string;
1001
+ nonce: string;
1002
+ body: string;
1003
+ apiSecret: string;
1004
+ }): string;
1005
+ declare function verifyRequestHMAC(input: {
1006
+ method: string;
1007
+ path: string;
1008
+ timestamp: string;
1009
+ nonce: string;
1010
+ body: string;
1011
+ apiSecret: string;
1012
+ signature: string;
1013
+ }): boolean;
1014
+
1015
+ declare const REPLAY_WINDOW_MS: number;
1016
+ type HmacFetchOptions = {
1017
+ apiKey: string;
1018
+ apiSecret: string;
1019
+ fetchImpl?: typeof fetch;
1020
+ nowMs?: () => number;
1021
+ nonceFn?: () => string;
1022
+ /** Optional scope claim forwarded as `X-Flur-Scope` (comma-joined). Backend remains authoritative. */
1023
+ scope?: readonly string[];
1024
+ };
1025
+ declare function createHmacFetch(opts: HmacFetchOptions): typeof fetch;
1026
+
1027
+ declare const PASS_KINDS: readonly ["ride-ticket", "transit-pass", "event-ticket", "voucher", "loyalty", "receipt-link"];
1028
+ type PassKind = (typeof PASS_KINDS)[number];
1029
+ declare const PASS_STATES: readonly ["issued", "active", "redeemed", "expired", "revoked"];
1030
+ type PassState = (typeof PASS_STATES)[number];
1031
+ /** JSON-serialisable metadata payload — bounded to keep QR/offline encodings tractable. */
1032
+ declare const PassMetadataSchema: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
1033
+ type PassMetadata = z.infer<typeof PassMetadataSchema>;
1034
+ declare const PassSchema: z.ZodEffects<z.ZodObject<{
1035
+ passId: z.ZodString;
1036
+ /** Optional client/template grouping id (server may omit). */
1037
+ templateId: z.ZodOptional<z.ZodString>;
1038
+ /** Optional human-facing holder identity (server may omit). The cryptographic binding
1039
+ * is `holderDevicePubkey` below. */
1040
+ holderUserId: z.ZodOptional<z.ZodString>;
1041
+ kind: z.ZodEnum<["ride-ticket", "transit-pass", "event-ticket", "voucher", "loyalty", "receipt-link"]>;
1042
+ issuerId: z.ZodString;
1043
+ issuedAtMs: z.ZodNumber;
1044
+ validFromMs: z.ZodNumber;
1045
+ validUntilMs: z.ZodNumber;
1046
+ state: z.ZodEnum<["issued", "active", "redeemed", "expired", "revoked"]>;
1047
+ metadata: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
1048
+ nonce: z.ZodString;
1049
+ /** Device id this pass is bound to (FK to backend `device_keys`). */
1050
+ holderDeviceId: z.ZodString;
1051
+ /** 32-byte hex Ed25519 public key of the bound device. The redemption signature
1052
+ * is verified against this key — it is the security-critical binding. */
1053
+ holderDevicePubkey: z.ZodString;
1054
+ /** Optional fixed amount for monetary passes (vouchers, gift cards) in kobo. */
1055
+ amountKobo: z.ZodOptional<z.ZodNumber>;
1056
+ /** ISO-4217-ish currency code; required on the wire. SDK builders default to NGN. */
1057
+ currency: z.ZodString;
1058
+ /** Monotonic redemption counter floor. Redemption.counter MUST be > counterSeed. */
1059
+ counterSeed: z.ZodNumber;
1060
+ /** Optional cumulative spend cap in kobo across all redemptions of this pass. */
1061
+ cumulativeCapKobo: z.ZodOptional<z.ZodNumber>;
1062
+ issuerSig: z.ZodString;
1063
+ }, "strip", z.ZodTypeAny, {
1064
+ nonce: string;
1065
+ currency: string;
1066
+ validFromMs: number;
1067
+ validUntilMs: number;
1068
+ counterSeed: number;
1069
+ issuerSig: string;
1070
+ passId: string;
1071
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1072
+ issuerId: string;
1073
+ issuedAtMs: number;
1074
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1075
+ metadata: Record<string, string | number | boolean | null>;
1076
+ holderDeviceId: string;
1077
+ holderDevicePubkey: string;
1078
+ cumulativeCapKobo?: number | undefined;
1079
+ amountKobo?: number | undefined;
1080
+ templateId?: string | undefined;
1081
+ holderUserId?: string | undefined;
1082
+ }, {
1083
+ nonce: string;
1084
+ currency: string;
1085
+ validFromMs: number;
1086
+ validUntilMs: number;
1087
+ counterSeed: number;
1088
+ issuerSig: string;
1089
+ passId: string;
1090
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1091
+ issuerId: string;
1092
+ issuedAtMs: number;
1093
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1094
+ metadata: Record<string, string | number | boolean | null>;
1095
+ holderDeviceId: string;
1096
+ holderDevicePubkey: string;
1097
+ cumulativeCapKobo?: number | undefined;
1098
+ amountKobo?: number | undefined;
1099
+ templateId?: string | undefined;
1100
+ holderUserId?: string | undefined;
1101
+ }>, {
1102
+ nonce: string;
1103
+ currency: string;
1104
+ validFromMs: number;
1105
+ validUntilMs: number;
1106
+ counterSeed: number;
1107
+ issuerSig: string;
1108
+ passId: string;
1109
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1110
+ issuerId: string;
1111
+ issuedAtMs: number;
1112
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1113
+ metadata: Record<string, string | number | boolean | null>;
1114
+ holderDeviceId: string;
1115
+ holderDevicePubkey: string;
1116
+ cumulativeCapKobo?: number | undefined;
1117
+ amountKobo?: number | undefined;
1118
+ templateId?: string | undefined;
1119
+ holderUserId?: string | undefined;
1120
+ }, {
1121
+ nonce: string;
1122
+ currency: string;
1123
+ validFromMs: number;
1124
+ validUntilMs: number;
1125
+ counterSeed: number;
1126
+ issuerSig: string;
1127
+ passId: string;
1128
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1129
+ issuerId: string;
1130
+ issuedAtMs: number;
1131
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1132
+ metadata: Record<string, string | number | boolean | null>;
1133
+ holderDeviceId: string;
1134
+ holderDevicePubkey: string;
1135
+ cumulativeCapKobo?: number | undefined;
1136
+ amountKobo?: number | undefined;
1137
+ templateId?: string | undefined;
1138
+ holderUserId?: string | undefined;
1139
+ }>;
1140
+ type Pass = z.infer<typeof PassSchema>;
1141
+ type UnsignedPass = Omit<Pass, 'issuerSig'>;
1142
+ type BuildPassInput = {
1143
+ passId: string;
1144
+ templateId?: string;
1145
+ holderUserId?: string;
1146
+ kind: PassKind;
1147
+ issuerId: string;
1148
+ issuedAtMs: number;
1149
+ validFromMs: number;
1150
+ validUntilMs: number;
1151
+ state: PassState;
1152
+ metadata: PassMetadata;
1153
+ nonce: string;
1154
+ holderDeviceId: string;
1155
+ holderDevicePubkey: string;
1156
+ amountKobo?: number;
1157
+ currency?: string;
1158
+ counterSeed: number;
1159
+ cumulativeCapKobo?: number;
1160
+ };
1161
+ declare function buildPass(input: BuildPassInput): UnsignedPass;
1162
+ declare function signPass(unsigned: UnsignedPass, issuerPrivateKey: Uint8Array): Pass;
1163
+ declare function verifyPass(pass: Pass, issuerPublicKey: Uint8Array): boolean;
1164
+ /**
1165
+ * Validity window check is done separately from signature verification so callers can
1166
+ * decide their clock-skew tolerance.
1167
+ */
1168
+ declare function isPassWithinValidity(pass: Pass, nowMs: number): boolean;
1169
+
1170
+ declare const RedemptionSchema: z.ZodObject<{
1171
+ pass: z.ZodEffects<z.ZodObject<{
1172
+ passId: z.ZodString;
1173
+ templateId: z.ZodOptional<z.ZodString>;
1174
+ holderUserId: z.ZodOptional<z.ZodString>;
1175
+ kind: z.ZodEnum<["ride-ticket", "transit-pass", "event-ticket", "voucher", "loyalty", "receipt-link"]>;
1176
+ issuerId: z.ZodString;
1177
+ issuedAtMs: z.ZodNumber;
1178
+ validFromMs: z.ZodNumber;
1179
+ validUntilMs: z.ZodNumber;
1180
+ state: z.ZodEnum<["issued", "active", "redeemed", "expired", "revoked"]>;
1181
+ metadata: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
1182
+ nonce: z.ZodString;
1183
+ holderDeviceId: z.ZodString;
1184
+ holderDevicePubkey: z.ZodString;
1185
+ amountKobo: z.ZodOptional<z.ZodNumber>;
1186
+ currency: z.ZodString;
1187
+ counterSeed: z.ZodNumber;
1188
+ cumulativeCapKobo: z.ZodOptional<z.ZodNumber>;
1189
+ issuerSig: z.ZodString;
1190
+ }, "strip", z.ZodTypeAny, {
1191
+ nonce: string;
1192
+ currency: string;
1193
+ validFromMs: number;
1194
+ validUntilMs: number;
1195
+ counterSeed: number;
1196
+ issuerSig: string;
1197
+ passId: string;
1198
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1199
+ issuerId: string;
1200
+ issuedAtMs: number;
1201
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1202
+ metadata: Record<string, string | number | boolean | null>;
1203
+ holderDeviceId: string;
1204
+ holderDevicePubkey: string;
1205
+ cumulativeCapKobo?: number | undefined;
1206
+ amountKobo?: number | undefined;
1207
+ templateId?: string | undefined;
1208
+ holderUserId?: string | undefined;
1209
+ }, {
1210
+ nonce: string;
1211
+ currency: string;
1212
+ validFromMs: number;
1213
+ validUntilMs: number;
1214
+ counterSeed: number;
1215
+ issuerSig: string;
1216
+ passId: string;
1217
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1218
+ issuerId: string;
1219
+ issuedAtMs: number;
1220
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1221
+ metadata: Record<string, string | number | boolean | null>;
1222
+ holderDeviceId: string;
1223
+ holderDevicePubkey: string;
1224
+ cumulativeCapKobo?: number | undefined;
1225
+ amountKobo?: number | undefined;
1226
+ templateId?: string | undefined;
1227
+ holderUserId?: string | undefined;
1228
+ }>, {
1229
+ nonce: string;
1230
+ currency: string;
1231
+ validFromMs: number;
1232
+ validUntilMs: number;
1233
+ counterSeed: number;
1234
+ issuerSig: string;
1235
+ passId: string;
1236
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1237
+ issuerId: string;
1238
+ issuedAtMs: number;
1239
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1240
+ metadata: Record<string, string | number | boolean | null>;
1241
+ holderDeviceId: string;
1242
+ holderDevicePubkey: string;
1243
+ cumulativeCapKobo?: number | undefined;
1244
+ amountKobo?: number | undefined;
1245
+ templateId?: string | undefined;
1246
+ holderUserId?: string | undefined;
1247
+ }, {
1248
+ nonce: string;
1249
+ currency: string;
1250
+ validFromMs: number;
1251
+ validUntilMs: number;
1252
+ counterSeed: number;
1253
+ issuerSig: string;
1254
+ passId: string;
1255
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1256
+ issuerId: string;
1257
+ issuedAtMs: number;
1258
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1259
+ metadata: Record<string, string | number | boolean | null>;
1260
+ holderDeviceId: string;
1261
+ holderDevicePubkey: string;
1262
+ cumulativeCapKobo?: number | undefined;
1263
+ amountKobo?: number | undefined;
1264
+ templateId?: string | undefined;
1265
+ holderUserId?: string | undefined;
1266
+ }>;
1267
+ redeemerId: z.ZodString;
1268
+ redeemedAtMs: z.ZodNumber;
1269
+ /** Strictly monotonic counter scoped to a single pass. Must be > pass.counterSeed
1270
+ * and > the redeemer's lastSeenCounter for this pass. */
1271
+ counter: z.ZodNumber;
1272
+ /** Amount being redeemed in kobo (0 for non-monetary passes like ride tickets). */
1273
+ amountKobo: z.ZodNumber;
1274
+ nonce: z.ZodString;
1275
+ holderSig: z.ZodString;
1276
+ }, "strip", z.ZodTypeAny, {
1277
+ nonce: string;
1278
+ amountKobo: number;
1279
+ pass: {
1280
+ nonce: string;
1281
+ currency: string;
1282
+ validFromMs: number;
1283
+ validUntilMs: number;
1284
+ counterSeed: number;
1285
+ issuerSig: string;
1286
+ passId: string;
1287
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1288
+ issuerId: string;
1289
+ issuedAtMs: number;
1290
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1291
+ metadata: Record<string, string | number | boolean | null>;
1292
+ holderDeviceId: string;
1293
+ holderDevicePubkey: string;
1294
+ cumulativeCapKobo?: number | undefined;
1295
+ amountKobo?: number | undefined;
1296
+ templateId?: string | undefined;
1297
+ holderUserId?: string | undefined;
1298
+ };
1299
+ redeemerId: string;
1300
+ redeemedAtMs: number;
1301
+ counter: number;
1302
+ holderSig: string;
1303
+ }, {
1304
+ nonce: string;
1305
+ amountKobo: number;
1306
+ pass: {
1307
+ nonce: string;
1308
+ currency: string;
1309
+ validFromMs: number;
1310
+ validUntilMs: number;
1311
+ counterSeed: number;
1312
+ issuerSig: string;
1313
+ passId: string;
1314
+ kind: "ride-ticket" | "transit-pass" | "event-ticket" | "voucher" | "loyalty" | "receipt-link";
1315
+ issuerId: string;
1316
+ issuedAtMs: number;
1317
+ state: "issued" | "active" | "redeemed" | "expired" | "revoked";
1318
+ metadata: Record<string, string | number | boolean | null>;
1319
+ holderDeviceId: string;
1320
+ holderDevicePubkey: string;
1321
+ cumulativeCapKobo?: number | undefined;
1322
+ amountKobo?: number | undefined;
1323
+ templateId?: string | undefined;
1324
+ holderUserId?: string | undefined;
1325
+ };
1326
+ redeemerId: string;
1327
+ redeemedAtMs: number;
1328
+ counter: number;
1329
+ holderSig: string;
1330
+ }>;
1331
+ type Redemption = z.infer<typeof RedemptionSchema>;
1332
+ type UnsignedRedemption = Omit<Redemption, 'holderSig'>;
1333
+ type BuildRedemptionInput = {
1334
+ pass: Pass;
1335
+ redeemerId: string;
1336
+ redeemedAtMs: number;
1337
+ counter: number;
1338
+ amountKobo?: number;
1339
+ nonce: string;
1340
+ /** Optional local clock — if supplied, fail-fast against pass.validUntilMs. */
1341
+ nowMs?: number;
1342
+ /** Last counter the holder has seen for this pass (default: pass.counterSeed). */
1343
+ lastSeenCounter?: number;
1344
+ /** Total kobo already redeemed against this pass (default: 0). Used with pass.cumulativeCapKobo. */
1345
+ cumulativeUsedKobo?: number;
1346
+ };
1347
+ declare function buildRedemption(input: BuildRedemptionInput): UnsignedRedemption;
1348
+ declare function signRedemption(unsigned: UnsignedRedemption, holderDevicePrivateKey: Uint8Array): Redemption;
1349
+ /**
1350
+ * Verifies, in order:
1351
+ * 1. The pass itself is signed by the issuer.
1352
+ * 2. The pass binds a `holderDevicePubkey` (top-level, security-critical).
1353
+ * 3. The redemption is signed by that bound device key.
1354
+ * 4. The redemption counter is strictly greater than pass.counterSeed.
1355
+ */
1356
+ declare function verifyRedemption(r: Redemption, issuerPublicKey: Uint8Array): boolean;
1357
+
1358
+ type PassesClientOptions = {
1359
+ baseUrl: string;
1360
+ /** Pre-configured fetch (typically `createHmacFetch(...)`). Falls back to global fetch. */
1361
+ fetchImpl?: typeof fetch;
1362
+ };
1363
+ type IssuePassInput = {
1364
+ /** Device this pass is bound to. Required (BE-19). */
1365
+ holderDeviceId: string;
1366
+ /** 32-byte hex Ed25519 public key of the bound device. Required (BE-19). */
1367
+ holderDevicePubkey: string;
1368
+ /** Pass kind (server may default for templated flows). */
1369
+ kind: PassKind;
1370
+ /** Optional fixed amount for monetary passes (kobo). */
1371
+ amountKobo?: number;
1372
+ /** Optional cumulative spend cap in kobo across all redemptions. */
1373
+ cumulativeCapKobo?: number;
1374
+ validFromMs: number;
1375
+ validUntilMs: number;
1376
+ metadata?: PassMetadata;
1377
+ /** Optional template grouping id (server may emit one if omitted). */
1378
+ templateId?: string;
1379
+ /** Optional human-facing holder identity. */
1380
+ holderUserId?: string;
1381
+ /** Optional client-supplied id for idempotency / retries. */
1382
+ passId?: string;
1383
+ };
1384
+ type ListPassesInput = {
1385
+ holderDeviceId?: string;
1386
+ holderUserId?: string;
1387
+ state?: PassState;
1388
+ kind?: PassKind;
1389
+ templateId?: string;
1390
+ limit?: number;
1391
+ cursor?: string;
1392
+ };
1393
+ type ListPassesResponse = {
1394
+ items: Pass[];
1395
+ nextCursor: string | null;
1396
+ };
1397
+ type RevokePassInput = {
1398
+ reason: string;
1399
+ };
1400
+ type PassesClient = {
1401
+ issuePass: (input: IssuePassInput) => Promise<Pass>;
1402
+ listPasses: (input: ListPassesInput) => Promise<ListPassesResponse>;
1403
+ getPass: (passId: string) => Promise<Pass>;
1404
+ redeemPass: (passId: string, redemption: Redemption) => Promise<Pass>;
1405
+ revokePass: (passId: string, input: RevokePassInput) => Promise<Pass>;
1406
+ /** Local Ed25519 verification of a pass envelope under the supplied issuer public key. */
1407
+ verifyPass: (pass: Pass, issuerPublicKey: Uint8Array) => boolean;
1408
+ };
1409
+ declare function createPassesClient(opts: PassesClientOptions): PassesClient;
1410
+
1411
+ declare const RECEIPT_CHANNELS: readonly ["cash", "pass"];
1412
+ type ReceiptChannel = (typeof RECEIPT_CHANNELS)[number];
1413
+ /** @deprecated use {@link RECEIPT_CHANNELS}. Will be removed before 1.0. */
1414
+ declare const RECEIPT_KINDS: readonly ["cash", "pass"];
1415
+ /** @deprecated use {@link ReceiptChannel}. */
1416
+ type ReceiptKind = ReceiptChannel;
1417
+ /** Free-form payload reserved for channel-specific extras (e.g. memo, intent metadata).
1418
+ * Top-level fields below are the security-relevant facts and must not be duplicated here. */
1419
+ declare const ReceiptPayloadSchema: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
1420
+ type ReceiptPayload = z.infer<typeof ReceiptPayloadSchema>;
1421
+ /** BE-20 wire schema: channel + exactly one of `intentId` (cash) or `passRedemptionId` (pass). */
1422
+ declare const ReceiptSchema: z.ZodEffects<z.ZodObject<{
1423
+ receiptId: z.ZodString;
1424
+ channel: z.ZodEnum<["cash", "pass"]>;
1425
+ /** Cash-channel: send_intents.id. Required when channel === 'cash'. */
1426
+ intentId: z.ZodOptional<z.ZodString>;
1427
+ /** Pass-channel: pass_redemptions.id. Required when channel === 'pass'. */
1428
+ passRedemptionId: z.ZodOptional<z.ZodString>;
1429
+ payerUserId: z.ZodString;
1430
+ payeeUserId: z.ZodString;
1431
+ amountKobo: z.ZodNumber;
1432
+ currency: z.ZodString;
1433
+ issuedAtMs: z.ZodNumber;
1434
+ issuerId: z.ZodString;
1435
+ payload: z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodNull]>>;
1436
+ issuerSig: z.ZodString;
1437
+ }, "strip", z.ZodTypeAny, {
1438
+ currency: string;
1439
+ issuerSig: string;
1440
+ amountKobo: number;
1441
+ issuerId: string;
1442
+ issuedAtMs: number;
1443
+ receiptId: string;
1444
+ channel: "pass" | "cash";
1445
+ payerUserId: string;
1446
+ payeeUserId: string;
1447
+ payload: Record<string, string | number | boolean | null>;
1448
+ intentId?: string | undefined;
1449
+ passRedemptionId?: string | undefined;
1450
+ }, {
1451
+ currency: string;
1452
+ issuerSig: string;
1453
+ amountKobo: number;
1454
+ issuerId: string;
1455
+ issuedAtMs: number;
1456
+ receiptId: string;
1457
+ channel: "pass" | "cash";
1458
+ payerUserId: string;
1459
+ payeeUserId: string;
1460
+ payload: Record<string, string | number | boolean | null>;
1461
+ intentId?: string | undefined;
1462
+ passRedemptionId?: string | undefined;
1463
+ }>, {
1464
+ currency: string;
1465
+ issuerSig: string;
1466
+ amountKobo: number;
1467
+ issuerId: string;
1468
+ issuedAtMs: number;
1469
+ receiptId: string;
1470
+ channel: "pass" | "cash";
1471
+ payerUserId: string;
1472
+ payeeUserId: string;
1473
+ payload: Record<string, string | number | boolean | null>;
1474
+ intentId?: string | undefined;
1475
+ passRedemptionId?: string | undefined;
1476
+ }, {
1477
+ currency: string;
1478
+ issuerSig: string;
1479
+ amountKobo: number;
1480
+ issuerId: string;
1481
+ issuedAtMs: number;
1482
+ receiptId: string;
1483
+ channel: "pass" | "cash";
1484
+ payerUserId: string;
1485
+ payeeUserId: string;
1486
+ payload: Record<string, string | number | boolean | null>;
1487
+ intentId?: string | undefined;
1488
+ passRedemptionId?: string | undefined;
1489
+ }>;
1490
+ type Receipt = z.infer<typeof ReceiptSchema>;
1491
+ type UnsignedReceipt = Omit<Receipt, 'issuerSig'>;
1492
+ type BuildReceiptInput = {
1493
+ receiptId: string;
1494
+ channel: ReceiptChannel;
1495
+ intentId?: string;
1496
+ passRedemptionId?: string;
1497
+ payerUserId: string;
1498
+ payeeUserId: string;
1499
+ amountKobo: number;
1500
+ currency: string;
1501
+ issuedAtMs: number;
1502
+ issuerId: string;
1503
+ payload?: ReceiptPayload;
1504
+ };
1505
+ declare function buildReceipt(input: BuildReceiptInput): UnsignedReceipt;
1506
+ declare function signReceipt(unsigned: UnsignedReceipt, issuerPrivateKey: Uint8Array): Receipt;
1507
+ declare function verifyReceipt(r: Receipt, issuerPublicKey: Uint8Array): boolean;
1508
+
1509
+ type ReceiptsClientOptions = {
1510
+ baseUrl: string;
1511
+ fetchImpl?: typeof fetch;
1512
+ };
1513
+ type IssueReceiptInput = {
1514
+ channel: ReceiptChannel;
1515
+ /** Required for cash channel; FK to send_intents.id. */
1516
+ intentId?: string;
1517
+ /** Required for pass channel; FK to pass_redemptions.id. */
1518
+ passRedemptionId?: string;
1519
+ payerUserId: string;
1520
+ payeeUserId: string;
1521
+ amountKobo: number;
1522
+ currency: string;
1523
+ payload?: ReceiptPayload;
1524
+ /** Optional client-supplied id for idempotency / retries. */
1525
+ receiptId?: string;
1526
+ };
1527
+ type ListReceiptsInput = {
1528
+ /** Filter by either side of the receipt. Server scopes by caller identity. */
1529
+ payerUserId?: string;
1530
+ payeeUserId?: string;
1531
+ channel?: ReceiptChannel;
1532
+ limit?: number;
1533
+ cursor?: string;
1534
+ };
1535
+ type ListReceiptsResponse = {
1536
+ items: Receipt[];
1537
+ nextCursor: string | null;
1538
+ };
1539
+ type ReceiptsClient = {
1540
+ issueReceipt: (input: IssueReceiptInput) => Promise<Receipt>;
1541
+ /** Alias of `getById`, matches addendum surface. */
1542
+ getReceipt: (receiptId: string) => Promise<Receipt>;
1543
+ getById: (receiptId: string) => Promise<Receipt>;
1544
+ /** Look up a cash-channel receipt by its originating intentId. */
1545
+ getByIntentId: (intentId: string) => Promise<Receipt>;
1546
+ /** Look up a pass-channel receipt by its originating passRedemptionId. */
1547
+ getByPassRedemptionId: (passRedemptionId: string) => Promise<Receipt>;
1548
+ listForUser: (input: ListReceiptsInput) => Promise<ListReceiptsResponse>;
1549
+ /** Local Ed25519 verification of a receipt envelope under the supplied issuer key. */
1550
+ verifyReceipt: (receipt: Receipt, issuerPublicKey: Uint8Array) => boolean;
1551
+ };
1552
+ declare function createReceiptsClient(opts: ReceiptsClientOptions): ReceiptsClient;
1553
+
1554
+ type FlurInitOptions = {
1555
+ apiKey: string;
1556
+ apiSecret: string;
1557
+ baseUrl: string;
1558
+ fetchImpl?: typeof fetch;
1559
+ /** When true, all QR generation is performed locally without any network call. */
1560
+ offlineMode?: boolean;
1561
+ /** Optional scope claim forwarded as `X-Flur-Scope`. Backend remains authoritative. */
1562
+ scope?: readonly string[];
1563
+ };
1564
+ type FlurPaymentEvent = {
1565
+ type: string;
1566
+ reference?: string;
1567
+ [k: string]: unknown;
1568
+ };
1569
+ type SubscribeOptions = {
1570
+ reference: string;
1571
+ onEvent: (event: FlurPaymentEvent) => void;
1572
+ onError?: (err: unknown) => void;
1573
+ };
1574
+ declare function generateStaticQR(input: Omit<NQRPayloadInput, 'pointOfInitiation'>): string;
1575
+ declare function generateDynamicQR(input: Omit<NQRPayloadInput, 'pointOfInitiation'>): string;
1576
+ declare function parseQR(payload: string): ParsedNQR;
1577
+ type CashNamespace = {
1578
+ generateStaticQR: typeof generateStaticQR;
1579
+ generateDynamicQR: typeof generateDynamicQR;
1580
+ parseQR: typeof parseQR;
1581
+ subscribeToPayments: (opts: SubscribeOptions) => () => void;
1582
+ };
1583
+ type FlurHandle = CashNamespace & {
1584
+ /** Cash / money: NQR generation, payment subscriptions, transfers (future). */
1585
+ cash: CashNamespace;
1586
+ /** Passes: tickets, vouchers, loyalty, transit. */
1587
+ passes: PassesClient;
1588
+ /** Receipts: signed, immutable views over cash + pass events. */
1589
+ receipts: ReceiptsClient;
1590
+ };
1591
+ declare function init(opts: FlurInitOptions): FlurHandle;
1592
+
1593
+ export { ADDITIONAL_DATA_SUBFIELD, type AccountActivityItem, type AccountSummaryResponse, type AdditionalData, type AuthLogoutInput, type AuthRefreshInput, type AuthRefreshResponse, type AuthorizeSendWithBiometricInput, type AuthorizedOptions, type BiometricSigner, type BuildPassInput, type BuildReceiptInput, type BuildRedemptionInput, type CashNamespace, type CreatePayLinkResponse, type CreateTransferOptions, type DeviceTrustState, type Ed25519KeyPair, FIELD, FlurApiError, FlurCapExceededError, FlurClient, type FlurClientOptions, FlurError, type FlurErrorCode, FlurExpiredError, type FlurHandle, type FlurInitOptions, type FlurPaymentEvent, FlurReplayError, type HmacFetchOptions, type IssuePassInput, type IssueReceiptInput, type ListPassesInput, type ListPassesResponse, type ListReceiptsInput, type ListReceiptsResponse, type ListTransactionsOptions, type MerchantAccountInfo, type Money, NGN_CURRENCY_CODE, NG_COUNTRY_CODE, NQRParseError, type NQRPayloadInput, type OAC, OACSchema, OAC_DEFAULT_CUMULATIVE_KOBO, OAC_DEFAULT_PER_TX_KOBO, OAC_DEFAULT_VALIDITY_MS, type OfflinePaymentAuthorization, OfflinePaymentAuthorizationSchema, type OfflinePaymentRequest, OfflinePaymentRequestSchema, type OnboardingCompleteInput, type OnboardingCompleteResponse, type OnboardingFallback, type OnboardingRiskReason, type OnboardingStartInput, type OnboardingStartResponse, PASS_KINDS, PASS_STATES, PAYLOAD_FORMAT_INDICATOR_VALUE, POINT_OF_INITIATION, type ParsedNQR, type Pass, type PassKind, type PassMetadata, PassMetadataSchema, PassSchema, type PassState, type PassesClient, type PassesClientOptions, type PinSetInput, type PinVerifyInput, type PushPlatform, type PushRegisterInput, RECEIPT_CHANNELS, RECEIPT_KINDS, REPLAY_WINDOW_MS, type Receipt, type ReceiptChannel, type ReceiptKind, type ReceiptPayload, ReceiptPayloadSchema, ReceiptSchema, type ReceiptsClient, type ReceiptsClientOptions, type RecipientResolveInput, type RecipientResolveResponse, type Redemption, RedemptionSchema, type RegisterDeviceInput, type RegisterDeviceResponse, type RegisterSendDeviceKeyInput, type ResolvePayLinkResponse, type RevokePassInput, type RoutingHint, type SendChallengeInput, type SendChallengeResponse, type SendMoneyInput, type SendMoneyOptions, type SendVerifyInput, type SendVerifyResponse, type SubscribeOptions, type TLVField, type TransactionDetailResponse, type TransactionDirection, type TransactionsListResponse, type TransferInput, type TransferResponse, type TransferStatus, type UnsignedOAC, type UnsignedOfflinePaymentAuthorization, type UnsignedOfflinePaymentRequest, type UnsignedPass, type UnsignedReceipt, type UnsignedRedemption, bodySha256Hex, buildAuthorization, buildOAC, buildPass, buildPaymentRequest, buildReceipt, buildRedemption, canonicalJSONBytes, canonicalJSONStringify, canonicalRequestString, constantTimeEqual, crc16ccitt, crc16ccittHex, createHmacFetch, createPassesClient, createReceiptsClient, decodeAuthorizationQR, decodeBase45, decodePaymentRequestQR, encodeAuthorizationQR, encodeBase45, encodeNQR, encodePaymentRequestQR, formatAmount, generateDynamicQR, generateKeyPair, generateStaticQR, init, isPassWithinValidity, moneyMinorToNumber, normalizeE164, parseAmountInput, parseNQR, parseQR, publicKeyFromPrivate, readTLV, routingHint, sign, signAuthorization, signCanonical, signOAC, signPass, signPaymentRequest, signReceipt, signRedemption, signRequestHMAC, verify, verifyAuthorization, verifyCanonical, verifyOAC, verifyPass, verifyPaymentRequest, verifyReceipt, verifyRedemption, verifyRequestHMAC, writeTLV };