@nokinc-flur/sdk 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
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
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -35,6 +45,9 @@ __export(index_exports, {
35
45
  COLLECTION_INTENT_STATUSES: () => COLLECTION_INTENT_STATUSES,
36
46
  COLLECTION_PAYMENT_STATUSES: () => COLLECTION_PAYMENT_STATUSES,
37
47
  CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS: () => CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS,
48
+ CONSUMER_PAYMENT_REQUEST_DOMAIN: () => CONSUMER_PAYMENT_REQUEST_DOMAIN,
49
+ CONSUMER_SETTLEMENT_DOMAIN: () => CONSUMER_SETTLEMENT_DOMAIN,
50
+ CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX: () => CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX,
38
51
  CUSTODIAL_MODES: () => CUSTODIAL_MODES,
39
52
  CollectionIntentSchema: () => CollectionIntentSchema,
40
53
  CollectionPaymentResultSchema: () => CollectionPaymentResultSchema,
@@ -44,6 +57,7 @@ __export(index_exports, {
44
57
  ConsumerOACRecordSchema: () => OACRecordSchema,
45
58
  ConsumerOACSchema: () => ConsumerOACSchema,
46
59
  ConsumerPaymentClaimSchema: () => ConsumerPaymentClaimSchema,
60
+ ConsumerPaymentRequestEnvelopeSchema: () => ConsumerPaymentRequestEnvelopeSchema,
47
61
  ConsumerSettleResultSchema: () => ConsumerSettleResultSchema,
48
62
  ConsumerSettlementSchema: () => ConsumerSettlementSchema,
49
63
  CreateCollectionIntentInputSchema: () => CreateCollectionIntentInputSchema,
@@ -85,6 +99,12 @@ __export(index_exports, {
85
99
  OAC_DEFAULT_PER_TX_KOBO: () => OAC_DEFAULT_PER_TX_KOBO,
86
100
  OAC_DEFAULT_VALIDITY_MS: () => OAC_DEFAULT_VALIDITY_MS,
87
101
  OFFLINE_CLAIM_SMS_PREFIX: () => OFFLINE_CLAIM_SMS_PREFIX,
102
+ OFFLINE_SMS_SETTLE_DOMAIN: () => OFFLINE_SMS_SETTLE_DOMAIN,
103
+ OFFLINE_SMS_SETTLE_HEADER_BYTES: () => OFFLINE_SMS_SETTLE_HEADER_BYTES,
104
+ OFFLINE_SMS_SETTLE_PREFIX: () => OFFLINE_SMS_SETTLE_PREFIX,
105
+ OFFLINE_SMS_SETTLE_SIGNATURE_BYTES: () => OFFLINE_SMS_SETTLE_SIGNATURE_BYTES,
106
+ OFFLINE_SMS_SETTLE_TOKEN_BYTES: () => OFFLINE_SMS_SETTLE_TOKEN_BYTES,
107
+ OFFLINE_SMS_SETTLE_VERSION: () => OFFLINE_SMS_SETTLE_VERSION,
88
108
  OfflineClaimArtifactSchema: () => OfflineClaimArtifactSchema,
89
109
  OfflinePaymentAuthorizationArtifactSchema: () => OfflinePaymentAuthorizationArtifactSchema,
90
110
  OfflinePaymentAuthorizationSchema: () => OfflinePaymentAuthorizationSchema,
@@ -102,6 +122,9 @@ __export(index_exports, {
102
122
  PASS_STATES: () => PASS_STATES,
103
123
  PAYLOAD_FORMAT_INDICATOR_VALUE: () => PAYLOAD_FORMAT_INDICATOR_VALUE,
104
124
  PAYOUT_DESTINATION_STATUSES: () => PAYOUT_DESTINATION_STATUSES,
125
+ PAY_CARD_DEFAULT_TTL_MS: () => PAY_CARD_DEFAULT_TTL_MS,
126
+ PAY_CARD_REFRESH_THRESHOLD_MS: () => PAY_CARD_REFRESH_THRESHOLD_MS,
127
+ PAY_CARD_URI_PREFIX: () => PAY_CARD_URI_PREFIX,
105
128
  POINT_OF_INITIATION: () => POINT_OF_INITIATION,
106
129
  PartnerFundingEventInputSchema: () => PartnerFundingEventInputSchema,
107
130
  PartnerFundingSchema: () => PartnerFundingSchema,
@@ -109,6 +132,7 @@ __export(index_exports, {
109
132
  PassArtifactSchema: () => PassArtifactSchema,
110
133
  PassMetadataSchema: () => PassMetadataSchema,
111
134
  PassSchema: () => PassSchema,
135
+ PayCardArtifactSchema: () => PayCardArtifactSchema,
112
136
  PayCollectionInputSchema: () => PayCollectionInputSchema,
113
137
  PaymentClaimSchema: () => PaymentClaimSchema,
114
138
  PaymentIntentArtifactSchema: () => PaymentIntentArtifactSchema,
@@ -144,18 +168,26 @@ __export(index_exports, {
144
168
  bodySha256Hex: () => bodySha256Hex,
145
169
  buildArtifactBody: () => buildArtifactBody,
146
170
  buildAuthorization: () => buildAuthorization,
171
+ buildConsumerPaymentRequest: () => buildConsumerPaymentRequest,
147
172
  buildOAC: () => buildOAC,
148
173
  buildPass: () => buildPass,
174
+ buildPayCardSigningInput: () => buildPayCardSigningInput,
149
175
  buildPaymentRequest: () => buildPaymentRequest,
150
176
  buildReceipt: () => buildReceipt,
151
177
  buildRedemption: () => buildRedemption,
178
+ buildSmsSettleHeader: () => buildSmsSettleHeader,
179
+ buildSmsSettleSignedBytes: () => domainTag,
152
180
  canonicalClaimSigningBytes: () => canonicalClaimSigningBytes,
153
181
  canonicalClaimSigningPayload: () => canonicalClaimSigningPayload,
154
182
  canonicalJSONBytes: () => canonicalJSONBytes,
155
183
  canonicalJSONStringify: () => canonicalJSONStringify,
156
184
  canonicalRequestString: () => canonicalRequestString,
185
+ computeConsumerClaimEncounterId: () => computeConsumerClaimEncounterId,
157
186
  computeEncounterId: () => computeEncounterId,
158
187
  constantTimeEqual: () => constantTimeEqual,
188
+ consumerPaymentRequestSigningBytes: () => consumerPaymentRequestSigningBytes,
189
+ consumerPaymentRequestSigningPayload: () => consumerPaymentRequestSigningPayload,
190
+ consumerSettlementSigningPayload: () => consumerSettlementSigningPayload,
159
191
  crc16ccitt: () => crc16ccitt,
160
192
  crc16ccittHex: () => crc16ccittHex,
161
193
  createAccountsClient: () => createAccountsClient,
@@ -173,28 +205,40 @@ __export(index_exports, {
173
205
  createPartnerFundingClient: () => createPartnerFundingClient,
174
206
  createPartnerProfileAdminClient: () => createPartnerProfileAdminClient,
175
207
  createPassesClient: () => createPassesClient,
208
+ createPayCardArtifactUri: () => createPayCardArtifactUri,
176
209
  createReceiptArtifactUri: () => createReceiptArtifactUri,
177
210
  createReceiptsClient: () => createReceiptsClient,
178
211
  createSoftwareP256Signer: () => createSoftwareP256Signer,
179
212
  decodeArtifactUri: () => decodeArtifactUri,
180
213
  decodeAuthorizationQR: () => decodeAuthorizationQR,
181
214
  decodeBase45: () => decodeBase45,
215
+ decodeConsumerSettlementReceiptQR: () => decodeConsumerSettlementReceiptQR,
182
216
  decodeOfflineClaimSmsMessage: () => decodeOfflineClaimSmsMessage,
217
+ decodeOfflineSmsSettleToken: () => decodeOfflineSmsSettleToken,
218
+ decodePayCardArtifact: () => decodePayCardArtifact,
183
219
  decodePaymentRequestQR: () => decodePaymentRequestQR,
220
+ decodeUnverifiedConsumerSettlementReceiptQR: () => decodeUnverifiedConsumerSettlementReceiptQR,
221
+ derToRawP256Signature: () => derToRawP256Signature,
184
222
  encodeArtifactUri: () => encodeArtifactUri,
185
223
  encodeAuthorizationQR: () => encodeAuthorizationQR,
186
224
  encodeBase45: () => encodeBase45,
225
+ encodeConsumerSettlementReceiptQR: () => encodeConsumerSettlementReceiptQR,
187
226
  encodeNQR: () => encodeNQR,
188
227
  encodeOfflineClaimSmsMessage: () => encodeOfflineClaimSmsMessage,
228
+ encodeOfflineSmsSettleToken: () => encodeOfflineSmsSettleToken,
189
229
  encodePaymentRequestQR: () => encodePaymentRequestQR,
190
230
  extractOfflineClaimSmsToken: () => extractOfflineClaimSmsToken,
231
+ extractOfflineSmsSettleToken: () => extractOfflineSmsSettleToken,
191
232
  formatAmount: () => formatAmount,
192
233
  generateDynamicQR: () => generateDynamicQR,
193
234
  generateStaticQR: () => generateStaticQR,
194
235
  init: () => init,
236
+ inspectPayCardFreshness: () => inspectPayCardFreshness,
237
+ isConsumerPaymentRequestExpired: () => isConsumerPaymentRequestExpired,
195
238
  isHardenedArtifactType: () => isHardenedArtifactType,
196
239
  isKnownArtifactType: () => isKnownArtifactType,
197
240
  isPassWithinValidity: () => isPassWithinValidity,
241
+ isPayCardArtifactUri: () => isPayCardArtifactUri,
198
242
  moneyMinorToNumber: () => moneyMinorToNumber,
199
243
  normalizeE164: () => normalizeE164,
200
244
  parseAmountInput: () => parseAmountInput,
@@ -204,6 +248,7 @@ __export(index_exports, {
204
248
  routingHint: () => routingHint,
205
249
  signArtifact: () => signArtifact,
206
250
  signAuthorization: () => signAuthorization,
251
+ signConsumerPaymentRequest: () => signConsumerPaymentRequest,
207
252
  signOAC: () => signOAC,
208
253
  signPartnerRequest: () => signPartnerRequest,
209
254
  signPass: () => signPass,
@@ -215,8 +260,13 @@ __export(index_exports, {
215
260
  verifyArtifactUri: () => verifyArtifactUri,
216
261
  verifyAuthorization: () => verifyAuthorization,
217
262
  verifyClaimSignature: () => verifyClaimSignature,
263
+ verifyConsumerPaymentRequest: () => verifyConsumerPaymentRequest,
264
+ verifyConsumerSettlement: () => verifyConsumerSettlement,
265
+ verifyConsumerSettlementReceiptQR: () => verifyConsumerSettlementReceiptQR,
218
266
  verifyOAC: () => verifyOAC,
267
+ verifyOfflineSmsSettleToken: () => verifyOfflineSmsSettleToken,
219
268
  verifyPass: () => verifyPass,
269
+ verifyPayCardArtifact: () => verifyPayCardArtifact,
220
270
  verifyPaymentRequest: () => verifyPaymentRequest,
221
271
  verifyReceipt: () => verifyReceipt,
222
272
  verifyRedemption: () => verifyRedemption,
@@ -3185,6 +3235,9 @@ function createAccountsClient(opts) {
3185
3235
  var import_zod13 = require("zod");
3186
3236
  var Sha256Hex = import_zod13.z.string().regex(/^[0-9a-f]{64}$/);
3187
3237
  var Base64Std3 = import_zod13.z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
3238
+ var ClaimNonce = import_zod13.z.string().min(8).max(128).refine((value) => !value.includes("|"), {
3239
+ message: "nonce must not contain |"
3240
+ });
3188
3241
  var ACCOUNT_FUNDED_OAC_MAX_TTL_MS = 1e3 * 60 * 60 * 24 * 7;
3189
3242
  var CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS = 1e3 * 60 * 60 * 24;
3190
3243
  var AttestationSecurityLevelSchema = import_zod13.z.enum([
@@ -3277,13 +3330,25 @@ var ConsumerPaymentClaimSchema = import_zod13.z.object({
3277
3330
  payerUserId: import_zod13.z.string().uuid(),
3278
3331
  payeeUserId: import_zod13.z.string().uuid(),
3279
3332
  payerDeviceId: import_zod13.z.string().min(1).max(128),
3280
- payerNonce: import_zod13.z.string().min(8).max(128),
3281
- payeeNonce: import_zod13.z.string().min(8).max(128),
3333
+ payerNonce: ClaimNonce,
3334
+ payeeNonce: ClaimNonce,
3282
3335
  amountKobo: import_zod13.z.number().int().positive(),
3283
3336
  currency: import_zod13.z.string().length(3).default("NGN"),
3284
3337
  occurredAtMs: import_zod13.z.number().int().nonnegative(),
3285
3338
  completedAtMs: import_zod13.z.number().int().nonnegative().optional(),
3286
3339
  contextId: import_zod13.z.string().max(128).optional(),
3340
+ requestId: import_zod13.z.string().uuid().optional(),
3341
+ requestMode: import_zod13.z.enum(["fixed", "editable"]).optional(),
3342
+ requestTakerUserId: import_zod13.z.string().uuid().optional(),
3343
+ requestAmountKobo: import_zod13.z.number().int().positive().optional(),
3344
+ requestCurrency: import_zod13.z.string().length(3).optional(),
3345
+ requestReference: import_zod13.z.string().max(128).nullable().optional(),
3346
+ requestCreatedAtMs: import_zod13.z.number().int().nonnegative().optional(),
3347
+ requestExpiresAtMs: import_zod13.z.number().int().positive().optional(),
3348
+ requestNonce: import_zod13.z.string().min(8).max(128).optional(),
3349
+ requestTakerDeviceId: import_zod13.z.string().min(1).max(128).nullable().optional(),
3350
+ requestTakerPubkeySpkiB64: Base64Std3.min(64).max(4096).optional(),
3351
+ requestTakerSignatureDerB64: Base64Std3.min(16).max(2048).optional(),
3287
3352
  payerPubkeySpkiB64: Base64Std3.min(64).max(4096),
3288
3353
  payerSignatureDerB64: Base64Std3.min(16).max(2048),
3289
3354
  payeePubkeySpkiB64: Base64Std3.min(64).max(4096).optional(),
@@ -3303,7 +3368,10 @@ var ConsumerSettlementSchema = import_zod13.z.object({
3303
3368
  ledgerRef: import_zod13.z.string().nullable(),
3304
3369
  /** ASN.1 DER ECDSA P-256 issuer signature, base64. */
3305
3370
  issuerSig: Base64Std3.min(16).max(2048),
3306
- createdAtMs: import_zod13.z.number().int().nonnegative()
3371
+ /** Canonical millisecond timestamp signed into the settlement receipt. */
3372
+ issuedAtMs: import_zod13.z.number().int().nonnegative(),
3373
+ /** Compatibility alias for API consumers that predate issuedAtMs. */
3374
+ createdAtMs: import_zod13.z.number().int().nonnegative().optional()
3307
3375
  });
3308
3376
  var ConsumerSettleResultSchema = import_zod13.z.object({
3309
3377
  settlement: ConsumerSettlementSchema,
@@ -3392,13 +3460,21 @@ function createMeOfflineClient(opts) {
3392
3460
  "/v1/me/offline/claims",
3393
3461
  ConsumerPaymentClaimSchema.parse(claim),
3394
3462
  (raw) => ConsumerSettleResultSchema.parse(raw)
3463
+ ),
3464
+ getSettlement: (idOrKey) => call(
3465
+ "GET",
3466
+ `/v1/me/offline/settlements/${encodeURIComponent(idOrKey)}`,
3467
+ void 0,
3468
+ (raw) => ConsumerSettlementSchema.parse(raw)
3395
3469
  )
3396
3470
  };
3397
3471
  }
3398
3472
 
3399
3473
  // src/me-offline/signer.ts
3400
3474
  var import_nist2 = require("@noble/curves/nist");
3475
+ var import_sha2 = require("@noble/hashes/sha2");
3401
3476
  var CLAIM_DOMAIN_V2 = "flur:consumer-offline:v2:claim";
3477
+ var ENCOUNTER_DOMAIN2 = "flur:consumer-offline:v1:encounter";
3402
3478
  function canonicalClaimSigningPayload(claim) {
3403
3479
  return {
3404
3480
  domain: CLAIM_DOMAIN_V2,
@@ -3419,6 +3495,27 @@ function canonicalClaimSigningPayload(claim) {
3419
3495
  function canonicalClaimSigningBytes(claim) {
3420
3496
  return canonicalJSONBytes(canonicalClaimSigningPayload(claim));
3421
3497
  }
3498
+ function computeConsumerClaimEncounterId(input) {
3499
+ const material = `${ENCOUNTER_DOMAIN2}|${[
3500
+ assertEncounterPart("oacId", input.oacId),
3501
+ assertEncounterPart("payerUserId", input.payerUserId),
3502
+ assertEncounterPart("payeeUserId", input.payeeUserId),
3503
+ assertEncounterPart("payerNonce", input.payerNonce),
3504
+ assertEncounterPart("payeeNonce", input.payeeNonce)
3505
+ ].join("|")}`;
3506
+ return bytesToHex5((0, import_sha2.sha256)(new TextEncoder().encode(material)));
3507
+ }
3508
+ function assertEncounterPart(field, value) {
3509
+ if (value.includes("|")) {
3510
+ throw new Error(`consumer encounter id ${field} must not contain |`);
3511
+ }
3512
+ return value;
3513
+ }
3514
+ function bytesToHex5(bytes) {
3515
+ let out = "";
3516
+ for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
3517
+ return out;
3518
+ }
3422
3519
  function bytesToBase642(bytes) {
3423
3520
  if (typeof Buffer !== "undefined") {
3424
3521
  return Buffer.from(bytes).toString("base64");
@@ -3514,38 +3611,192 @@ function verifyClaimSignature(input) {
3514
3611
  }
3515
3612
  }
3516
3613
 
3517
- // src/me-offline/sms.ts
3518
- var OFFLINE_CLAIM_SMS_PREFIX = "FLURC1.";
3519
- var TOKEN_RE = /(?:^|\s)(FLURC1\.[A-Za-z0-9_-]+={0,2})(?:\s|$)/;
3520
- function encodeOfflineClaimSmsMessage(claim) {
3521
- const parsed = ConsumerPaymentClaimSchema.parse(claim);
3522
- const json = JSON.stringify(parsed);
3523
- return `${OFFLINE_CLAIM_SMS_PREFIX}${base64UrlEncodeUtf8(json)}`;
3614
+ // src/me-offline/request.ts
3615
+ var import_zod14 = require("zod");
3616
+ var Base64Std4 = import_zod14.z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
3617
+ var CONSUMER_PAYMENT_REQUEST_DOMAIN = "flur:consumer-offline:v1:request";
3618
+ var ConsumerPaymentRequestEnvelopeSchema = import_zod14.z.object({
3619
+ requestId: import_zod14.z.string().uuid(),
3620
+ mode: import_zod14.z.enum(["fixed", "editable"]),
3621
+ takerUserId: import_zod14.z.string().uuid(),
3622
+ amountKobo: import_zod14.z.number().int().positive(),
3623
+ currency: import_zod14.z.string().length(3).default("NGN"),
3624
+ reference: import_zod14.z.string().max(128).nullable().default(null),
3625
+ createdAtMs: import_zod14.z.number().int().nonnegative(),
3626
+ expiresAtMs: import_zod14.z.number().int().positive(),
3627
+ nonce: import_zod14.z.string().min(8).max(128),
3628
+ takerDeviceId: import_zod14.z.string().min(1).max(128).nullable().default(null),
3629
+ takerPubkeySpkiB64: Base64Std4.min(64).max(4096).optional(),
3630
+ takerSignatureDerB64: Base64Std4.min(16).max(2048).optional()
3631
+ }).superRefine((value, ctx) => {
3632
+ if (value.expiresAtMs <= value.createdAtMs) {
3633
+ ctx.addIssue({
3634
+ code: import_zod14.z.ZodIssueCode.custom,
3635
+ path: ["expiresAtMs"],
3636
+ message: "expiresAtMs must be greater than createdAtMs"
3637
+ });
3638
+ }
3639
+ const hasSignature = Boolean(
3640
+ value.takerPubkeySpkiB64 || value.takerSignatureDerB64
3641
+ );
3642
+ if (value.mode === "fixed" || hasSignature) {
3643
+ if (!value.takerDeviceId) {
3644
+ ctx.addIssue({
3645
+ code: import_zod14.z.ZodIssueCode.custom,
3646
+ path: ["takerDeviceId"],
3647
+ message: "signed requests require takerDeviceId"
3648
+ });
3649
+ }
3650
+ if (!value.takerPubkeySpkiB64) {
3651
+ ctx.addIssue({
3652
+ code: import_zod14.z.ZodIssueCode.custom,
3653
+ path: ["takerPubkeySpkiB64"],
3654
+ message: "signed requests require takerPubkeySpkiB64"
3655
+ });
3656
+ }
3657
+ if (!value.takerSignatureDerB64) {
3658
+ ctx.addIssue({
3659
+ code: import_zod14.z.ZodIssueCode.custom,
3660
+ path: ["takerSignatureDerB64"],
3661
+ message: "signed requests require takerSignatureDerB64"
3662
+ });
3663
+ }
3664
+ }
3665
+ });
3666
+ function buildConsumerPaymentRequest(input) {
3667
+ const unsigned = {
3668
+ requestId: input.requestId,
3669
+ mode: input.mode,
3670
+ takerUserId: input.takerUserId,
3671
+ amountKobo: input.amountKobo,
3672
+ currency: input.currency ?? "NGN",
3673
+ reference: input.reference ?? null,
3674
+ createdAtMs: input.createdAtMs,
3675
+ expiresAtMs: input.expiresAtMs,
3676
+ nonce: input.nonce,
3677
+ takerDeviceId: input.takerDeviceId ?? null
3678
+ };
3679
+ if (unsigned.mode === "fixed" && !unsigned.takerDeviceId) {
3680
+ throw new Error("fixed requests require takerDeviceId");
3681
+ }
3682
+ if (unsigned.expiresAtMs <= unsigned.createdAtMs) {
3683
+ throw new Error("expiresAtMs must be greater than createdAtMs");
3684
+ }
3685
+ return unsigned;
3524
3686
  }
3525
- function decodeOfflineClaimSmsMessage(message) {
3526
- const token = extractOfflineClaimSmsToken(message);
3527
- if (!token) {
3528
- throw new Error("offline claim SMS token not found");
3687
+ function consumerPaymentRequestSigningPayload(request) {
3688
+ return {
3689
+ domain: CONSUMER_PAYMENT_REQUEST_DOMAIN,
3690
+ version: 1,
3691
+ requestId: request.requestId,
3692
+ mode: request.mode,
3693
+ takerUserId: request.takerUserId,
3694
+ amountKobo: request.amountKobo,
3695
+ currency: request.currency,
3696
+ reference: request.reference ?? null,
3697
+ createdAtMs: request.createdAtMs,
3698
+ expiresAtMs: request.expiresAtMs,
3699
+ nonce: request.nonce,
3700
+ takerDeviceId: request.takerDeviceId ?? null
3701
+ };
3702
+ }
3703
+ function consumerPaymentRequestSigningBytes(request) {
3704
+ return canonicalJSONBytes(consumerPaymentRequestSigningPayload(request));
3705
+ }
3706
+ async function signConsumerPaymentRequest(unsigned, signer) {
3707
+ if (signer.alg !== "p256") {
3708
+ throw new Error("consumer payment requests require p256 signer");
3529
3709
  }
3530
- const encoded = token.slice(OFFLINE_CLAIM_SMS_PREFIX.length);
3710
+ const publicKey = await signer.getPublicKey();
3711
+ if (publicKey.alg !== "p256") {
3712
+ throw new Error("consumer payment requests require p256 public key");
3713
+ }
3714
+ const signature = await signer.sign(
3715
+ consumerPaymentRequestSigningBytes(unsigned)
3716
+ );
3717
+ return ConsumerPaymentRequestEnvelopeSchema.parse({
3718
+ ...unsigned,
3719
+ takerPubkeySpkiB64: publicKey.publicKey,
3720
+ takerSignatureDerB64: signature.signature
3721
+ });
3722
+ }
3723
+ function verifyConsumerPaymentRequest(request) {
3724
+ const parsed = ConsumerPaymentRequestEnvelopeSchema.safeParse(request);
3725
+ if (!parsed.success) return false;
3726
+ const value = parsed.data;
3727
+ if (!value.takerPubkeySpkiB64 || !value.takerSignatureDerB64) return false;
3728
+ return verifyClaimSignature({
3729
+ alg: "p256",
3730
+ bytes: consumerPaymentRequestSigningBytes(value),
3731
+ signature: value.takerSignatureDerB64,
3732
+ publicKey: value.takerPubkeySpkiB64
3733
+ });
3734
+ }
3735
+ function isConsumerPaymentRequestExpired(request, nowMs = Date.now()) {
3736
+ const parsed = ConsumerPaymentRequestEnvelopeSchema.parse(request);
3737
+ return parsed.expiresAtMs <= nowMs;
3738
+ }
3739
+
3740
+ // src/me-offline/settlement.ts
3741
+ var CONSUMER_SETTLEMENT_DOMAIN = "flur:consumer-offline:v1:settlement";
3742
+ var CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX = "FLURSR1.";
3743
+ function consumerSettlementSigningPayload(settlement) {
3744
+ return {
3745
+ domain: CONSUMER_SETTLEMENT_DOMAIN,
3746
+ settlementId: settlement.settlementId,
3747
+ settlementKey: settlement.settlementKey,
3748
+ encounterId: settlement.encounterId,
3749
+ oacId: settlement.oacId,
3750
+ payerUserId: settlement.payerUserId,
3751
+ payeeUserId: settlement.payeeUserId,
3752
+ amountKobo: settlement.amountKobo,
3753
+ currency: settlement.currency,
3754
+ status: settlement.status,
3755
+ reviewReason: settlement.reviewReason,
3756
+ ledgerRef: settlement.ledgerRef,
3757
+ issuedAtMs: settlement.issuedAtMs
3758
+ };
3759
+ }
3760
+ function verifyConsumerSettlement(settlement, issuerPublicKeySpkiB64) {
3761
+ const parsed = ConsumerSettlementSchema.safeParse(settlement);
3762
+ if (!parsed.success) return false;
3763
+ return verifyIssuerP256(
3764
+ canonicalJSONBytes(consumerSettlementSigningPayload(parsed.data)),
3765
+ parsed.data.issuerSig,
3766
+ issuerPublicKeySpkiB64
3767
+ );
3768
+ }
3769
+ function encodeConsumerSettlementReceiptQR(settlement) {
3770
+ const parsed = ConsumerSettlementSchema.parse(settlement);
3771
+ return `${CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX}${base64UrlEncodeUtf8(
3772
+ JSON.stringify(parsed)
3773
+ )}`;
3774
+ }
3775
+ function decodeUnverifiedConsumerSettlementReceiptQR(value) {
3776
+ if (!value.startsWith(CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX)) {
3777
+ throw new Error("not a Flur consumer settlement receipt QR");
3778
+ }
3779
+ const encoded = value.slice(CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX.length);
3531
3780
  let raw;
3532
3781
  try {
3533
3782
  raw = JSON.parse(base64UrlDecodeUtf8(encoded));
3534
3783
  } catch {
3535
- throw new Error("offline claim SMS token is malformed");
3784
+ throw new Error("consumer settlement receipt QR is malformed");
3536
3785
  }
3537
- const parsed = ConsumerPaymentClaimSchema.safeParse(raw);
3538
- if (!parsed.success) {
3539
- throw new Error("offline claim SMS token is invalid");
3786
+ return ConsumerSettlementSchema.parse(raw);
3787
+ }
3788
+ function verifyConsumerSettlementReceiptQR(value, issuerPublicKeySpkiB64) {
3789
+ const settlement = decodeUnverifiedConsumerSettlementReceiptQR(value);
3790
+ if (!verifyConsumerSettlement(settlement, issuerPublicKeySpkiB64)) {
3791
+ throw new Error("consumer settlement receipt QR signature invalid");
3540
3792
  }
3541
- return parsed.data;
3793
+ return settlement;
3542
3794
  }
3543
- function extractOfflineClaimSmsToken(message) {
3544
- const trimmed = message.trim();
3545
- if (trimmed.startsWith(OFFLINE_CLAIM_SMS_PREFIX)) {
3546
- return trimmed.split(/\s+/, 1)[0] ?? null;
3795
+ function decodeConsumerSettlementReceiptQR(value, issuerPublicKeySpkiB64) {
3796
+ if (!issuerPublicKeySpkiB64) {
3797
+ return decodeUnverifiedConsumerSettlementReceiptQR(value);
3547
3798
  }
3548
- return TOKEN_RE.exec(message)?.[1] ?? null;
3799
+ return verifyConsumerSettlementReceiptQR(value, issuerPublicKeySpkiB64);
3549
3800
  }
3550
3801
  function base64UrlEncodeUtf8(input) {
3551
3802
  const bytes = new TextEncoder().encode(input);
@@ -3575,15 +3826,281 @@ function base64UrlDecodeUtf8(input) {
3575
3826
  throw new Error("base64 decoder unavailable");
3576
3827
  }
3577
3828
 
3829
+ // src/me-offline/sms.ts
3830
+ var import_nist3 = require("@noble/curves/nist");
3831
+ var OFFLINE_CLAIM_SMS_PREFIX = "FLURC1.";
3832
+ var CLAIM_TOKEN_RE = /(?:^|\s)(FLURC1\.[A-Za-z0-9_-]+={0,2})(?:\s|$)/;
3833
+ function encodeOfflineClaimSmsMessage(claim) {
3834
+ const parsed = ConsumerPaymentClaimSchema.parse(claim);
3835
+ return `${OFFLINE_CLAIM_SMS_PREFIX}${base64UrlEncodeUtf82(
3836
+ JSON.stringify(parsed)
3837
+ )}`;
3838
+ }
3839
+ function decodeOfflineClaimSmsMessage(message) {
3840
+ const token = extractOfflineClaimSmsToken(message);
3841
+ if (!token) throw new Error("offline claim QR token not found");
3842
+ const encoded = token.slice(OFFLINE_CLAIM_SMS_PREFIX.length);
3843
+ let raw;
3844
+ try {
3845
+ raw = JSON.parse(base64UrlDecodeUtf82(encoded));
3846
+ } catch {
3847
+ throw new Error("offline claim QR token is malformed");
3848
+ }
3849
+ const parsed = ConsumerPaymentClaimSchema.safeParse(raw);
3850
+ if (!parsed.success) throw new Error("offline claim QR token is invalid");
3851
+ return parsed.data;
3852
+ }
3853
+ function extractOfflineClaimSmsToken(message) {
3854
+ const trimmed = message.trim();
3855
+ if (trimmed.startsWith(OFFLINE_CLAIM_SMS_PREFIX)) {
3856
+ return trimmed.split(/\s+/, 1)[0] ?? null;
3857
+ }
3858
+ return CLAIM_TOKEN_RE.exec(message)?.[1] ?? null;
3859
+ }
3860
+ var OFFLINE_SMS_SETTLE_PREFIX = "FLURA1.";
3861
+ var OFFLINE_SMS_SETTLE_DOMAIN = "flur:consumer-offline:v1:attest";
3862
+ var OFFLINE_SMS_SETTLE_TOKEN_BYTES = 112;
3863
+ var OFFLINE_SMS_SETTLE_HEADER_BYTES = 48;
3864
+ var OFFLINE_SMS_SETTLE_SIGNATURE_BYTES = 64;
3865
+ var OFFLINE_SMS_SETTLE_VERSION = 1;
3866
+ var TOKEN_RE = /(?:^|[\s,;:()<>"'])(FLURA1\.[A-Za-z0-9_-]{150})/;
3867
+ async function encodeOfflineSmsSettleToken(input, signer) {
3868
+ const header = await buildSmsSettleHeader(input);
3869
+ const sig = await signer.signRaw(domainTag(header));
3870
+ if (sig.length !== OFFLINE_SMS_SETTLE_SIGNATURE_BYTES) {
3871
+ throw new Error(
3872
+ `FLURA1: signer returned ${sig.length}-byte sig; expected ${OFFLINE_SMS_SETTLE_SIGNATURE_BYTES}`
3873
+ );
3874
+ }
3875
+ const out = new Uint8Array(OFFLINE_SMS_SETTLE_TOKEN_BYTES);
3876
+ out.set(header, 0);
3877
+ out.set(sig, OFFLINE_SMS_SETTLE_HEADER_BYTES);
3878
+ return `${OFFLINE_SMS_SETTLE_PREFIX}${bytesToBase64Url(out)}`;
3879
+ }
3880
+ async function buildSmsSettleHeader(input) {
3881
+ assertSafeUint64(input.amountKobo, "amountKobo");
3882
+ if (input.amountKobo <= 0) {
3883
+ throw new Error("FLURA1: amountKobo must be greater than zero");
3884
+ }
3885
+ assertSafeUint48(input.occurredAtMs, "occurredAtMs");
3886
+ const encounterPrefix = (await sha2565(utf8(input.encounterId))).slice(0, 16);
3887
+ const payerPrefix = uuidToBytes(input.payerUserId).slice(0, 8);
3888
+ const payeePrefix = uuidToBytes(input.payeeUserId).slice(0, 8);
3889
+ const header = new Uint8Array(OFFLINE_SMS_SETTLE_HEADER_BYTES);
3890
+ const dv = new DataView(header.buffer);
3891
+ header[0] = OFFLINE_SMS_SETTLE_VERSION;
3892
+ header[1] = 0;
3893
+ header.set(encounterPrefix, 2);
3894
+ header.set(payerPrefix, 18);
3895
+ header.set(payeePrefix, 26);
3896
+ writeUint64BE(dv, 34, input.amountKobo);
3897
+ writeUint48BE(dv, 42, input.occurredAtMs);
3898
+ return header;
3899
+ }
3900
+ function domainTag(header) {
3901
+ if (header.length !== OFFLINE_SMS_SETTLE_HEADER_BYTES) {
3902
+ throw new Error(
3903
+ `FLURA1: header must be ${OFFLINE_SMS_SETTLE_HEADER_BYTES} bytes`
3904
+ );
3905
+ }
3906
+ const domain = utf8(OFFLINE_SMS_SETTLE_DOMAIN);
3907
+ const out = new Uint8Array(domain.length + header.length);
3908
+ out.set(domain, 0);
3909
+ out.set(header, domain.length);
3910
+ return out;
3911
+ }
3912
+ function decodeOfflineSmsSettleToken(message) {
3913
+ const token = extractOfflineSmsSettleToken(message);
3914
+ if (!token) throw new Error("FLURA1: token not found");
3915
+ const encoded = token.slice(OFFLINE_SMS_SETTLE_PREFIX.length);
3916
+ let bytes;
3917
+ try {
3918
+ bytes = base64UrlToBytes(encoded);
3919
+ } catch {
3920
+ throw new Error("FLURA1: token base64url is malformed");
3921
+ }
3922
+ if (bytesToBase64Url(bytes) !== encoded) {
3923
+ throw new Error("FLURA1: token base64url is malformed");
3924
+ }
3925
+ if (bytes.length !== OFFLINE_SMS_SETTLE_TOKEN_BYTES) {
3926
+ throw new Error(
3927
+ `FLURA1: expected ${OFFLINE_SMS_SETTLE_TOKEN_BYTES} bytes, got ${bytes.length}`
3928
+ );
3929
+ }
3930
+ const version = bytes[0];
3931
+ const flags = bytes[1];
3932
+ if (version !== OFFLINE_SMS_SETTLE_VERSION) {
3933
+ throw new Error(`FLURA1: unsupported version ${version}`);
3934
+ }
3935
+ if (flags !== 0) {
3936
+ throw new Error(`FLURA1: reserved flags must be 0, got ${flags}`);
3937
+ }
3938
+ const header = bytes.slice(0, OFFLINE_SMS_SETTLE_HEADER_BYTES);
3939
+ const signature = bytes.slice(OFFLINE_SMS_SETTLE_HEADER_BYTES);
3940
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
3941
+ const amountKobo = readUint64BE(dv, 34);
3942
+ if (amountKobo <= 0) {
3943
+ throw new Error("FLURA1: amountKobo must be greater than zero");
3944
+ }
3945
+ return {
3946
+ version,
3947
+ flags,
3948
+ encounterIdPrefixHex: bytesToHex6(bytes.slice(2, 18)),
3949
+ payerUserIdPrefixHex: bytesToHex6(bytes.slice(18, 26)),
3950
+ payeeUserIdPrefixHex: bytesToHex6(bytes.slice(26, 34)),
3951
+ amountKobo,
3952
+ occurredAtMs: readUint48BE(dv, 42),
3953
+ signature,
3954
+ header,
3955
+ signedBytes: domainTag(header)
3956
+ };
3957
+ }
3958
+ function extractOfflineSmsSettleToken(message) {
3959
+ const trimmed = message.trim();
3960
+ if (trimmed.startsWith(OFFLINE_SMS_SETTLE_PREFIX)) {
3961
+ return trimmed.split(/\s+/, 1)[0] ?? null;
3962
+ }
3963
+ return TOKEN_RE.exec(message)?.[1] ?? null;
3964
+ }
3965
+ function verifyOfflineSmsSettleToken(decoded, payerPubkeySpkiB64) {
3966
+ try {
3967
+ const pubRaw = p256SpkiB64ToRaw(payerPubkeySpkiB64);
3968
+ return import_nist3.p256.verify(decoded.signature, decoded.signedBytes, pubRaw, {
3969
+ prehash: true,
3970
+ format: "compact"
3971
+ });
3972
+ } catch {
3973
+ return false;
3974
+ }
3975
+ }
3976
+ function derToRawP256Signature(derBytes) {
3977
+ const sig = import_nist3.p256.Signature.fromBytes(derBytes, "der");
3978
+ const raw = sig.toBytes("compact");
3979
+ if (raw.length !== OFFLINE_SMS_SETTLE_SIGNATURE_BYTES) {
3980
+ throw new Error(
3981
+ `FLURA1: DER\u2192raw produced ${raw.length} bytes; expected ${OFFLINE_SMS_SETTLE_SIGNATURE_BYTES}`
3982
+ );
3983
+ }
3984
+ return raw;
3985
+ }
3986
+ function utf8(s) {
3987
+ return new TextEncoder().encode(s);
3988
+ }
3989
+ async function sha2565(bytes) {
3990
+ const subtle = typeof globalThis !== "undefined" && globalThis.crypto?.subtle || void 0;
3991
+ if (subtle) {
3992
+ const digest = await subtle.digest("SHA-256", bytes);
3993
+ return new Uint8Array(digest);
3994
+ }
3995
+ const { sha256: nobleSha256 } = await import("@noble/hashes/sha2");
3996
+ return nobleSha256(bytes);
3997
+ }
3998
+ function uuidToBytes(uuid) {
3999
+ const hex = uuid.replace(/-/g, "").toLowerCase();
4000
+ if (hex.length !== 32 || !/^[0-9a-f]{32}$/.test(hex)) {
4001
+ throw new Error(`FLURA1: invalid UUID: ${uuid}`);
4002
+ }
4003
+ const out = new Uint8Array(16);
4004
+ for (let i = 0; i < 16; i++) {
4005
+ out[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
4006
+ }
4007
+ return out;
4008
+ }
4009
+ function assertSafeUint64(value, field) {
4010
+ if (!Number.isInteger(value) || value < 0) {
4011
+ throw new Error(`FLURA1: ${field} must be a non-negative integer`);
4012
+ }
4013
+ if (value > Number.MAX_SAFE_INTEGER) {
4014
+ throw new Error(`FLURA1: ${field} exceeds Number.MAX_SAFE_INTEGER`);
4015
+ }
4016
+ }
4017
+ function assertSafeUint48(value, field) {
4018
+ assertSafeUint64(value, field);
4019
+ if (value > 281474976710655) {
4020
+ throw new Error(`FLURA1: ${field} exceeds uint48 range`);
4021
+ }
4022
+ }
4023
+ function writeUint64BE(dv, offset, value) {
4024
+ const high = Math.floor(value / 4294967296);
4025
+ const low = value >>> 0;
4026
+ dv.setUint32(offset, high, false);
4027
+ dv.setUint32(offset + 4, low, false);
4028
+ }
4029
+ function readUint64BE(dv, offset) {
4030
+ const high = dv.getUint32(offset, false);
4031
+ const low = dv.getUint32(offset + 4, false);
4032
+ if (high > 2097151) {
4033
+ throw new Error("FLURA1: amountKobo exceeds Number.MAX_SAFE_INTEGER");
4034
+ }
4035
+ return high * 4294967296 + low;
4036
+ }
4037
+ function writeUint48BE(dv, offset, value) {
4038
+ const high = Math.floor(value / 65536);
4039
+ const low = value & 65535;
4040
+ dv.setUint32(offset, high, false);
4041
+ dv.setUint16(offset + 4, low, false);
4042
+ }
4043
+ function readUint48BE(dv, offset) {
4044
+ const high = dv.getUint32(offset, false);
4045
+ const low = dv.getUint16(offset + 4, false);
4046
+ return high * 65536 + low;
4047
+ }
4048
+ function bytesToHex6(bytes) {
4049
+ let out = "";
4050
+ for (let i = 0; i < bytes.length; i++) {
4051
+ out += bytes[i].toString(16).padStart(2, "0");
4052
+ }
4053
+ return out;
4054
+ }
4055
+ function bytesToBase64Url(bytes) {
4056
+ let base64;
4057
+ if (typeof Buffer !== "undefined") {
4058
+ base64 = Buffer.from(bytes).toString("base64");
4059
+ } else {
4060
+ let binary = "";
4061
+ for (let i = 0; i < bytes.length; i++) {
4062
+ binary += String.fromCharCode(bytes[i]);
4063
+ }
4064
+ if (typeof btoa !== "function") {
4065
+ throw new Error("FLURA1: base64 encoder unavailable");
4066
+ }
4067
+ base64 = btoa(binary);
4068
+ }
4069
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
4070
+ }
4071
+ function base64UrlEncodeUtf82(input) {
4072
+ return bytesToBase64Url(utf8(input));
4073
+ }
4074
+ function base64UrlDecodeUtf82(input) {
4075
+ return new TextDecoder().decode(base64UrlToBytes(input));
4076
+ }
4077
+ function base64UrlToBytes(input) {
4078
+ const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
4079
+ const padded = base64.padEnd(
4080
+ base64.length + (4 - base64.length % 4) % 4,
4081
+ "="
4082
+ );
4083
+ if (typeof Buffer !== "undefined") {
4084
+ return new Uint8Array(Buffer.from(padded, "base64"));
4085
+ }
4086
+ if (typeof atob !== "function") {
4087
+ throw new Error("FLURA1: base64 decoder unavailable");
4088
+ }
4089
+ const binary = atob(padded);
4090
+ const out = new Uint8Array(binary.length);
4091
+ for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
4092
+ return out;
4093
+ }
4094
+
3578
4095
  // src/partner-funding/client.ts
3579
- var import_zod14 = require("zod");
3580
- var MinorString = import_zod14.z.string().regex(/^-?\d+$/);
3581
- var PositiveMinor = import_zod14.z.union([
3582
- import_zod14.z.number().int().positive(),
3583
- import_zod14.z.string().regex(/^[1-9]\d{0,18}$/)
4096
+ var import_zod15 = require("zod");
4097
+ var MinorString = import_zod15.z.string().regex(/^-?\d+$/);
4098
+ var PositiveMinor = import_zod15.z.union([
4099
+ import_zod15.z.number().int().positive(),
4100
+ import_zod15.z.string().regex(/^[1-9]\d{0,18}$/)
3584
4101
  ]);
3585
- var Currency = import_zod14.z.string().trim().length(3).transform((v) => v.toUpperCase());
3586
- var Metadata = import_zod14.z.record(import_zod14.z.unknown());
4102
+ var Currency = import_zod15.z.string().trim().length(3).transform((v) => v.toUpperCase());
4103
+ var Metadata = import_zod15.z.record(import_zod15.z.unknown());
3587
4104
  var PARTNER_KINDS = ["bank", "merchant"];
3588
4105
  var CUSTODIAL_MODES = ["agent_of_bank", "flur_virtual_pool"];
3589
4106
  var PARTNER_PROFILE_STATUSES = [
@@ -3610,126 +4127,126 @@ var WITHDRAWAL_STATES = [
3610
4127
  "failed",
3611
4128
  "reversed"
3612
4129
  ];
3613
- var PartnerProfileSchema = import_zod14.z.object({
3614
- partnerAccountId: import_zod14.z.string().uuid(),
3615
- kind: import_zod14.z.enum(PARTNER_KINDS),
3616
- custodialMode: import_zod14.z.enum(CUSTODIAL_MODES),
3617
- displayName: import_zod14.z.string(),
3618
- bankCode: import_zod14.z.string().nullable(),
3619
- poolAccountNumber: import_zod14.z.string().nullable(),
3620
- status: import_zod14.z.enum(PARTNER_PROFILE_STATUSES),
4130
+ var PartnerProfileSchema = import_zod15.z.object({
4131
+ partnerAccountId: import_zod15.z.string().uuid(),
4132
+ kind: import_zod15.z.enum(PARTNER_KINDS),
4133
+ custodialMode: import_zod15.z.enum(CUSTODIAL_MODES),
4134
+ displayName: import_zod15.z.string(),
4135
+ bankCode: import_zod15.z.string().nullable(),
4136
+ poolAccountNumber: import_zod15.z.string().nullable(),
4137
+ status: import_zod15.z.enum(PARTNER_PROFILE_STATUSES),
3621
4138
  metadata: Metadata,
3622
- createdAtMs: import_zod14.z.number().int().nonnegative(),
3623
- updatedAtMs: import_zod14.z.number().int().nonnegative()
4139
+ createdAtMs: import_zod15.z.number().int().nonnegative(),
4140
+ updatedAtMs: import_zod15.z.number().int().nonnegative()
3624
4141
  });
3625
- var UpsertPartnerProfileInputSchema = import_zod14.z.object({
3626
- kind: import_zod14.z.enum(PARTNER_KINDS),
3627
- custodialMode: import_zod14.z.enum(CUSTODIAL_MODES),
3628
- displayName: import_zod14.z.string().trim().min(1).max(200),
3629
- bankCode: import_zod14.z.string().trim().min(1).max(64).optional(),
3630
- poolAccountNumber: import_zod14.z.string().trim().min(1).max(64).optional(),
4142
+ var UpsertPartnerProfileInputSchema = import_zod15.z.object({
4143
+ kind: import_zod15.z.enum(PARTNER_KINDS),
4144
+ custodialMode: import_zod15.z.enum(CUSTODIAL_MODES),
4145
+ displayName: import_zod15.z.string().trim().min(1).max(200),
4146
+ bankCode: import_zod15.z.string().trim().min(1).max(64).optional(),
4147
+ poolAccountNumber: import_zod15.z.string().trim().min(1).max(64).optional(),
3631
4148
  metadata: Metadata.optional()
3632
4149
  });
3633
- var PartnerFundingEventInputSchema = import_zod14.z.object({
3634
- externalRef: import_zod14.z.string().trim().min(8).max(128),
3635
- direction: import_zod14.z.enum(PARTNER_FUNDING_DIRECTIONS).optional(),
3636
- userId: import_zod14.z.string().uuid().optional(),
3637
- accountId: import_zod14.z.string().uuid().optional(),
4150
+ var PartnerFundingEventInputSchema = import_zod15.z.object({
4151
+ externalRef: import_zod15.z.string().trim().min(8).max(128),
4152
+ direction: import_zod15.z.enum(PARTNER_FUNDING_DIRECTIONS).optional(),
4153
+ userId: import_zod15.z.string().uuid().optional(),
4154
+ accountId: import_zod15.z.string().uuid().optional(),
3638
4155
  amountMinor: PositiveMinor,
3639
4156
  currency: Currency,
3640
- fundingSource: import_zod14.z.string().trim().min(1).max(64).optional(),
4157
+ fundingSource: import_zod15.z.string().trim().min(1).max(64).optional(),
3641
4158
  providerMetadata: Metadata.optional()
3642
4159
  });
3643
- var PartnerFundingSchema = import_zod14.z.object({
3644
- fundingId: import_zod14.z.string().uuid(),
3645
- partnerId: import_zod14.z.string().uuid(),
3646
- accountId: import_zod14.z.string().uuid(),
3647
- userId: import_zod14.z.string().uuid().nullable(),
3648
- direction: import_zod14.z.enum(PARTNER_FUNDING_DIRECTIONS),
3649
- currency: import_zod14.z.string(),
4160
+ var PartnerFundingSchema = import_zod15.z.object({
4161
+ fundingId: import_zod15.z.string().uuid(),
4162
+ partnerId: import_zod15.z.string().uuid(),
4163
+ accountId: import_zod15.z.string().uuid(),
4164
+ userId: import_zod15.z.string().uuid().nullable(),
4165
+ direction: import_zod15.z.enum(PARTNER_FUNDING_DIRECTIONS),
4166
+ currency: import_zod15.z.string(),
3650
4167
  amountMinor: MinorString,
3651
- externalRef: import_zod14.z.string(),
3652
- status: import_zod14.z.enum(PARTNER_FUNDING_STATUSES),
3653
- fundingSource: import_zod14.z.string(),
3654
- ledgerRef: import_zod14.z.string(),
4168
+ externalRef: import_zod15.z.string(),
4169
+ status: import_zod15.z.enum(PARTNER_FUNDING_STATUSES),
4170
+ fundingSource: import_zod15.z.string(),
4171
+ ledgerRef: import_zod15.z.string(),
3655
4172
  providerMetadata: Metadata,
3656
- createdAtMs: import_zod14.z.number().int().nonnegative(),
3657
- updatedAtMs: import_zod14.z.number().int().nonnegative()
4173
+ createdAtMs: import_zod15.z.number().int().nonnegative(),
4174
+ updatedAtMs: import_zod15.z.number().int().nonnegative()
3658
4175
  });
3659
- var IngestFundingResultSchema = import_zod14.z.object({
4176
+ var IngestFundingResultSchema = import_zod15.z.object({
3660
4177
  funding: PartnerFundingSchema,
3661
- replayed: import_zod14.z.boolean()
4178
+ replayed: import_zod15.z.boolean()
3662
4179
  });
3663
- var PayoutDestinationSchema = import_zod14.z.object({
3664
- destinationId: import_zod14.z.string().uuid(),
3665
- accountId: import_zod14.z.string().uuid(),
3666
- partnerId: import_zod14.z.string().uuid(),
3667
- bankCode: import_zod14.z.string(),
3668
- accountNumber: import_zod14.z.string(),
3669
- accountName: import_zod14.z.string(),
3670
- status: import_zod14.z.enum(PAYOUT_DESTINATION_STATUSES),
3671
- verifiedAtMs: import_zod14.z.number().int().nonnegative().nullable(),
4180
+ var PayoutDestinationSchema = import_zod15.z.object({
4181
+ destinationId: import_zod15.z.string().uuid(),
4182
+ accountId: import_zod15.z.string().uuid(),
4183
+ partnerId: import_zod15.z.string().uuid(),
4184
+ bankCode: import_zod15.z.string(),
4185
+ accountNumber: import_zod15.z.string(),
4186
+ accountName: import_zod15.z.string(),
4187
+ status: import_zod15.z.enum(PAYOUT_DESTINATION_STATUSES),
4188
+ verifiedAtMs: import_zod15.z.number().int().nonnegative().nullable(),
3672
4189
  metadata: Metadata,
3673
- createdAtMs: import_zod14.z.number().int().nonnegative(),
3674
- updatedAtMs: import_zod14.z.number().int().nonnegative()
4190
+ createdAtMs: import_zod15.z.number().int().nonnegative(),
4191
+ updatedAtMs: import_zod15.z.number().int().nonnegative()
3675
4192
  });
3676
- var CreatePayoutDestinationInputSchema = import_zod14.z.object({
3677
- partnerId: import_zod14.z.string().uuid(),
3678
- bankCode: import_zod14.z.string().trim().min(1).max(32),
3679
- accountNumber: import_zod14.z.string().trim().min(4).max(64),
3680
- accountName: import_zod14.z.string().trim().min(1).max(200),
4193
+ var CreatePayoutDestinationInputSchema = import_zod15.z.object({
4194
+ partnerId: import_zod15.z.string().uuid(),
4195
+ bankCode: import_zod15.z.string().trim().min(1).max(32),
4196
+ accountNumber: import_zod15.z.string().trim().min(4).max(64),
4197
+ accountName: import_zod15.z.string().trim().min(1).max(200),
3681
4198
  metadata: Metadata.optional()
3682
4199
  });
3683
- var ListPayoutDestinationsResultSchema = import_zod14.z.object({
3684
- items: import_zod14.z.array(PayoutDestinationSchema)
4200
+ var ListPayoutDestinationsResultSchema = import_zod15.z.object({
4201
+ items: import_zod15.z.array(PayoutDestinationSchema)
3685
4202
  });
3686
- var WithdrawalSchema = import_zod14.z.object({
3687
- withdrawalId: import_zod14.z.string().uuid(),
3688
- accountId: import_zod14.z.string().uuid(),
3689
- userId: import_zod14.z.string().uuid(),
3690
- partnerId: import_zod14.z.string().uuid(),
3691
- destinationId: import_zod14.z.string().uuid(),
3692
- currency: import_zod14.z.string(),
4203
+ var WithdrawalSchema = import_zod15.z.object({
4204
+ withdrawalId: import_zod15.z.string().uuid(),
4205
+ accountId: import_zod15.z.string().uuid(),
4206
+ userId: import_zod15.z.string().uuid(),
4207
+ partnerId: import_zod15.z.string().uuid(),
4208
+ destinationId: import_zod15.z.string().uuid(),
4209
+ currency: import_zod15.z.string(),
3693
4210
  amountMinor: MinorString,
3694
- state: import_zod14.z.enum(WITHDRAWAL_STATES),
3695
- idempotencyKey: import_zod14.z.string(),
3696
- providerRef: import_zod14.z.string().nullable(),
3697
- lastError: import_zod14.z.string().nullable(),
3698
- ledgerRef: import_zod14.z.string(),
3699
- reverseLedgerRef: import_zod14.z.string().nullable(),
4211
+ state: import_zod15.z.enum(WITHDRAWAL_STATES),
4212
+ idempotencyKey: import_zod15.z.string(),
4213
+ providerRef: import_zod15.z.string().nullable(),
4214
+ lastError: import_zod15.z.string().nullable(),
4215
+ ledgerRef: import_zod15.z.string(),
4216
+ reverseLedgerRef: import_zod15.z.string().nullable(),
3700
4217
  metadata: Metadata,
3701
- createdAtMs: import_zod14.z.number().int().nonnegative(),
3702
- updatedAtMs: import_zod14.z.number().int().nonnegative()
4218
+ createdAtMs: import_zod15.z.number().int().nonnegative(),
4219
+ updatedAtMs: import_zod15.z.number().int().nonnegative()
3703
4220
  });
3704
- var CreateWithdrawalInputSchema = import_zod14.z.object({
3705
- destinationId: import_zod14.z.string().uuid(),
4221
+ var CreateWithdrawalInputSchema = import_zod15.z.object({
4222
+ destinationId: import_zod15.z.string().uuid(),
3706
4223
  amountMinor: PositiveMinor,
3707
4224
  currency: Currency,
3708
- idempotencyKey: import_zod14.z.string().trim().min(8).max(128),
4225
+ idempotencyKey: import_zod15.z.string().trim().min(8).max(128),
3709
4226
  metadata: Metadata.optional()
3710
4227
  });
3711
- var CreateWithdrawalResultSchema = import_zod14.z.object({
4228
+ var CreateWithdrawalResultSchema = import_zod15.z.object({
3712
4229
  withdrawal: WithdrawalSchema,
3713
- replayed: import_zod14.z.boolean()
4230
+ replayed: import_zod15.z.boolean()
3714
4231
  });
3715
- var PayoutEventInputSchema = import_zod14.z.object({
3716
- externalRef: import_zod14.z.string().trim().min(8).max(128),
3717
- withdrawalId: import_zod14.z.string().uuid().optional(),
3718
- state: import_zod14.z.enum(["submitted", "processing", "paid", "failed"]),
3719
- providerRef: import_zod14.z.string().trim().min(1).max(128).optional(),
3720
- failureCode: import_zod14.z.string().trim().max(64).optional(),
3721
- failureMessage: import_zod14.z.string().trim().max(512).optional(),
4232
+ var PayoutEventInputSchema = import_zod15.z.object({
4233
+ externalRef: import_zod15.z.string().trim().min(8).max(128),
4234
+ withdrawalId: import_zod15.z.string().uuid().optional(),
4235
+ state: import_zod15.z.enum(["submitted", "processing", "paid", "failed"]),
4236
+ providerRef: import_zod15.z.string().trim().min(1).max(128).optional(),
4237
+ failureCode: import_zod15.z.string().trim().max(64).optional(),
4238
+ failureMessage: import_zod15.z.string().trim().max(512).optional(),
3722
4239
  providerMetadata: Metadata.optional()
3723
4240
  });
3724
- var RecordPayoutEventResultSchema = import_zod14.z.object({
4241
+ var RecordPayoutEventResultSchema = import_zod15.z.object({
3725
4242
  withdrawal: WithdrawalSchema,
3726
- replayed: import_zod14.z.boolean()
4243
+ replayed: import_zod15.z.boolean()
3727
4244
  });
3728
- var ReconciliationReportSchema = import_zod14.z.object({
3729
- partnerId: import_zod14.z.string().uuid(),
3730
- currency: import_zod14.z.string(),
3731
- fromMs: import_zod14.z.number().int().nonnegative(),
3732
- toMs: import_zod14.z.number().int().nonnegative(),
4245
+ var ReconciliationReportSchema = import_zod15.z.object({
4246
+ partnerId: import_zod15.z.string().uuid(),
4247
+ currency: import_zod15.z.string(),
4248
+ fromMs: import_zod15.z.number().int().nonnegative(),
4249
+ toMs: import_zod15.z.number().int().nonnegative(),
3733
4250
  fundingsCreditMinor: MinorString,
3734
4251
  fundingsDebitMinor: MinorString,
3735
4252
  withdrawalsPaidMinor: MinorString,
@@ -3738,7 +4255,7 @@ var ReconciliationReportSchema = import_zod14.z.object({
3738
4255
  expectedReserveBalanceMinor: MinorString,
3739
4256
  actualReserveBalanceMinor: MinorString,
3740
4257
  imbalanceMinor: MinorString,
3741
- generatedAtMs: import_zod14.z.number().int().nonnegative()
4258
+ generatedAtMs: import_zod15.z.number().int().nonnegative()
3742
4259
  });
3743
4260
  function createPartnerFundingClient(partner) {
3744
4261
  return {
@@ -3890,19 +4407,19 @@ function createPartnerProfileAdminClient(opts) {
3890
4407
  }
3891
4408
 
3892
4409
  // src/artifacts/envelope.ts
3893
- var import_zod15 = require("zod");
4410
+ var import_zod16 = require("zod");
3894
4411
  var FLUR_ARTIFACT_URI_SCHEME = "flur";
3895
4412
  var FLUR_ARTIFACT_VERSION = 1;
3896
4413
  var FLUR_ARTIFACT_URI_PREFIX = `${FLUR_ARTIFACT_URI_SCHEME}://v${FLUR_ARTIFACT_VERSION}/`;
3897
4414
  var ArtifactTypeRe = /^[a-z][a-z0-9_]{1,63}$/;
3898
- var ArtifactHeaderSchema = import_zod15.z.object({
3899
- v: import_zod15.z.literal(FLUR_ARTIFACT_VERSION),
3900
- t: import_zod15.z.string().regex(ArtifactTypeRe, "invalid artifact type"),
3901
- iss: import_zod15.z.string().min(1).max(128),
3902
- kid: import_zod15.z.string().min(1).max(128),
3903
- iat: import_zod15.z.number().int().nonnegative(),
3904
- exp: import_zod15.z.number().int().positive().optional(),
3905
- nonce: import_zod15.z.string().min(8).max(64).regex(/^[A-Za-z0-9_-]+$/, "nonce must be url-safe")
4415
+ var ArtifactHeaderSchema = import_zod16.z.object({
4416
+ v: import_zod16.z.literal(FLUR_ARTIFACT_VERSION),
4417
+ t: import_zod16.z.string().regex(ArtifactTypeRe, "invalid artifact type"),
4418
+ iss: import_zod16.z.string().min(1).max(128),
4419
+ kid: import_zod16.z.string().min(1).max(128),
4420
+ iat: import_zod16.z.number().int().nonnegative(),
4421
+ exp: import_zod16.z.number().int().positive().optional(),
4422
+ nonce: import_zod16.z.string().min(8).max(64).regex(/^[A-Za-z0-9_-]+$/, "nonce must be url-safe")
3906
4423
  });
3907
4424
  var FlurArtifactError = class extends Error {
3908
4425
  constructor(message, code) {
@@ -4041,7 +4558,7 @@ function verifyArtifactSignature(decoded, publicKeySpkiB64, options = {}) {
4041
4558
  }
4042
4559
 
4043
4560
  // src/artifacts/types.ts
4044
- var import_zod16 = require("zod");
4561
+ var import_zod17 = require("zod");
4045
4562
  var ARTIFACT_TYPES = {
4046
4563
  OFFLINE_PAYMENT_AUTHORIZATION: "offline_payment_authorization",
4047
4564
  RECEIPT: "receipt",
@@ -4054,34 +4571,37 @@ var ARTIFACT_TYPES = {
4054
4571
  LEDGER_JOURNAL_ENTRY: "ledger_journal_entry",
4055
4572
  STATEMENT: "statement",
4056
4573
  PASS: "pass",
4057
- IDENTITY: "identity"
4574
+ IDENTITY: "identity",
4575
+ // Tier B: holder-signed identity attestation for offline trust. The
4576
+ // envelope.iat / envelope.exp express the card's canonical lifetime.
4577
+ PAY_CARD: "pay_card"
4058
4578
  };
4059
- var HexString = (length) => import_zod16.z.string().regex(
4579
+ var HexString = (length) => import_zod17.z.string().regex(
4060
4580
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
4061
4581
  `expected ${length}-byte hex string`
4062
4582
  );
4063
- var OfflinePaymentAuthorizationArtifactSchema = import_zod16.z.object({
4583
+ var OfflinePaymentAuthorizationArtifactSchema = import_zod17.z.object({
4064
4584
  authorization: OfflinePaymentAuthorizationSchema
4065
4585
  });
4066
- var ReceiptArtifactSchema = import_zod16.z.object({
4067
- receiptId: import_zod16.z.string().min(1).max(64),
4068
- paymentReference: import_zod16.z.string().min(1).max(64),
4069
- payerUserId: import_zod16.z.string().min(1).max(64).optional(),
4070
- payeeUserId: import_zod16.z.string().min(1).max(64),
4071
- amountKobo: import_zod16.z.number().int().positive(),
4072
- currency: import_zod16.z.literal("NGN"),
4073
- channel: import_zod16.z.enum(["online", "offline_reconciled", "pay_link", "nqr"]),
4074
- settledAtMs: import_zod16.z.number().int().positive(),
4075
- ledgerTxnId: import_zod16.z.string().min(1).max(64).optional(),
4076
- memo: import_zod16.z.string().max(140).optional(),
4586
+ var ReceiptArtifactSchema = import_zod17.z.object({
4587
+ receiptId: import_zod17.z.string().min(1).max(64),
4588
+ paymentReference: import_zod17.z.string().min(1).max(64),
4589
+ payerUserId: import_zod17.z.string().min(1).max(64).optional(),
4590
+ payeeUserId: import_zod17.z.string().min(1).max(64),
4591
+ amountKobo: import_zod17.z.number().int().positive(),
4592
+ currency: import_zod17.z.literal("NGN"),
4593
+ channel: import_zod17.z.enum(["online", "offline_reconciled", "pay_link", "nqr"]),
4594
+ settledAtMs: import_zod17.z.number().int().positive(),
4595
+ ledgerTxnId: import_zod17.z.string().min(1).max(64).optional(),
4596
+ memo: import_zod17.z.string().max(140).optional(),
4077
4597
  hashChainPrev: HexString(32).optional()
4078
4598
  });
4079
- var ShortId = import_zod16.z.string().min(1).max(64);
4080
- var PositiveInt = import_zod16.z.number().int().positive();
4081
- var NonNegativeInt = import_zod16.z.number().int().nonnegative();
4082
- var Currency2 = import_zod16.z.literal("NGN");
4083
- var Memo = import_zod16.z.string().max(140);
4084
- var NqrPaymentRequestArtifactSchema = import_zod16.z.object({
4599
+ var ShortId = import_zod17.z.string().min(1).max(64);
4600
+ var PositiveInt = import_zod17.z.number().int().positive();
4601
+ var NonNegativeInt = import_zod17.z.number().int().nonnegative();
4602
+ var Currency2 = import_zod17.z.literal("NGN");
4603
+ var Memo = import_zod17.z.string().max(140);
4604
+ var NqrPaymentRequestArtifactSchema = import_zod17.z.object({
4085
4605
  requestId: ShortId,
4086
4606
  payeeUserId: ShortId,
4087
4607
  amountKobo: PositiveInt.optional(),
@@ -4089,7 +4609,7 @@ var NqrPaymentRequestArtifactSchema = import_zod16.z.object({
4089
4609
  memo: Memo.optional(),
4090
4610
  expiresAtMs: PositiveInt.optional()
4091
4611
  });
4092
- var PaymentIntentArtifactSchema = import_zod16.z.object({
4612
+ var PaymentIntentArtifactSchema = import_zod17.z.object({
4093
4613
  intentId: ShortId,
4094
4614
  payerUserId: ShortId,
4095
4615
  payeeUserId: ShortId,
@@ -4098,7 +4618,7 @@ var PaymentIntentArtifactSchema = import_zod16.z.object({
4098
4618
  idempotencyKey: ShortId,
4099
4619
  createdAtMs: PositiveInt
4100
4620
  });
4101
- var OfflineClaimArtifactSchema = import_zod16.z.object({
4621
+ var OfflineClaimArtifactSchema = import_zod17.z.object({
4102
4622
  claimId: ShortId,
4103
4623
  authorizationId: ShortId,
4104
4624
  payeeUserId: ShortId,
@@ -4107,10 +4627,10 @@ var OfflineClaimArtifactSchema = import_zod16.z.object({
4107
4627
  claimedAtMs: PositiveInt,
4108
4628
  paymentReference: ShortId.optional()
4109
4629
  });
4110
- var SettlementRecordArtifactSchema = import_zod16.z.object({
4630
+ var SettlementRecordArtifactSchema = import_zod17.z.object({
4111
4631
  settlementId: ShortId,
4112
4632
  ledgerTxnId: ShortId,
4113
- sourceRefType: import_zod16.z.enum([
4633
+ sourceRefType: import_zod17.z.enum([
4114
4634
  "offline_authorization",
4115
4635
  "offline_claim",
4116
4636
  "transfer",
@@ -4121,12 +4641,12 @@ var SettlementRecordArtifactSchema = import_zod16.z.object({
4121
4641
  currency: Currency2,
4122
4642
  settledAtMs: PositiveInt
4123
4643
  });
4124
- var ReversalRecordArtifactSchema = import_zod16.z.object({
4644
+ var ReversalRecordArtifactSchema = import_zod17.z.object({
4125
4645
  reversalId: ShortId,
4126
4646
  originalTxnId: ShortId,
4127
4647
  amountKobo: PositiveInt,
4128
4648
  currency: Currency2,
4129
- reason: import_zod16.z.enum([
4649
+ reason: import_zod17.z.enum([
4130
4650
  "user_dispute",
4131
4651
  "fraud",
4132
4652
  "duplicate",
@@ -4136,7 +4656,7 @@ var ReversalRecordArtifactSchema = import_zod16.z.object({
4136
4656
  reversedAtMs: PositiveInt,
4137
4657
  memo: Memo.optional()
4138
4658
  });
4139
- var LedgerJournalEntryArtifactSchema = import_zod16.z.object({
4659
+ var LedgerJournalEntryArtifactSchema = import_zod17.z.object({
4140
4660
  entryId: ShortId,
4141
4661
  journalId: ShortId,
4142
4662
  debitAccountId: ShortId,
@@ -4147,13 +4667,13 @@ var LedgerJournalEntryArtifactSchema = import_zod16.z.object({
4147
4667
  refType: ShortId.optional(),
4148
4668
  refId: ShortId.optional()
4149
4669
  });
4150
- var StatementArtifactSchema = import_zod16.z.object({
4670
+ var StatementArtifactSchema = import_zod17.z.object({
4151
4671
  statementId: ShortId,
4152
4672
  userId: ShortId,
4153
4673
  periodStartMs: PositiveInt,
4154
4674
  periodEndMs: PositiveInt,
4155
- openingBalanceKobo: import_zod16.z.number().int(),
4156
- closingBalanceKobo: import_zod16.z.number().int(),
4675
+ openingBalanceKobo: import_zod17.z.number().int(),
4676
+ closingBalanceKobo: import_zod17.z.number().int(),
4157
4677
  transactionCount: NonNegativeInt,
4158
4678
  currency: Currency2,
4159
4679
  hashChainPrev: HexString(32).optional()
@@ -4161,16 +4681,16 @@ var StatementArtifactSchema = import_zod16.z.object({
4161
4681
  message: "periodEndMs must be greater than periodStartMs",
4162
4682
  path: ["periodEndMs"]
4163
4683
  });
4164
- var PassArtifactSchema = import_zod16.z.object({
4684
+ var PassArtifactSchema = import_zod17.z.object({
4165
4685
  passId: ShortId,
4166
4686
  holderId: ShortId,
4167
- category: import_zod16.z.enum(["membership", "ticket", "loyalty", "access", "voucher"]),
4168
- title: import_zod16.z.string().min(1).max(120),
4687
+ category: import_zod17.z.enum(["membership", "ticket", "loyalty", "access", "voucher"]),
4688
+ title: import_zod17.z.string().min(1).max(120),
4169
4689
  validFromMs: PositiveInt,
4170
4690
  validUntilMs: PositiveInt.optional(),
4171
- metadata: import_zod16.z.record(
4172
- import_zod16.z.string().min(1).max(64),
4173
- import_zod16.z.union([import_zod16.z.string().max(280), import_zod16.z.number(), import_zod16.z.boolean()])
4691
+ metadata: import_zod17.z.record(
4692
+ import_zod17.z.string().min(1).max(64),
4693
+ import_zod17.z.union([import_zod17.z.string().max(280), import_zod17.z.number(), import_zod17.z.boolean()])
4174
4694
  ).optional()
4175
4695
  }).refine(
4176
4696
  (v) => v.validUntilMs === void 0 || v.validUntilMs > v.validFromMs,
@@ -4179,10 +4699,10 @@ var PassArtifactSchema = import_zod16.z.object({
4179
4699
  path: ["validUntilMs"]
4180
4700
  }
4181
4701
  );
4182
- var IdentityArtifactSchema = import_zod16.z.object({
4702
+ var IdentityArtifactSchema = import_zod17.z.object({
4183
4703
  attestationId: ShortId,
4184
4704
  subjectId: ShortId,
4185
- claimType: import_zod16.z.enum([
4705
+ claimType: import_zod17.z.enum([
4186
4706
  "phone_verified",
4187
4707
  "email_verified",
4188
4708
  "bvn_verified",
@@ -4192,6 +4712,12 @@ var IdentityArtifactSchema = import_zod16.z.object({
4192
4712
  claimValueHash: HexString(32),
4193
4713
  attestedAtMs: PositiveInt
4194
4714
  });
4715
+ var PayCardArtifactSchema = import_zod17.z.object({
4716
+ userId: ShortId,
4717
+ phoneE164: import_zod17.z.string().regex(/^\+[1-9]\d{7,14}$/, "phoneE164 must be normalised E.164"),
4718
+ displayName: import_zod17.z.string().min(1).max(64),
4719
+ devicePubKeySpkiB64: import_zod17.z.string().min(64).max(256).regex(/^[A-Za-z0-9+/]+=*$/, "devicePubKeySpkiB64 must be standard base64")
4720
+ });
4195
4721
  var ARTIFACT_BODY_SCHEMAS = {
4196
4722
  [ARTIFACT_TYPES.OFFLINE_PAYMENT_AUTHORIZATION]: OfflinePaymentAuthorizationArtifactSchema,
4197
4723
  [ARTIFACT_TYPES.RECEIPT]: ReceiptArtifactSchema,
@@ -4203,7 +4729,8 @@ var ARTIFACT_BODY_SCHEMAS = {
4203
4729
  [ARTIFACT_TYPES.LEDGER_JOURNAL_ENTRY]: LedgerJournalEntryArtifactSchema,
4204
4730
  [ARTIFACT_TYPES.STATEMENT]: StatementArtifactSchema,
4205
4731
  [ARTIFACT_TYPES.PASS]: PassArtifactSchema,
4206
- [ARTIFACT_TYPES.IDENTITY]: IdentityArtifactSchema
4732
+ [ARTIFACT_TYPES.IDENTITY]: IdentityArtifactSchema,
4733
+ [ARTIFACT_TYPES.PAY_CARD]: PayCardArtifactSchema
4207
4734
  };
4208
4735
  var HARDENED_ARTIFACT_TYPES = /* @__PURE__ */ new Set([
4209
4736
  ARTIFACT_TYPES.OFFLINE_PAYMENT_AUTHORIZATION,
@@ -4216,7 +4743,8 @@ var HARDENED_ARTIFACT_TYPES = /* @__PURE__ */ new Set([
4216
4743
  ARTIFACT_TYPES.LEDGER_JOURNAL_ENTRY,
4217
4744
  ARTIFACT_TYPES.STATEMENT,
4218
4745
  ARTIFACT_TYPES.PASS,
4219
- ARTIFACT_TYPES.IDENTITY
4746
+ ARTIFACT_TYPES.IDENTITY,
4747
+ ARTIFACT_TYPES.PAY_CARD
4220
4748
  ]);
4221
4749
  function isKnownArtifactType(t) {
4222
4750
  return Object.values(ARTIFACT_TYPES).includes(t);
@@ -4301,6 +4829,130 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4301
4829
  type: ARTIFACT_TYPES.OFFLINE_PAYMENT_AUTHORIZATION
4302
4830
  });
4303
4831
  }
4832
+
4833
+ // src/artifacts/paycard.ts
4834
+ var PAY_CARD_DEFAULT_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
4835
+ var PAY_CARD_REFRESH_THRESHOLD_MS = 30 * 24 * 60 * 60 * 1e3;
4836
+ var PAY_CARD_URI_PREFIX = `${FLUR_ARTIFACT_URI_PREFIX}${ARTIFACT_TYPES.PAY_CARD}/`;
4837
+ function createPayCardArtifactUri(input) {
4838
+ if (input.data.userId !== input.issuer) {
4839
+ throw new FlurArtifactError(
4840
+ "pay_card.data.userId must equal envelope issuer",
4841
+ "INVALID_BODY"
4842
+ );
4843
+ }
4844
+ const iat = input.issuedAtSeconds ?? Math.floor(Date.now() / 1e3);
4845
+ const exp = input.expiresAtSeconds ?? iat + Math.floor(PAY_CARD_DEFAULT_TTL_MS / 1e3);
4846
+ return createArtifactUri({
4847
+ type: ARTIFACT_TYPES.PAY_CARD,
4848
+ issuer: input.issuer,
4849
+ keyId: input.keyId,
4850
+ privateKey: input.privateKey,
4851
+ nonce: input.nonce,
4852
+ issuedAtSeconds: iat,
4853
+ expiresAtSeconds: exp,
4854
+ data: input.data
4855
+ });
4856
+ }
4857
+ function isPayCardArtifactUri(uri) {
4858
+ return typeof uri === "string" && uri.startsWith(PAY_CARD_URI_PREFIX);
4859
+ }
4860
+ function decodePayCardArtifact(uri) {
4861
+ if (!isPayCardArtifactUri(uri)) {
4862
+ throw new FlurArtifactError(
4863
+ `URI does not start with ${PAY_CARD_URI_PREFIX}`,
4864
+ "INVALID_URI"
4865
+ );
4866
+ }
4867
+ const decoded = decodeArtifactUri(uri);
4868
+ if (decoded.type !== ARTIFACT_TYPES.PAY_CARD) {
4869
+ throw new FlurArtifactError(
4870
+ `Expected pay_card, got ${decoded.type}`,
4871
+ "TYPE_MISMATCH"
4872
+ );
4873
+ }
4874
+ const parsed = PayCardArtifactSchema.safeParse(decoded.body.data);
4875
+ if (!parsed.success) {
4876
+ throw new FlurArtifactError(
4877
+ `pay_card body invalid: ${parsed.error.message}`,
4878
+ "INVALID_BODY"
4879
+ );
4880
+ }
4881
+ if (parsed.data.userId !== decoded.body.iss) {
4882
+ throw new FlurArtifactError(
4883
+ "pay_card.data.userId must equal envelope issuer",
4884
+ "INVALID_BODY"
4885
+ );
4886
+ }
4887
+ return {
4888
+ body: {
4889
+ ...decoded.body,
4890
+ data: parsed.data
4891
+ },
4892
+ sig: decoded.sig,
4893
+ decoded
4894
+ };
4895
+ }
4896
+ function verifyPayCardArtifact(uri, publicKeySpkiB64, options = {}) {
4897
+ if (!isPayCardArtifactUri(uri)) {
4898
+ throw new FlurArtifactError(
4899
+ `URI does not start with ${PAY_CARD_URI_PREFIX}`,
4900
+ "INVALID_URI"
4901
+ );
4902
+ }
4903
+ const verified = verifyArtifactUri(
4904
+ uri,
4905
+ publicKeySpkiB64,
4906
+ options
4907
+ );
4908
+ if (verified.decoded.type !== ARTIFACT_TYPES.PAY_CARD) {
4909
+ throw new FlurArtifactError(
4910
+ `Expected pay_card, got ${verified.decoded.type}`,
4911
+ "TYPE_MISMATCH"
4912
+ );
4913
+ }
4914
+ if (verified.body.data.userId !== verified.body.iss) {
4915
+ throw new FlurArtifactError(
4916
+ "pay_card.data.userId must equal envelope issuer",
4917
+ "INVALID_BODY"
4918
+ );
4919
+ }
4920
+ return {
4921
+ body: verified.body,
4922
+ sig: verified.sig,
4923
+ decoded: verified.decoded
4924
+ };
4925
+ }
4926
+ function inspectPayCardFreshness(decoded, nowMs = Date.now()) {
4927
+ const exp = decoded.body.exp;
4928
+ if (exp === void 0) return "no_expiry";
4929
+ const remainingMs = exp * 1e3 - nowMs;
4930
+ if (remainingMs <= 0) return "expired";
4931
+ if (remainingMs <= PAY_CARD_REFRESH_THRESHOLD_MS)
4932
+ return "refresh_recommended";
4933
+ return "fresh";
4934
+ }
4935
+ function buildPayCardSigningInput(input) {
4936
+ if (input.data.userId !== input.issuer) {
4937
+ throw new FlurArtifactError(
4938
+ "pay_card.data.userId must equal envelope issuer",
4939
+ "INVALID_BODY"
4940
+ );
4941
+ }
4942
+ const parsedData = PayCardArtifactSchema.parse(input.data);
4943
+ const iat = input.issuedAtSeconds ?? Math.floor(Date.now() / 1e3);
4944
+ const exp = input.expiresAtSeconds ?? iat + Math.floor(PAY_CARD_DEFAULT_TTL_MS / 1e3);
4945
+ const body = buildArtifactBody({
4946
+ type: ARTIFACT_TYPES.PAY_CARD,
4947
+ issuer: input.issuer,
4948
+ keyId: input.keyId,
4949
+ data: parsedData,
4950
+ nonce: input.nonce,
4951
+ issuedAtSeconds: iat,
4952
+ expiresAtSeconds: exp
4953
+ });
4954
+ return { body, bodyBytes: canonicalJSONBytes(body) };
4955
+ }
4304
4956
  // Annotate the CommonJS export names for ESM import in node:
4305
4957
  0 && (module.exports = {
4306
4958
  ACCOUNT_FUNDED_OAC_MAX_TTL_MS,
@@ -4318,6 +4970,9 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4318
4970
  COLLECTION_INTENT_STATUSES,
4319
4971
  COLLECTION_PAYMENT_STATUSES,
4320
4972
  CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS,
4973
+ CONSUMER_PAYMENT_REQUEST_DOMAIN,
4974
+ CONSUMER_SETTLEMENT_DOMAIN,
4975
+ CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX,
4321
4976
  CUSTODIAL_MODES,
4322
4977
  CollectionIntentSchema,
4323
4978
  CollectionPaymentResultSchema,
@@ -4327,6 +4982,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4327
4982
  ConsumerOACRecordSchema,
4328
4983
  ConsumerOACSchema,
4329
4984
  ConsumerPaymentClaimSchema,
4985
+ ConsumerPaymentRequestEnvelopeSchema,
4330
4986
  ConsumerSettleResultSchema,
4331
4987
  ConsumerSettlementSchema,
4332
4988
  CreateCollectionIntentInputSchema,
@@ -4368,6 +5024,12 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4368
5024
  OAC_DEFAULT_PER_TX_KOBO,
4369
5025
  OAC_DEFAULT_VALIDITY_MS,
4370
5026
  OFFLINE_CLAIM_SMS_PREFIX,
5027
+ OFFLINE_SMS_SETTLE_DOMAIN,
5028
+ OFFLINE_SMS_SETTLE_HEADER_BYTES,
5029
+ OFFLINE_SMS_SETTLE_PREFIX,
5030
+ OFFLINE_SMS_SETTLE_SIGNATURE_BYTES,
5031
+ OFFLINE_SMS_SETTLE_TOKEN_BYTES,
5032
+ OFFLINE_SMS_SETTLE_VERSION,
4371
5033
  OfflineClaimArtifactSchema,
4372
5034
  OfflinePaymentAuthorizationArtifactSchema,
4373
5035
  OfflinePaymentAuthorizationSchema,
@@ -4385,6 +5047,9 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4385
5047
  PASS_STATES,
4386
5048
  PAYLOAD_FORMAT_INDICATOR_VALUE,
4387
5049
  PAYOUT_DESTINATION_STATUSES,
5050
+ PAY_CARD_DEFAULT_TTL_MS,
5051
+ PAY_CARD_REFRESH_THRESHOLD_MS,
5052
+ PAY_CARD_URI_PREFIX,
4388
5053
  POINT_OF_INITIATION,
4389
5054
  PartnerFundingEventInputSchema,
4390
5055
  PartnerFundingSchema,
@@ -4392,6 +5057,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4392
5057
  PassArtifactSchema,
4393
5058
  PassMetadataSchema,
4394
5059
  PassSchema,
5060
+ PayCardArtifactSchema,
4395
5061
  PayCollectionInputSchema,
4396
5062
  PaymentClaimSchema,
4397
5063
  PaymentIntentArtifactSchema,
@@ -4427,18 +5093,26 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4427
5093
  bodySha256Hex,
4428
5094
  buildArtifactBody,
4429
5095
  buildAuthorization,
5096
+ buildConsumerPaymentRequest,
4430
5097
  buildOAC,
4431
5098
  buildPass,
5099
+ buildPayCardSigningInput,
4432
5100
  buildPaymentRequest,
4433
5101
  buildReceipt,
4434
5102
  buildRedemption,
5103
+ buildSmsSettleHeader,
5104
+ buildSmsSettleSignedBytes,
4435
5105
  canonicalClaimSigningBytes,
4436
5106
  canonicalClaimSigningPayload,
4437
5107
  canonicalJSONBytes,
4438
5108
  canonicalJSONStringify,
4439
5109
  canonicalRequestString,
5110
+ computeConsumerClaimEncounterId,
4440
5111
  computeEncounterId,
4441
5112
  constantTimeEqual,
5113
+ consumerPaymentRequestSigningBytes,
5114
+ consumerPaymentRequestSigningPayload,
5115
+ consumerSettlementSigningPayload,
4442
5116
  crc16ccitt,
4443
5117
  crc16ccittHex,
4444
5118
  createAccountsClient,
@@ -4456,28 +5130,40 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4456
5130
  createPartnerFundingClient,
4457
5131
  createPartnerProfileAdminClient,
4458
5132
  createPassesClient,
5133
+ createPayCardArtifactUri,
4459
5134
  createReceiptArtifactUri,
4460
5135
  createReceiptsClient,
4461
5136
  createSoftwareP256Signer,
4462
5137
  decodeArtifactUri,
4463
5138
  decodeAuthorizationQR,
4464
5139
  decodeBase45,
5140
+ decodeConsumerSettlementReceiptQR,
4465
5141
  decodeOfflineClaimSmsMessage,
5142
+ decodeOfflineSmsSettleToken,
5143
+ decodePayCardArtifact,
4466
5144
  decodePaymentRequestQR,
5145
+ decodeUnverifiedConsumerSettlementReceiptQR,
5146
+ derToRawP256Signature,
4467
5147
  encodeArtifactUri,
4468
5148
  encodeAuthorizationQR,
4469
5149
  encodeBase45,
5150
+ encodeConsumerSettlementReceiptQR,
4470
5151
  encodeNQR,
4471
5152
  encodeOfflineClaimSmsMessage,
5153
+ encodeOfflineSmsSettleToken,
4472
5154
  encodePaymentRequestQR,
4473
5155
  extractOfflineClaimSmsToken,
5156
+ extractOfflineSmsSettleToken,
4474
5157
  formatAmount,
4475
5158
  generateDynamicQR,
4476
5159
  generateStaticQR,
4477
5160
  init,
5161
+ inspectPayCardFreshness,
5162
+ isConsumerPaymentRequestExpired,
4478
5163
  isHardenedArtifactType,
4479
5164
  isKnownArtifactType,
4480
5165
  isPassWithinValidity,
5166
+ isPayCardArtifactUri,
4481
5167
  moneyMinorToNumber,
4482
5168
  normalizeE164,
4483
5169
  parseAmountInput,
@@ -4487,6 +5173,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4487
5173
  routingHint,
4488
5174
  signArtifact,
4489
5175
  signAuthorization,
5176
+ signConsumerPaymentRequest,
4490
5177
  signOAC,
4491
5178
  signPartnerRequest,
4492
5179
  signPass,
@@ -4498,8 +5185,13 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4498
5185
  verifyArtifactUri,
4499
5186
  verifyAuthorization,
4500
5187
  verifyClaimSignature,
5188
+ verifyConsumerPaymentRequest,
5189
+ verifyConsumerSettlement,
5190
+ verifyConsumerSettlementReceiptQR,
4501
5191
  verifyOAC,
5192
+ verifyOfflineSmsSettleToken,
4502
5193
  verifyPass,
5194
+ verifyPayCardArtifact,
4503
5195
  verifyPaymentRequest,
4504
5196
  verifyReceipt,
4505
5197
  verifyRedemption,