@nokinc-flur/sdk 2.2.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 +145 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +265 -7
- package/dist/index.d.ts +265 -7
- package/dist/index.js +136 -8
- 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,
|
|
@@ -185,6 +189,7 @@ __export(index_exports, {
|
|
|
185
189
|
computeConsumerClaimEncounterId: () => computeConsumerClaimEncounterId,
|
|
186
190
|
computeEncounterId: () => computeEncounterId,
|
|
187
191
|
constantTimeEqual: () => constantTimeEqual,
|
|
192
|
+
consumerOacSigningPayload: () => consumerOacSigningPayload,
|
|
188
193
|
consumerPaymentRequestSigningBytes: () => consumerPaymentRequestSigningBytes,
|
|
189
194
|
consumerPaymentRequestSigningPayload: () => consumerPaymentRequestSigningPayload,
|
|
190
195
|
consumerSettlementSigningPayload: () => consumerSettlementSigningPayload,
|
|
@@ -217,11 +222,13 @@ __export(index_exports, {
|
|
|
217
222
|
decodeOfflineSmsSettleToken: () => decodeOfflineSmsSettleToken,
|
|
218
223
|
decodePayCardArtifact: () => decodePayCardArtifact,
|
|
219
224
|
decodePaymentRequestQR: () => decodePaymentRequestQR,
|
|
225
|
+
decodeUnverifiedConsumerOacQR: () => decodeUnverifiedConsumerOacQR,
|
|
220
226
|
decodeUnverifiedConsumerSettlementReceiptQR: () => decodeUnverifiedConsumerSettlementReceiptQR,
|
|
221
227
|
derToRawP256Signature: () => derToRawP256Signature,
|
|
222
228
|
encodeArtifactUri: () => encodeArtifactUri,
|
|
223
229
|
encodeAuthorizationQR: () => encodeAuthorizationQR,
|
|
224
230
|
encodeBase45: () => encodeBase45,
|
|
231
|
+
encodeConsumerOacQR: () => encodeConsumerOacQR,
|
|
225
232
|
encodeConsumerSettlementReceiptQR: () => encodeConsumerSettlementReceiptQR,
|
|
226
233
|
encodeNQR: () => encodeNQR,
|
|
227
234
|
encodeOfflineClaimSmsMessage: () => encodeOfflineClaimSmsMessage,
|
|
@@ -234,6 +241,7 @@ __export(index_exports, {
|
|
|
234
241
|
generateStaticQR: () => generateStaticQR,
|
|
235
242
|
init: () => init,
|
|
236
243
|
inspectPayCardFreshness: () => inspectPayCardFreshness,
|
|
244
|
+
isConsumerOacQR: () => isConsumerOacQR,
|
|
237
245
|
isConsumerPaymentRequestExpired: () => isConsumerPaymentRequestExpired,
|
|
238
246
|
isHardenedArtifactType: () => isHardenedArtifactType,
|
|
239
247
|
isKnownArtifactType: () => isKnownArtifactType,
|
|
@@ -264,6 +272,7 @@ __export(index_exports, {
|
|
|
264
272
|
verifyConsumerSettlement: () => verifyConsumerSettlement,
|
|
265
273
|
verifyConsumerSettlementReceiptQR: () => verifyConsumerSettlementReceiptQR,
|
|
266
274
|
verifyOAC: () => verifyOAC,
|
|
275
|
+
verifyOacOffline: () => verifyOacOffline,
|
|
267
276
|
verifyOfflineSmsSettleToken: () => verifyOfflineSmsSettleToken,
|
|
268
277
|
verifyPass: () => verifyPass,
|
|
269
278
|
verifyPayCardArtifact: () => verifyPayCardArtifact,
|
|
@@ -3238,7 +3247,17 @@ var Base64Std3 = import_zod13.z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
|
|
|
3238
3247
|
var ClaimNonce = import_zod13.z.string().min(8).max(128).refine((value) => !value.includes("|"), {
|
|
3239
3248
|
message: "nonce must not contain |"
|
|
3240
3249
|
});
|
|
3241
|
-
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
|
+
});
|
|
3242
3261
|
var CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS = 1e3 * 60 * 60 * 24;
|
|
3243
3262
|
var AttestationSecurityLevelSchema = import_zod13.z.enum([
|
|
3244
3263
|
"STRONGBOX",
|
|
@@ -3292,13 +3311,28 @@ var ConsumerOACSchema = import_zod13.z.object({
|
|
|
3292
3311
|
alg: import_zod13.z.literal("p256"),
|
|
3293
3312
|
/** P-256 SubjectPublicKeyInfo DER, base64. */
|
|
3294
3313
|
devicePubkeySpkiB64: Base64Std3.min(64).max(4096),
|
|
3295
|
-
|
|
3296
|
-
|
|
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(),
|
|
3297
3323
|
currency: import_zod13.z.string().length(3),
|
|
3298
3324
|
validFromMs: import_zod13.z.number().int().nonnegative(),
|
|
3299
3325
|
validUntilMs: import_zod13.z.number().int().nonnegative(),
|
|
3300
3326
|
counterSeed: import_zod13.z.number().int().nonnegative(),
|
|
3301
|
-
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)
|
|
3302
3336
|
});
|
|
3303
3337
|
var SignedConsumerOACSchema = import_zod13.z.object({
|
|
3304
3338
|
oac: ConsumerOACSchema,
|
|
@@ -3466,6 +3500,12 @@ function createMeOfflineClient(opts) {
|
|
|
3466
3500
|
`/v1/me/offline/settlements/${encodeURIComponent(idOrKey)}`,
|
|
3467
3501
|
void 0,
|
|
3468
3502
|
(raw) => ConsumerSettlementSchema.parse(raw)
|
|
3503
|
+
),
|
|
3504
|
+
getIssuerKeys: () => call(
|
|
3505
|
+
"GET",
|
|
3506
|
+
"/v1/issuer/keys",
|
|
3507
|
+
void 0,
|
|
3508
|
+
(raw) => IssuerTrustBundleSchema.parse(raw)
|
|
3469
3509
|
)
|
|
3470
3510
|
};
|
|
3471
3511
|
}
|
|
@@ -3826,13 +3866,101 @@ function base64UrlDecodeUtf8(input) {
|
|
|
3826
3866
|
throw new Error("base64 decoder unavailable");
|
|
3827
3867
|
}
|
|
3828
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
|
+
|
|
3829
3957
|
// src/me-offline/sms.ts
|
|
3830
3958
|
var import_nist3 = require("@noble/curves/nist");
|
|
3831
3959
|
var OFFLINE_CLAIM_SMS_PREFIX = "FLURC1.";
|
|
3832
3960
|
var CLAIM_TOKEN_RE = /(?:^|\s)(FLURC1\.[A-Za-z0-9_-]+={0,2})(?:\s|$)/;
|
|
3833
3961
|
function encodeOfflineClaimSmsMessage(claim) {
|
|
3834
3962
|
const parsed = ConsumerPaymentClaimSchema.parse(claim);
|
|
3835
|
-
return `${OFFLINE_CLAIM_SMS_PREFIX}${
|
|
3963
|
+
return `${OFFLINE_CLAIM_SMS_PREFIX}${base64UrlEncodeUtf83(
|
|
3836
3964
|
JSON.stringify(parsed)
|
|
3837
3965
|
)}`;
|
|
3838
3966
|
}
|
|
@@ -3842,7 +3970,7 @@ function decodeOfflineClaimSmsMessage(message) {
|
|
|
3842
3970
|
const encoded = token.slice(OFFLINE_CLAIM_SMS_PREFIX.length);
|
|
3843
3971
|
let raw;
|
|
3844
3972
|
try {
|
|
3845
|
-
raw = JSON.parse(
|
|
3973
|
+
raw = JSON.parse(base64UrlDecodeUtf83(encoded));
|
|
3846
3974
|
} catch {
|
|
3847
3975
|
throw new Error("offline claim QR token is malformed");
|
|
3848
3976
|
}
|
|
@@ -4068,10 +4196,10 @@ function bytesToBase64Url(bytes) {
|
|
|
4068
4196
|
}
|
|
4069
4197
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
4070
4198
|
}
|
|
4071
|
-
function
|
|
4199
|
+
function base64UrlEncodeUtf83(input) {
|
|
4072
4200
|
return bytesToBase64Url(utf8(input));
|
|
4073
4201
|
}
|
|
4074
|
-
function
|
|
4202
|
+
function base64UrlDecodeUtf83(input) {
|
|
4075
4203
|
return new TextDecoder().decode(base64UrlToBytes(input));
|
|
4076
4204
|
}
|
|
4077
4205
|
function base64UrlToBytes(input) {
|
|
@@ -4969,6 +5097,8 @@ function buildPayCardSigningInput(input) {
|
|
|
4969
5097
|
CLAIM_DOMAIN_V2,
|
|
4970
5098
|
COLLECTION_INTENT_STATUSES,
|
|
4971
5099
|
COLLECTION_PAYMENT_STATUSES,
|
|
5100
|
+
CONSUMER_OAC_DOMAIN,
|
|
5101
|
+
CONSUMER_OAC_QR_PREFIX,
|
|
4972
5102
|
CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS,
|
|
4973
5103
|
CONSUMER_PAYMENT_REQUEST_DOMAIN,
|
|
4974
5104
|
CONSUMER_SETTLEMENT_DOMAIN,
|
|
@@ -5007,6 +5137,8 @@ function buildPayCardSigningInput(input) {
|
|
|
5007
5137
|
IdentityArtifactSchema,
|
|
5008
5138
|
IngestFundingResultSchema,
|
|
5009
5139
|
IssueAccountOacInputSchema,
|
|
5140
|
+
IssuerTrustBundleSchema,
|
|
5141
|
+
IssuerTrustKeySchema,
|
|
5010
5142
|
LedgerJournalEntryArtifactSchema,
|
|
5011
5143
|
ListPayoutDestinationsResultSchema,
|
|
5012
5144
|
MEMBERSHIP_ROLES,
|
|
@@ -5110,6 +5242,7 @@ function buildPayCardSigningInput(input) {
|
|
|
5110
5242
|
computeConsumerClaimEncounterId,
|
|
5111
5243
|
computeEncounterId,
|
|
5112
5244
|
constantTimeEqual,
|
|
5245
|
+
consumerOacSigningPayload,
|
|
5113
5246
|
consumerPaymentRequestSigningBytes,
|
|
5114
5247
|
consumerPaymentRequestSigningPayload,
|
|
5115
5248
|
consumerSettlementSigningPayload,
|
|
@@ -5142,11 +5275,13 @@ function buildPayCardSigningInput(input) {
|
|
|
5142
5275
|
decodeOfflineSmsSettleToken,
|
|
5143
5276
|
decodePayCardArtifact,
|
|
5144
5277
|
decodePaymentRequestQR,
|
|
5278
|
+
decodeUnverifiedConsumerOacQR,
|
|
5145
5279
|
decodeUnverifiedConsumerSettlementReceiptQR,
|
|
5146
5280
|
derToRawP256Signature,
|
|
5147
5281
|
encodeArtifactUri,
|
|
5148
5282
|
encodeAuthorizationQR,
|
|
5149
5283
|
encodeBase45,
|
|
5284
|
+
encodeConsumerOacQR,
|
|
5150
5285
|
encodeConsumerSettlementReceiptQR,
|
|
5151
5286
|
encodeNQR,
|
|
5152
5287
|
encodeOfflineClaimSmsMessage,
|
|
@@ -5159,6 +5294,7 @@ function buildPayCardSigningInput(input) {
|
|
|
5159
5294
|
generateStaticQR,
|
|
5160
5295
|
init,
|
|
5161
5296
|
inspectPayCardFreshness,
|
|
5297
|
+
isConsumerOacQR,
|
|
5162
5298
|
isConsumerPaymentRequestExpired,
|
|
5163
5299
|
isHardenedArtifactType,
|
|
5164
5300
|
isKnownArtifactType,
|
|
@@ -5189,6 +5325,7 @@ function buildPayCardSigningInput(input) {
|
|
|
5189
5325
|
verifyConsumerSettlement,
|
|
5190
5326
|
verifyConsumerSettlementReceiptQR,
|
|
5191
5327
|
verifyOAC,
|
|
5328
|
+
verifyOacOffline,
|
|
5192
5329
|
verifyOfflineSmsSettleToken,
|
|
5193
5330
|
verifyPass,
|
|
5194
5331
|
verifyPayCardArtifact,
|