@nokinc-flur/sdk 2.1.0 → 2.3.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
@@ -44,6 +44,8 @@ __export(index_exports, {
44
44
  CLAIM_DOMAIN_V2: () => CLAIM_DOMAIN_V2,
45
45
  COLLECTION_INTENT_STATUSES: () => COLLECTION_INTENT_STATUSES,
46
46
  COLLECTION_PAYMENT_STATUSES: () => COLLECTION_PAYMENT_STATUSES,
47
+ CONSUMER_OAC_DOMAIN: () => CONSUMER_OAC_DOMAIN,
48
+ CONSUMER_OAC_QR_PREFIX: () => CONSUMER_OAC_QR_PREFIX,
47
49
  CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS: () => CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS,
48
50
  CONSUMER_PAYMENT_REQUEST_DOMAIN: () => CONSUMER_PAYMENT_REQUEST_DOMAIN,
49
51
  CONSUMER_SETTLEMENT_DOMAIN: () => CONSUMER_SETTLEMENT_DOMAIN,
@@ -82,6 +84,8 @@ __export(index_exports, {
82
84
  IdentityArtifactSchema: () => IdentityArtifactSchema,
83
85
  IngestFundingResultSchema: () => IngestFundingResultSchema,
84
86
  IssueAccountOacInputSchema: () => IssueAccountOacInputSchema,
87
+ IssuerTrustBundleSchema: () => IssuerTrustBundleSchema,
88
+ IssuerTrustKeySchema: () => IssuerTrustKeySchema,
85
89
  LedgerJournalEntryArtifactSchema: () => LedgerJournalEntryArtifactSchema,
86
90
  ListPayoutDestinationsResultSchema: () => ListPayoutDestinationsResultSchema,
87
91
  MEMBERSHIP_ROLES: () => MEMBERSHIP_ROLES,
@@ -122,6 +126,9 @@ __export(index_exports, {
122
126
  PASS_STATES: () => PASS_STATES,
123
127
  PAYLOAD_FORMAT_INDICATOR_VALUE: () => PAYLOAD_FORMAT_INDICATOR_VALUE,
124
128
  PAYOUT_DESTINATION_STATUSES: () => PAYOUT_DESTINATION_STATUSES,
129
+ PAY_CARD_DEFAULT_TTL_MS: () => PAY_CARD_DEFAULT_TTL_MS,
130
+ PAY_CARD_REFRESH_THRESHOLD_MS: () => PAY_CARD_REFRESH_THRESHOLD_MS,
131
+ PAY_CARD_URI_PREFIX: () => PAY_CARD_URI_PREFIX,
125
132
  POINT_OF_INITIATION: () => POINT_OF_INITIATION,
126
133
  PartnerFundingEventInputSchema: () => PartnerFundingEventInputSchema,
127
134
  PartnerFundingSchema: () => PartnerFundingSchema,
@@ -129,6 +136,7 @@ __export(index_exports, {
129
136
  PassArtifactSchema: () => PassArtifactSchema,
130
137
  PassMetadataSchema: () => PassMetadataSchema,
131
138
  PassSchema: () => PassSchema,
139
+ PayCardArtifactSchema: () => PayCardArtifactSchema,
132
140
  PayCollectionInputSchema: () => PayCollectionInputSchema,
133
141
  PaymentClaimSchema: () => PaymentClaimSchema,
134
142
  PaymentIntentArtifactSchema: () => PaymentIntentArtifactSchema,
@@ -167,6 +175,7 @@ __export(index_exports, {
167
175
  buildConsumerPaymentRequest: () => buildConsumerPaymentRequest,
168
176
  buildOAC: () => buildOAC,
169
177
  buildPass: () => buildPass,
178
+ buildPayCardSigningInput: () => buildPayCardSigningInput,
170
179
  buildPaymentRequest: () => buildPaymentRequest,
171
180
  buildReceipt: () => buildReceipt,
172
181
  buildRedemption: () => buildRedemption,
@@ -180,6 +189,7 @@ __export(index_exports, {
180
189
  computeConsumerClaimEncounterId: () => computeConsumerClaimEncounterId,
181
190
  computeEncounterId: () => computeEncounterId,
182
191
  constantTimeEqual: () => constantTimeEqual,
192
+ consumerOacSigningPayload: () => consumerOacSigningPayload,
183
193
  consumerPaymentRequestSigningBytes: () => consumerPaymentRequestSigningBytes,
184
194
  consumerPaymentRequestSigningPayload: () => consumerPaymentRequestSigningPayload,
185
195
  consumerSettlementSigningPayload: () => consumerSettlementSigningPayload,
@@ -200,6 +210,7 @@ __export(index_exports, {
200
210
  createPartnerFundingClient: () => createPartnerFundingClient,
201
211
  createPartnerProfileAdminClient: () => createPartnerProfileAdminClient,
202
212
  createPassesClient: () => createPassesClient,
213
+ createPayCardArtifactUri: () => createPayCardArtifactUri,
203
214
  createReceiptArtifactUri: () => createReceiptArtifactUri,
204
215
  createReceiptsClient: () => createReceiptsClient,
205
216
  createSoftwareP256Signer: () => createSoftwareP256Signer,
@@ -209,12 +220,15 @@ __export(index_exports, {
209
220
  decodeConsumerSettlementReceiptQR: () => decodeConsumerSettlementReceiptQR,
210
221
  decodeOfflineClaimSmsMessage: () => decodeOfflineClaimSmsMessage,
211
222
  decodeOfflineSmsSettleToken: () => decodeOfflineSmsSettleToken,
223
+ decodePayCardArtifact: () => decodePayCardArtifact,
212
224
  decodePaymentRequestQR: () => decodePaymentRequestQR,
225
+ decodeUnverifiedConsumerOacQR: () => decodeUnverifiedConsumerOacQR,
213
226
  decodeUnverifiedConsumerSettlementReceiptQR: () => decodeUnverifiedConsumerSettlementReceiptQR,
214
227
  derToRawP256Signature: () => derToRawP256Signature,
215
228
  encodeArtifactUri: () => encodeArtifactUri,
216
229
  encodeAuthorizationQR: () => encodeAuthorizationQR,
217
230
  encodeBase45: () => encodeBase45,
231
+ encodeConsumerOacQR: () => encodeConsumerOacQR,
218
232
  encodeConsumerSettlementReceiptQR: () => encodeConsumerSettlementReceiptQR,
219
233
  encodeNQR: () => encodeNQR,
220
234
  encodeOfflineClaimSmsMessage: () => encodeOfflineClaimSmsMessage,
@@ -226,10 +240,13 @@ __export(index_exports, {
226
240
  generateDynamicQR: () => generateDynamicQR,
227
241
  generateStaticQR: () => generateStaticQR,
228
242
  init: () => init,
243
+ inspectPayCardFreshness: () => inspectPayCardFreshness,
244
+ isConsumerOacQR: () => isConsumerOacQR,
229
245
  isConsumerPaymentRequestExpired: () => isConsumerPaymentRequestExpired,
230
246
  isHardenedArtifactType: () => isHardenedArtifactType,
231
247
  isKnownArtifactType: () => isKnownArtifactType,
232
248
  isPassWithinValidity: () => isPassWithinValidity,
249
+ isPayCardArtifactUri: () => isPayCardArtifactUri,
233
250
  moneyMinorToNumber: () => moneyMinorToNumber,
234
251
  normalizeE164: () => normalizeE164,
235
252
  parseAmountInput: () => parseAmountInput,
@@ -255,8 +272,10 @@ __export(index_exports, {
255
272
  verifyConsumerSettlement: () => verifyConsumerSettlement,
256
273
  verifyConsumerSettlementReceiptQR: () => verifyConsumerSettlementReceiptQR,
257
274
  verifyOAC: () => verifyOAC,
275
+ verifyOacOffline: () => verifyOacOffline,
258
276
  verifyOfflineSmsSettleToken: () => verifyOfflineSmsSettleToken,
259
277
  verifyPass: () => verifyPass,
278
+ verifyPayCardArtifact: () => verifyPayCardArtifact,
260
279
  verifyPaymentRequest: () => verifyPaymentRequest,
261
280
  verifyReceipt: () => verifyReceipt,
262
281
  verifyRedemption: () => verifyRedemption,
@@ -3228,7 +3247,17 @@ var Base64Std3 = import_zod13.z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
3228
3247
  var ClaimNonce = import_zod13.z.string().min(8).max(128).refine((value) => !value.includes("|"), {
3229
3248
  message: "nonce must not contain |"
3230
3249
  });
3231
- var ACCOUNT_FUNDED_OAC_MAX_TTL_MS = 1e3 * 60 * 60 * 24 * 7;
3250
+ var ACCOUNT_FUNDED_OAC_MAX_TTL_MS = 1e3 * 60 * 60 * 24;
3251
+ var IssuerTrustKeySchema = import_zod13.z.object({
3252
+ issuerId: import_zod13.z.string().min(1).max(128),
3253
+ alg: import_zod13.z.literal("p256"),
3254
+ publicKeySpkiB64: Base64Std3.min(64).max(4096),
3255
+ notBeforeMs: import_zod13.z.number().int().nonnegative().optional(),
3256
+ notAfterMs: import_zod13.z.number().int().positive().optional()
3257
+ });
3258
+ var IssuerTrustBundleSchema = import_zod13.z.object({
3259
+ keys: import_zod13.z.array(IssuerTrustKeySchema).min(1)
3260
+ });
3232
3261
  var CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS = 1e3 * 60 * 60 * 24;
3233
3262
  var AttestationSecurityLevelSchema = import_zod13.z.enum([
3234
3263
  "STRONGBOX",
@@ -3282,13 +3311,28 @@ var ConsumerOACSchema = import_zod13.z.object({
3282
3311
  alg: import_zod13.z.literal("p256"),
3283
3312
  /** P-256 SubjectPublicKeyInfo DER, base64. */
3284
3313
  devicePubkeySpkiB64: Base64Std3.min(64).max(4096),
3285
- perTxCapKobo: import_zod13.z.number().int().positive(),
3286
- cumulativeCapKobo: import_zod13.z.number().int().positive(),
3314
+ /**
3315
+ * Per-transaction / cumulative offline spend ceilings. Zero is valid and
3316
+ * denotes an identity-only OAC: the credential proves who the holder is and
3317
+ * binds their device key for offline-verifiable first contact, but carries
3318
+ * no offline spend authority (every claim against it routes to REVIEW).
3319
+ * Issued to zero-balance wallets so they can still be paid offline.
3320
+ */
3321
+ perTxCapKobo: import_zod13.z.number().int().nonnegative(),
3322
+ cumulativeCapKobo: import_zod13.z.number().int().nonnegative(),
3287
3323
  currency: import_zod13.z.string().length(3),
3288
3324
  validFromMs: import_zod13.z.number().int().nonnegative(),
3289
3325
  validUntilMs: import_zod13.z.number().int().nonnegative(),
3290
3326
  counterSeed: import_zod13.z.number().int().nonnegative(),
3291
- issuedAtMs: import_zod13.z.number().int().nonnegative()
3327
+ issuedAtMs: import_zod13.z.number().int().nonnegative(),
3328
+ /**
3329
+ * Issuer-attested identity folded into the OAC so a single signed
3330
+ * credential serves both Tier B offline-verifiable identity and offline
3331
+ * spend authority. Verified offline against a pinned issuer key; the
3332
+ * backend remains authoritative at settlement.
3333
+ */
3334
+ phoneE164: import_zod13.z.string().regex(/^\+[1-9]\d{7,14}$/),
3335
+ displayName: import_zod13.z.string().min(1).max(64)
3292
3336
  });
3293
3337
  var SignedConsumerOACSchema = import_zod13.z.object({
3294
3338
  oac: ConsumerOACSchema,
@@ -3456,6 +3500,12 @@ function createMeOfflineClient(opts) {
3456
3500
  `/v1/me/offline/settlements/${encodeURIComponent(idOrKey)}`,
3457
3501
  void 0,
3458
3502
  (raw) => ConsumerSettlementSchema.parse(raw)
3503
+ ),
3504
+ getIssuerKeys: () => call(
3505
+ "GET",
3506
+ "/v1/issuer/keys",
3507
+ void 0,
3508
+ (raw) => IssuerTrustBundleSchema.parse(raw)
3459
3509
  )
3460
3510
  };
3461
3511
  }
@@ -3816,13 +3866,101 @@ function base64UrlDecodeUtf8(input) {
3816
3866
  throw new Error("base64 decoder unavailable");
3817
3867
  }
3818
3868
 
3869
+ // src/me-offline/oac.ts
3870
+ var CONSUMER_OAC_DOMAIN = "flur:consumer-offline:v1:oac";
3871
+ function consumerOacSigningPayload(oac) {
3872
+ return { domain: CONSUMER_OAC_DOMAIN, ...oac };
3873
+ }
3874
+ function verifyOacOffline(signed, trustedKeys, options = {}) {
3875
+ const parsed = SignedConsumerOACSchema.safeParse(signed);
3876
+ if (!parsed.success) return { ok: false, reason: "malformed" };
3877
+ const oacParsed = ConsumerOACSchema.safeParse(parsed.data.oac);
3878
+ if (!oacParsed.success) return { ok: false, reason: "malformed" };
3879
+ const oac = oacParsed.data;
3880
+ const nowMs = options.nowMs ?? Date.now();
3881
+ const pinned = trustedKeys.filter(
3882
+ (k) => k.issuerId === oac.issuerId && (k.notBeforeMs === void 0 || nowMs >= k.notBeforeMs) && (k.notAfterMs === void 0 || nowMs <= k.notAfterMs)
3883
+ );
3884
+ if (pinned.length === 0) return { ok: false, reason: "untrusted_issuer" };
3885
+ const signingBytes = canonicalJSONBytes(consumerOacSigningPayload(oac));
3886
+ const signatureOk = pinned.some(
3887
+ (k) => verifyIssuerP256(signingBytes, parsed.data.issuerSig, k.publicKeySpkiB64)
3888
+ );
3889
+ if (!signatureOk) return { ok: false, reason: "signature_invalid" };
3890
+ if (oac.validUntilMs - oac.validFromMs > ACCOUNT_FUNDED_OAC_MAX_TTL_MS) {
3891
+ return { ok: false, reason: "window_too_long" };
3892
+ }
3893
+ if (nowMs < oac.validFromMs) return { ok: false, reason: "not_yet_valid" };
3894
+ if (nowMs >= oac.validUntilMs) return { ok: false, reason: "expired" };
3895
+ return {
3896
+ ok: true,
3897
+ oac,
3898
+ identity: {
3899
+ oacId: oac.oacId,
3900
+ issuerId: oac.issuerId,
3901
+ userId: oac.userId,
3902
+ phoneE164: oac.phoneE164,
3903
+ displayName: oac.displayName,
3904
+ devicePubkeySpkiB64: oac.devicePubkeySpkiB64
3905
+ }
3906
+ };
3907
+ }
3908
+ var CONSUMER_OAC_QR_PREFIX = "FLUROAC1.";
3909
+ function isConsumerOacQR(value) {
3910
+ return value.startsWith(CONSUMER_OAC_QR_PREFIX);
3911
+ }
3912
+ function encodeConsumerOacQR(signed) {
3913
+ const parsed = SignedConsumerOACSchema.parse(signed);
3914
+ return `${CONSUMER_OAC_QR_PREFIX}${base64UrlEncodeUtf82(JSON.stringify(parsed))}`;
3915
+ }
3916
+ function decodeUnverifiedConsumerOacQR(value) {
3917
+ if (!value.startsWith(CONSUMER_OAC_QR_PREFIX)) {
3918
+ throw new Error("not a Flur consumer OAC QR");
3919
+ }
3920
+ const encoded = value.slice(CONSUMER_OAC_QR_PREFIX.length);
3921
+ let raw;
3922
+ try {
3923
+ raw = JSON.parse(base64UrlDecodeUtf82(encoded));
3924
+ } catch {
3925
+ throw new Error("consumer OAC QR is malformed");
3926
+ }
3927
+ return SignedConsumerOACSchema.parse(raw);
3928
+ }
3929
+ function base64UrlEncodeUtf82(input) {
3930
+ const bytes = new TextEncoder().encode(input);
3931
+ let binary = "";
3932
+ for (const byte of bytes) binary += String.fromCharCode(byte);
3933
+ const base64 = typeof btoa === "function" ? btoa(binary) : typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : void 0;
3934
+ if (!base64) throw new Error("base64 encoder unavailable");
3935
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
3936
+ }
3937
+ function base64UrlDecodeUtf82(input) {
3938
+ const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
3939
+ const padded = base64.padEnd(
3940
+ base64.length + (4 - base64.length % 4) % 4,
3941
+ "="
3942
+ );
3943
+ if (typeof atob === "function") {
3944
+ const binary = atob(padded);
3945
+ const bytes = new Uint8Array(binary.length);
3946
+ for (let index = 0; index < binary.length; index++) {
3947
+ bytes[index] = binary.charCodeAt(index);
3948
+ }
3949
+ return new TextDecoder().decode(bytes);
3950
+ }
3951
+ if (typeof Buffer !== "undefined") {
3952
+ return Buffer.from(padded, "base64").toString("utf8");
3953
+ }
3954
+ throw new Error("base64 decoder unavailable");
3955
+ }
3956
+
3819
3957
  // src/me-offline/sms.ts
3820
3958
  var import_nist3 = require("@noble/curves/nist");
3821
3959
  var OFFLINE_CLAIM_SMS_PREFIX = "FLURC1.";
3822
3960
  var CLAIM_TOKEN_RE = /(?:^|\s)(FLURC1\.[A-Za-z0-9_-]+={0,2})(?:\s|$)/;
3823
3961
  function encodeOfflineClaimSmsMessage(claim) {
3824
3962
  const parsed = ConsumerPaymentClaimSchema.parse(claim);
3825
- return `${OFFLINE_CLAIM_SMS_PREFIX}${base64UrlEncodeUtf82(
3963
+ return `${OFFLINE_CLAIM_SMS_PREFIX}${base64UrlEncodeUtf83(
3826
3964
  JSON.stringify(parsed)
3827
3965
  )}`;
3828
3966
  }
@@ -3832,7 +3970,7 @@ function decodeOfflineClaimSmsMessage(message) {
3832
3970
  const encoded = token.slice(OFFLINE_CLAIM_SMS_PREFIX.length);
3833
3971
  let raw;
3834
3972
  try {
3835
- raw = JSON.parse(base64UrlDecodeUtf82(encoded));
3973
+ raw = JSON.parse(base64UrlDecodeUtf83(encoded));
3836
3974
  } catch {
3837
3975
  throw new Error("offline claim QR token is malformed");
3838
3976
  }
@@ -4058,10 +4196,10 @@ function bytesToBase64Url(bytes) {
4058
4196
  }
4059
4197
  return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
4060
4198
  }
4061
- function base64UrlEncodeUtf82(input) {
4199
+ function base64UrlEncodeUtf83(input) {
4062
4200
  return bytesToBase64Url(utf8(input));
4063
4201
  }
4064
- function base64UrlDecodeUtf82(input) {
4202
+ function base64UrlDecodeUtf83(input) {
4065
4203
  return new TextDecoder().decode(base64UrlToBytes(input));
4066
4204
  }
4067
4205
  function base64UrlToBytes(input) {
@@ -4561,7 +4699,10 @@ var ARTIFACT_TYPES = {
4561
4699
  LEDGER_JOURNAL_ENTRY: "ledger_journal_entry",
4562
4700
  STATEMENT: "statement",
4563
4701
  PASS: "pass",
4564
- IDENTITY: "identity"
4702
+ IDENTITY: "identity",
4703
+ // Tier B: holder-signed identity attestation for offline trust. The
4704
+ // envelope.iat / envelope.exp express the card's canonical lifetime.
4705
+ PAY_CARD: "pay_card"
4565
4706
  };
4566
4707
  var HexString = (length) => import_zod17.z.string().regex(
4567
4708
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
@@ -4699,6 +4840,12 @@ var IdentityArtifactSchema = import_zod17.z.object({
4699
4840
  claimValueHash: HexString(32),
4700
4841
  attestedAtMs: PositiveInt
4701
4842
  });
4843
+ var PayCardArtifactSchema = import_zod17.z.object({
4844
+ userId: ShortId,
4845
+ phoneE164: import_zod17.z.string().regex(/^\+[1-9]\d{7,14}$/, "phoneE164 must be normalised E.164"),
4846
+ displayName: import_zod17.z.string().min(1).max(64),
4847
+ devicePubKeySpkiB64: import_zod17.z.string().min(64).max(256).regex(/^[A-Za-z0-9+/]+=*$/, "devicePubKeySpkiB64 must be standard base64")
4848
+ });
4702
4849
  var ARTIFACT_BODY_SCHEMAS = {
4703
4850
  [ARTIFACT_TYPES.OFFLINE_PAYMENT_AUTHORIZATION]: OfflinePaymentAuthorizationArtifactSchema,
4704
4851
  [ARTIFACT_TYPES.RECEIPT]: ReceiptArtifactSchema,
@@ -4710,7 +4857,8 @@ var ARTIFACT_BODY_SCHEMAS = {
4710
4857
  [ARTIFACT_TYPES.LEDGER_JOURNAL_ENTRY]: LedgerJournalEntryArtifactSchema,
4711
4858
  [ARTIFACT_TYPES.STATEMENT]: StatementArtifactSchema,
4712
4859
  [ARTIFACT_TYPES.PASS]: PassArtifactSchema,
4713
- [ARTIFACT_TYPES.IDENTITY]: IdentityArtifactSchema
4860
+ [ARTIFACT_TYPES.IDENTITY]: IdentityArtifactSchema,
4861
+ [ARTIFACT_TYPES.PAY_CARD]: PayCardArtifactSchema
4714
4862
  };
4715
4863
  var HARDENED_ARTIFACT_TYPES = /* @__PURE__ */ new Set([
4716
4864
  ARTIFACT_TYPES.OFFLINE_PAYMENT_AUTHORIZATION,
@@ -4723,7 +4871,8 @@ var HARDENED_ARTIFACT_TYPES = /* @__PURE__ */ new Set([
4723
4871
  ARTIFACT_TYPES.LEDGER_JOURNAL_ENTRY,
4724
4872
  ARTIFACT_TYPES.STATEMENT,
4725
4873
  ARTIFACT_TYPES.PASS,
4726
- ARTIFACT_TYPES.IDENTITY
4874
+ ARTIFACT_TYPES.IDENTITY,
4875
+ ARTIFACT_TYPES.PAY_CARD
4727
4876
  ]);
4728
4877
  function isKnownArtifactType(t) {
4729
4878
  return Object.values(ARTIFACT_TYPES).includes(t);
@@ -4808,6 +4957,130 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4808
4957
  type: ARTIFACT_TYPES.OFFLINE_PAYMENT_AUTHORIZATION
4809
4958
  });
4810
4959
  }
4960
+
4961
+ // src/artifacts/paycard.ts
4962
+ var PAY_CARD_DEFAULT_TTL_MS = 90 * 24 * 60 * 60 * 1e3;
4963
+ var PAY_CARD_REFRESH_THRESHOLD_MS = 30 * 24 * 60 * 60 * 1e3;
4964
+ var PAY_CARD_URI_PREFIX = `${FLUR_ARTIFACT_URI_PREFIX}${ARTIFACT_TYPES.PAY_CARD}/`;
4965
+ function createPayCardArtifactUri(input) {
4966
+ if (input.data.userId !== input.issuer) {
4967
+ throw new FlurArtifactError(
4968
+ "pay_card.data.userId must equal envelope issuer",
4969
+ "INVALID_BODY"
4970
+ );
4971
+ }
4972
+ const iat = input.issuedAtSeconds ?? Math.floor(Date.now() / 1e3);
4973
+ const exp = input.expiresAtSeconds ?? iat + Math.floor(PAY_CARD_DEFAULT_TTL_MS / 1e3);
4974
+ return createArtifactUri({
4975
+ type: ARTIFACT_TYPES.PAY_CARD,
4976
+ issuer: input.issuer,
4977
+ keyId: input.keyId,
4978
+ privateKey: input.privateKey,
4979
+ nonce: input.nonce,
4980
+ issuedAtSeconds: iat,
4981
+ expiresAtSeconds: exp,
4982
+ data: input.data
4983
+ });
4984
+ }
4985
+ function isPayCardArtifactUri(uri) {
4986
+ return typeof uri === "string" && uri.startsWith(PAY_CARD_URI_PREFIX);
4987
+ }
4988
+ function decodePayCardArtifact(uri) {
4989
+ if (!isPayCardArtifactUri(uri)) {
4990
+ throw new FlurArtifactError(
4991
+ `URI does not start with ${PAY_CARD_URI_PREFIX}`,
4992
+ "INVALID_URI"
4993
+ );
4994
+ }
4995
+ const decoded = decodeArtifactUri(uri);
4996
+ if (decoded.type !== ARTIFACT_TYPES.PAY_CARD) {
4997
+ throw new FlurArtifactError(
4998
+ `Expected pay_card, got ${decoded.type}`,
4999
+ "TYPE_MISMATCH"
5000
+ );
5001
+ }
5002
+ const parsed = PayCardArtifactSchema.safeParse(decoded.body.data);
5003
+ if (!parsed.success) {
5004
+ throw new FlurArtifactError(
5005
+ `pay_card body invalid: ${parsed.error.message}`,
5006
+ "INVALID_BODY"
5007
+ );
5008
+ }
5009
+ if (parsed.data.userId !== decoded.body.iss) {
5010
+ throw new FlurArtifactError(
5011
+ "pay_card.data.userId must equal envelope issuer",
5012
+ "INVALID_BODY"
5013
+ );
5014
+ }
5015
+ return {
5016
+ body: {
5017
+ ...decoded.body,
5018
+ data: parsed.data
5019
+ },
5020
+ sig: decoded.sig,
5021
+ decoded
5022
+ };
5023
+ }
5024
+ function verifyPayCardArtifact(uri, publicKeySpkiB64, options = {}) {
5025
+ if (!isPayCardArtifactUri(uri)) {
5026
+ throw new FlurArtifactError(
5027
+ `URI does not start with ${PAY_CARD_URI_PREFIX}`,
5028
+ "INVALID_URI"
5029
+ );
5030
+ }
5031
+ const verified = verifyArtifactUri(
5032
+ uri,
5033
+ publicKeySpkiB64,
5034
+ options
5035
+ );
5036
+ if (verified.decoded.type !== ARTIFACT_TYPES.PAY_CARD) {
5037
+ throw new FlurArtifactError(
5038
+ `Expected pay_card, got ${verified.decoded.type}`,
5039
+ "TYPE_MISMATCH"
5040
+ );
5041
+ }
5042
+ if (verified.body.data.userId !== verified.body.iss) {
5043
+ throw new FlurArtifactError(
5044
+ "pay_card.data.userId must equal envelope issuer",
5045
+ "INVALID_BODY"
5046
+ );
5047
+ }
5048
+ return {
5049
+ body: verified.body,
5050
+ sig: verified.sig,
5051
+ decoded: verified.decoded
5052
+ };
5053
+ }
5054
+ function inspectPayCardFreshness(decoded, nowMs = Date.now()) {
5055
+ const exp = decoded.body.exp;
5056
+ if (exp === void 0) return "no_expiry";
5057
+ const remainingMs = exp * 1e3 - nowMs;
5058
+ if (remainingMs <= 0) return "expired";
5059
+ if (remainingMs <= PAY_CARD_REFRESH_THRESHOLD_MS)
5060
+ return "refresh_recommended";
5061
+ return "fresh";
5062
+ }
5063
+ function buildPayCardSigningInput(input) {
5064
+ if (input.data.userId !== input.issuer) {
5065
+ throw new FlurArtifactError(
5066
+ "pay_card.data.userId must equal envelope issuer",
5067
+ "INVALID_BODY"
5068
+ );
5069
+ }
5070
+ const parsedData = PayCardArtifactSchema.parse(input.data);
5071
+ const iat = input.issuedAtSeconds ?? Math.floor(Date.now() / 1e3);
5072
+ const exp = input.expiresAtSeconds ?? iat + Math.floor(PAY_CARD_DEFAULT_TTL_MS / 1e3);
5073
+ const body = buildArtifactBody({
5074
+ type: ARTIFACT_TYPES.PAY_CARD,
5075
+ issuer: input.issuer,
5076
+ keyId: input.keyId,
5077
+ data: parsedData,
5078
+ nonce: input.nonce,
5079
+ issuedAtSeconds: iat,
5080
+ expiresAtSeconds: exp
5081
+ });
5082
+ return { body, bodyBytes: canonicalJSONBytes(body) };
5083
+ }
4811
5084
  // Annotate the CommonJS export names for ESM import in node:
4812
5085
  0 && (module.exports = {
4813
5086
  ACCOUNT_FUNDED_OAC_MAX_TTL_MS,
@@ -4824,6 +5097,8 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4824
5097
  CLAIM_DOMAIN_V2,
4825
5098
  COLLECTION_INTENT_STATUSES,
4826
5099
  COLLECTION_PAYMENT_STATUSES,
5100
+ CONSUMER_OAC_DOMAIN,
5101
+ CONSUMER_OAC_QR_PREFIX,
4827
5102
  CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS,
4828
5103
  CONSUMER_PAYMENT_REQUEST_DOMAIN,
4829
5104
  CONSUMER_SETTLEMENT_DOMAIN,
@@ -4862,6 +5137,8 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4862
5137
  IdentityArtifactSchema,
4863
5138
  IngestFundingResultSchema,
4864
5139
  IssueAccountOacInputSchema,
5140
+ IssuerTrustBundleSchema,
5141
+ IssuerTrustKeySchema,
4865
5142
  LedgerJournalEntryArtifactSchema,
4866
5143
  ListPayoutDestinationsResultSchema,
4867
5144
  MEMBERSHIP_ROLES,
@@ -4902,6 +5179,9 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4902
5179
  PASS_STATES,
4903
5180
  PAYLOAD_FORMAT_INDICATOR_VALUE,
4904
5181
  PAYOUT_DESTINATION_STATUSES,
5182
+ PAY_CARD_DEFAULT_TTL_MS,
5183
+ PAY_CARD_REFRESH_THRESHOLD_MS,
5184
+ PAY_CARD_URI_PREFIX,
4905
5185
  POINT_OF_INITIATION,
4906
5186
  PartnerFundingEventInputSchema,
4907
5187
  PartnerFundingSchema,
@@ -4909,6 +5189,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4909
5189
  PassArtifactSchema,
4910
5190
  PassMetadataSchema,
4911
5191
  PassSchema,
5192
+ PayCardArtifactSchema,
4912
5193
  PayCollectionInputSchema,
4913
5194
  PaymentClaimSchema,
4914
5195
  PaymentIntentArtifactSchema,
@@ -4947,6 +5228,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4947
5228
  buildConsumerPaymentRequest,
4948
5229
  buildOAC,
4949
5230
  buildPass,
5231
+ buildPayCardSigningInput,
4950
5232
  buildPaymentRequest,
4951
5233
  buildReceipt,
4952
5234
  buildRedemption,
@@ -4960,6 +5242,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4960
5242
  computeConsumerClaimEncounterId,
4961
5243
  computeEncounterId,
4962
5244
  constantTimeEqual,
5245
+ consumerOacSigningPayload,
4963
5246
  consumerPaymentRequestSigningBytes,
4964
5247
  consumerPaymentRequestSigningPayload,
4965
5248
  consumerSettlementSigningPayload,
@@ -4980,6 +5263,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4980
5263
  createPartnerFundingClient,
4981
5264
  createPartnerProfileAdminClient,
4982
5265
  createPassesClient,
5266
+ createPayCardArtifactUri,
4983
5267
  createReceiptArtifactUri,
4984
5268
  createReceiptsClient,
4985
5269
  createSoftwareP256Signer,
@@ -4989,12 +5273,15 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
4989
5273
  decodeConsumerSettlementReceiptQR,
4990
5274
  decodeOfflineClaimSmsMessage,
4991
5275
  decodeOfflineSmsSettleToken,
5276
+ decodePayCardArtifact,
4992
5277
  decodePaymentRequestQR,
5278
+ decodeUnverifiedConsumerOacQR,
4993
5279
  decodeUnverifiedConsumerSettlementReceiptQR,
4994
5280
  derToRawP256Signature,
4995
5281
  encodeArtifactUri,
4996
5282
  encodeAuthorizationQR,
4997
5283
  encodeBase45,
5284
+ encodeConsumerOacQR,
4998
5285
  encodeConsumerSettlementReceiptQR,
4999
5286
  encodeNQR,
5000
5287
  encodeOfflineClaimSmsMessage,
@@ -5006,10 +5293,13 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
5006
5293
  generateDynamicQR,
5007
5294
  generateStaticQR,
5008
5295
  init,
5296
+ inspectPayCardFreshness,
5297
+ isConsumerOacQR,
5009
5298
  isConsumerPaymentRequestExpired,
5010
5299
  isHardenedArtifactType,
5011
5300
  isKnownArtifactType,
5012
5301
  isPassWithinValidity,
5302
+ isPayCardArtifactUri,
5013
5303
  moneyMinorToNumber,
5014
5304
  normalizeE164,
5015
5305
  parseAmountInput,
@@ -5035,8 +5325,10 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
5035
5325
  verifyConsumerSettlement,
5036
5326
  verifyConsumerSettlementReceiptQR,
5037
5327
  verifyOAC,
5328
+ verifyOacOffline,
5038
5329
  verifyOfflineSmsSettleToken,
5039
5330
  verifyPass,
5331
+ verifyPayCardArtifact,
5040
5332
  verifyPaymentRequest,
5041
5333
  verifyReceipt,
5042
5334
  verifyRedemption,