@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 +303 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +449 -7
- package/dist/index.d.ts +449 -7
- package/dist/index.js +284 -11
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
3286
|
-
|
|
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}${
|
|
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(
|
|
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
|
|
4199
|
+
function base64UrlEncodeUtf83(input) {
|
|
4062
4200
|
return bytesToBase64Url(utf8(input));
|
|
4063
4201
|
}
|
|
4064
|
-
function
|
|
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,
|