@nokinc-flur/sdk 1.1.5 → 2.1.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 +1417 -1038
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1146 -2049
- package/dist/index.d.ts +1146 -2049
- package/dist/index.js +1375 -1028
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,201 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
import { z as
|
|
2
|
+
import { z as z4 } from "zod";
|
|
3
3
|
|
|
4
4
|
// src/contracts.ts
|
|
5
|
-
import { z as z2 } from "zod";
|
|
6
|
-
|
|
7
|
-
// src/me-offline/client.ts
|
|
8
5
|
import { z } from "zod";
|
|
6
|
+
var E164Regex = /^\+[1-9]\d{7,14}$/;
|
|
7
|
+
var UuidSchema = z.string().uuid();
|
|
8
|
+
var IsoDateSchema = z.string().datetime({ offset: true });
|
|
9
|
+
var CurrencySchema = z.string().trim().length(3).transform((value) => value.toUpperCase());
|
|
10
|
+
var HealthResponseSchema = z.object({
|
|
11
|
+
ok: z.boolean()
|
|
12
|
+
});
|
|
13
|
+
var WelcomeResponseSchema = z.object({
|
|
14
|
+
message: z.string()
|
|
15
|
+
});
|
|
16
|
+
var OnboardingStartRequestSchema = z.object({
|
|
17
|
+
phoneE164: z.string().regex(E164Regex),
|
|
18
|
+
appInstanceId: z.string().min(3),
|
|
19
|
+
platform: z.enum(["android", "ios", "web"]),
|
|
20
|
+
turnstileToken: z.string().min(20).optional(),
|
|
21
|
+
appAttestation: z.object({
|
|
22
|
+
provider: z.enum(["android", "ios", "web"]),
|
|
23
|
+
token: z.string().min(24)
|
|
24
|
+
}).optional(),
|
|
25
|
+
firstName: z.string().trim().min(1).max(80).optional(),
|
|
26
|
+
lastName: z.string().trim().min(1).max(80).optional()
|
|
27
|
+
});
|
|
28
|
+
var OnboardingStartResponseSchema = z.object({
|
|
29
|
+
requestId: z.string().min(1),
|
|
30
|
+
checkUrl: z.string().url().optional(),
|
|
31
|
+
expiresInSec: z.number().int().positive(),
|
|
32
|
+
fallback: z.enum(["SILENT_AUTH", "OTP"])
|
|
33
|
+
});
|
|
34
|
+
var OnboardingCompleteRequestSchema = z.object({
|
|
35
|
+
requestId: z.string().min(1),
|
|
36
|
+
code: z.string().min(1).max(32),
|
|
37
|
+
appInstanceId: z.string().min(3),
|
|
38
|
+
fingerprintHash: z.string().min(3).optional()
|
|
39
|
+
});
|
|
40
|
+
var OnboardingCompleteResponseSchema = z.object({
|
|
41
|
+
sessionToken: z.string().min(1),
|
|
42
|
+
userId: UuidSchema,
|
|
43
|
+
restricted: z.boolean(),
|
|
44
|
+
risk_reasons: z.array(
|
|
45
|
+
z.enum(["SIM_SWAP_RECENT", "ROAMING", "CARRIER_CHANGED"])
|
|
46
|
+
),
|
|
47
|
+
stepUpRequired: z.boolean().optional(),
|
|
48
|
+
riskStatus: z.enum(["ok", "unavailable"]).optional()
|
|
49
|
+
});
|
|
50
|
+
var RegisterDeviceRequestSchema = z.object({
|
|
51
|
+
userId: UuidSchema,
|
|
52
|
+
appInstanceId: z.string().min(3),
|
|
53
|
+
platform: z.string().min(2),
|
|
54
|
+
model: z.string().optional(),
|
|
55
|
+
networkSignals: z.object({
|
|
56
|
+
ip: z.string().min(3),
|
|
57
|
+
asn: z.number().int().optional(),
|
|
58
|
+
country: z.string().min(2).optional(),
|
|
59
|
+
carrier: z.string().optional()
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
var RegisterDeviceResponseSchema = z.object({
|
|
63
|
+
deviceId: z.string().min(1),
|
|
64
|
+
fingerprintHash: z.string().min(1),
|
|
65
|
+
driftScore: z.number(),
|
|
66
|
+
trustState: z.enum(["TRUSTED_PRIMARY", "TRUSTED_SECONDARY", "UNVERIFIED"]),
|
|
67
|
+
stepUpRequired: z.boolean()
|
|
68
|
+
});
|
|
69
|
+
var AuthRefreshRequestSchema = z.object({
|
|
70
|
+
userId: UuidSchema,
|
|
71
|
+
refreshToken: z.string().min(8),
|
|
72
|
+
appInstanceId: z.string().min(3),
|
|
73
|
+
fingerprintHash: z.string().min(3)
|
|
74
|
+
});
|
|
75
|
+
var AuthRefreshResponseSchema = z.object({
|
|
76
|
+
refreshToken: z.string().min(8),
|
|
77
|
+
stepUpRequired: z.boolean()
|
|
78
|
+
});
|
|
79
|
+
var AuthLogoutRequestSchema = z.object({
|
|
80
|
+
userId: UuidSchema,
|
|
81
|
+
refreshToken: z.string().min(8)
|
|
82
|
+
});
|
|
83
|
+
var PinSetRequestSchema = z.object({
|
|
84
|
+
userId: UuidSchema,
|
|
85
|
+
pin: z.string().regex(/^\d{6}$/)
|
|
86
|
+
});
|
|
87
|
+
var PinVerifyRequestSchema = z.object({
|
|
88
|
+
userId: UuidSchema,
|
|
89
|
+
pin: z.string().regex(/^\d{6}$/)
|
|
90
|
+
});
|
|
91
|
+
var OkResponseSchema = z.object({
|
|
92
|
+
ok: z.boolean()
|
|
93
|
+
});
|
|
94
|
+
var RegisterSendDeviceKeyRequestSchema = z.object({
|
|
95
|
+
userId: UuidSchema,
|
|
96
|
+
deviceId: z.string().min(3),
|
|
97
|
+
publicKey: z.string().min(32)
|
|
98
|
+
});
|
|
99
|
+
var SEND_AUTH_PURPOSES = ["send_money", "offline_revoke"];
|
|
100
|
+
var SendChallengeRequestSchema = z.object({
|
|
101
|
+
userId: UuidSchema,
|
|
102
|
+
deviceId: z.string().min(3),
|
|
103
|
+
purpose: z.enum(SEND_AUTH_PURPOSES).optional()
|
|
104
|
+
});
|
|
105
|
+
var SendChallengeResponseSchema = z.object({
|
|
106
|
+
challengeId: UuidSchema,
|
|
107
|
+
nonce: z.string().min(1),
|
|
108
|
+
expiresAt: IsoDateSchema
|
|
109
|
+
});
|
|
110
|
+
var SendVerifyRequestSchema = z.object({
|
|
111
|
+
userId: UuidSchema,
|
|
112
|
+
deviceId: z.string().min(3),
|
|
113
|
+
challengeId: UuidSchema,
|
|
114
|
+
signature: z.string().min(16)
|
|
115
|
+
});
|
|
116
|
+
var SendVerifyResponseSchema = z.object({
|
|
117
|
+
sendAuthToken: z.string().min(16)
|
|
118
|
+
});
|
|
119
|
+
var ResolveRecipientRequestSchema = z.object({
|
|
120
|
+
identifier: z.string().min(3)
|
|
121
|
+
});
|
|
122
|
+
var ResolveRecipientResponseSchema = z.object({
|
|
123
|
+
recipientUserId: UuidSchema,
|
|
124
|
+
displayName: z.string().min(1),
|
|
125
|
+
normalizedIdentifier: z.string().regex(E164Regex),
|
|
126
|
+
isActive: z.boolean()
|
|
127
|
+
});
|
|
128
|
+
var CreateTransferRequestSchema = z.object({
|
|
129
|
+
recipientIdentifier: z.string().min(3),
|
|
130
|
+
amountMinor: z.number().int().positive(),
|
|
131
|
+
currency: CurrencySchema,
|
|
132
|
+
sendAuthToken: z.string().min(16)
|
|
133
|
+
});
|
|
134
|
+
var TransferStatusSchema = z.enum(["SETTLED", "PENDING_REVIEW", "DECLINED"]);
|
|
135
|
+
var TransferResponseSchema = z.object({
|
|
136
|
+
transactionId: z.string().min(1),
|
|
137
|
+
status: TransferStatusSchema,
|
|
138
|
+
userStatus: TransferStatusSchema,
|
|
139
|
+
recipientName: z.string().min(1),
|
|
140
|
+
timestamp: IsoDateSchema
|
|
141
|
+
});
|
|
142
|
+
var DirectionSchema = z.enum(["OUTGOING", "INCOMING"]);
|
|
143
|
+
var AccountActivityItemSchema = z.object({
|
|
144
|
+
id: z.string().min(1),
|
|
145
|
+
type: z.string().min(1),
|
|
146
|
+
direction: DirectionSchema,
|
|
147
|
+
name: z.string().min(1),
|
|
148
|
+
identifier: z.string().min(1),
|
|
149
|
+
amountMinor: z.number().int(),
|
|
150
|
+
currency: CurrencySchema,
|
|
151
|
+
status: z.string().min(1),
|
|
152
|
+
timestamp: IsoDateSchema
|
|
153
|
+
});
|
|
154
|
+
var AccountSummaryResponseSchema = z.object({
|
|
155
|
+
/** Authenticated user's stable internal id. */
|
|
156
|
+
userId: UuidSchema,
|
|
157
|
+
/**
|
|
158
|
+
* 10-digit Nigeria Uniform Bank Account Number (NUBAN) allocated by the
|
|
159
|
+
* bank partner. `null` when the user has no partner-allocated account yet.
|
|
160
|
+
*/
|
|
161
|
+
nuban: z.string().regex(/^[0-9]{10}$/).nullable(),
|
|
162
|
+
balance: z.number().int(),
|
|
163
|
+
currency: CurrencySchema,
|
|
164
|
+
dailySendLimit: z.number().int().nonnegative(),
|
|
165
|
+
dailySendRemaining: z.number().int().nonnegative(),
|
|
166
|
+
kycTier: z.string().min(1),
|
|
167
|
+
kycStatus: z.string().min(1),
|
|
168
|
+
recentActivity: z.array(AccountActivityItemSchema)
|
|
169
|
+
});
|
|
170
|
+
var TransactionsListResponseSchema = z.object({
|
|
171
|
+
items: z.array(AccountActivityItemSchema),
|
|
172
|
+
nextCursor: z.string().nullable()
|
|
173
|
+
});
|
|
174
|
+
var TransactionDetailResponseSchema = z.object({
|
|
175
|
+
transactionId: z.string().min(1),
|
|
176
|
+
type: z.string().min(1),
|
|
177
|
+
direction: DirectionSchema,
|
|
178
|
+
counterpartyName: z.string().min(1),
|
|
179
|
+
counterpartyIdentifier: z.string().min(1),
|
|
180
|
+
amountMinor: z.number().int(),
|
|
181
|
+
currency: CurrencySchema,
|
|
182
|
+
status: z.string().min(1),
|
|
183
|
+
timestamp: IsoDateSchema
|
|
184
|
+
});
|
|
185
|
+
var PushRegisterRequestSchema = z.object({
|
|
186
|
+
deviceId: z.string().min(3),
|
|
187
|
+
platform: z.enum(["ios", "android", "web"]),
|
|
188
|
+
token: z.string().min(16)
|
|
189
|
+
});
|
|
190
|
+
var CreatePayLinkResponseSchema = z.object({
|
|
191
|
+
token: z.string().min(1)
|
|
192
|
+
});
|
|
193
|
+
var ResolvePayLinkResponseSchema = z.object({
|
|
194
|
+
recipientUserId: UuidSchema,
|
|
195
|
+
displayName: z.string().min(1),
|
|
196
|
+
normalizedIdentifier: z.string().regex(E164Regex),
|
|
197
|
+
isActive: z.boolean()
|
|
198
|
+
});
|
|
9
199
|
|
|
10
200
|
// src/errors.ts
|
|
11
201
|
var backendErrorCodeSet = /* @__PURE__ */ new Set([
|
|
@@ -109,556 +299,9 @@ async function mapToFlurError(res) {
|
|
|
109
299
|
});
|
|
110
300
|
}
|
|
111
301
|
|
|
112
|
-
// src/me-offline/client.ts
|
|
113
|
-
var Hex64 = z.string().regex(/^[0-9a-f]{64}$/i);
|
|
114
|
-
var Sha256Hex = z.string().regex(/^[0-9a-f]{64}$/i);
|
|
115
|
-
var Base64Std = z.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
|
|
116
|
-
var RegisterDeviceKeyInputSchema = z.object({
|
|
117
|
-
deviceId: z.string().min(1).max(128),
|
|
118
|
-
publicKeyHex: Hex64
|
|
119
|
-
});
|
|
120
|
-
var AttestationSecurityLevelSchema = z.enum([
|
|
121
|
-
"STRONGBOX",
|
|
122
|
-
"TEE",
|
|
123
|
-
"SECURE_ENCLAVE",
|
|
124
|
-
"SOFTWARE"
|
|
125
|
-
]);
|
|
126
|
-
var DeviceKeyAlgSchema = z.literal("p256");
|
|
127
|
-
var RegisterDeviceKeyP256InputSchema = z.object({
|
|
128
|
-
deviceId: z.string().min(1).max(128),
|
|
129
|
-
/** P-256 SubjectPublicKeyInfo DER, base64. */
|
|
130
|
-
publicKeySpkiB64: Base64Std.min(64).max(4096),
|
|
131
|
-
/** Base64 of the server-issued enrollment challenge string. */
|
|
132
|
-
challengeB64: Base64Std.min(8).max(1024),
|
|
133
|
-
/** iOS App Attest payload or Android X.509 Key Attestation chain. */
|
|
134
|
-
attestationChainB64: z.array(Base64Std.min(16).max(16384)).min(1).max(16),
|
|
135
|
-
securityLevel: AttestationSecurityLevelSchema
|
|
136
|
-
});
|
|
137
|
-
var P256EnrollmentChallengeInputSchema = z.object({
|
|
138
|
-
deviceId: z.string().min(1).max(128)
|
|
139
|
-
});
|
|
140
|
-
var P256EnrollmentChallengeResultSchema = z.object({
|
|
141
|
-
challenge: z.string().min(16),
|
|
142
|
-
expiresAtMs: z.number().int().positive()
|
|
143
|
-
});
|
|
144
|
-
var DeviceKeyRecordSchema = z.object({
|
|
145
|
-
id: z.string().uuid(),
|
|
146
|
-
userId: z.string().uuid(),
|
|
147
|
-
deviceId: z.string(),
|
|
148
|
-
/** Always 'p256' on the consumer offline rail. Field retained for forward-compat. */
|
|
149
|
-
alg: DeviceKeyAlgSchema.default("p256"),
|
|
150
|
-
/** Legacy ed25519 hex key. Always null on new records (kept for back-compat reads). */
|
|
151
|
-
publicKeyHex: Hex64.nullable().default(null),
|
|
152
|
-
/** P-256 SubjectPublicKeyInfo DER, base64. Required for new records. */
|
|
153
|
-
publicKeySpkiB64: Base64Std.nullable().default(null),
|
|
154
|
-
securityLevel: AttestationSecurityLevelSchema.nullable().default(null),
|
|
155
|
-
hardwareBacked: z.boolean().default(false),
|
|
156
|
-
attestedAtMs: z.number().int().nonnegative().nullable().default(null),
|
|
157
|
-
createdAtMs: z.number().int().nonnegative(),
|
|
158
|
-
revokedAtMs: z.number().int().nonnegative().nullable()
|
|
159
|
-
});
|
|
160
|
-
var ConsumerOACSchema = z.object({
|
|
161
|
-
oacId: z.string().uuid(),
|
|
162
|
-
issuerId: z.string().min(1).max(64),
|
|
163
|
-
userId: z.string().uuid(),
|
|
164
|
-
deviceId: z.string().min(1).max(128),
|
|
165
|
-
/**
|
|
166
|
-
* Always 'p256'. Required on the wire (backend always emits it).
|
|
167
|
-
* Kept as a literal so input/output infer identically and the schema
|
|
168
|
-
* can be nested inside other response shapes without Zod input/output
|
|
169
|
-
* divergence under tsup DTS bundling.
|
|
170
|
-
*/
|
|
171
|
-
alg: z.literal("p256"),
|
|
172
|
-
/** P-256 SubjectPublicKeyInfo DER, base64. */
|
|
173
|
-
devicePubkeySpkiB64: Base64Std.min(64).max(4096),
|
|
174
|
-
perTxCapKobo: z.number().int().positive(),
|
|
175
|
-
cumulativeCapKobo: z.number().int().positive(),
|
|
176
|
-
currency: z.string().length(3),
|
|
177
|
-
validFromMs: z.number().int().nonnegative(),
|
|
178
|
-
validUntilMs: z.number().int().nonnegative(),
|
|
179
|
-
counterSeed: z.number().int().nonnegative(),
|
|
180
|
-
issuedAtMs: z.number().int().nonnegative()
|
|
181
|
-
});
|
|
182
|
-
var SignedConsumerOACSchema = z.object({
|
|
183
|
-
oac: ConsumerOACSchema,
|
|
184
|
-
/** ASN.1 DER ECDSA P-256 issuer signature, base64. */
|
|
185
|
-
issuerSig: Base64Std.min(16).max(2048),
|
|
186
|
-
/** Issuer's P-256 public key as SubjectPublicKeyInfo DER, base64. */
|
|
187
|
-
issuerPublicKeySpkiB64: Base64Std.min(64).max(4096)
|
|
188
|
-
});
|
|
189
|
-
var OACRecordSchema = SignedConsumerOACSchema.extend({
|
|
190
|
-
currentOfflineSpentKobo: z.number().int().nonnegative(),
|
|
191
|
-
status: z.enum([
|
|
192
|
-
"active",
|
|
193
|
-
"superseded",
|
|
194
|
-
"expired",
|
|
195
|
-
"revoked",
|
|
196
|
-
"disabling",
|
|
197
|
-
"draining",
|
|
198
|
-
"closed"
|
|
199
|
-
]),
|
|
200
|
-
supersededAtMs: z.number().int().nonnegative().nullable(),
|
|
201
|
-
revokedAtMs: z.number().int().nonnegative().nullable(),
|
|
202
|
-
holdId: z.string().uuid().nullable().optional()
|
|
203
|
-
});
|
|
204
|
-
var IssueOACInputSchema = z.object({
|
|
205
|
-
deviceId: z.string().min(1).max(128),
|
|
206
|
-
perTxCapKobo: z.number().int().positive().optional(),
|
|
207
|
-
cumulativeCapKobo: z.number().int().positive().optional(),
|
|
208
|
-
ttlMs: z.number().int().min(6e4).max(1e3 * 60 * 60 * 24 * 7).optional(),
|
|
209
|
-
spendableOnlineKobo: z.number().int().nonnegative().optional()
|
|
210
|
-
});
|
|
211
|
-
var IssueAccountOacInputSchema = z.object({
|
|
212
|
-
deviceId: z.string().min(1).max(128),
|
|
213
|
-
perTxCapKobo: z.number().int().positive().optional(),
|
|
214
|
-
cumulativeCapKobo: z.number().int().positive().optional(),
|
|
215
|
-
ttlMs: z.number().int().min(6e4).max(1e3 * 60 * 60 * 24 * 7).optional()
|
|
216
|
-
});
|
|
217
|
-
var EnableOfflineInputSchema = z.object({
|
|
218
|
-
deviceId: z.string().min(1).max(128),
|
|
219
|
-
amountKobo: z.number().int().positive(),
|
|
220
|
-
perTxCapKobo: z.number().int().positive().optional(),
|
|
221
|
-
ttlMs: z.number().int().min(6e4).max(1e3 * 60 * 60 * 24 * 7).optional(),
|
|
222
|
-
installId: z.string().min(1).max(128),
|
|
223
|
-
partnerId: z.string().min(1).max(64).optional()
|
|
224
|
-
});
|
|
225
|
-
var ProvisionOfflineAllowanceInputSchema = EnableOfflineInputSchema;
|
|
226
|
-
var DisableOfflineInputSchema = z.object({
|
|
227
|
-
deviceId: z.string().min(1).max(128),
|
|
228
|
-
installId: z.string().min(1).max(128).optional(),
|
|
229
|
-
claims: z.array(z.unknown()).max(256).optional()
|
|
230
|
-
});
|
|
231
|
-
var OfflineHoldRecordSchema = z.object({
|
|
232
|
-
holdId: z.string().uuid(),
|
|
233
|
-
userId: z.string().uuid(),
|
|
234
|
-
deviceId: z.string(),
|
|
235
|
-
partnerId: z.string(),
|
|
236
|
-
adapterKind: z.string(),
|
|
237
|
-
externalHoldRef: z.string().nullable(),
|
|
238
|
-
amountKobo: z.number().int().nonnegative(),
|
|
239
|
-
capturedKobo: z.number().int().nonnegative(),
|
|
240
|
-
releasedKobo: z.number().int().nonnegative(),
|
|
241
|
-
remainingKobo: z.number().int().nonnegative(),
|
|
242
|
-
currency: z.string().length(3),
|
|
243
|
-
status: z.enum([
|
|
244
|
-
"placing",
|
|
245
|
-
"active",
|
|
246
|
-
"disabling",
|
|
247
|
-
"draining",
|
|
248
|
-
"closed",
|
|
249
|
-
"revoked",
|
|
250
|
-
"failed"
|
|
251
|
-
]),
|
|
252
|
-
installId: z.string().nullable(),
|
|
253
|
-
drainDeadlineMs: z.number().int().nonnegative(),
|
|
254
|
-
disableRequestedAtMs: z.number().int().nonnegative().nullable(),
|
|
255
|
-
createdAtMs: z.number().int().nonnegative(),
|
|
256
|
-
closedAtMs: z.number().int().nonnegative().nullable(),
|
|
257
|
-
isTrusted: z.boolean().optional()
|
|
258
|
-
});
|
|
259
|
-
var EnableOfflineResultSchema = z.object({
|
|
260
|
-
hold: OfflineHoldRecordSchema,
|
|
261
|
-
oac: OACRecordSchema
|
|
262
|
-
});
|
|
263
|
-
var ProvisionOfflineAllowanceResultSchema = EnableOfflineResultSchema;
|
|
264
|
-
var DisableOfflineResultSchema = z.object({
|
|
265
|
-
hold: OfflineHoldRecordSchema,
|
|
266
|
-
trusted: z.boolean(),
|
|
267
|
-
settledClaims: z.number().int().nonnegative()
|
|
268
|
-
});
|
|
269
|
-
var OfflineStatusResultSchema = z.object({
|
|
270
|
-
hold: OfflineHoldRecordSchema.nullable(),
|
|
271
|
-
active: OACRecordSchema.nullable()
|
|
272
|
-
});
|
|
273
|
-
var OfflineStateResultSchema = z.object({
|
|
274
|
-
active: OACRecordSchema.nullable()
|
|
275
|
-
});
|
|
276
|
-
var ConsumerPaymentClaimSchema = z.object({
|
|
277
|
-
/** Always 'p256'. Retained for forward-compat and as an explicit domain marker. */
|
|
278
|
-
alg: z.literal("p256").default("p256"),
|
|
279
|
-
oacId: z.string().uuid(),
|
|
280
|
-
encounterId: Sha256Hex.optional(),
|
|
281
|
-
payerUserId: z.string().uuid(),
|
|
282
|
-
payeeUserId: z.string().uuid(),
|
|
283
|
-
payerDeviceId: z.string().min(1).max(128),
|
|
284
|
-
payerNonce: z.string().min(8).max(128),
|
|
285
|
-
payeeNonce: z.string().min(8).max(128),
|
|
286
|
-
amountKobo: z.number().int().positive(),
|
|
287
|
-
currency: z.string().length(3).default("NGN"),
|
|
288
|
-
occurredAtMs: z.number().int().nonnegative(),
|
|
289
|
-
completedAtMs: z.number().int().nonnegative().optional(),
|
|
290
|
-
contextId: z.string().max(128).optional(),
|
|
291
|
-
payerPubkeySpkiB64: Base64Std.min(64).max(4096),
|
|
292
|
-
payerSignatureDerB64: Base64Std.min(16).max(2048),
|
|
293
|
-
payeePubkeySpkiB64: Base64Std.min(64).max(4096).optional(),
|
|
294
|
-
payeeSignatureDerB64: Base64Std.min(16).max(2048).optional()
|
|
295
|
-
});
|
|
296
|
-
var ConsumerSettlementSchema = z.object({
|
|
297
|
-
settlementId: z.string().uuid(),
|
|
298
|
-
settlementKey: Sha256Hex,
|
|
299
|
-
encounterId: Sha256Hex,
|
|
300
|
-
oacId: z.string().uuid(),
|
|
301
|
-
payerUserId: z.string().uuid(),
|
|
302
|
-
payeeUserId: z.string().uuid(),
|
|
303
|
-
amountKobo: z.number().int().positive(),
|
|
304
|
-
currency: z.string().length(3),
|
|
305
|
-
status: z.enum(["SETTLED", "REVIEW"]),
|
|
306
|
-
reviewReason: z.string().nullable(),
|
|
307
|
-
ledgerRef: z.string().nullable(),
|
|
308
|
-
/** ASN.1 DER ECDSA P-256 issuer signature, base64. */
|
|
309
|
-
issuerSig: Base64Std.min(16).max(2048),
|
|
310
|
-
createdAtMs: z.number().int().nonnegative()
|
|
311
|
-
});
|
|
312
|
-
var ConsumerSettleResultSchema = z.object({
|
|
313
|
-
settlement: ConsumerSettlementSchema,
|
|
314
|
-
encounterId: Sha256Hex,
|
|
315
|
-
replayed: z.boolean()
|
|
316
|
-
});
|
|
317
|
-
var RevokeDeviceKeyInputSchema = z.object({
|
|
318
|
-
deviceId: z.string().min(1).max(128),
|
|
319
|
-
/** Step-up token from /api/v1/auth/send/verify with purpose='offline_revoke'. */
|
|
320
|
-
sendAuthToken: z.string().min(16)
|
|
321
|
-
});
|
|
322
|
-
function createMeOfflineClient(opts) {
|
|
323
|
-
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
324
|
-
if (!fetchImpl) {
|
|
325
|
-
throw new Error("createMeOfflineClient: no fetch implementation available");
|
|
326
|
-
}
|
|
327
|
-
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
328
|
-
async function call(method, path, body, parser) {
|
|
329
|
-
const init2 = {
|
|
330
|
-
method,
|
|
331
|
-
headers: {
|
|
332
|
-
"content-type": "application/json",
|
|
333
|
-
accept: "application/json"
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
if (body !== void 0) init2.body = JSON.stringify(body);
|
|
337
|
-
const resp = await fetchImpl(`${baseUrl}${path}`, init2);
|
|
338
|
-
const text = await resp.text();
|
|
339
|
-
let raw = void 0;
|
|
340
|
-
if (text) {
|
|
341
|
-
try {
|
|
342
|
-
raw = JSON.parse(text);
|
|
343
|
-
} catch {
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
if (!resp.ok) {
|
|
347
|
-
const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
|
|
348
|
-
const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
|
|
349
|
-
throw new FlurApiError(resp.status, code, message, raw);
|
|
350
|
-
}
|
|
351
|
-
return parser(raw);
|
|
352
|
-
}
|
|
353
|
-
const deviceKeyItems = z.object({ items: z.array(DeviceKeyRecordSchema) });
|
|
354
|
-
return {
|
|
355
|
-
registerDeviceKey: (input) => call(
|
|
356
|
-
"POST",
|
|
357
|
-
"/v1/me/offline/keys",
|
|
358
|
-
RegisterDeviceKeyInputSchema.parse(input),
|
|
359
|
-
(raw) => DeviceKeyRecordSchema.parse(raw)
|
|
360
|
-
),
|
|
361
|
-
issueP256EnrollmentChallenge: (input) => call(
|
|
362
|
-
"POST",
|
|
363
|
-
"/v1/me/offline/keys/p256/challenge",
|
|
364
|
-
P256EnrollmentChallengeInputSchema.parse(input),
|
|
365
|
-
(raw) => P256EnrollmentChallengeResultSchema.parse(raw)
|
|
366
|
-
),
|
|
367
|
-
registerDeviceKeyP256: (input) => call(
|
|
368
|
-
"POST",
|
|
369
|
-
"/v1/me/offline/keys/p256",
|
|
370
|
-
RegisterDeviceKeyP256InputSchema.parse(input),
|
|
371
|
-
(raw) => DeviceKeyRecordSchema.parse(raw)
|
|
372
|
-
),
|
|
373
|
-
listDeviceKeys: () => call(
|
|
374
|
-
"GET",
|
|
375
|
-
"/v1/me/offline/keys",
|
|
376
|
-
void 0,
|
|
377
|
-
(raw) => deviceKeyItems.parse(raw)
|
|
378
|
-
),
|
|
379
|
-
revokeDeviceKey: (input) => call(
|
|
380
|
-
"POST",
|
|
381
|
-
"/v1/me/offline/keys/revoke",
|
|
382
|
-
RevokeDeviceKeyInputSchema.parse(input),
|
|
383
|
-
() => void 0
|
|
384
|
-
),
|
|
385
|
-
issueAccountOac: (input) => call(
|
|
386
|
-
"POST",
|
|
387
|
-
"/v1/me/offline/oac",
|
|
388
|
-
IssueAccountOacInputSchema.parse(input),
|
|
389
|
-
(raw) => OACRecordSchema.parse(raw)
|
|
390
|
-
),
|
|
391
|
-
provisionAllowance: (input) => call(
|
|
392
|
-
"POST",
|
|
393
|
-
"/v1/me/offline/allowance",
|
|
394
|
-
ProvisionOfflineAllowanceInputSchema.parse(input),
|
|
395
|
-
(raw) => ProvisionOfflineAllowanceResultSchema.parse(raw)
|
|
396
|
-
),
|
|
397
|
-
enable: (input) => call(
|
|
398
|
-
"POST",
|
|
399
|
-
"/v1/me/offline/enable",
|
|
400
|
-
EnableOfflineInputSchema.parse(input),
|
|
401
|
-
(raw) => EnableOfflineResultSchema.parse(raw)
|
|
402
|
-
),
|
|
403
|
-
refresh: (input) => call(
|
|
404
|
-
"POST",
|
|
405
|
-
"/v1/me/offline/refresh",
|
|
406
|
-
IssueOACInputSchema.parse(input),
|
|
407
|
-
(raw) => OACRecordSchema.parse(raw)
|
|
408
|
-
),
|
|
409
|
-
disable: (input) => call(
|
|
410
|
-
"POST",
|
|
411
|
-
"/v1/me/offline/disable",
|
|
412
|
-
DisableOfflineInputSchema.parse(input),
|
|
413
|
-
(raw) => DisableOfflineResultSchema.parse(raw)
|
|
414
|
-
),
|
|
415
|
-
getStatus: (deviceId) => {
|
|
416
|
-
const qs = deviceId ? `?deviceId=${encodeURIComponent(deviceId)}` : "";
|
|
417
|
-
return call(
|
|
418
|
-
"GET",
|
|
419
|
-
`/v1/me/offline/status${qs}`,
|
|
420
|
-
void 0,
|
|
421
|
-
(raw) => OfflineStatusResultSchema.parse(raw)
|
|
422
|
-
);
|
|
423
|
-
},
|
|
424
|
-
getState: (deviceId) => {
|
|
425
|
-
const qs = deviceId ? `?deviceId=${encodeURIComponent(deviceId)}` : "";
|
|
426
|
-
return call(
|
|
427
|
-
"GET",
|
|
428
|
-
`/v1/me/offline/state${qs}`,
|
|
429
|
-
void 0,
|
|
430
|
-
(raw) => OfflineStateResultSchema.parse(raw)
|
|
431
|
-
);
|
|
432
|
-
},
|
|
433
|
-
submitClaim: (claim) => call(
|
|
434
|
-
"POST",
|
|
435
|
-
"/v1/me/offline/claims",
|
|
436
|
-
ConsumerPaymentClaimSchema.parse(claim),
|
|
437
|
-
(raw) => ConsumerSettleResultSchema.parse(raw)
|
|
438
|
-
)
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// src/contracts.ts
|
|
443
|
-
var E164Regex = /^\+[1-9]\d{7,14}$/;
|
|
444
|
-
var UuidSchema = z2.string().uuid();
|
|
445
|
-
var IsoDateSchema = z2.string().datetime({ offset: true });
|
|
446
|
-
var CurrencySchema = z2.string().trim().length(3).transform((value) => value.toUpperCase());
|
|
447
|
-
var HealthResponseSchema = z2.object({
|
|
448
|
-
ok: z2.boolean()
|
|
449
|
-
});
|
|
450
|
-
var WelcomeResponseSchema = z2.object({
|
|
451
|
-
message: z2.string()
|
|
452
|
-
});
|
|
453
|
-
var OnboardingStartRequestSchema = z2.object({
|
|
454
|
-
phoneE164: z2.string().regex(E164Regex),
|
|
455
|
-
appInstanceId: z2.string().min(3),
|
|
456
|
-
platform: z2.enum(["android", "ios", "web"]),
|
|
457
|
-
turnstileToken: z2.string().min(20).optional(),
|
|
458
|
-
appAttestation: z2.object({
|
|
459
|
-
provider: z2.enum(["android", "ios", "web"]),
|
|
460
|
-
token: z2.string().min(24)
|
|
461
|
-
}).optional(),
|
|
462
|
-
firstName: z2.string().trim().min(1).max(80).optional(),
|
|
463
|
-
lastName: z2.string().trim().min(1).max(80).optional()
|
|
464
|
-
});
|
|
465
|
-
var OnboardingStartResponseSchema = z2.object({
|
|
466
|
-
requestId: z2.string().min(1),
|
|
467
|
-
checkUrl: z2.string().url().optional(),
|
|
468
|
-
expiresInSec: z2.number().int().positive(),
|
|
469
|
-
fallback: z2.enum(["SILENT_AUTH", "OTP"])
|
|
470
|
-
});
|
|
471
|
-
var OnboardingCompleteRequestSchema = z2.object({
|
|
472
|
-
requestId: z2.string().min(1),
|
|
473
|
-
code: z2.string().min(1).max(32),
|
|
474
|
-
appInstanceId: z2.string().min(3),
|
|
475
|
-
fingerprintHash: z2.string().min(3).optional()
|
|
476
|
-
});
|
|
477
|
-
var OnboardingCompleteResponseSchema = z2.object({
|
|
478
|
-
sessionToken: z2.string().min(1),
|
|
479
|
-
userId: UuidSchema,
|
|
480
|
-
restricted: z2.boolean(),
|
|
481
|
-
risk_reasons: z2.array(
|
|
482
|
-
z2.enum(["SIM_SWAP_RECENT", "ROAMING", "CARRIER_CHANGED"])
|
|
483
|
-
),
|
|
484
|
-
stepUpRequired: z2.boolean().optional(),
|
|
485
|
-
riskStatus: z2.enum(["ok", "unavailable"]).optional()
|
|
486
|
-
});
|
|
487
|
-
var RegisterDeviceRequestSchema = z2.object({
|
|
488
|
-
userId: UuidSchema,
|
|
489
|
-
appInstanceId: z2.string().min(3),
|
|
490
|
-
platform: z2.string().min(2),
|
|
491
|
-
model: z2.string().optional(),
|
|
492
|
-
networkSignals: z2.object({
|
|
493
|
-
ip: z2.string().min(3),
|
|
494
|
-
asn: z2.number().int().optional(),
|
|
495
|
-
country: z2.string().min(2).optional(),
|
|
496
|
-
carrier: z2.string().optional()
|
|
497
|
-
})
|
|
498
|
-
});
|
|
499
|
-
var RegisterDeviceResponseSchema = z2.object({
|
|
500
|
-
deviceId: z2.string().min(1),
|
|
501
|
-
fingerprintHash: z2.string().min(1),
|
|
502
|
-
driftScore: z2.number(),
|
|
503
|
-
trustState: z2.enum(["TRUSTED_PRIMARY", "TRUSTED_SECONDARY", "UNVERIFIED"]),
|
|
504
|
-
stepUpRequired: z2.boolean()
|
|
505
|
-
});
|
|
506
|
-
var AuthRefreshRequestSchema = z2.object({
|
|
507
|
-
userId: UuidSchema,
|
|
508
|
-
refreshToken: z2.string().min(8),
|
|
509
|
-
appInstanceId: z2.string().min(3),
|
|
510
|
-
fingerprintHash: z2.string().min(3)
|
|
511
|
-
});
|
|
512
|
-
var AuthRefreshResponseSchema = z2.object({
|
|
513
|
-
refreshToken: z2.string().min(8),
|
|
514
|
-
stepUpRequired: z2.boolean()
|
|
515
|
-
});
|
|
516
|
-
var AuthLogoutRequestSchema = z2.object({
|
|
517
|
-
userId: UuidSchema,
|
|
518
|
-
refreshToken: z2.string().min(8)
|
|
519
|
-
});
|
|
520
|
-
var PinSetRequestSchema = z2.object({
|
|
521
|
-
userId: UuidSchema,
|
|
522
|
-
pin: z2.string().regex(/^\d{6}$/)
|
|
523
|
-
});
|
|
524
|
-
var PinVerifyRequestSchema = z2.object({
|
|
525
|
-
userId: UuidSchema,
|
|
526
|
-
pin: z2.string().regex(/^\d{6}$/)
|
|
527
|
-
});
|
|
528
|
-
var OkResponseSchema = z2.object({
|
|
529
|
-
ok: z2.boolean()
|
|
530
|
-
});
|
|
531
|
-
var RegisterSendDeviceKeyRequestSchema = z2.object({
|
|
532
|
-
userId: UuidSchema,
|
|
533
|
-
deviceId: z2.string().min(3),
|
|
534
|
-
publicKey: z2.string().min(32)
|
|
535
|
-
});
|
|
536
|
-
var SEND_AUTH_PURPOSES = ["send_money", "offline_revoke"];
|
|
537
|
-
var SendChallengeRequestSchema = z2.object({
|
|
538
|
-
userId: UuidSchema,
|
|
539
|
-
deviceId: z2.string().min(3),
|
|
540
|
-
purpose: z2.enum(SEND_AUTH_PURPOSES).optional()
|
|
541
|
-
});
|
|
542
|
-
var SendChallengeResponseSchema = z2.object({
|
|
543
|
-
challengeId: UuidSchema,
|
|
544
|
-
nonce: z2.string().min(1),
|
|
545
|
-
expiresAt: IsoDateSchema
|
|
546
|
-
});
|
|
547
|
-
var SendVerifyRequestSchema = z2.object({
|
|
548
|
-
userId: UuidSchema,
|
|
549
|
-
deviceId: z2.string().min(3),
|
|
550
|
-
challengeId: UuidSchema,
|
|
551
|
-
signature: z2.string().min(16)
|
|
552
|
-
});
|
|
553
|
-
var SendVerifyResponseSchema = z2.object({
|
|
554
|
-
sendAuthToken: z2.string().min(16)
|
|
555
|
-
});
|
|
556
|
-
var ResolveRecipientRequestSchema = z2.object({
|
|
557
|
-
identifier: z2.string().min(3)
|
|
558
|
-
});
|
|
559
|
-
var ResolveRecipientResponseSchema = z2.object({
|
|
560
|
-
recipientUserId: UuidSchema,
|
|
561
|
-
displayName: z2.string().min(1),
|
|
562
|
-
normalizedIdentifier: z2.string().regex(E164Regex),
|
|
563
|
-
isActive: z2.boolean()
|
|
564
|
-
});
|
|
565
|
-
var CreateTransferRequestSchema = z2.object({
|
|
566
|
-
recipientIdentifier: z2.string().min(3),
|
|
567
|
-
amountMinor: z2.number().int().positive(),
|
|
568
|
-
currency: CurrencySchema,
|
|
569
|
-
sendAuthToken: z2.string().min(16)
|
|
570
|
-
});
|
|
571
|
-
var TransferStatusSchema = z2.enum(["SETTLED", "PENDING_REVIEW", "DECLINED"]);
|
|
572
|
-
var TransferResponseSchema = z2.object({
|
|
573
|
-
transactionId: z2.string().min(1),
|
|
574
|
-
status: TransferStatusSchema,
|
|
575
|
-
userStatus: TransferStatusSchema,
|
|
576
|
-
recipientName: z2.string().min(1),
|
|
577
|
-
timestamp: IsoDateSchema,
|
|
578
|
-
/**
|
|
579
|
-
* Fresh issuer-signed OACs returned by the backend's post-commit
|
|
580
|
-
* rotation hook on `SETTLED` transfers.
|
|
581
|
-
*
|
|
582
|
-
* In the current backend implementation this hook rotates LEGACY
|
|
583
|
-
* hold-backed OACs only. Account-funded (no-hold) OACs introduced by
|
|
584
|
-
* the unified-pay-rails refactor are NOT rotated here — their
|
|
585
|
-
* cumulative spend counter and main-balance check are enforced at
|
|
586
|
-
* claim submission time via `createTransfer` under the per-sender
|
|
587
|
-
* advisory lock, so a stale no-hold OAC cannot authorise overspending
|
|
588
|
-
* (it can only resolve to REVIEW on submission).
|
|
589
|
-
*
|
|
590
|
-
* Optional: omitted when the sender has no registered hold-backed
|
|
591
|
-
* devices, when the refresh failed (best-effort), or on retry replays
|
|
592
|
-
* that pre-date the refresh. When present, the entries supersede any
|
|
593
|
-
* locally cached OACs for the listed devices; when absent, clients
|
|
594
|
-
* should fall back to `/v1/me/offline/refresh`.
|
|
595
|
-
*
|
|
596
|
-
* Each entry is validated against the canonical `OACRecordSchema`,
|
|
597
|
-
* so consumers can trust the runtime shape matches the static type.
|
|
598
|
-
*/
|
|
599
|
-
offlineOacs: OACRecordSchema.array().optional()
|
|
600
|
-
});
|
|
601
|
-
var DirectionSchema = z2.enum(["OUTGOING", "INCOMING"]);
|
|
602
|
-
var AccountActivityItemSchema = z2.object({
|
|
603
|
-
id: z2.string().min(1),
|
|
604
|
-
type: z2.string().min(1),
|
|
605
|
-
direction: DirectionSchema,
|
|
606
|
-
name: z2.string().min(1),
|
|
607
|
-
identifier: z2.string().min(1),
|
|
608
|
-
amountMinor: z2.number().int(),
|
|
609
|
-
currency: CurrencySchema,
|
|
610
|
-
status: z2.string().min(1),
|
|
611
|
-
timestamp: IsoDateSchema
|
|
612
|
-
});
|
|
613
|
-
var AccountSummaryResponseSchema = z2.object({
|
|
614
|
-
/** Authenticated user's stable internal id. */
|
|
615
|
-
userId: UuidSchema,
|
|
616
|
-
/**
|
|
617
|
-
* 10-digit Nigeria Uniform Bank Account Number (NUBAN) allocated by the
|
|
618
|
-
* bank partner. `null` when the user has no partner-allocated account yet.
|
|
619
|
-
*/
|
|
620
|
-
nuban: z2.string().regex(/^[0-9]{10}$/).nullable(),
|
|
621
|
-
balance: z2.number().int(),
|
|
622
|
-
currency: CurrencySchema,
|
|
623
|
-
dailySendLimit: z2.number().int().nonnegative(),
|
|
624
|
-
dailySendRemaining: z2.number().int().nonnegative(),
|
|
625
|
-
kycTier: z2.string().min(1),
|
|
626
|
-
kycStatus: z2.string().min(1),
|
|
627
|
-
recentActivity: z2.array(AccountActivityItemSchema)
|
|
628
|
-
});
|
|
629
|
-
var TransactionsListResponseSchema = z2.object({
|
|
630
|
-
items: z2.array(AccountActivityItemSchema),
|
|
631
|
-
nextCursor: z2.string().nullable()
|
|
632
|
-
});
|
|
633
|
-
var TransactionDetailResponseSchema = z2.object({
|
|
634
|
-
transactionId: z2.string().min(1),
|
|
635
|
-
type: z2.string().min(1),
|
|
636
|
-
direction: DirectionSchema,
|
|
637
|
-
counterpartyName: z2.string().min(1),
|
|
638
|
-
counterpartyIdentifier: z2.string().min(1),
|
|
639
|
-
amountMinor: z2.number().int(),
|
|
640
|
-
currency: CurrencySchema,
|
|
641
|
-
status: z2.string().min(1),
|
|
642
|
-
timestamp: IsoDateSchema
|
|
643
|
-
});
|
|
644
|
-
var PushRegisterRequestSchema = z2.object({
|
|
645
|
-
deviceId: z2.string().min(3),
|
|
646
|
-
platform: z2.enum(["ios", "android", "web"]),
|
|
647
|
-
token: z2.string().min(16)
|
|
648
|
-
});
|
|
649
|
-
var CreatePayLinkResponseSchema = z2.object({
|
|
650
|
-
token: z2.string().min(1)
|
|
651
|
-
});
|
|
652
|
-
var ResolvePayLinkResponseSchema = z2.object({
|
|
653
|
-
recipientUserId: UuidSchema,
|
|
654
|
-
displayName: z2.string().min(1),
|
|
655
|
-
normalizedIdentifier: z2.string().regex(E164Regex),
|
|
656
|
-
isActive: z2.boolean()
|
|
657
|
-
});
|
|
658
|
-
|
|
659
302
|
// src/primitives.ts
|
|
660
|
-
import { z as
|
|
661
|
-
var CurrencyCodeSchema =
|
|
303
|
+
import { z as z2 } from "zod";
|
|
304
|
+
var CurrencyCodeSchema = z2.string().trim().length(3).transform((value) => value.toUpperCase());
|
|
662
305
|
var currencyFractionDigits = {
|
|
663
306
|
NGN: 2,
|
|
664
307
|
USD: 2,
|
|
@@ -752,7 +395,7 @@ function moneyMinorToNumber(amountMinor) {
|
|
|
752
395
|
}
|
|
753
396
|
|
|
754
397
|
// src/collections/client.ts
|
|
755
|
-
import { z as
|
|
398
|
+
import { z as z3 } from "zod";
|
|
756
399
|
var MERCHANT_PROFILE_STATUSES = [
|
|
757
400
|
"pending",
|
|
758
401
|
"active",
|
|
@@ -782,172 +425,172 @@ var MERCHANT_PAYOUT_STATUSES = [
|
|
|
782
425
|
"failed",
|
|
783
426
|
"cancelled"
|
|
784
427
|
];
|
|
785
|
-
var MoneyKoboSchema =
|
|
786
|
-
var MetadataSchema =
|
|
787
|
-
|
|
428
|
+
var MoneyKoboSchema = z3.number().int().positive().max(Number.MAX_SAFE_INTEGER);
|
|
429
|
+
var MetadataSchema = z3.record(
|
|
430
|
+
z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()])
|
|
788
431
|
);
|
|
789
|
-
var CurrencySchema2 =
|
|
790
|
-
var ReferenceSchema =
|
|
791
|
-
var MerchantProfileSchema =
|
|
792
|
-
accountId:
|
|
793
|
-
legalName:
|
|
794
|
-
tradingName:
|
|
795
|
-
merchantCategoryCode:
|
|
796
|
-
nqrMerchantId:
|
|
797
|
-
settlementBankCode:
|
|
798
|
-
settlementAccountNumber:
|
|
799
|
-
settlementAccountName:
|
|
800
|
-
settlementSchedule:
|
|
801
|
-
status:
|
|
802
|
-
offlineEnabled:
|
|
432
|
+
var CurrencySchema2 = z3.string().trim().length(3).transform((value) => value.toUpperCase());
|
|
433
|
+
var ReferenceSchema = z3.string().trim().min(6).max(128).regex(/^[A-Za-z0-9._:-]+$/);
|
|
434
|
+
var MerchantProfileSchema = z3.object({
|
|
435
|
+
accountId: z3.string().uuid(),
|
|
436
|
+
legalName: z3.string(),
|
|
437
|
+
tradingName: z3.string(),
|
|
438
|
+
merchantCategoryCode: z3.string().regex(/^\d{4}$/),
|
|
439
|
+
nqrMerchantId: z3.string(),
|
|
440
|
+
settlementBankCode: z3.string(),
|
|
441
|
+
settlementAccountNumber: z3.string(),
|
|
442
|
+
settlementAccountName: z3.string(),
|
|
443
|
+
settlementSchedule: z3.enum(SETTLEMENT_SCHEDULES),
|
|
444
|
+
status: z3.enum(MERCHANT_PROFILE_STATUSES),
|
|
445
|
+
offlineEnabled: z3.boolean(),
|
|
803
446
|
perTxLimitKobo: MoneyKoboSchema,
|
|
804
447
|
dailyLimitKobo: MoneyKoboSchema,
|
|
805
448
|
metadata: MetadataSchema,
|
|
806
|
-
createdAtMs:
|
|
807
|
-
updatedAtMs:
|
|
449
|
+
createdAtMs: z3.number().int().nonnegative(),
|
|
450
|
+
updatedAtMs: z3.number().int().nonnegative()
|
|
808
451
|
});
|
|
809
|
-
var UpsertMerchantProfileInputSchema =
|
|
810
|
-
legalName:
|
|
811
|
-
tradingName:
|
|
812
|
-
merchantCategoryCode:
|
|
813
|
-
nqrMerchantId:
|
|
814
|
-
settlementBankCode:
|
|
815
|
-
settlementAccountNumber:
|
|
816
|
-
settlementAccountName:
|
|
817
|
-
settlementSchedule:
|
|
818
|
-
status:
|
|
819
|
-
offlineEnabled:
|
|
452
|
+
var UpsertMerchantProfileInputSchema = z3.object({
|
|
453
|
+
legalName: z3.string().trim().min(1).max(200),
|
|
454
|
+
tradingName: z3.string().trim().min(1).max(25),
|
|
455
|
+
merchantCategoryCode: z3.string().trim().regex(/^\d{4}$/),
|
|
456
|
+
nqrMerchantId: z3.string().trim().min(3).max(64),
|
|
457
|
+
settlementBankCode: z3.string().trim().min(2).max(16),
|
|
458
|
+
settlementAccountNumber: z3.string().trim().min(5).max(32),
|
|
459
|
+
settlementAccountName: z3.string().trim().min(1).max(200),
|
|
460
|
+
settlementSchedule: z3.enum(SETTLEMENT_SCHEDULES).optional(),
|
|
461
|
+
status: z3.enum(MERCHANT_PROFILE_STATUSES).optional(),
|
|
462
|
+
offlineEnabled: z3.boolean().optional(),
|
|
820
463
|
perTxLimitKobo: MoneyKoboSchema.optional(),
|
|
821
464
|
dailyLimitKobo: MoneyKoboSchema.optional(),
|
|
822
465
|
metadata: MetadataSchema.optional()
|
|
823
466
|
});
|
|
824
|
-
var CollectionIntentSchema =
|
|
825
|
-
intentId:
|
|
826
|
-
accountId:
|
|
827
|
-
terminalId:
|
|
828
|
-
reference:
|
|
829
|
-
amountKobo:
|
|
830
|
-
currency:
|
|
831
|
-
status:
|
|
832
|
-
description:
|
|
833
|
-
nqrPayload:
|
|
834
|
-
provider:
|
|
835
|
-
providerReference:
|
|
467
|
+
var CollectionIntentSchema = z3.object({
|
|
468
|
+
intentId: z3.string().uuid(),
|
|
469
|
+
accountId: z3.string().uuid(),
|
|
470
|
+
terminalId: z3.string().uuid().nullable(),
|
|
471
|
+
reference: z3.string(),
|
|
472
|
+
amountKobo: z3.number().int().positive().nullable(),
|
|
473
|
+
currency: z3.string().length(3),
|
|
474
|
+
status: z3.enum(COLLECTION_INTENT_STATUSES),
|
|
475
|
+
description: z3.string().nullable(),
|
|
476
|
+
nqrPayload: z3.string(),
|
|
477
|
+
provider: z3.string(),
|
|
478
|
+
providerReference: z3.string().nullable(),
|
|
836
479
|
metadata: MetadataSchema,
|
|
837
|
-
expiresAtMs:
|
|
838
|
-
paidAtMs:
|
|
839
|
-
createdAtMs:
|
|
840
|
-
updatedAtMs:
|
|
480
|
+
expiresAtMs: z3.number().int().nonnegative().nullable(),
|
|
481
|
+
paidAtMs: z3.number().int().nonnegative().nullable(),
|
|
482
|
+
createdAtMs: z3.number().int().nonnegative(),
|
|
483
|
+
updatedAtMs: z3.number().int().nonnegative()
|
|
841
484
|
});
|
|
842
|
-
var CreateCollectionIntentInputSchema =
|
|
485
|
+
var CreateCollectionIntentInputSchema = z3.object({
|
|
843
486
|
reference: ReferenceSchema.optional(),
|
|
844
487
|
amountKobo: MoneyKoboSchema.optional(),
|
|
845
488
|
currency: CurrencySchema2.optional(),
|
|
846
|
-
terminalId:
|
|
847
|
-
terminalLabel:
|
|
848
|
-
merchantCity:
|
|
849
|
-
description:
|
|
850
|
-
expiresAtMs:
|
|
851
|
-
provider:
|
|
489
|
+
terminalId: z3.string().uuid().optional(),
|
|
490
|
+
terminalLabel: z3.string().trim().min(1).max(25).optional(),
|
|
491
|
+
merchantCity: z3.string().trim().min(1).max(15).optional(),
|
|
492
|
+
description: z3.string().trim().min(1).max(280).optional(),
|
|
493
|
+
expiresAtMs: z3.number().int().positive().optional(),
|
|
494
|
+
provider: z3.string().trim().min(1).max(40).optional(),
|
|
852
495
|
metadata: MetadataSchema.optional()
|
|
853
496
|
});
|
|
854
|
-
var PublicCollectionIntentSchema =
|
|
855
|
-
intentId:
|
|
856
|
-
reference:
|
|
857
|
-
amountKobo:
|
|
858
|
-
currency:
|
|
859
|
-
status:
|
|
860
|
-
merchantAccountId:
|
|
861
|
-
merchantName:
|
|
862
|
-
merchantCategoryCode:
|
|
863
|
-
description:
|
|
864
|
-
expiresAtMs:
|
|
497
|
+
var PublicCollectionIntentSchema = z3.object({
|
|
498
|
+
intentId: z3.string().uuid(),
|
|
499
|
+
reference: z3.string(),
|
|
500
|
+
amountKobo: z3.number().int().positive().nullable(),
|
|
501
|
+
currency: z3.string().length(3),
|
|
502
|
+
status: z3.enum(COLLECTION_INTENT_STATUSES),
|
|
503
|
+
merchantAccountId: z3.string().uuid(),
|
|
504
|
+
merchantName: z3.string(),
|
|
505
|
+
merchantCategoryCode: z3.string(),
|
|
506
|
+
description: z3.string().nullable(),
|
|
507
|
+
expiresAtMs: z3.number().int().nonnegative().nullable()
|
|
865
508
|
});
|
|
866
|
-
var PayCollectionInputSchema =
|
|
509
|
+
var PayCollectionInputSchema = z3.object({
|
|
867
510
|
reference: ReferenceSchema,
|
|
868
511
|
amountKobo: MoneyKoboSchema.optional(),
|
|
869
512
|
currency: CurrencySchema2.optional(),
|
|
870
|
-
idempotencyKey:
|
|
513
|
+
idempotencyKey: z3.string().trim().min(8).max(160).optional()
|
|
871
514
|
});
|
|
872
|
-
var CollectionPaymentSchema =
|
|
873
|
-
paymentId:
|
|
874
|
-
intentId:
|
|
875
|
-
accountId:
|
|
876
|
-
payerUserId:
|
|
877
|
-
merchantOwnerUserId:
|
|
515
|
+
var CollectionPaymentSchema = z3.object({
|
|
516
|
+
paymentId: z3.string().uuid(),
|
|
517
|
+
intentId: z3.string().uuid(),
|
|
518
|
+
accountId: z3.string().uuid(),
|
|
519
|
+
payerUserId: z3.string().uuid().nullable(),
|
|
520
|
+
merchantOwnerUserId: z3.string().uuid(),
|
|
878
521
|
amountKobo: MoneyKoboSchema,
|
|
879
|
-
currency:
|
|
880
|
-
status:
|
|
881
|
-
provider:
|
|
882
|
-
providerReference:
|
|
883
|
-
idempotencyKey:
|
|
884
|
-
ledgerRef:
|
|
885
|
-
failureCode:
|
|
886
|
-
failureMessage:
|
|
887
|
-
paidAtMs:
|
|
888
|
-
createdAtMs:
|
|
889
|
-
updatedAtMs:
|
|
522
|
+
currency: z3.string().length(3),
|
|
523
|
+
status: z3.enum(COLLECTION_PAYMENT_STATUSES),
|
|
524
|
+
provider: z3.string(),
|
|
525
|
+
providerReference: z3.string().nullable(),
|
|
526
|
+
idempotencyKey: z3.string().nullable(),
|
|
527
|
+
ledgerRef: z3.string(),
|
|
528
|
+
failureCode: z3.string().nullable(),
|
|
529
|
+
failureMessage: z3.string().nullable(),
|
|
530
|
+
paidAtMs: z3.number().int().nonnegative().nullable(),
|
|
531
|
+
createdAtMs: z3.number().int().nonnegative(),
|
|
532
|
+
updatedAtMs: z3.number().int().nonnegative()
|
|
890
533
|
});
|
|
891
|
-
var CollectionPaymentResultSchema =
|
|
534
|
+
var CollectionPaymentResultSchema = z3.object({
|
|
892
535
|
payment: CollectionPaymentSchema,
|
|
893
536
|
intent: CollectionIntentSchema,
|
|
894
|
-
receipt:
|
|
895
|
-
replayed:
|
|
537
|
+
receipt: z3.unknown().optional(),
|
|
538
|
+
replayed: z3.boolean()
|
|
896
539
|
});
|
|
897
|
-
var CollectionReportSummarySchema =
|
|
898
|
-
accountId:
|
|
899
|
-
fromMs:
|
|
900
|
-
toMs:
|
|
901
|
-
currency:
|
|
902
|
-
paidCount:
|
|
903
|
-
paidAmountKobo:
|
|
904
|
-
pendingCount:
|
|
905
|
-
failedCount:
|
|
906
|
-
reversedCount:
|
|
907
|
-
availableBalanceKobo:
|
|
540
|
+
var CollectionReportSummarySchema = z3.object({
|
|
541
|
+
accountId: z3.string().uuid(),
|
|
542
|
+
fromMs: z3.number().int().nonnegative(),
|
|
543
|
+
toMs: z3.number().int().nonnegative(),
|
|
544
|
+
currency: z3.string().length(3),
|
|
545
|
+
paidCount: z3.number().int().nonnegative(),
|
|
546
|
+
paidAmountKobo: z3.number().int().nonnegative(),
|
|
547
|
+
pendingCount: z3.number().int().nonnegative(),
|
|
548
|
+
failedCount: z3.number().int().nonnegative(),
|
|
549
|
+
reversedCount: z3.number().int().nonnegative(),
|
|
550
|
+
availableBalanceKobo: z3.number().int().nonnegative()
|
|
908
551
|
});
|
|
909
|
-
var CollectionStatementSchema =
|
|
910
|
-
accountId:
|
|
911
|
-
year:
|
|
912
|
-
month:
|
|
913
|
-
currency:
|
|
914
|
-
totalPaidKobo:
|
|
915
|
-
items:
|
|
552
|
+
var CollectionStatementSchema = z3.object({
|
|
553
|
+
accountId: z3.string().uuid(),
|
|
554
|
+
year: z3.number().int(),
|
|
555
|
+
month: z3.number().int().min(1).max(12),
|
|
556
|
+
currency: z3.string().length(3),
|
|
557
|
+
totalPaidKobo: z3.number().int().nonnegative(),
|
|
558
|
+
items: z3.array(CollectionPaymentSchema)
|
|
916
559
|
});
|
|
917
|
-
var CreatePayoutInputSchema =
|
|
560
|
+
var CreatePayoutInputSchema = z3.object({
|
|
918
561
|
amountKobo: MoneyKoboSchema,
|
|
919
562
|
currency: CurrencySchema2.optional(),
|
|
920
|
-
idempotencyKey:
|
|
563
|
+
idempotencyKey: z3.string().trim().min(8).max(160)
|
|
921
564
|
});
|
|
922
|
-
var MerchantPayoutSchema =
|
|
923
|
-
payoutId:
|
|
924
|
-
accountId:
|
|
565
|
+
var MerchantPayoutSchema = z3.object({
|
|
566
|
+
payoutId: z3.string().uuid(),
|
|
567
|
+
accountId: z3.string().uuid(),
|
|
925
568
|
amountKobo: MoneyKoboSchema,
|
|
926
|
-
currency:
|
|
927
|
-
status:
|
|
928
|
-
idempotencyKey:
|
|
929
|
-
ledgerRef:
|
|
930
|
-
providerReference:
|
|
931
|
-
requestedByUserId:
|
|
932
|
-
failureCode:
|
|
933
|
-
failureMessage:
|
|
934
|
-
createdAtMs:
|
|
935
|
-
updatedAtMs:
|
|
569
|
+
currency: z3.string().length(3),
|
|
570
|
+
status: z3.enum(MERCHANT_PAYOUT_STATUSES),
|
|
571
|
+
idempotencyKey: z3.string().nullable(),
|
|
572
|
+
ledgerRef: z3.string(),
|
|
573
|
+
providerReference: z3.string().nullable(),
|
|
574
|
+
requestedByUserId: z3.string().uuid().nullable(),
|
|
575
|
+
failureCode: z3.string().nullable(),
|
|
576
|
+
failureMessage: z3.string().nullable(),
|
|
577
|
+
createdAtMs: z3.number().int().nonnegative(),
|
|
578
|
+
updatedAtMs: z3.number().int().nonnegative()
|
|
936
579
|
});
|
|
937
|
-
var ProviderEventInputSchema =
|
|
938
|
-
provider:
|
|
939
|
-
eventId:
|
|
940
|
-
eventType:
|
|
941
|
-
payload:
|
|
580
|
+
var ProviderEventInputSchema = z3.object({
|
|
581
|
+
provider: z3.string().trim().min(1).max(80),
|
|
582
|
+
eventId: z3.string().trim().min(1).max(160),
|
|
583
|
+
eventType: z3.string().trim().min(1).max(120),
|
|
584
|
+
payload: z3.record(z3.unknown()).optional()
|
|
942
585
|
});
|
|
943
|
-
var ProviderEventRecordSchema =
|
|
944
|
-
id:
|
|
945
|
-
provider:
|
|
946
|
-
eventId:
|
|
947
|
-
eventType:
|
|
948
|
-
signatureVerified:
|
|
949
|
-
receivedAtMs:
|
|
950
|
-
processedAtMs:
|
|
586
|
+
var ProviderEventRecordSchema = z3.object({
|
|
587
|
+
id: z3.string().uuid(),
|
|
588
|
+
provider: z3.string(),
|
|
589
|
+
eventId: z3.string(),
|
|
590
|
+
eventType: z3.string(),
|
|
591
|
+
signatureVerified: z3.boolean(),
|
|
592
|
+
receivedAtMs: z3.number().int().nonnegative(),
|
|
593
|
+
processedAtMs: z3.number().int().nonnegative().nullable()
|
|
951
594
|
});
|
|
952
595
|
function createCollectionsClient(opts) {
|
|
953
596
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
@@ -1462,7 +1105,7 @@ var FlurClient = class {
|
|
|
1462
1105
|
try {
|
|
1463
1106
|
body = requestSchema ? JSON.stringify(requestSchema.parse(input)) : init2.body;
|
|
1464
1107
|
} catch (err) {
|
|
1465
|
-
if (err instanceof
|
|
1108
|
+
if (err instanceof z4.ZodError) {
|
|
1466
1109
|
throw new FlurError("Invalid request payload", "INVALID_REQUEST", {
|
|
1467
1110
|
details: err.flatten()
|
|
1468
1111
|
});
|
|
@@ -1490,7 +1133,7 @@ var FlurClient = class {
|
|
|
1490
1133
|
try {
|
|
1491
1134
|
return responseSchema.parse(payload);
|
|
1492
1135
|
} catch (err) {
|
|
1493
|
-
if (err instanceof
|
|
1136
|
+
if (err instanceof z4.ZodError) {
|
|
1494
1137
|
throw new FlurError(
|
|
1495
1138
|
"SDK contract validation failed",
|
|
1496
1139
|
"INVALID_REQUEST",
|
|
@@ -1921,7 +1564,7 @@ function constantTimeEqual(a, b) {
|
|
|
1921
1564
|
}
|
|
1922
1565
|
|
|
1923
1566
|
// src/offline/oac.ts
|
|
1924
|
-
import { z as
|
|
1567
|
+
import { z as z5 } from "zod";
|
|
1925
1568
|
|
|
1926
1569
|
// src/crypto/p256-issuer.ts
|
|
1927
1570
|
import { p256 } from "@noble/curves/nist";
|
|
@@ -2003,20 +1646,20 @@ function verifyIssuerP256(bytes, signatureB64, issuerPublicKeySpkiB64) {
|
|
|
2003
1646
|
var OAC_DEFAULT_PER_TX_KOBO = 5e5;
|
|
2004
1647
|
var OAC_DEFAULT_CUMULATIVE_KOBO = 2e6;
|
|
2005
1648
|
var OAC_DEFAULT_VALIDITY_MS = 24 * 60 * 60 * 1e3;
|
|
2006
|
-
var
|
|
2007
|
-
var OACSchema =
|
|
2008
|
-
userId:
|
|
2009
|
-
deviceId:
|
|
1649
|
+
var Base64Std = z5.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (standard) string");
|
|
1650
|
+
var OACSchema = z5.object({
|
|
1651
|
+
userId: z5.string().min(1),
|
|
1652
|
+
deviceId: z5.string().min(1),
|
|
2010
1653
|
/** SubjectPublicKeyInfo DER, base64 (P-256). */
|
|
2011
|
-
devicePublicKey:
|
|
2012
|
-
perTxCapKobo:
|
|
2013
|
-
cumulativeCapKobo:
|
|
2014
|
-
validFromMs:
|
|
2015
|
-
validUntilMs:
|
|
2016
|
-
counterSeed:
|
|
2017
|
-
nonce:
|
|
1654
|
+
devicePublicKey: Base64Std,
|
|
1655
|
+
perTxCapKobo: z5.number().int().nonnegative(),
|
|
1656
|
+
cumulativeCapKobo: z5.number().int().nonnegative(),
|
|
1657
|
+
validFromMs: z5.number().int().nonnegative(),
|
|
1658
|
+
validUntilMs: z5.number().int().positive(),
|
|
1659
|
+
counterSeed: z5.number().int().nonnegative(),
|
|
1660
|
+
nonce: z5.string().min(1),
|
|
2018
1661
|
/** ASN.1 DER ECDSA(SHA-256) signature, base64. */
|
|
2019
|
-
issuerSig:
|
|
1662
|
+
issuerSig: Base64Std
|
|
2020
1663
|
}).refine((v) => v.validUntilMs > v.validFromMs, {
|
|
2021
1664
|
message: "validUntilMs must be greater than validFromMs"
|
|
2022
1665
|
}).refine((v) => v.perTxCapKobo <= v.cumulativeCapKobo, {
|
|
@@ -2110,19 +1753,19 @@ function decodeBase45(s) {
|
|
|
2110
1753
|
}
|
|
2111
1754
|
|
|
2112
1755
|
// src/offline/messages.ts
|
|
2113
|
-
import { z as
|
|
2114
|
-
var Base64Sig =
|
|
2115
|
-
var OfflinePaymentRequestSchema =
|
|
2116
|
-
reference:
|
|
2117
|
-
amountKobo:
|
|
1756
|
+
import { z as z6 } from "zod";
|
|
1757
|
+
var Base64Sig = z6.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (standard) signature");
|
|
1758
|
+
var OfflinePaymentRequestSchema = z6.object({
|
|
1759
|
+
reference: z6.string().min(1),
|
|
1760
|
+
amountKobo: z6.number().int().positive(),
|
|
2118
1761
|
merchantOAC: OACSchema,
|
|
2119
|
-
expiresAtMs:
|
|
1762
|
+
expiresAtMs: z6.number().int().positive(),
|
|
2120
1763
|
merchantSig: Base64Sig
|
|
2121
1764
|
});
|
|
2122
|
-
var OfflinePaymentAuthorizationSchema =
|
|
1765
|
+
var OfflinePaymentAuthorizationSchema = z6.object({
|
|
2123
1766
|
request: OfflinePaymentRequestSchema,
|
|
2124
1767
|
payerOAC: OACSchema,
|
|
2125
|
-
payerCounter:
|
|
1768
|
+
payerCounter: z6.number().int().positive(),
|
|
2126
1769
|
payerSig: Base64Sig
|
|
2127
1770
|
});
|
|
2128
1771
|
function buildPaymentRequest(input) {
|
|
@@ -2234,60 +1877,60 @@ function decodeAuthorizationQR(s) {
|
|
|
2234
1877
|
}
|
|
2235
1878
|
|
|
2236
1879
|
// src/offline/settlements.ts
|
|
2237
|
-
import { z as
|
|
1880
|
+
import { z as z7 } from "zod";
|
|
2238
1881
|
import { sha256 } from "@noble/hashes/sha256";
|
|
2239
1882
|
import { bytesToHex } from "@noble/hashes/utils";
|
|
2240
|
-
var OfflineTokenSchema =
|
|
2241
|
-
tokenId:
|
|
2242
|
-
tokenSerial:
|
|
2243
|
-
issuerAccountId:
|
|
2244
|
-
payerUserId:
|
|
2245
|
-
maxAmountKobo:
|
|
2246
|
-
currency:
|
|
2247
|
-
issuedAtMs:
|
|
2248
|
-
expiresAtMs:
|
|
2249
|
-
issuerSig:
|
|
1883
|
+
var OfflineTokenSchema = z7.object({
|
|
1884
|
+
tokenId: z7.string().uuid(),
|
|
1885
|
+
tokenSerial: z7.string(),
|
|
1886
|
+
issuerAccountId: z7.string().uuid(),
|
|
1887
|
+
payerUserId: z7.string().uuid(),
|
|
1888
|
+
maxAmountKobo: z7.number().int().positive(),
|
|
1889
|
+
currency: z7.string().length(3),
|
|
1890
|
+
issuedAtMs: z7.number().int().nonnegative(),
|
|
1891
|
+
expiresAtMs: z7.number().int().nonnegative(),
|
|
1892
|
+
issuerSig: z7.string()
|
|
2250
1893
|
});
|
|
2251
|
-
var PaymentClaimSchema =
|
|
2252
|
-
encounterId:
|
|
2253
|
-
tokenSerial:
|
|
2254
|
-
payerUserId:
|
|
2255
|
-
payeeUserId:
|
|
2256
|
-
payerNonce:
|
|
2257
|
-
payeeNonce:
|
|
2258
|
-
amountKobo:
|
|
2259
|
-
currency:
|
|
2260
|
-
occurredAtMs:
|
|
2261
|
-
completedAtMs:
|
|
2262
|
-
contextId:
|
|
1894
|
+
var PaymentClaimSchema = z7.object({
|
|
1895
|
+
encounterId: z7.string().regex(/^[0-9a-f]{64}$/i).optional(),
|
|
1896
|
+
tokenSerial: z7.string(),
|
|
1897
|
+
payerUserId: z7.string().uuid(),
|
|
1898
|
+
payeeUserId: z7.string().uuid(),
|
|
1899
|
+
payerNonce: z7.string(),
|
|
1900
|
+
payeeNonce: z7.string(),
|
|
1901
|
+
amountKobo: z7.number().int().positive(),
|
|
1902
|
+
currency: z7.string().length(3).default("NGN"),
|
|
1903
|
+
occurredAtMs: z7.number().int().nonnegative(),
|
|
1904
|
+
completedAtMs: z7.number().int().nonnegative().optional(),
|
|
1905
|
+
contextId: z7.string().optional(),
|
|
2263
1906
|
// Stage 2c: P-256 device keys are now SubjectPublicKeyInfo DER, base64.
|
|
2264
1907
|
// Signatures are ASN.1 DER ECDSA(SHA-256), base64. Backwards-incompatible
|
|
2265
1908
|
// wire change; the backend has the matching widening in offline-settlements
|
|
2266
1909
|
// service + zod schema.
|
|
2267
|
-
payerPubkey:
|
|
2268
|
-
payerSignature:
|
|
2269
|
-
payeePubkey:
|
|
2270
|
-
payeeSignature:
|
|
1910
|
+
payerPubkey: z7.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
|
|
1911
|
+
payerSignature: z7.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
|
|
1912
|
+
payeePubkey: z7.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/).optional(),
|
|
1913
|
+
payeeSignature: z7.string().min(16).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/).optional()
|
|
2271
1914
|
});
|
|
2272
|
-
var SettlementSchema =
|
|
2273
|
-
settlementId:
|
|
2274
|
-
settlementKey:
|
|
2275
|
-
encounterId:
|
|
2276
|
-
issuerAccountId:
|
|
2277
|
-
tokenSerial:
|
|
2278
|
-
payerUserId:
|
|
2279
|
-
payeeUserId:
|
|
2280
|
-
amountKobo:
|
|
2281
|
-
currency:
|
|
2282
|
-
receiptId:
|
|
2283
|
-
status:
|
|
2284
|
-
issuerSig:
|
|
2285
|
-
createdAtMs:
|
|
1915
|
+
var SettlementSchema = z7.object({
|
|
1916
|
+
settlementId: z7.string().uuid(),
|
|
1917
|
+
settlementKey: z7.string().regex(/^[0-9a-f]{64}$/i),
|
|
1918
|
+
encounterId: z7.string().regex(/^[0-9a-f]{64}$/i),
|
|
1919
|
+
issuerAccountId: z7.string().uuid(),
|
|
1920
|
+
tokenSerial: z7.string(),
|
|
1921
|
+
payerUserId: z7.string().uuid(),
|
|
1922
|
+
payeeUserId: z7.string().uuid(),
|
|
1923
|
+
amountKobo: z7.number().int().nonnegative(),
|
|
1924
|
+
currency: z7.string().length(3),
|
|
1925
|
+
receiptId: z7.string().nullable(),
|
|
1926
|
+
status: z7.enum(["SETTLED", "REVIEW", "REJECTED"]),
|
|
1927
|
+
issuerSig: z7.string(),
|
|
1928
|
+
createdAtMs: z7.number().int().nonnegative()
|
|
2286
1929
|
});
|
|
2287
|
-
var SettleResponseSchema =
|
|
1930
|
+
var SettleResponseSchema = z7.object({
|
|
2288
1931
|
settlement: SettlementSchema,
|
|
2289
|
-
encounterId:
|
|
2290
|
-
replayed:
|
|
1932
|
+
encounterId: z7.string().regex(/^[0-9a-f]{64}$/i),
|
|
1933
|
+
replayed: z7.boolean()
|
|
2291
1934
|
});
|
|
2292
1935
|
var ENCOUNTER_DOMAIN = "offline:v1:encounter";
|
|
2293
1936
|
async function sha256Hex(input) {
|
|
@@ -2461,7 +2104,7 @@ function createHmacFetch(opts) {
|
|
|
2461
2104
|
}
|
|
2462
2105
|
|
|
2463
2106
|
// src/partner/client.ts
|
|
2464
|
-
import { z as
|
|
2107
|
+
import { z as z8 } from "zod";
|
|
2465
2108
|
import { sha256 as sha2563 } from "@noble/hashes/sha256";
|
|
2466
2109
|
import { hmac as hmac2 } from "@noble/hashes/hmac";
|
|
2467
2110
|
import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils";
|
|
@@ -2485,18 +2128,18 @@ var PARTNER_SCOPES = [
|
|
|
2485
2128
|
"partner:payout:write",
|
|
2486
2129
|
"partner:reconciliation:read"
|
|
2487
2130
|
];
|
|
2488
|
-
var ApiCredentialPublicSchema =
|
|
2489
|
-
id:
|
|
2490
|
-
accountId:
|
|
2491
|
-
keyId:
|
|
2492
|
-
scopes:
|
|
2493
|
-
label:
|
|
2494
|
-
createdAtMs:
|
|
2495
|
-
lastUsedAtMs:
|
|
2496
|
-
revokedAtMs:
|
|
2131
|
+
var ApiCredentialPublicSchema = z8.object({
|
|
2132
|
+
id: z8.string().uuid(),
|
|
2133
|
+
accountId: z8.string().uuid(),
|
|
2134
|
+
keyId: z8.string(),
|
|
2135
|
+
scopes: z8.array(z8.enum(PARTNER_SCOPES)),
|
|
2136
|
+
label: z8.string().nullable(),
|
|
2137
|
+
createdAtMs: z8.number().int().nonnegative(),
|
|
2138
|
+
lastUsedAtMs: z8.number().int().nonnegative().nullable(),
|
|
2139
|
+
revokedAtMs: z8.number().int().nonnegative().nullable()
|
|
2497
2140
|
});
|
|
2498
2141
|
var MintedApiCredentialSchema = ApiCredentialPublicSchema.extend({
|
|
2499
|
-
secret:
|
|
2142
|
+
secret: z8.string().min(1)
|
|
2500
2143
|
});
|
|
2501
2144
|
var enc = new TextEncoder();
|
|
2502
2145
|
async function sha256Hex2(input) {
|
|
@@ -2653,8 +2296,8 @@ function createApiCredentialsAdminClient(opts) {
|
|
|
2653
2296
|
}
|
|
2654
2297
|
return parser(raw);
|
|
2655
2298
|
}
|
|
2656
|
-
const listSchema =
|
|
2657
|
-
items:
|
|
2299
|
+
const listSchema = z8.object({
|
|
2300
|
+
items: z8.array(ApiCredentialPublicSchema)
|
|
2658
2301
|
});
|
|
2659
2302
|
return {
|
|
2660
2303
|
list: (accountId) => call(
|
|
@@ -2679,7 +2322,7 @@ function createApiCredentialsAdminClient(opts) {
|
|
|
2679
2322
|
}
|
|
2680
2323
|
|
|
2681
2324
|
// src/passes/pass.ts
|
|
2682
|
-
import { z as
|
|
2325
|
+
import { z as z9 } from "zod";
|
|
2683
2326
|
var PASS_KINDS = [
|
|
2684
2327
|
"ride-ticket",
|
|
2685
2328
|
"transit-pass",
|
|
@@ -2695,39 +2338,39 @@ var PASS_STATES = [
|
|
|
2695
2338
|
"expired",
|
|
2696
2339
|
"revoked"
|
|
2697
2340
|
];
|
|
2698
|
-
var PassMetadataSchema =
|
|
2699
|
-
|
|
2341
|
+
var PassMetadataSchema = z9.record(
|
|
2342
|
+
z9.union([z9.string(), z9.number(), z9.boolean(), z9.null()])
|
|
2700
2343
|
);
|
|
2701
|
-
var PassSchema =
|
|
2702
|
-
passId:
|
|
2344
|
+
var PassSchema = z9.object({
|
|
2345
|
+
passId: z9.string().min(1),
|
|
2703
2346
|
/** Optional client/template grouping id (server may omit). */
|
|
2704
|
-
templateId:
|
|
2347
|
+
templateId: z9.string().min(1).optional(),
|
|
2705
2348
|
/** Optional human-facing holder identity (server may omit). The cryptographic binding
|
|
2706
2349
|
* is `holderDevicePubkey` below. */
|
|
2707
|
-
holderUserId:
|
|
2708
|
-
kind:
|
|
2709
|
-
issuerId:
|
|
2710
|
-
issuedAtMs:
|
|
2711
|
-
validFromMs:
|
|
2712
|
-
validUntilMs:
|
|
2713
|
-
state:
|
|
2350
|
+
holderUserId: z9.string().min(1).optional(),
|
|
2351
|
+
kind: z9.enum(PASS_KINDS),
|
|
2352
|
+
issuerId: z9.string().min(1),
|
|
2353
|
+
issuedAtMs: z9.number().int().nonnegative(),
|
|
2354
|
+
validFromMs: z9.number().int().nonnegative(),
|
|
2355
|
+
validUntilMs: z9.number().int().positive(),
|
|
2356
|
+
state: z9.enum(PASS_STATES),
|
|
2714
2357
|
metadata: PassMetadataSchema,
|
|
2715
|
-
nonce:
|
|
2358
|
+
nonce: z9.string().min(1),
|
|
2716
2359
|
/** Device id this pass is bound to (FK to backend `device_keys`). */
|
|
2717
|
-
holderDeviceId:
|
|
2360
|
+
holderDeviceId: z9.string().min(1),
|
|
2718
2361
|
/** SubjectPublicKeyInfo DER (P-256) of the bound device, base64. The redemption
|
|
2719
2362
|
* signature is verified against this key — it is the security-critical binding. */
|
|
2720
|
-
holderDevicePubkey:
|
|
2363
|
+
holderDevicePubkey: z9.string().min(64).max(4096).regex(/^[A-Za-z0-9+/]+={0,2}$/),
|
|
2721
2364
|
/** Optional fixed amount for monetary passes (vouchers, gift cards) in kobo. */
|
|
2722
|
-
amountKobo:
|
|
2365
|
+
amountKobo: z9.number().int().nonnegative().optional(),
|
|
2723
2366
|
/** ISO-4217-ish currency code; required on the wire. SDK builders default to NGN. */
|
|
2724
|
-
currency:
|
|
2367
|
+
currency: z9.string().min(3).max(8),
|
|
2725
2368
|
/** Monotonic redemption counter floor. Redemption.counter MUST be > counterSeed. */
|
|
2726
|
-
counterSeed:
|
|
2369
|
+
counterSeed: z9.number().int().nonnegative(),
|
|
2727
2370
|
/** Optional cumulative spend cap in kobo across all redemptions of this pass. */
|
|
2728
|
-
cumulativeCapKobo:
|
|
2371
|
+
cumulativeCapKobo: z9.number().int().nonnegative().optional(),
|
|
2729
2372
|
/** ASN.1 DER ECDSA P-256 signature, base64. */
|
|
2730
|
-
issuerSig:
|
|
2373
|
+
issuerSig: z9.string().min(64).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/)
|
|
2731
2374
|
}).refine((v) => v.validUntilMs > v.validFromMs, {
|
|
2732
2375
|
message: "validUntilMs must be greater than validFromMs"
|
|
2733
2376
|
});
|
|
@@ -2784,20 +2427,20 @@ function isPassWithinValidity(pass, nowMs) {
|
|
|
2784
2427
|
}
|
|
2785
2428
|
|
|
2786
2429
|
// src/passes/redemption.ts
|
|
2787
|
-
import { z as
|
|
2788
|
-
var
|
|
2789
|
-
var RedemptionSchema =
|
|
2430
|
+
import { z as z10 } from "zod";
|
|
2431
|
+
var Base64Std2 = z10.string().min(16).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/, "expected base64 (std)");
|
|
2432
|
+
var RedemptionSchema = z10.object({
|
|
2790
2433
|
pass: PassSchema,
|
|
2791
|
-
redeemerId:
|
|
2792
|
-
redeemedAtMs:
|
|
2434
|
+
redeemerId: z10.string().min(1),
|
|
2435
|
+
redeemedAtMs: z10.number().int().nonnegative(),
|
|
2793
2436
|
/** Strictly monotonic counter scoped to a single pass. Must be > pass.counterSeed
|
|
2794
2437
|
* and > the redeemer's lastSeenCounter for this pass. */
|
|
2795
|
-
counter:
|
|
2438
|
+
counter: z10.number().int().positive(),
|
|
2796
2439
|
/** Amount being redeemed in kobo (0 for non-monetary passes like ride tickets). */
|
|
2797
|
-
amountKobo:
|
|
2798
|
-
nonce:
|
|
2440
|
+
amountKobo: z10.number().int().nonnegative(),
|
|
2441
|
+
nonce: z10.string().min(1),
|
|
2799
2442
|
/** ASN.1 DER ECDSA P-256 signature over canonicalJSONBytes(unsigned), base64. */
|
|
2800
|
-
holderSig:
|
|
2443
|
+
holderSig: Base64Std2
|
|
2801
2444
|
});
|
|
2802
2445
|
var REDEEMABLE_STATES = /* @__PURE__ */ new Set(["issued", "active"]);
|
|
2803
2446
|
function buildRedemption(input) {
|
|
@@ -2879,40 +2522,40 @@ function verifyRedemption(r, issuerPublicKeySpkiB64) {
|
|
|
2879
2522
|
}
|
|
2880
2523
|
|
|
2881
2524
|
// src/receipts/receipt.ts
|
|
2882
|
-
import { z as
|
|
2525
|
+
import { z as z11 } from "zod";
|
|
2883
2526
|
var RECEIPT_CHANNELS = ["cash", "pass"];
|
|
2884
2527
|
var RECEIPT_KINDS = RECEIPT_CHANNELS;
|
|
2885
|
-
var ReceiptPayloadSchema =
|
|
2886
|
-
|
|
2528
|
+
var ReceiptPayloadSchema = z11.record(
|
|
2529
|
+
z11.union([z11.string(), z11.number(), z11.boolean(), z11.null()])
|
|
2887
2530
|
);
|
|
2888
|
-
var ReceiptSchema =
|
|
2889
|
-
receiptId:
|
|
2890
|
-
channel:
|
|
2531
|
+
var ReceiptSchema = z11.object({
|
|
2532
|
+
receiptId: z11.string().min(1),
|
|
2533
|
+
channel: z11.enum(RECEIPT_CHANNELS),
|
|
2891
2534
|
/** Cash-channel: send_intents.id. Required when channel === 'cash'. */
|
|
2892
|
-
intentId:
|
|
2535
|
+
intentId: z11.string().min(1).optional(),
|
|
2893
2536
|
/** Pass-channel: pass_redemptions.id. Required when channel === 'pass'. */
|
|
2894
|
-
passRedemptionId:
|
|
2895
|
-
payerUserId:
|
|
2896
|
-
payeeUserId:
|
|
2897
|
-
amountKobo:
|
|
2898
|
-
currency:
|
|
2899
|
-
issuedAtMs:
|
|
2900
|
-
issuerId:
|
|
2537
|
+
passRedemptionId: z11.string().min(1).optional(),
|
|
2538
|
+
payerUserId: z11.string().min(1),
|
|
2539
|
+
payeeUserId: z11.string().min(1),
|
|
2540
|
+
amountKobo: z11.number().int().nonnegative(),
|
|
2541
|
+
currency: z11.string().min(3).max(8),
|
|
2542
|
+
issuedAtMs: z11.number().int().nonnegative(),
|
|
2543
|
+
issuerId: z11.string().min(1),
|
|
2901
2544
|
payload: ReceiptPayloadSchema,
|
|
2902
2545
|
/** ASN.1 DER ECDSA P-256 signature, base64. */
|
|
2903
|
-
issuerSig:
|
|
2546
|
+
issuerSig: z11.string().min(64).max(2048).regex(/^[A-Za-z0-9+/]+={0,2}$/)
|
|
2904
2547
|
}).superRefine((v, ctx) => {
|
|
2905
2548
|
if (v.channel === "cash") {
|
|
2906
2549
|
if (!v.intentId) {
|
|
2907
2550
|
ctx.addIssue({
|
|
2908
|
-
code:
|
|
2551
|
+
code: z11.ZodIssueCode.custom,
|
|
2909
2552
|
message: "cash receipts require intentId",
|
|
2910
2553
|
path: ["intentId"]
|
|
2911
2554
|
});
|
|
2912
2555
|
}
|
|
2913
2556
|
if (v.passRedemptionId) {
|
|
2914
2557
|
ctx.addIssue({
|
|
2915
|
-
code:
|
|
2558
|
+
code: z11.ZodIssueCode.custom,
|
|
2916
2559
|
message: "cash receipts must not carry passRedemptionId",
|
|
2917
2560
|
path: ["passRedemptionId"]
|
|
2918
2561
|
});
|
|
@@ -2920,14 +2563,14 @@ var ReceiptSchema = z12.object({
|
|
|
2920
2563
|
} else if (v.channel === "pass") {
|
|
2921
2564
|
if (!v.passRedemptionId) {
|
|
2922
2565
|
ctx.addIssue({
|
|
2923
|
-
code:
|
|
2566
|
+
code: z11.ZodIssueCode.custom,
|
|
2924
2567
|
message: "pass receipts require passRedemptionId",
|
|
2925
2568
|
path: ["passRedemptionId"]
|
|
2926
2569
|
});
|
|
2927
2570
|
}
|
|
2928
2571
|
if (v.intentId) {
|
|
2929
2572
|
ctx.addIssue({
|
|
2930
|
-
code:
|
|
2573
|
+
code: z11.ZodIssueCode.custom,
|
|
2931
2574
|
message: "pass receipts must not carry intentId",
|
|
2932
2575
|
path: ["intentId"]
|
|
2933
2576
|
});
|
|
@@ -3229,28 +2872,244 @@ function init(opts) {
|
|
|
3229
2872
|
}
|
|
3230
2873
|
|
|
3231
2874
|
// src/accounts/client.ts
|
|
3232
|
-
import { z as
|
|
2875
|
+
import { z as z12 } from "zod";
|
|
3233
2876
|
var ACCOUNT_TYPES = ["personal", "business", "partner"];
|
|
3234
2877
|
var ACCOUNT_STATUSES = ["active", "suspended", "closed"];
|
|
3235
2878
|
var MEMBERSHIP_ROLES = ["owner", "admin", "driver", "staff"];
|
|
3236
|
-
var AccountSchema =
|
|
3237
|
-
accountId:
|
|
3238
|
-
type:
|
|
3239
|
-
displayName:
|
|
3240
|
-
status:
|
|
3241
|
-
ownerUserId:
|
|
3242
|
-
createdAtMs:
|
|
2879
|
+
var AccountSchema = z12.object({
|
|
2880
|
+
accountId: z12.string().uuid(),
|
|
2881
|
+
type: z12.enum(ACCOUNT_TYPES),
|
|
2882
|
+
displayName: z12.string().min(1),
|
|
2883
|
+
status: z12.enum(ACCOUNT_STATUSES),
|
|
2884
|
+
ownerUserId: z12.string().uuid().nullable(),
|
|
2885
|
+
createdAtMs: z12.number().int().nonnegative()
|
|
2886
|
+
});
|
|
2887
|
+
var AccountMembershipSchema = z12.object({
|
|
2888
|
+
accountId: z12.string().uuid(),
|
|
2889
|
+
userId: z12.string().uuid(),
|
|
2890
|
+
role: z12.enum(MEMBERSHIP_ROLES),
|
|
2891
|
+
createdAtMs: z12.number().int().nonnegative()
|
|
2892
|
+
});
|
|
2893
|
+
function createAccountsClient(opts) {
|
|
2894
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
2895
|
+
if (!fetchImpl) {
|
|
2896
|
+
throw new Error("createAccountsClient: no fetch implementation available");
|
|
2897
|
+
}
|
|
2898
|
+
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
2899
|
+
async function call(method, path, body, parser) {
|
|
2900
|
+
const init2 = {
|
|
2901
|
+
method,
|
|
2902
|
+
headers: {
|
|
2903
|
+
"content-type": "application/json",
|
|
2904
|
+
accept: "application/json"
|
|
2905
|
+
}
|
|
2906
|
+
};
|
|
2907
|
+
if (body !== void 0) init2.body = JSON.stringify(body);
|
|
2908
|
+
const resp = await fetchImpl(`${baseUrl}${path}`, init2);
|
|
2909
|
+
const text = await resp.text();
|
|
2910
|
+
let raw = void 0;
|
|
2911
|
+
if (text) {
|
|
2912
|
+
try {
|
|
2913
|
+
raw = JSON.parse(text);
|
|
2914
|
+
} catch {
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
if (!resp.ok) {
|
|
2918
|
+
const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
|
|
2919
|
+
const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
|
|
2920
|
+
throw new FlurApiError(resp.status, code, message, raw);
|
|
2921
|
+
}
|
|
2922
|
+
return parser(raw);
|
|
2923
|
+
}
|
|
2924
|
+
const itemsSchema = z12.object({ items: z12.array(AccountSchema) });
|
|
2925
|
+
const memberItemsSchema = z12.object({
|
|
2926
|
+
items: z12.array(AccountMembershipSchema)
|
|
2927
|
+
});
|
|
2928
|
+
return {
|
|
2929
|
+
listMyAccounts: () => call(
|
|
2930
|
+
"GET",
|
|
2931
|
+
"/v1/accounts/me",
|
|
2932
|
+
void 0,
|
|
2933
|
+
(raw) => itemsSchema.parse(raw)
|
|
2934
|
+
),
|
|
2935
|
+
getAccount: (accountId) => call(
|
|
2936
|
+
"GET",
|
|
2937
|
+
`/v1/accounts/${encodeURIComponent(accountId)}`,
|
|
2938
|
+
void 0,
|
|
2939
|
+
(raw) => AccountSchema.parse(raw)
|
|
2940
|
+
),
|
|
2941
|
+
listMembers: (accountId) => call(
|
|
2942
|
+
"GET",
|
|
2943
|
+
`/v1/accounts/${encodeURIComponent(accountId)}/members`,
|
|
2944
|
+
void 0,
|
|
2945
|
+
(raw) => memberItemsSchema.parse(raw)
|
|
2946
|
+
),
|
|
2947
|
+
createBusinessAccount: (input) => call("POST", "/v1/accounts", input, (raw) => AccountSchema.parse(raw)),
|
|
2948
|
+
addMember: (accountId, input) => call(
|
|
2949
|
+
"POST",
|
|
2950
|
+
`/v1/accounts/${encodeURIComponent(accountId)}/members`,
|
|
2951
|
+
input,
|
|
2952
|
+
(raw) => AccountMembershipSchema.parse(raw)
|
|
2953
|
+
)
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
// src/me-offline/client.ts
|
|
2958
|
+
import { z as z13 } from "zod";
|
|
2959
|
+
var Sha256Hex = z13.string().regex(/^[0-9a-f]{64}$/);
|
|
2960
|
+
var Base64Std3 = z13.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
|
|
2961
|
+
var ClaimNonce = z13.string().min(8).max(128).refine((value) => !value.includes("|"), {
|
|
2962
|
+
message: "nonce must not contain |"
|
|
2963
|
+
});
|
|
2964
|
+
var ACCOUNT_FUNDED_OAC_MAX_TTL_MS = 1e3 * 60 * 60 * 24 * 7;
|
|
2965
|
+
var CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS = 1e3 * 60 * 60 * 24;
|
|
2966
|
+
var AttestationSecurityLevelSchema = z13.enum([
|
|
2967
|
+
"STRONGBOX",
|
|
2968
|
+
"TEE",
|
|
2969
|
+
"SECURE_ENCLAVE",
|
|
2970
|
+
"SOFTWARE"
|
|
2971
|
+
]);
|
|
2972
|
+
var DeviceKeyAlgSchema = z13.literal("p256");
|
|
2973
|
+
var RegisterDeviceKeyP256InputSchema = z13.object({
|
|
2974
|
+
deviceId: z13.string().min(1).max(128),
|
|
2975
|
+
/** P-256 SubjectPublicKeyInfo DER, base64. */
|
|
2976
|
+
publicKeySpkiB64: Base64Std3.min(64).max(4096),
|
|
2977
|
+
/** Base64 of the server-issued enrollment challenge string. */
|
|
2978
|
+
challengeB64: Base64Std3.min(8).max(1024),
|
|
2979
|
+
/** iOS App Attest payload or Android X.509 Key Attestation chain. */
|
|
2980
|
+
attestationChainB64: z13.array(Base64Std3.min(16).max(16384)).min(1).max(16),
|
|
2981
|
+
securityLevel: AttestationSecurityLevelSchema
|
|
2982
|
+
});
|
|
2983
|
+
var P256EnrollmentChallengeInputSchema = z13.object({
|
|
2984
|
+
deviceId: z13.string().min(1).max(128)
|
|
2985
|
+
});
|
|
2986
|
+
var P256EnrollmentChallengeResultSchema = z13.object({
|
|
2987
|
+
challenge: z13.string().min(16),
|
|
2988
|
+
expiresAtMs: z13.number().int().positive()
|
|
2989
|
+
});
|
|
2990
|
+
var DeviceKeyRecordSchema = z13.object({
|
|
2991
|
+
id: z13.string().uuid(),
|
|
2992
|
+
userId: z13.string().uuid(),
|
|
2993
|
+
deviceId: z13.string(),
|
|
2994
|
+
/** Always 'p256' on the consumer offline rail. Field retained for forward-compat. */
|
|
2995
|
+
alg: DeviceKeyAlgSchema.default("p256"),
|
|
2996
|
+
/** P-256 SubjectPublicKeyInfo DER, base64. */
|
|
2997
|
+
publicKeySpkiB64: Base64Std3.nullable().default(null),
|
|
2998
|
+
securityLevel: AttestationSecurityLevelSchema.nullable().default(null),
|
|
2999
|
+
hardwareBacked: z13.boolean().default(false),
|
|
3000
|
+
attestedAtMs: z13.number().int().nonnegative().nullable().default(null),
|
|
3001
|
+
createdAtMs: z13.number().int().nonnegative(),
|
|
3002
|
+
revokedAtMs: z13.number().int().nonnegative().nullable()
|
|
3003
|
+
});
|
|
3004
|
+
var ConsumerOACSchema = z13.object({
|
|
3005
|
+
oacId: z13.string().uuid(),
|
|
3006
|
+
issuerId: z13.string().min(1).max(64),
|
|
3007
|
+
userId: z13.string().uuid(),
|
|
3008
|
+
deviceId: z13.string().min(1).max(128),
|
|
3009
|
+
/**
|
|
3010
|
+
* Always 'p256'. Required on the wire (backend always emits it).
|
|
3011
|
+
* Kept as a literal so input/output infer identically and the schema
|
|
3012
|
+
* can be nested inside other response shapes without Zod input/output
|
|
3013
|
+
* divergence under tsup DTS bundling.
|
|
3014
|
+
*/
|
|
3015
|
+
alg: z13.literal("p256"),
|
|
3016
|
+
/** P-256 SubjectPublicKeyInfo DER, base64. */
|
|
3017
|
+
devicePubkeySpkiB64: Base64Std3.min(64).max(4096),
|
|
3018
|
+
perTxCapKobo: z13.number().int().positive(),
|
|
3019
|
+
cumulativeCapKobo: z13.number().int().positive(),
|
|
3020
|
+
currency: z13.string().length(3),
|
|
3021
|
+
validFromMs: z13.number().int().nonnegative(),
|
|
3022
|
+
validUntilMs: z13.number().int().nonnegative(),
|
|
3023
|
+
counterSeed: z13.number().int().nonnegative(),
|
|
3024
|
+
issuedAtMs: z13.number().int().nonnegative()
|
|
3025
|
+
});
|
|
3026
|
+
var SignedConsumerOACSchema = z13.object({
|
|
3027
|
+
oac: ConsumerOACSchema,
|
|
3028
|
+
/** ASN.1 DER ECDSA P-256 issuer signature, base64. */
|
|
3029
|
+
issuerSig: Base64Std3.min(16).max(2048),
|
|
3030
|
+
/** Issuer's P-256 public key as SubjectPublicKeyInfo DER, base64. */
|
|
3031
|
+
issuerPublicKeySpkiB64: Base64Std3.min(64).max(4096)
|
|
3243
3032
|
});
|
|
3244
|
-
var
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3033
|
+
var OACRecordSchema = SignedConsumerOACSchema.extend({
|
|
3034
|
+
currentOfflineSpentKobo: z13.number().int().nonnegative(),
|
|
3035
|
+
status: z13.enum(["active", "superseded", "expired", "revoked"]),
|
|
3036
|
+
supersededAtMs: z13.number().int().nonnegative().nullable(),
|
|
3037
|
+
revokedAtMs: z13.number().int().nonnegative().nullable()
|
|
3249
3038
|
});
|
|
3250
|
-
|
|
3039
|
+
var IssueAccountOacInputSchema = z13.object({
|
|
3040
|
+
deviceId: z13.string().min(1).max(128),
|
|
3041
|
+
perTxCapKobo: z13.number().int().positive().optional(),
|
|
3042
|
+
cumulativeCapKobo: z13.number().int().positive().optional(),
|
|
3043
|
+
ttlMs: z13.number().int().min(6e4).max(ACCOUNT_FUNDED_OAC_MAX_TTL_MS).optional()
|
|
3044
|
+
});
|
|
3045
|
+
var OfflineStatusResultSchema = z13.object({
|
|
3046
|
+
active: OACRecordSchema.nullable()
|
|
3047
|
+
});
|
|
3048
|
+
var ConsumerPaymentClaimSchema = z13.object({
|
|
3049
|
+
/** Always 'p256'. Retained for forward-compat and as an explicit domain marker. */
|
|
3050
|
+
alg: z13.literal("p256").default("p256"),
|
|
3051
|
+
oacId: z13.string().uuid(),
|
|
3052
|
+
encounterId: Sha256Hex.optional(),
|
|
3053
|
+
payerUserId: z13.string().uuid(),
|
|
3054
|
+
payeeUserId: z13.string().uuid(),
|
|
3055
|
+
payerDeviceId: z13.string().min(1).max(128),
|
|
3056
|
+
payerNonce: ClaimNonce,
|
|
3057
|
+
payeeNonce: ClaimNonce,
|
|
3058
|
+
amountKobo: z13.number().int().positive(),
|
|
3059
|
+
currency: z13.string().length(3).default("NGN"),
|
|
3060
|
+
occurredAtMs: z13.number().int().nonnegative(),
|
|
3061
|
+
completedAtMs: z13.number().int().nonnegative().optional(),
|
|
3062
|
+
contextId: z13.string().max(128).optional(),
|
|
3063
|
+
requestId: z13.string().uuid().optional(),
|
|
3064
|
+
requestMode: z13.enum(["fixed", "editable"]).optional(),
|
|
3065
|
+
requestTakerUserId: z13.string().uuid().optional(),
|
|
3066
|
+
requestAmountKobo: z13.number().int().positive().optional(),
|
|
3067
|
+
requestCurrency: z13.string().length(3).optional(),
|
|
3068
|
+
requestReference: z13.string().max(128).nullable().optional(),
|
|
3069
|
+
requestCreatedAtMs: z13.number().int().nonnegative().optional(),
|
|
3070
|
+
requestExpiresAtMs: z13.number().int().positive().optional(),
|
|
3071
|
+
requestNonce: z13.string().min(8).max(128).optional(),
|
|
3072
|
+
requestTakerDeviceId: z13.string().min(1).max(128).nullable().optional(),
|
|
3073
|
+
requestTakerPubkeySpkiB64: Base64Std3.min(64).max(4096).optional(),
|
|
3074
|
+
requestTakerSignatureDerB64: Base64Std3.min(16).max(2048).optional(),
|
|
3075
|
+
payerPubkeySpkiB64: Base64Std3.min(64).max(4096),
|
|
3076
|
+
payerSignatureDerB64: Base64Std3.min(16).max(2048),
|
|
3077
|
+
payeePubkeySpkiB64: Base64Std3.min(64).max(4096).optional(),
|
|
3078
|
+
payeeSignatureDerB64: Base64Std3.min(16).max(2048).optional()
|
|
3079
|
+
});
|
|
3080
|
+
var ConsumerSettlementSchema = z13.object({
|
|
3081
|
+
settlementId: z13.string().uuid(),
|
|
3082
|
+
settlementKey: Sha256Hex,
|
|
3083
|
+
encounterId: Sha256Hex,
|
|
3084
|
+
oacId: z13.string().uuid(),
|
|
3085
|
+
payerUserId: z13.string().uuid(),
|
|
3086
|
+
payeeUserId: z13.string().uuid(),
|
|
3087
|
+
amountKobo: z13.number().int().positive(),
|
|
3088
|
+
currency: z13.string().length(3),
|
|
3089
|
+
status: z13.enum(["SETTLED", "REVIEW"]),
|
|
3090
|
+
reviewReason: z13.string().nullable(),
|
|
3091
|
+
ledgerRef: z13.string().nullable(),
|
|
3092
|
+
/** ASN.1 DER ECDSA P-256 issuer signature, base64. */
|
|
3093
|
+
issuerSig: Base64Std3.min(16).max(2048),
|
|
3094
|
+
/** Canonical millisecond timestamp signed into the settlement receipt. */
|
|
3095
|
+
issuedAtMs: z13.number().int().nonnegative(),
|
|
3096
|
+
/** Compatibility alias for API consumers that predate issuedAtMs. */
|
|
3097
|
+
createdAtMs: z13.number().int().nonnegative().optional()
|
|
3098
|
+
});
|
|
3099
|
+
var ConsumerSettleResultSchema = z13.object({
|
|
3100
|
+
settlement: ConsumerSettlementSchema,
|
|
3101
|
+
encounterId: Sha256Hex,
|
|
3102
|
+
replayed: z13.boolean()
|
|
3103
|
+
});
|
|
3104
|
+
var RevokeDeviceKeyInputSchema = z13.object({
|
|
3105
|
+
deviceId: z13.string().min(1).max(128),
|
|
3106
|
+
/** Step-up token from /api/v1/auth/send/verify with purpose='offline_revoke'. */
|
|
3107
|
+
sendAuthToken: z13.string().min(16)
|
|
3108
|
+
});
|
|
3109
|
+
function createMeOfflineClient(opts) {
|
|
3251
3110
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
3252
3111
|
if (!fetchImpl) {
|
|
3253
|
-
throw new Error("
|
|
3112
|
+
throw new Error("createMeOfflineClient: no fetch implementation available");
|
|
3254
3113
|
}
|
|
3255
3114
|
const baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
3256
3115
|
async function call(method, path, body, parser) {
|
|
@@ -3278,42 +3137,67 @@ function createAccountsClient(opts) {
|
|
|
3278
3137
|
}
|
|
3279
3138
|
return parser(raw);
|
|
3280
3139
|
}
|
|
3281
|
-
const
|
|
3282
|
-
const memberItemsSchema = z13.object({
|
|
3283
|
-
items: z13.array(AccountMembershipSchema)
|
|
3284
|
-
});
|
|
3140
|
+
const deviceKeyItems = z13.object({ items: z13.array(DeviceKeyRecordSchema) });
|
|
3285
3141
|
return {
|
|
3286
|
-
|
|
3287
|
-
"
|
|
3288
|
-
"/v1/
|
|
3289
|
-
|
|
3290
|
-
(raw) =>
|
|
3142
|
+
issueP256EnrollmentChallenge: (input) => call(
|
|
3143
|
+
"POST",
|
|
3144
|
+
"/v1/me/offline/keys/p256/challenge",
|
|
3145
|
+
P256EnrollmentChallengeInputSchema.parse(input),
|
|
3146
|
+
(raw) => P256EnrollmentChallengeResultSchema.parse(raw)
|
|
3291
3147
|
),
|
|
3292
|
-
|
|
3293
|
-
"
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
(raw) =>
|
|
3148
|
+
registerDeviceKeyP256: (input) => call(
|
|
3149
|
+
"POST",
|
|
3150
|
+
"/v1/me/offline/keys/p256",
|
|
3151
|
+
RegisterDeviceKeyP256InputSchema.parse(input),
|
|
3152
|
+
(raw) => DeviceKeyRecordSchema.parse(raw)
|
|
3297
3153
|
),
|
|
3298
|
-
|
|
3154
|
+
listDeviceKeys: () => call(
|
|
3299
3155
|
"GET",
|
|
3300
|
-
|
|
3156
|
+
"/v1/me/offline/keys",
|
|
3301
3157
|
void 0,
|
|
3302
|
-
(raw) =>
|
|
3158
|
+
(raw) => deviceKeyItems.parse(raw)
|
|
3303
3159
|
),
|
|
3304
|
-
|
|
3305
|
-
addMember: (accountId, input) => call(
|
|
3160
|
+
revokeDeviceKey: (input) => call(
|
|
3306
3161
|
"POST",
|
|
3307
|
-
|
|
3308
|
-
input,
|
|
3309
|
-
(
|
|
3162
|
+
"/v1/me/offline/keys/revoke",
|
|
3163
|
+
RevokeDeviceKeyInputSchema.parse(input),
|
|
3164
|
+
() => void 0
|
|
3165
|
+
),
|
|
3166
|
+
issueAccountOac: (input) => call(
|
|
3167
|
+
"POST",
|
|
3168
|
+
"/v1/me/offline/oac",
|
|
3169
|
+
IssueAccountOacInputSchema.parse(input),
|
|
3170
|
+
(raw) => OACRecordSchema.parse(raw)
|
|
3171
|
+
),
|
|
3172
|
+
getStatus: (deviceId) => {
|
|
3173
|
+
const qs = deviceId ? `?deviceId=${encodeURIComponent(deviceId)}` : "";
|
|
3174
|
+
return call(
|
|
3175
|
+
"GET",
|
|
3176
|
+
`/v1/me/offline/status${qs}`,
|
|
3177
|
+
void 0,
|
|
3178
|
+
(raw) => OfflineStatusResultSchema.parse(raw)
|
|
3179
|
+
);
|
|
3180
|
+
},
|
|
3181
|
+
submitClaim: (claim) => call(
|
|
3182
|
+
"POST",
|
|
3183
|
+
"/v1/me/offline/claims",
|
|
3184
|
+
ConsumerPaymentClaimSchema.parse(claim),
|
|
3185
|
+
(raw) => ConsumerSettleResultSchema.parse(raw)
|
|
3186
|
+
),
|
|
3187
|
+
getSettlement: (idOrKey) => call(
|
|
3188
|
+
"GET",
|
|
3189
|
+
`/v1/me/offline/settlements/${encodeURIComponent(idOrKey)}`,
|
|
3190
|
+
void 0,
|
|
3191
|
+
(raw) => ConsumerSettlementSchema.parse(raw)
|
|
3310
3192
|
)
|
|
3311
3193
|
};
|
|
3312
3194
|
}
|
|
3313
3195
|
|
|
3314
3196
|
// src/me-offline/signer.ts
|
|
3315
3197
|
import { p256 as p2562 } from "@noble/curves/nist";
|
|
3198
|
+
import { sha256 as sha2564 } from "@noble/hashes/sha2";
|
|
3316
3199
|
var CLAIM_DOMAIN_V2 = "flur:consumer-offline:v2:claim";
|
|
3200
|
+
var ENCOUNTER_DOMAIN2 = "flur:consumer-offline:v1:encounter";
|
|
3317
3201
|
function canonicalClaimSigningPayload(claim) {
|
|
3318
3202
|
return {
|
|
3319
3203
|
domain: CLAIM_DOMAIN_V2,
|
|
@@ -3334,6 +3218,27 @@ function canonicalClaimSigningPayload(claim) {
|
|
|
3334
3218
|
function canonicalClaimSigningBytes(claim) {
|
|
3335
3219
|
return canonicalJSONBytes(canonicalClaimSigningPayload(claim));
|
|
3336
3220
|
}
|
|
3221
|
+
function computeConsumerClaimEncounterId(input) {
|
|
3222
|
+
const material = `${ENCOUNTER_DOMAIN2}|${[
|
|
3223
|
+
assertEncounterPart("oacId", input.oacId),
|
|
3224
|
+
assertEncounterPart("payerUserId", input.payerUserId),
|
|
3225
|
+
assertEncounterPart("payeeUserId", input.payeeUserId),
|
|
3226
|
+
assertEncounterPart("payerNonce", input.payerNonce),
|
|
3227
|
+
assertEncounterPart("payeeNonce", input.payeeNonce)
|
|
3228
|
+
].join("|")}`;
|
|
3229
|
+
return bytesToHex5(sha2564(new TextEncoder().encode(material)));
|
|
3230
|
+
}
|
|
3231
|
+
function assertEncounterPart(field, value) {
|
|
3232
|
+
if (value.includes("|")) {
|
|
3233
|
+
throw new Error(`consumer encounter id ${field} must not contain |`);
|
|
3234
|
+
}
|
|
3235
|
+
return value;
|
|
3236
|
+
}
|
|
3237
|
+
function bytesToHex5(bytes) {
|
|
3238
|
+
let out = "";
|
|
3239
|
+
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
|
|
3240
|
+
return out;
|
|
3241
|
+
}
|
|
3337
3242
|
function bytesToBase642(bytes) {
|
|
3338
3243
|
if (typeof Buffer !== "undefined") {
|
|
3339
3244
|
return Buffer.from(bytes).toString("base64");
|
|
@@ -3429,38 +3334,192 @@ function verifyClaimSignature(input) {
|
|
|
3429
3334
|
}
|
|
3430
3335
|
}
|
|
3431
3336
|
|
|
3432
|
-
// src/me-offline/
|
|
3433
|
-
|
|
3434
|
-
var
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3337
|
+
// src/me-offline/request.ts
|
|
3338
|
+
import { z as z14 } from "zod";
|
|
3339
|
+
var Base64Std4 = z14.string().regex(/^[A-Za-z0-9+/]+={0,2}$/);
|
|
3340
|
+
var CONSUMER_PAYMENT_REQUEST_DOMAIN = "flur:consumer-offline:v1:request";
|
|
3341
|
+
var ConsumerPaymentRequestEnvelopeSchema = z14.object({
|
|
3342
|
+
requestId: z14.string().uuid(),
|
|
3343
|
+
mode: z14.enum(["fixed", "editable"]),
|
|
3344
|
+
takerUserId: z14.string().uuid(),
|
|
3345
|
+
amountKobo: z14.number().int().positive(),
|
|
3346
|
+
currency: z14.string().length(3).default("NGN"),
|
|
3347
|
+
reference: z14.string().max(128).nullable().default(null),
|
|
3348
|
+
createdAtMs: z14.number().int().nonnegative(),
|
|
3349
|
+
expiresAtMs: z14.number().int().positive(),
|
|
3350
|
+
nonce: z14.string().min(8).max(128),
|
|
3351
|
+
takerDeviceId: z14.string().min(1).max(128).nullable().default(null),
|
|
3352
|
+
takerPubkeySpkiB64: Base64Std4.min(64).max(4096).optional(),
|
|
3353
|
+
takerSignatureDerB64: Base64Std4.min(16).max(2048).optional()
|
|
3354
|
+
}).superRefine((value, ctx) => {
|
|
3355
|
+
if (value.expiresAtMs <= value.createdAtMs) {
|
|
3356
|
+
ctx.addIssue({
|
|
3357
|
+
code: z14.ZodIssueCode.custom,
|
|
3358
|
+
path: ["expiresAtMs"],
|
|
3359
|
+
message: "expiresAtMs must be greater than createdAtMs"
|
|
3360
|
+
});
|
|
3361
|
+
}
|
|
3362
|
+
const hasSignature = Boolean(
|
|
3363
|
+
value.takerPubkeySpkiB64 || value.takerSignatureDerB64
|
|
3364
|
+
);
|
|
3365
|
+
if (value.mode === "fixed" || hasSignature) {
|
|
3366
|
+
if (!value.takerDeviceId) {
|
|
3367
|
+
ctx.addIssue({
|
|
3368
|
+
code: z14.ZodIssueCode.custom,
|
|
3369
|
+
path: ["takerDeviceId"],
|
|
3370
|
+
message: "signed requests require takerDeviceId"
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
if (!value.takerPubkeySpkiB64) {
|
|
3374
|
+
ctx.addIssue({
|
|
3375
|
+
code: z14.ZodIssueCode.custom,
|
|
3376
|
+
path: ["takerPubkeySpkiB64"],
|
|
3377
|
+
message: "signed requests require takerPubkeySpkiB64"
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3380
|
+
if (!value.takerSignatureDerB64) {
|
|
3381
|
+
ctx.addIssue({
|
|
3382
|
+
code: z14.ZodIssueCode.custom,
|
|
3383
|
+
path: ["takerSignatureDerB64"],
|
|
3384
|
+
message: "signed requests require takerSignatureDerB64"
|
|
3385
|
+
});
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
});
|
|
3389
|
+
function buildConsumerPaymentRequest(input) {
|
|
3390
|
+
const unsigned = {
|
|
3391
|
+
requestId: input.requestId,
|
|
3392
|
+
mode: input.mode,
|
|
3393
|
+
takerUserId: input.takerUserId,
|
|
3394
|
+
amountKobo: input.amountKobo,
|
|
3395
|
+
currency: input.currency ?? "NGN",
|
|
3396
|
+
reference: input.reference ?? null,
|
|
3397
|
+
createdAtMs: input.createdAtMs,
|
|
3398
|
+
expiresAtMs: input.expiresAtMs,
|
|
3399
|
+
nonce: input.nonce,
|
|
3400
|
+
takerDeviceId: input.takerDeviceId ?? null
|
|
3401
|
+
};
|
|
3402
|
+
if (unsigned.mode === "fixed" && !unsigned.takerDeviceId) {
|
|
3403
|
+
throw new Error("fixed requests require takerDeviceId");
|
|
3404
|
+
}
|
|
3405
|
+
if (unsigned.expiresAtMs <= unsigned.createdAtMs) {
|
|
3406
|
+
throw new Error("expiresAtMs must be greater than createdAtMs");
|
|
3407
|
+
}
|
|
3408
|
+
return unsigned;
|
|
3439
3409
|
}
|
|
3440
|
-
function
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3410
|
+
function consumerPaymentRequestSigningPayload(request) {
|
|
3411
|
+
return {
|
|
3412
|
+
domain: CONSUMER_PAYMENT_REQUEST_DOMAIN,
|
|
3413
|
+
version: 1,
|
|
3414
|
+
requestId: request.requestId,
|
|
3415
|
+
mode: request.mode,
|
|
3416
|
+
takerUserId: request.takerUserId,
|
|
3417
|
+
amountKobo: request.amountKobo,
|
|
3418
|
+
currency: request.currency,
|
|
3419
|
+
reference: request.reference ?? null,
|
|
3420
|
+
createdAtMs: request.createdAtMs,
|
|
3421
|
+
expiresAtMs: request.expiresAtMs,
|
|
3422
|
+
nonce: request.nonce,
|
|
3423
|
+
takerDeviceId: request.takerDeviceId ?? null
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
function consumerPaymentRequestSigningBytes(request) {
|
|
3427
|
+
return canonicalJSONBytes(consumerPaymentRequestSigningPayload(request));
|
|
3428
|
+
}
|
|
3429
|
+
async function signConsumerPaymentRequest(unsigned, signer) {
|
|
3430
|
+
if (signer.alg !== "p256") {
|
|
3431
|
+
throw new Error("consumer payment requests require p256 signer");
|
|
3444
3432
|
}
|
|
3445
|
-
const
|
|
3433
|
+
const publicKey = await signer.getPublicKey();
|
|
3434
|
+
if (publicKey.alg !== "p256") {
|
|
3435
|
+
throw new Error("consumer payment requests require p256 public key");
|
|
3436
|
+
}
|
|
3437
|
+
const signature = await signer.sign(
|
|
3438
|
+
consumerPaymentRequestSigningBytes(unsigned)
|
|
3439
|
+
);
|
|
3440
|
+
return ConsumerPaymentRequestEnvelopeSchema.parse({
|
|
3441
|
+
...unsigned,
|
|
3442
|
+
takerPubkeySpkiB64: publicKey.publicKey,
|
|
3443
|
+
takerSignatureDerB64: signature.signature
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
3446
|
+
function verifyConsumerPaymentRequest(request) {
|
|
3447
|
+
const parsed = ConsumerPaymentRequestEnvelopeSchema.safeParse(request);
|
|
3448
|
+
if (!parsed.success) return false;
|
|
3449
|
+
const value = parsed.data;
|
|
3450
|
+
if (!value.takerPubkeySpkiB64 || !value.takerSignatureDerB64) return false;
|
|
3451
|
+
return verifyClaimSignature({
|
|
3452
|
+
alg: "p256",
|
|
3453
|
+
bytes: consumerPaymentRequestSigningBytes(value),
|
|
3454
|
+
signature: value.takerSignatureDerB64,
|
|
3455
|
+
publicKey: value.takerPubkeySpkiB64
|
|
3456
|
+
});
|
|
3457
|
+
}
|
|
3458
|
+
function isConsumerPaymentRequestExpired(request, nowMs = Date.now()) {
|
|
3459
|
+
const parsed = ConsumerPaymentRequestEnvelopeSchema.parse(request);
|
|
3460
|
+
return parsed.expiresAtMs <= nowMs;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
// src/me-offline/settlement.ts
|
|
3464
|
+
var CONSUMER_SETTLEMENT_DOMAIN = "flur:consumer-offline:v1:settlement";
|
|
3465
|
+
var CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX = "FLURSR1.";
|
|
3466
|
+
function consumerSettlementSigningPayload(settlement) {
|
|
3467
|
+
return {
|
|
3468
|
+
domain: CONSUMER_SETTLEMENT_DOMAIN,
|
|
3469
|
+
settlementId: settlement.settlementId,
|
|
3470
|
+
settlementKey: settlement.settlementKey,
|
|
3471
|
+
encounterId: settlement.encounterId,
|
|
3472
|
+
oacId: settlement.oacId,
|
|
3473
|
+
payerUserId: settlement.payerUserId,
|
|
3474
|
+
payeeUserId: settlement.payeeUserId,
|
|
3475
|
+
amountKobo: settlement.amountKobo,
|
|
3476
|
+
currency: settlement.currency,
|
|
3477
|
+
status: settlement.status,
|
|
3478
|
+
reviewReason: settlement.reviewReason,
|
|
3479
|
+
ledgerRef: settlement.ledgerRef,
|
|
3480
|
+
issuedAtMs: settlement.issuedAtMs
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3483
|
+
function verifyConsumerSettlement(settlement, issuerPublicKeySpkiB64) {
|
|
3484
|
+
const parsed = ConsumerSettlementSchema.safeParse(settlement);
|
|
3485
|
+
if (!parsed.success) return false;
|
|
3486
|
+
return verifyIssuerP256(
|
|
3487
|
+
canonicalJSONBytes(consumerSettlementSigningPayload(parsed.data)),
|
|
3488
|
+
parsed.data.issuerSig,
|
|
3489
|
+
issuerPublicKeySpkiB64
|
|
3490
|
+
);
|
|
3491
|
+
}
|
|
3492
|
+
function encodeConsumerSettlementReceiptQR(settlement) {
|
|
3493
|
+
const parsed = ConsumerSettlementSchema.parse(settlement);
|
|
3494
|
+
return `${CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX}${base64UrlEncodeUtf8(
|
|
3495
|
+
JSON.stringify(parsed)
|
|
3496
|
+
)}`;
|
|
3497
|
+
}
|
|
3498
|
+
function decodeUnverifiedConsumerSettlementReceiptQR(value) {
|
|
3499
|
+
if (!value.startsWith(CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX)) {
|
|
3500
|
+
throw new Error("not a Flur consumer settlement receipt QR");
|
|
3501
|
+
}
|
|
3502
|
+
const encoded = value.slice(CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX.length);
|
|
3446
3503
|
let raw;
|
|
3447
3504
|
try {
|
|
3448
3505
|
raw = JSON.parse(base64UrlDecodeUtf8(encoded));
|
|
3449
3506
|
} catch {
|
|
3450
|
-
throw new Error("
|
|
3507
|
+
throw new Error("consumer settlement receipt QR is malformed");
|
|
3451
3508
|
}
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3509
|
+
return ConsumerSettlementSchema.parse(raw);
|
|
3510
|
+
}
|
|
3511
|
+
function verifyConsumerSettlementReceiptQR(value, issuerPublicKeySpkiB64) {
|
|
3512
|
+
const settlement = decodeUnverifiedConsumerSettlementReceiptQR(value);
|
|
3513
|
+
if (!verifyConsumerSettlement(settlement, issuerPublicKeySpkiB64)) {
|
|
3514
|
+
throw new Error("consumer settlement receipt QR signature invalid");
|
|
3455
3515
|
}
|
|
3456
|
-
return
|
|
3516
|
+
return settlement;
|
|
3457
3517
|
}
|
|
3458
|
-
function
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
return trimmed.split(/\s+/, 1)[0] ?? null;
|
|
3518
|
+
function decodeConsumerSettlementReceiptQR(value, issuerPublicKeySpkiB64) {
|
|
3519
|
+
if (!issuerPublicKeySpkiB64) {
|
|
3520
|
+
return decodeUnverifiedConsumerSettlementReceiptQR(value);
|
|
3462
3521
|
}
|
|
3463
|
-
return
|
|
3522
|
+
return verifyConsumerSettlementReceiptQR(value, issuerPublicKeySpkiB64);
|
|
3464
3523
|
}
|
|
3465
3524
|
function base64UrlEncodeUtf8(input) {
|
|
3466
3525
|
const bytes = new TextEncoder().encode(input);
|
|
@@ -3490,15 +3549,281 @@ function base64UrlDecodeUtf8(input) {
|
|
|
3490
3549
|
throw new Error("base64 decoder unavailable");
|
|
3491
3550
|
}
|
|
3492
3551
|
|
|
3552
|
+
// src/me-offline/sms.ts
|
|
3553
|
+
import { p256 as p2563 } from "@noble/curves/nist";
|
|
3554
|
+
var OFFLINE_CLAIM_SMS_PREFIX = "FLURC1.";
|
|
3555
|
+
var CLAIM_TOKEN_RE = /(?:^|\s)(FLURC1\.[A-Za-z0-9_-]+={0,2})(?:\s|$)/;
|
|
3556
|
+
function encodeOfflineClaimSmsMessage(claim) {
|
|
3557
|
+
const parsed = ConsumerPaymentClaimSchema.parse(claim);
|
|
3558
|
+
return `${OFFLINE_CLAIM_SMS_PREFIX}${base64UrlEncodeUtf82(
|
|
3559
|
+
JSON.stringify(parsed)
|
|
3560
|
+
)}`;
|
|
3561
|
+
}
|
|
3562
|
+
function decodeOfflineClaimSmsMessage(message) {
|
|
3563
|
+
const token = extractOfflineClaimSmsToken(message);
|
|
3564
|
+
if (!token) throw new Error("offline claim QR token not found");
|
|
3565
|
+
const encoded = token.slice(OFFLINE_CLAIM_SMS_PREFIX.length);
|
|
3566
|
+
let raw;
|
|
3567
|
+
try {
|
|
3568
|
+
raw = JSON.parse(base64UrlDecodeUtf82(encoded));
|
|
3569
|
+
} catch {
|
|
3570
|
+
throw new Error("offline claim QR token is malformed");
|
|
3571
|
+
}
|
|
3572
|
+
const parsed = ConsumerPaymentClaimSchema.safeParse(raw);
|
|
3573
|
+
if (!parsed.success) throw new Error("offline claim QR token is invalid");
|
|
3574
|
+
return parsed.data;
|
|
3575
|
+
}
|
|
3576
|
+
function extractOfflineClaimSmsToken(message) {
|
|
3577
|
+
const trimmed = message.trim();
|
|
3578
|
+
if (trimmed.startsWith(OFFLINE_CLAIM_SMS_PREFIX)) {
|
|
3579
|
+
return trimmed.split(/\s+/, 1)[0] ?? null;
|
|
3580
|
+
}
|
|
3581
|
+
return CLAIM_TOKEN_RE.exec(message)?.[1] ?? null;
|
|
3582
|
+
}
|
|
3583
|
+
var OFFLINE_SMS_SETTLE_PREFIX = "FLURA1.";
|
|
3584
|
+
var OFFLINE_SMS_SETTLE_DOMAIN = "flur:consumer-offline:v1:attest";
|
|
3585
|
+
var OFFLINE_SMS_SETTLE_TOKEN_BYTES = 112;
|
|
3586
|
+
var OFFLINE_SMS_SETTLE_HEADER_BYTES = 48;
|
|
3587
|
+
var OFFLINE_SMS_SETTLE_SIGNATURE_BYTES = 64;
|
|
3588
|
+
var OFFLINE_SMS_SETTLE_VERSION = 1;
|
|
3589
|
+
var TOKEN_RE = /(?:^|[\s,;:()<>"'])(FLURA1\.[A-Za-z0-9_-]{150})/;
|
|
3590
|
+
async function encodeOfflineSmsSettleToken(input, signer) {
|
|
3591
|
+
const header = await buildSmsSettleHeader(input);
|
|
3592
|
+
const sig = await signer.signRaw(domainTag(header));
|
|
3593
|
+
if (sig.length !== OFFLINE_SMS_SETTLE_SIGNATURE_BYTES) {
|
|
3594
|
+
throw new Error(
|
|
3595
|
+
`FLURA1: signer returned ${sig.length}-byte sig; expected ${OFFLINE_SMS_SETTLE_SIGNATURE_BYTES}`
|
|
3596
|
+
);
|
|
3597
|
+
}
|
|
3598
|
+
const out = new Uint8Array(OFFLINE_SMS_SETTLE_TOKEN_BYTES);
|
|
3599
|
+
out.set(header, 0);
|
|
3600
|
+
out.set(sig, OFFLINE_SMS_SETTLE_HEADER_BYTES);
|
|
3601
|
+
return `${OFFLINE_SMS_SETTLE_PREFIX}${bytesToBase64Url(out)}`;
|
|
3602
|
+
}
|
|
3603
|
+
async function buildSmsSettleHeader(input) {
|
|
3604
|
+
assertSafeUint64(input.amountKobo, "amountKobo");
|
|
3605
|
+
if (input.amountKobo <= 0) {
|
|
3606
|
+
throw new Error("FLURA1: amountKobo must be greater than zero");
|
|
3607
|
+
}
|
|
3608
|
+
assertSafeUint48(input.occurredAtMs, "occurredAtMs");
|
|
3609
|
+
const encounterPrefix = (await sha2565(utf8(input.encounterId))).slice(0, 16);
|
|
3610
|
+
const payerPrefix = uuidToBytes(input.payerUserId).slice(0, 8);
|
|
3611
|
+
const payeePrefix = uuidToBytes(input.payeeUserId).slice(0, 8);
|
|
3612
|
+
const header = new Uint8Array(OFFLINE_SMS_SETTLE_HEADER_BYTES);
|
|
3613
|
+
const dv = new DataView(header.buffer);
|
|
3614
|
+
header[0] = OFFLINE_SMS_SETTLE_VERSION;
|
|
3615
|
+
header[1] = 0;
|
|
3616
|
+
header.set(encounterPrefix, 2);
|
|
3617
|
+
header.set(payerPrefix, 18);
|
|
3618
|
+
header.set(payeePrefix, 26);
|
|
3619
|
+
writeUint64BE(dv, 34, input.amountKobo);
|
|
3620
|
+
writeUint48BE(dv, 42, input.occurredAtMs);
|
|
3621
|
+
return header;
|
|
3622
|
+
}
|
|
3623
|
+
function domainTag(header) {
|
|
3624
|
+
if (header.length !== OFFLINE_SMS_SETTLE_HEADER_BYTES) {
|
|
3625
|
+
throw new Error(
|
|
3626
|
+
`FLURA1: header must be ${OFFLINE_SMS_SETTLE_HEADER_BYTES} bytes`
|
|
3627
|
+
);
|
|
3628
|
+
}
|
|
3629
|
+
const domain = utf8(OFFLINE_SMS_SETTLE_DOMAIN);
|
|
3630
|
+
const out = new Uint8Array(domain.length + header.length);
|
|
3631
|
+
out.set(domain, 0);
|
|
3632
|
+
out.set(header, domain.length);
|
|
3633
|
+
return out;
|
|
3634
|
+
}
|
|
3635
|
+
function decodeOfflineSmsSettleToken(message) {
|
|
3636
|
+
const token = extractOfflineSmsSettleToken(message);
|
|
3637
|
+
if (!token) throw new Error("FLURA1: token not found");
|
|
3638
|
+
const encoded = token.slice(OFFLINE_SMS_SETTLE_PREFIX.length);
|
|
3639
|
+
let bytes;
|
|
3640
|
+
try {
|
|
3641
|
+
bytes = base64UrlToBytes(encoded);
|
|
3642
|
+
} catch {
|
|
3643
|
+
throw new Error("FLURA1: token base64url is malformed");
|
|
3644
|
+
}
|
|
3645
|
+
if (bytesToBase64Url(bytes) !== encoded) {
|
|
3646
|
+
throw new Error("FLURA1: token base64url is malformed");
|
|
3647
|
+
}
|
|
3648
|
+
if (bytes.length !== OFFLINE_SMS_SETTLE_TOKEN_BYTES) {
|
|
3649
|
+
throw new Error(
|
|
3650
|
+
`FLURA1: expected ${OFFLINE_SMS_SETTLE_TOKEN_BYTES} bytes, got ${bytes.length}`
|
|
3651
|
+
);
|
|
3652
|
+
}
|
|
3653
|
+
const version = bytes[0];
|
|
3654
|
+
const flags = bytes[1];
|
|
3655
|
+
if (version !== OFFLINE_SMS_SETTLE_VERSION) {
|
|
3656
|
+
throw new Error(`FLURA1: unsupported version ${version}`);
|
|
3657
|
+
}
|
|
3658
|
+
if (flags !== 0) {
|
|
3659
|
+
throw new Error(`FLURA1: reserved flags must be 0, got ${flags}`);
|
|
3660
|
+
}
|
|
3661
|
+
const header = bytes.slice(0, OFFLINE_SMS_SETTLE_HEADER_BYTES);
|
|
3662
|
+
const signature = bytes.slice(OFFLINE_SMS_SETTLE_HEADER_BYTES);
|
|
3663
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
3664
|
+
const amountKobo = readUint64BE(dv, 34);
|
|
3665
|
+
if (amountKobo <= 0) {
|
|
3666
|
+
throw new Error("FLURA1: amountKobo must be greater than zero");
|
|
3667
|
+
}
|
|
3668
|
+
return {
|
|
3669
|
+
version,
|
|
3670
|
+
flags,
|
|
3671
|
+
encounterIdPrefixHex: bytesToHex6(bytes.slice(2, 18)),
|
|
3672
|
+
payerUserIdPrefixHex: bytesToHex6(bytes.slice(18, 26)),
|
|
3673
|
+
payeeUserIdPrefixHex: bytesToHex6(bytes.slice(26, 34)),
|
|
3674
|
+
amountKobo,
|
|
3675
|
+
occurredAtMs: readUint48BE(dv, 42),
|
|
3676
|
+
signature,
|
|
3677
|
+
header,
|
|
3678
|
+
signedBytes: domainTag(header)
|
|
3679
|
+
};
|
|
3680
|
+
}
|
|
3681
|
+
function extractOfflineSmsSettleToken(message) {
|
|
3682
|
+
const trimmed = message.trim();
|
|
3683
|
+
if (trimmed.startsWith(OFFLINE_SMS_SETTLE_PREFIX)) {
|
|
3684
|
+
return trimmed.split(/\s+/, 1)[0] ?? null;
|
|
3685
|
+
}
|
|
3686
|
+
return TOKEN_RE.exec(message)?.[1] ?? null;
|
|
3687
|
+
}
|
|
3688
|
+
function verifyOfflineSmsSettleToken(decoded, payerPubkeySpkiB64) {
|
|
3689
|
+
try {
|
|
3690
|
+
const pubRaw = p256SpkiB64ToRaw(payerPubkeySpkiB64);
|
|
3691
|
+
return p2563.verify(decoded.signature, decoded.signedBytes, pubRaw, {
|
|
3692
|
+
prehash: true,
|
|
3693
|
+
format: "compact"
|
|
3694
|
+
});
|
|
3695
|
+
} catch {
|
|
3696
|
+
return false;
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
function derToRawP256Signature(derBytes) {
|
|
3700
|
+
const sig = p2563.Signature.fromBytes(derBytes, "der");
|
|
3701
|
+
const raw = sig.toBytes("compact");
|
|
3702
|
+
if (raw.length !== OFFLINE_SMS_SETTLE_SIGNATURE_BYTES) {
|
|
3703
|
+
throw new Error(
|
|
3704
|
+
`FLURA1: DER\u2192raw produced ${raw.length} bytes; expected ${OFFLINE_SMS_SETTLE_SIGNATURE_BYTES}`
|
|
3705
|
+
);
|
|
3706
|
+
}
|
|
3707
|
+
return raw;
|
|
3708
|
+
}
|
|
3709
|
+
function utf8(s) {
|
|
3710
|
+
return new TextEncoder().encode(s);
|
|
3711
|
+
}
|
|
3712
|
+
async function sha2565(bytes) {
|
|
3713
|
+
const subtle = typeof globalThis !== "undefined" && globalThis.crypto?.subtle || void 0;
|
|
3714
|
+
if (subtle) {
|
|
3715
|
+
const digest = await subtle.digest("SHA-256", bytes);
|
|
3716
|
+
return new Uint8Array(digest);
|
|
3717
|
+
}
|
|
3718
|
+
const { sha256: nobleSha256 } = await import("@noble/hashes/sha2");
|
|
3719
|
+
return nobleSha256(bytes);
|
|
3720
|
+
}
|
|
3721
|
+
function uuidToBytes(uuid) {
|
|
3722
|
+
const hex = uuid.replace(/-/g, "").toLowerCase();
|
|
3723
|
+
if (hex.length !== 32 || !/^[0-9a-f]{32}$/.test(hex)) {
|
|
3724
|
+
throw new Error(`FLURA1: invalid UUID: ${uuid}`);
|
|
3725
|
+
}
|
|
3726
|
+
const out = new Uint8Array(16);
|
|
3727
|
+
for (let i = 0; i < 16; i++) {
|
|
3728
|
+
out[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
3729
|
+
}
|
|
3730
|
+
return out;
|
|
3731
|
+
}
|
|
3732
|
+
function assertSafeUint64(value, field) {
|
|
3733
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
3734
|
+
throw new Error(`FLURA1: ${field} must be a non-negative integer`);
|
|
3735
|
+
}
|
|
3736
|
+
if (value > Number.MAX_SAFE_INTEGER) {
|
|
3737
|
+
throw new Error(`FLURA1: ${field} exceeds Number.MAX_SAFE_INTEGER`);
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
function assertSafeUint48(value, field) {
|
|
3741
|
+
assertSafeUint64(value, field);
|
|
3742
|
+
if (value > 281474976710655) {
|
|
3743
|
+
throw new Error(`FLURA1: ${field} exceeds uint48 range`);
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
function writeUint64BE(dv, offset, value) {
|
|
3747
|
+
const high = Math.floor(value / 4294967296);
|
|
3748
|
+
const low = value >>> 0;
|
|
3749
|
+
dv.setUint32(offset, high, false);
|
|
3750
|
+
dv.setUint32(offset + 4, low, false);
|
|
3751
|
+
}
|
|
3752
|
+
function readUint64BE(dv, offset) {
|
|
3753
|
+
const high = dv.getUint32(offset, false);
|
|
3754
|
+
const low = dv.getUint32(offset + 4, false);
|
|
3755
|
+
if (high > 2097151) {
|
|
3756
|
+
throw new Error("FLURA1: amountKobo exceeds Number.MAX_SAFE_INTEGER");
|
|
3757
|
+
}
|
|
3758
|
+
return high * 4294967296 + low;
|
|
3759
|
+
}
|
|
3760
|
+
function writeUint48BE(dv, offset, value) {
|
|
3761
|
+
const high = Math.floor(value / 65536);
|
|
3762
|
+
const low = value & 65535;
|
|
3763
|
+
dv.setUint32(offset, high, false);
|
|
3764
|
+
dv.setUint16(offset + 4, low, false);
|
|
3765
|
+
}
|
|
3766
|
+
function readUint48BE(dv, offset) {
|
|
3767
|
+
const high = dv.getUint32(offset, false);
|
|
3768
|
+
const low = dv.getUint16(offset + 4, false);
|
|
3769
|
+
return high * 65536 + low;
|
|
3770
|
+
}
|
|
3771
|
+
function bytesToHex6(bytes) {
|
|
3772
|
+
let out = "";
|
|
3773
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
3774
|
+
out += bytes[i].toString(16).padStart(2, "0");
|
|
3775
|
+
}
|
|
3776
|
+
return out;
|
|
3777
|
+
}
|
|
3778
|
+
function bytesToBase64Url(bytes) {
|
|
3779
|
+
let base64;
|
|
3780
|
+
if (typeof Buffer !== "undefined") {
|
|
3781
|
+
base64 = Buffer.from(bytes).toString("base64");
|
|
3782
|
+
} else {
|
|
3783
|
+
let binary = "";
|
|
3784
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
3785
|
+
binary += String.fromCharCode(bytes[i]);
|
|
3786
|
+
}
|
|
3787
|
+
if (typeof btoa !== "function") {
|
|
3788
|
+
throw new Error("FLURA1: base64 encoder unavailable");
|
|
3789
|
+
}
|
|
3790
|
+
base64 = btoa(binary);
|
|
3791
|
+
}
|
|
3792
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
3793
|
+
}
|
|
3794
|
+
function base64UrlEncodeUtf82(input) {
|
|
3795
|
+
return bytesToBase64Url(utf8(input));
|
|
3796
|
+
}
|
|
3797
|
+
function base64UrlDecodeUtf82(input) {
|
|
3798
|
+
return new TextDecoder().decode(base64UrlToBytes(input));
|
|
3799
|
+
}
|
|
3800
|
+
function base64UrlToBytes(input) {
|
|
3801
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
3802
|
+
const padded = base64.padEnd(
|
|
3803
|
+
base64.length + (4 - base64.length % 4) % 4,
|
|
3804
|
+
"="
|
|
3805
|
+
);
|
|
3806
|
+
if (typeof Buffer !== "undefined") {
|
|
3807
|
+
return new Uint8Array(Buffer.from(padded, "base64"));
|
|
3808
|
+
}
|
|
3809
|
+
if (typeof atob !== "function") {
|
|
3810
|
+
throw new Error("FLURA1: base64 decoder unavailable");
|
|
3811
|
+
}
|
|
3812
|
+
const binary = atob(padded);
|
|
3813
|
+
const out = new Uint8Array(binary.length);
|
|
3814
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
3815
|
+
return out;
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3493
3818
|
// src/partner-funding/client.ts
|
|
3494
|
-
import { z as
|
|
3495
|
-
var MinorString =
|
|
3496
|
-
var PositiveMinor =
|
|
3497
|
-
|
|
3498
|
-
|
|
3819
|
+
import { z as z15 } from "zod";
|
|
3820
|
+
var MinorString = z15.string().regex(/^-?\d+$/);
|
|
3821
|
+
var PositiveMinor = z15.union([
|
|
3822
|
+
z15.number().int().positive(),
|
|
3823
|
+
z15.string().regex(/^[1-9]\d{0,18}$/)
|
|
3499
3824
|
]);
|
|
3500
|
-
var Currency =
|
|
3501
|
-
var Metadata =
|
|
3825
|
+
var Currency = z15.string().trim().length(3).transform((v) => v.toUpperCase());
|
|
3826
|
+
var Metadata = z15.record(z15.unknown());
|
|
3502
3827
|
var PARTNER_KINDS = ["bank", "merchant"];
|
|
3503
3828
|
var CUSTODIAL_MODES = ["agent_of_bank", "flur_virtual_pool"];
|
|
3504
3829
|
var PARTNER_PROFILE_STATUSES = [
|
|
@@ -3525,126 +3850,126 @@ var WITHDRAWAL_STATES = [
|
|
|
3525
3850
|
"failed",
|
|
3526
3851
|
"reversed"
|
|
3527
3852
|
];
|
|
3528
|
-
var PartnerProfileSchema =
|
|
3529
|
-
partnerAccountId:
|
|
3530
|
-
kind:
|
|
3531
|
-
custodialMode:
|
|
3532
|
-
displayName:
|
|
3533
|
-
bankCode:
|
|
3534
|
-
poolAccountNumber:
|
|
3535
|
-
status:
|
|
3853
|
+
var PartnerProfileSchema = z15.object({
|
|
3854
|
+
partnerAccountId: z15.string().uuid(),
|
|
3855
|
+
kind: z15.enum(PARTNER_KINDS),
|
|
3856
|
+
custodialMode: z15.enum(CUSTODIAL_MODES),
|
|
3857
|
+
displayName: z15.string(),
|
|
3858
|
+
bankCode: z15.string().nullable(),
|
|
3859
|
+
poolAccountNumber: z15.string().nullable(),
|
|
3860
|
+
status: z15.enum(PARTNER_PROFILE_STATUSES),
|
|
3536
3861
|
metadata: Metadata,
|
|
3537
|
-
createdAtMs:
|
|
3538
|
-
updatedAtMs:
|
|
3862
|
+
createdAtMs: z15.number().int().nonnegative(),
|
|
3863
|
+
updatedAtMs: z15.number().int().nonnegative()
|
|
3539
3864
|
});
|
|
3540
|
-
var UpsertPartnerProfileInputSchema =
|
|
3541
|
-
kind:
|
|
3542
|
-
custodialMode:
|
|
3543
|
-
displayName:
|
|
3544
|
-
bankCode:
|
|
3545
|
-
poolAccountNumber:
|
|
3865
|
+
var UpsertPartnerProfileInputSchema = z15.object({
|
|
3866
|
+
kind: z15.enum(PARTNER_KINDS),
|
|
3867
|
+
custodialMode: z15.enum(CUSTODIAL_MODES),
|
|
3868
|
+
displayName: z15.string().trim().min(1).max(200),
|
|
3869
|
+
bankCode: z15.string().trim().min(1).max(64).optional(),
|
|
3870
|
+
poolAccountNumber: z15.string().trim().min(1).max(64).optional(),
|
|
3546
3871
|
metadata: Metadata.optional()
|
|
3547
3872
|
});
|
|
3548
|
-
var PartnerFundingEventInputSchema =
|
|
3549
|
-
externalRef:
|
|
3550
|
-
direction:
|
|
3551
|
-
userId:
|
|
3552
|
-
accountId:
|
|
3873
|
+
var PartnerFundingEventInputSchema = z15.object({
|
|
3874
|
+
externalRef: z15.string().trim().min(8).max(128),
|
|
3875
|
+
direction: z15.enum(PARTNER_FUNDING_DIRECTIONS).optional(),
|
|
3876
|
+
userId: z15.string().uuid().optional(),
|
|
3877
|
+
accountId: z15.string().uuid().optional(),
|
|
3553
3878
|
amountMinor: PositiveMinor,
|
|
3554
3879
|
currency: Currency,
|
|
3555
|
-
fundingSource:
|
|
3880
|
+
fundingSource: z15.string().trim().min(1).max(64).optional(),
|
|
3556
3881
|
providerMetadata: Metadata.optional()
|
|
3557
3882
|
});
|
|
3558
|
-
var PartnerFundingSchema =
|
|
3559
|
-
fundingId:
|
|
3560
|
-
partnerId:
|
|
3561
|
-
accountId:
|
|
3562
|
-
userId:
|
|
3563
|
-
direction:
|
|
3564
|
-
currency:
|
|
3883
|
+
var PartnerFundingSchema = z15.object({
|
|
3884
|
+
fundingId: z15.string().uuid(),
|
|
3885
|
+
partnerId: z15.string().uuid(),
|
|
3886
|
+
accountId: z15.string().uuid(),
|
|
3887
|
+
userId: z15.string().uuid().nullable(),
|
|
3888
|
+
direction: z15.enum(PARTNER_FUNDING_DIRECTIONS),
|
|
3889
|
+
currency: z15.string(),
|
|
3565
3890
|
amountMinor: MinorString,
|
|
3566
|
-
externalRef:
|
|
3567
|
-
status:
|
|
3568
|
-
fundingSource:
|
|
3569
|
-
ledgerRef:
|
|
3891
|
+
externalRef: z15.string(),
|
|
3892
|
+
status: z15.enum(PARTNER_FUNDING_STATUSES),
|
|
3893
|
+
fundingSource: z15.string(),
|
|
3894
|
+
ledgerRef: z15.string(),
|
|
3570
3895
|
providerMetadata: Metadata,
|
|
3571
|
-
createdAtMs:
|
|
3572
|
-
updatedAtMs:
|
|
3896
|
+
createdAtMs: z15.number().int().nonnegative(),
|
|
3897
|
+
updatedAtMs: z15.number().int().nonnegative()
|
|
3573
3898
|
});
|
|
3574
|
-
var IngestFundingResultSchema =
|
|
3899
|
+
var IngestFundingResultSchema = z15.object({
|
|
3575
3900
|
funding: PartnerFundingSchema,
|
|
3576
|
-
replayed:
|
|
3901
|
+
replayed: z15.boolean()
|
|
3577
3902
|
});
|
|
3578
|
-
var PayoutDestinationSchema =
|
|
3579
|
-
destinationId:
|
|
3580
|
-
accountId:
|
|
3581
|
-
partnerId:
|
|
3582
|
-
bankCode:
|
|
3583
|
-
accountNumber:
|
|
3584
|
-
accountName:
|
|
3585
|
-
status:
|
|
3586
|
-
verifiedAtMs:
|
|
3903
|
+
var PayoutDestinationSchema = z15.object({
|
|
3904
|
+
destinationId: z15.string().uuid(),
|
|
3905
|
+
accountId: z15.string().uuid(),
|
|
3906
|
+
partnerId: z15.string().uuid(),
|
|
3907
|
+
bankCode: z15.string(),
|
|
3908
|
+
accountNumber: z15.string(),
|
|
3909
|
+
accountName: z15.string(),
|
|
3910
|
+
status: z15.enum(PAYOUT_DESTINATION_STATUSES),
|
|
3911
|
+
verifiedAtMs: z15.number().int().nonnegative().nullable(),
|
|
3587
3912
|
metadata: Metadata,
|
|
3588
|
-
createdAtMs:
|
|
3589
|
-
updatedAtMs:
|
|
3913
|
+
createdAtMs: z15.number().int().nonnegative(),
|
|
3914
|
+
updatedAtMs: z15.number().int().nonnegative()
|
|
3590
3915
|
});
|
|
3591
|
-
var CreatePayoutDestinationInputSchema =
|
|
3592
|
-
partnerId:
|
|
3593
|
-
bankCode:
|
|
3594
|
-
accountNumber:
|
|
3595
|
-
accountName:
|
|
3916
|
+
var CreatePayoutDestinationInputSchema = z15.object({
|
|
3917
|
+
partnerId: z15.string().uuid(),
|
|
3918
|
+
bankCode: z15.string().trim().min(1).max(32),
|
|
3919
|
+
accountNumber: z15.string().trim().min(4).max(64),
|
|
3920
|
+
accountName: z15.string().trim().min(1).max(200),
|
|
3596
3921
|
metadata: Metadata.optional()
|
|
3597
3922
|
});
|
|
3598
|
-
var ListPayoutDestinationsResultSchema =
|
|
3599
|
-
items:
|
|
3923
|
+
var ListPayoutDestinationsResultSchema = z15.object({
|
|
3924
|
+
items: z15.array(PayoutDestinationSchema)
|
|
3600
3925
|
});
|
|
3601
|
-
var WithdrawalSchema =
|
|
3602
|
-
withdrawalId:
|
|
3603
|
-
accountId:
|
|
3604
|
-
userId:
|
|
3605
|
-
partnerId:
|
|
3606
|
-
destinationId:
|
|
3607
|
-
currency:
|
|
3926
|
+
var WithdrawalSchema = z15.object({
|
|
3927
|
+
withdrawalId: z15.string().uuid(),
|
|
3928
|
+
accountId: z15.string().uuid(),
|
|
3929
|
+
userId: z15.string().uuid(),
|
|
3930
|
+
partnerId: z15.string().uuid(),
|
|
3931
|
+
destinationId: z15.string().uuid(),
|
|
3932
|
+
currency: z15.string(),
|
|
3608
3933
|
amountMinor: MinorString,
|
|
3609
|
-
state:
|
|
3610
|
-
idempotencyKey:
|
|
3611
|
-
providerRef:
|
|
3612
|
-
lastError:
|
|
3613
|
-
ledgerRef:
|
|
3614
|
-
reverseLedgerRef:
|
|
3934
|
+
state: z15.enum(WITHDRAWAL_STATES),
|
|
3935
|
+
idempotencyKey: z15.string(),
|
|
3936
|
+
providerRef: z15.string().nullable(),
|
|
3937
|
+
lastError: z15.string().nullable(),
|
|
3938
|
+
ledgerRef: z15.string(),
|
|
3939
|
+
reverseLedgerRef: z15.string().nullable(),
|
|
3615
3940
|
metadata: Metadata,
|
|
3616
|
-
createdAtMs:
|
|
3617
|
-
updatedAtMs:
|
|
3941
|
+
createdAtMs: z15.number().int().nonnegative(),
|
|
3942
|
+
updatedAtMs: z15.number().int().nonnegative()
|
|
3618
3943
|
});
|
|
3619
|
-
var CreateWithdrawalInputSchema =
|
|
3620
|
-
destinationId:
|
|
3944
|
+
var CreateWithdrawalInputSchema = z15.object({
|
|
3945
|
+
destinationId: z15.string().uuid(),
|
|
3621
3946
|
amountMinor: PositiveMinor,
|
|
3622
3947
|
currency: Currency,
|
|
3623
|
-
idempotencyKey:
|
|
3948
|
+
idempotencyKey: z15.string().trim().min(8).max(128),
|
|
3624
3949
|
metadata: Metadata.optional()
|
|
3625
3950
|
});
|
|
3626
|
-
var CreateWithdrawalResultSchema =
|
|
3951
|
+
var CreateWithdrawalResultSchema = z15.object({
|
|
3627
3952
|
withdrawal: WithdrawalSchema,
|
|
3628
|
-
replayed:
|
|
3953
|
+
replayed: z15.boolean()
|
|
3629
3954
|
});
|
|
3630
|
-
var PayoutEventInputSchema =
|
|
3631
|
-
externalRef:
|
|
3632
|
-
withdrawalId:
|
|
3633
|
-
state:
|
|
3634
|
-
providerRef:
|
|
3635
|
-
failureCode:
|
|
3636
|
-
failureMessage:
|
|
3955
|
+
var PayoutEventInputSchema = z15.object({
|
|
3956
|
+
externalRef: z15.string().trim().min(8).max(128),
|
|
3957
|
+
withdrawalId: z15.string().uuid().optional(),
|
|
3958
|
+
state: z15.enum(["submitted", "processing", "paid", "failed"]),
|
|
3959
|
+
providerRef: z15.string().trim().min(1).max(128).optional(),
|
|
3960
|
+
failureCode: z15.string().trim().max(64).optional(),
|
|
3961
|
+
failureMessage: z15.string().trim().max(512).optional(),
|
|
3637
3962
|
providerMetadata: Metadata.optional()
|
|
3638
3963
|
});
|
|
3639
|
-
var RecordPayoutEventResultSchema =
|
|
3964
|
+
var RecordPayoutEventResultSchema = z15.object({
|
|
3640
3965
|
withdrawal: WithdrawalSchema,
|
|
3641
|
-
replayed:
|
|
3966
|
+
replayed: z15.boolean()
|
|
3642
3967
|
});
|
|
3643
|
-
var ReconciliationReportSchema =
|
|
3644
|
-
partnerId:
|
|
3645
|
-
currency:
|
|
3646
|
-
fromMs:
|
|
3647
|
-
toMs:
|
|
3968
|
+
var ReconciliationReportSchema = z15.object({
|
|
3969
|
+
partnerId: z15.string().uuid(),
|
|
3970
|
+
currency: z15.string(),
|
|
3971
|
+
fromMs: z15.number().int().nonnegative(),
|
|
3972
|
+
toMs: z15.number().int().nonnegative(),
|
|
3648
3973
|
fundingsCreditMinor: MinorString,
|
|
3649
3974
|
fundingsDebitMinor: MinorString,
|
|
3650
3975
|
withdrawalsPaidMinor: MinorString,
|
|
@@ -3653,7 +3978,7 @@ var ReconciliationReportSchema = z14.object({
|
|
|
3653
3978
|
expectedReserveBalanceMinor: MinorString,
|
|
3654
3979
|
actualReserveBalanceMinor: MinorString,
|
|
3655
3980
|
imbalanceMinor: MinorString,
|
|
3656
|
-
generatedAtMs:
|
|
3981
|
+
generatedAtMs: z15.number().int().nonnegative()
|
|
3657
3982
|
});
|
|
3658
3983
|
function createPartnerFundingClient(partner) {
|
|
3659
3984
|
return {
|
|
@@ -3805,19 +4130,19 @@ function createPartnerProfileAdminClient(opts) {
|
|
|
3805
4130
|
}
|
|
3806
4131
|
|
|
3807
4132
|
// src/artifacts/envelope.ts
|
|
3808
|
-
import { z as
|
|
4133
|
+
import { z as z16 } from "zod";
|
|
3809
4134
|
var FLUR_ARTIFACT_URI_SCHEME = "flur";
|
|
3810
4135
|
var FLUR_ARTIFACT_VERSION = 1;
|
|
3811
4136
|
var FLUR_ARTIFACT_URI_PREFIX = `${FLUR_ARTIFACT_URI_SCHEME}://v${FLUR_ARTIFACT_VERSION}/`;
|
|
3812
4137
|
var ArtifactTypeRe = /^[a-z][a-z0-9_]{1,63}$/;
|
|
3813
|
-
var ArtifactHeaderSchema =
|
|
3814
|
-
v:
|
|
3815
|
-
t:
|
|
3816
|
-
iss:
|
|
3817
|
-
kid:
|
|
3818
|
-
iat:
|
|
3819
|
-
exp:
|
|
3820
|
-
nonce:
|
|
4138
|
+
var ArtifactHeaderSchema = z16.object({
|
|
4139
|
+
v: z16.literal(FLUR_ARTIFACT_VERSION),
|
|
4140
|
+
t: z16.string().regex(ArtifactTypeRe, "invalid artifact type"),
|
|
4141
|
+
iss: z16.string().min(1).max(128),
|
|
4142
|
+
kid: z16.string().min(1).max(128),
|
|
4143
|
+
iat: z16.number().int().nonnegative(),
|
|
4144
|
+
exp: z16.number().int().positive().optional(),
|
|
4145
|
+
nonce: z16.string().min(8).max(64).regex(/^[A-Za-z0-9_-]+$/, "nonce must be url-safe")
|
|
3821
4146
|
});
|
|
3822
4147
|
var FlurArtifactError = class extends Error {
|
|
3823
4148
|
constructor(message, code) {
|
|
@@ -3956,7 +4281,7 @@ function verifyArtifactSignature(decoded, publicKeySpkiB64, options = {}) {
|
|
|
3956
4281
|
}
|
|
3957
4282
|
|
|
3958
4283
|
// src/artifacts/types.ts
|
|
3959
|
-
import { z as
|
|
4284
|
+
import { z as z17 } from "zod";
|
|
3960
4285
|
var ARTIFACT_TYPES = {
|
|
3961
4286
|
OFFLINE_PAYMENT_AUTHORIZATION: "offline_payment_authorization",
|
|
3962
4287
|
RECEIPT: "receipt",
|
|
@@ -3971,32 +4296,32 @@ var ARTIFACT_TYPES = {
|
|
|
3971
4296
|
PASS: "pass",
|
|
3972
4297
|
IDENTITY: "identity"
|
|
3973
4298
|
};
|
|
3974
|
-
var HexString = (length) =>
|
|
4299
|
+
var HexString = (length) => z17.string().regex(
|
|
3975
4300
|
new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
|
|
3976
4301
|
`expected ${length}-byte hex string`
|
|
3977
4302
|
);
|
|
3978
|
-
var OfflinePaymentAuthorizationArtifactSchema =
|
|
4303
|
+
var OfflinePaymentAuthorizationArtifactSchema = z17.object({
|
|
3979
4304
|
authorization: OfflinePaymentAuthorizationSchema
|
|
3980
4305
|
});
|
|
3981
|
-
var ReceiptArtifactSchema =
|
|
3982
|
-
receiptId:
|
|
3983
|
-
paymentReference:
|
|
3984
|
-
payerUserId:
|
|
3985
|
-
payeeUserId:
|
|
3986
|
-
amountKobo:
|
|
3987
|
-
currency:
|
|
3988
|
-
channel:
|
|
3989
|
-
settledAtMs:
|
|
3990
|
-
ledgerTxnId:
|
|
3991
|
-
memo:
|
|
4306
|
+
var ReceiptArtifactSchema = z17.object({
|
|
4307
|
+
receiptId: z17.string().min(1).max(64),
|
|
4308
|
+
paymentReference: z17.string().min(1).max(64),
|
|
4309
|
+
payerUserId: z17.string().min(1).max(64).optional(),
|
|
4310
|
+
payeeUserId: z17.string().min(1).max(64),
|
|
4311
|
+
amountKobo: z17.number().int().positive(),
|
|
4312
|
+
currency: z17.literal("NGN"),
|
|
4313
|
+
channel: z17.enum(["online", "offline_reconciled", "pay_link", "nqr"]),
|
|
4314
|
+
settledAtMs: z17.number().int().positive(),
|
|
4315
|
+
ledgerTxnId: z17.string().min(1).max(64).optional(),
|
|
4316
|
+
memo: z17.string().max(140).optional(),
|
|
3992
4317
|
hashChainPrev: HexString(32).optional()
|
|
3993
4318
|
});
|
|
3994
|
-
var ShortId =
|
|
3995
|
-
var PositiveInt =
|
|
3996
|
-
var NonNegativeInt =
|
|
3997
|
-
var Currency2 =
|
|
3998
|
-
var Memo =
|
|
3999
|
-
var NqrPaymentRequestArtifactSchema =
|
|
4319
|
+
var ShortId = z17.string().min(1).max(64);
|
|
4320
|
+
var PositiveInt = z17.number().int().positive();
|
|
4321
|
+
var NonNegativeInt = z17.number().int().nonnegative();
|
|
4322
|
+
var Currency2 = z17.literal("NGN");
|
|
4323
|
+
var Memo = z17.string().max(140);
|
|
4324
|
+
var NqrPaymentRequestArtifactSchema = z17.object({
|
|
4000
4325
|
requestId: ShortId,
|
|
4001
4326
|
payeeUserId: ShortId,
|
|
4002
4327
|
amountKobo: PositiveInt.optional(),
|
|
@@ -4004,7 +4329,7 @@ var NqrPaymentRequestArtifactSchema = z16.object({
|
|
|
4004
4329
|
memo: Memo.optional(),
|
|
4005
4330
|
expiresAtMs: PositiveInt.optional()
|
|
4006
4331
|
});
|
|
4007
|
-
var PaymentIntentArtifactSchema =
|
|
4332
|
+
var PaymentIntentArtifactSchema = z17.object({
|
|
4008
4333
|
intentId: ShortId,
|
|
4009
4334
|
payerUserId: ShortId,
|
|
4010
4335
|
payeeUserId: ShortId,
|
|
@@ -4013,7 +4338,7 @@ var PaymentIntentArtifactSchema = z16.object({
|
|
|
4013
4338
|
idempotencyKey: ShortId,
|
|
4014
4339
|
createdAtMs: PositiveInt
|
|
4015
4340
|
});
|
|
4016
|
-
var OfflineClaimArtifactSchema =
|
|
4341
|
+
var OfflineClaimArtifactSchema = z17.object({
|
|
4017
4342
|
claimId: ShortId,
|
|
4018
4343
|
authorizationId: ShortId,
|
|
4019
4344
|
payeeUserId: ShortId,
|
|
@@ -4022,10 +4347,10 @@ var OfflineClaimArtifactSchema = z16.object({
|
|
|
4022
4347
|
claimedAtMs: PositiveInt,
|
|
4023
4348
|
paymentReference: ShortId.optional()
|
|
4024
4349
|
});
|
|
4025
|
-
var SettlementRecordArtifactSchema =
|
|
4350
|
+
var SettlementRecordArtifactSchema = z17.object({
|
|
4026
4351
|
settlementId: ShortId,
|
|
4027
4352
|
ledgerTxnId: ShortId,
|
|
4028
|
-
sourceRefType:
|
|
4353
|
+
sourceRefType: z17.enum([
|
|
4029
4354
|
"offline_authorization",
|
|
4030
4355
|
"offline_claim",
|
|
4031
4356
|
"transfer",
|
|
@@ -4036,12 +4361,12 @@ var SettlementRecordArtifactSchema = z16.object({
|
|
|
4036
4361
|
currency: Currency2,
|
|
4037
4362
|
settledAtMs: PositiveInt
|
|
4038
4363
|
});
|
|
4039
|
-
var ReversalRecordArtifactSchema =
|
|
4364
|
+
var ReversalRecordArtifactSchema = z17.object({
|
|
4040
4365
|
reversalId: ShortId,
|
|
4041
4366
|
originalTxnId: ShortId,
|
|
4042
4367
|
amountKobo: PositiveInt,
|
|
4043
4368
|
currency: Currency2,
|
|
4044
|
-
reason:
|
|
4369
|
+
reason: z17.enum([
|
|
4045
4370
|
"user_dispute",
|
|
4046
4371
|
"fraud",
|
|
4047
4372
|
"duplicate",
|
|
@@ -4051,7 +4376,7 @@ var ReversalRecordArtifactSchema = z16.object({
|
|
|
4051
4376
|
reversedAtMs: PositiveInt,
|
|
4052
4377
|
memo: Memo.optional()
|
|
4053
4378
|
});
|
|
4054
|
-
var LedgerJournalEntryArtifactSchema =
|
|
4379
|
+
var LedgerJournalEntryArtifactSchema = z17.object({
|
|
4055
4380
|
entryId: ShortId,
|
|
4056
4381
|
journalId: ShortId,
|
|
4057
4382
|
debitAccountId: ShortId,
|
|
@@ -4062,13 +4387,13 @@ var LedgerJournalEntryArtifactSchema = z16.object({
|
|
|
4062
4387
|
refType: ShortId.optional(),
|
|
4063
4388
|
refId: ShortId.optional()
|
|
4064
4389
|
});
|
|
4065
|
-
var StatementArtifactSchema =
|
|
4390
|
+
var StatementArtifactSchema = z17.object({
|
|
4066
4391
|
statementId: ShortId,
|
|
4067
4392
|
userId: ShortId,
|
|
4068
4393
|
periodStartMs: PositiveInt,
|
|
4069
4394
|
periodEndMs: PositiveInt,
|
|
4070
|
-
openingBalanceKobo:
|
|
4071
|
-
closingBalanceKobo:
|
|
4395
|
+
openingBalanceKobo: z17.number().int(),
|
|
4396
|
+
closingBalanceKobo: z17.number().int(),
|
|
4072
4397
|
transactionCount: NonNegativeInt,
|
|
4073
4398
|
currency: Currency2,
|
|
4074
4399
|
hashChainPrev: HexString(32).optional()
|
|
@@ -4076,16 +4401,16 @@ var StatementArtifactSchema = z16.object({
|
|
|
4076
4401
|
message: "periodEndMs must be greater than periodStartMs",
|
|
4077
4402
|
path: ["periodEndMs"]
|
|
4078
4403
|
});
|
|
4079
|
-
var PassArtifactSchema =
|
|
4404
|
+
var PassArtifactSchema = z17.object({
|
|
4080
4405
|
passId: ShortId,
|
|
4081
4406
|
holderId: ShortId,
|
|
4082
|
-
category:
|
|
4083
|
-
title:
|
|
4407
|
+
category: z17.enum(["membership", "ticket", "loyalty", "access", "voucher"]),
|
|
4408
|
+
title: z17.string().min(1).max(120),
|
|
4084
4409
|
validFromMs: PositiveInt,
|
|
4085
4410
|
validUntilMs: PositiveInt.optional(),
|
|
4086
|
-
metadata:
|
|
4087
|
-
|
|
4088
|
-
|
|
4411
|
+
metadata: z17.record(
|
|
4412
|
+
z17.string().min(1).max(64),
|
|
4413
|
+
z17.union([z17.string().max(280), z17.number(), z17.boolean()])
|
|
4089
4414
|
).optional()
|
|
4090
4415
|
}).refine(
|
|
4091
4416
|
(v) => v.validUntilMs === void 0 || v.validUntilMs > v.validFromMs,
|
|
@@ -4094,10 +4419,10 @@ var PassArtifactSchema = z16.object({
|
|
|
4094
4419
|
path: ["validUntilMs"]
|
|
4095
4420
|
}
|
|
4096
4421
|
);
|
|
4097
|
-
var IdentityArtifactSchema =
|
|
4422
|
+
var IdentityArtifactSchema = z17.object({
|
|
4098
4423
|
attestationId: ShortId,
|
|
4099
4424
|
subjectId: ShortId,
|
|
4100
|
-
claimType:
|
|
4425
|
+
claimType: z17.enum([
|
|
4101
4426
|
"phone_verified",
|
|
4102
4427
|
"email_verified",
|
|
4103
4428
|
"bvn_verified",
|
|
@@ -4217,6 +4542,7 @@ function createOfflinePaymentAuthorizationArtifactUri(input) {
|
|
|
4217
4542
|
});
|
|
4218
4543
|
}
|
|
4219
4544
|
export {
|
|
4545
|
+
ACCOUNT_FUNDED_OAC_MAX_TTL_MS,
|
|
4220
4546
|
ACCOUNT_STATUSES,
|
|
4221
4547
|
ACCOUNT_TYPES,
|
|
4222
4548
|
ADDITIONAL_DATA_SUBFIELD,
|
|
@@ -4230,6 +4556,10 @@ export {
|
|
|
4230
4556
|
CLAIM_DOMAIN_V2,
|
|
4231
4557
|
COLLECTION_INTENT_STATUSES,
|
|
4232
4558
|
COLLECTION_PAYMENT_STATUSES,
|
|
4559
|
+
CONSUMER_OFFLINE_CLAIM_SUBMIT_GRACE_MS,
|
|
4560
|
+
CONSUMER_PAYMENT_REQUEST_DOMAIN,
|
|
4561
|
+
CONSUMER_SETTLEMENT_DOMAIN,
|
|
4562
|
+
CONSUMER_SETTLEMENT_RECEIPT_QR_PREFIX,
|
|
4233
4563
|
CUSTODIAL_MODES,
|
|
4234
4564
|
CollectionIntentSchema,
|
|
4235
4565
|
CollectionPaymentResultSchema,
|
|
@@ -4239,6 +4569,7 @@ export {
|
|
|
4239
4569
|
OACRecordSchema as ConsumerOACRecordSchema,
|
|
4240
4570
|
ConsumerOACSchema,
|
|
4241
4571
|
ConsumerPaymentClaimSchema,
|
|
4572
|
+
ConsumerPaymentRequestEnvelopeSchema,
|
|
4242
4573
|
ConsumerSettleResultSchema,
|
|
4243
4574
|
ConsumerSettlementSchema,
|
|
4244
4575
|
CreateCollectionIntentInputSchema,
|
|
@@ -4248,10 +4579,6 @@ export {
|
|
|
4248
4579
|
CreateWithdrawalResultSchema,
|
|
4249
4580
|
DeviceKeyAlgSchema,
|
|
4250
4581
|
DeviceKeyRecordSchema,
|
|
4251
|
-
DisableOfflineInputSchema,
|
|
4252
|
-
DisableOfflineResultSchema,
|
|
4253
|
-
EnableOfflineInputSchema,
|
|
4254
|
-
EnableOfflineResultSchema,
|
|
4255
4582
|
FIELD,
|
|
4256
4583
|
FLUR_ARTIFACT_URI_PREFIX,
|
|
4257
4584
|
FLUR_ARTIFACT_URI_SCHEME,
|
|
@@ -4267,7 +4594,6 @@ export {
|
|
|
4267
4594
|
IdentityArtifactSchema,
|
|
4268
4595
|
IngestFundingResultSchema,
|
|
4269
4596
|
IssueAccountOacInputSchema,
|
|
4270
|
-
IssueOACInputSchema,
|
|
4271
4597
|
LedgerJournalEntryArtifactSchema,
|
|
4272
4598
|
ListPayoutDestinationsResultSchema,
|
|
4273
4599
|
MEMBERSHIP_ROLES,
|
|
@@ -4285,12 +4611,16 @@ export {
|
|
|
4285
4611
|
OAC_DEFAULT_PER_TX_KOBO,
|
|
4286
4612
|
OAC_DEFAULT_VALIDITY_MS,
|
|
4287
4613
|
OFFLINE_CLAIM_SMS_PREFIX,
|
|
4614
|
+
OFFLINE_SMS_SETTLE_DOMAIN,
|
|
4615
|
+
OFFLINE_SMS_SETTLE_HEADER_BYTES,
|
|
4616
|
+
OFFLINE_SMS_SETTLE_PREFIX,
|
|
4617
|
+
OFFLINE_SMS_SETTLE_SIGNATURE_BYTES,
|
|
4618
|
+
OFFLINE_SMS_SETTLE_TOKEN_BYTES,
|
|
4619
|
+
OFFLINE_SMS_SETTLE_VERSION,
|
|
4288
4620
|
OfflineClaimArtifactSchema,
|
|
4289
|
-
OfflineHoldRecordSchema,
|
|
4290
4621
|
OfflinePaymentAuthorizationArtifactSchema,
|
|
4291
4622
|
OfflinePaymentAuthorizationSchema,
|
|
4292
4623
|
OfflinePaymentRequestSchema,
|
|
4293
|
-
OfflineStateResultSchema,
|
|
4294
4624
|
OfflineStatusResultSchema,
|
|
4295
4625
|
OfflineTokenSchema,
|
|
4296
4626
|
P256EnrollmentChallengeInputSchema,
|
|
@@ -4318,8 +4648,6 @@ export {
|
|
|
4318
4648
|
PayoutEventInputSchema,
|
|
4319
4649
|
ProviderEventInputSchema,
|
|
4320
4650
|
ProviderEventRecordSchema,
|
|
4321
|
-
ProvisionOfflineAllowanceInputSchema,
|
|
4322
|
-
ProvisionOfflineAllowanceResultSchema,
|
|
4323
4651
|
PublicCollectionIntentSchema,
|
|
4324
4652
|
RECEIPT_CHANNELS,
|
|
4325
4653
|
RECEIPT_KINDS,
|
|
@@ -4330,7 +4658,6 @@ export {
|
|
|
4330
4658
|
ReconciliationReportSchema,
|
|
4331
4659
|
RecordPayoutEventResultSchema,
|
|
4332
4660
|
RedemptionSchema,
|
|
4333
|
-
RegisterDeviceKeyInputSchema,
|
|
4334
4661
|
RegisterDeviceKeyP256InputSchema,
|
|
4335
4662
|
ReversalRecordArtifactSchema,
|
|
4336
4663
|
RevokeDeviceKeyInputSchema,
|
|
@@ -4349,18 +4676,25 @@ export {
|
|
|
4349
4676
|
bodySha256Hex,
|
|
4350
4677
|
buildArtifactBody,
|
|
4351
4678
|
buildAuthorization,
|
|
4679
|
+
buildConsumerPaymentRequest,
|
|
4352
4680
|
buildOAC,
|
|
4353
4681
|
buildPass,
|
|
4354
4682
|
buildPaymentRequest,
|
|
4355
4683
|
buildReceipt,
|
|
4356
4684
|
buildRedemption,
|
|
4685
|
+
buildSmsSettleHeader,
|
|
4686
|
+
domainTag as buildSmsSettleSignedBytes,
|
|
4357
4687
|
canonicalClaimSigningBytes,
|
|
4358
4688
|
canonicalClaimSigningPayload,
|
|
4359
4689
|
canonicalJSONBytes,
|
|
4360
4690
|
canonicalJSONStringify,
|
|
4361
4691
|
canonicalRequestString,
|
|
4692
|
+
computeConsumerClaimEncounterId,
|
|
4362
4693
|
computeEncounterId,
|
|
4363
4694
|
constantTimeEqual,
|
|
4695
|
+
consumerPaymentRequestSigningBytes,
|
|
4696
|
+
consumerPaymentRequestSigningPayload,
|
|
4697
|
+
consumerSettlementSigningPayload,
|
|
4364
4698
|
crc16ccitt,
|
|
4365
4699
|
crc16ccittHex,
|
|
4366
4700
|
createAccountsClient,
|
|
@@ -4384,19 +4718,27 @@ export {
|
|
|
4384
4718
|
decodeArtifactUri,
|
|
4385
4719
|
decodeAuthorizationQR,
|
|
4386
4720
|
decodeBase45,
|
|
4721
|
+
decodeConsumerSettlementReceiptQR,
|
|
4387
4722
|
decodeOfflineClaimSmsMessage,
|
|
4723
|
+
decodeOfflineSmsSettleToken,
|
|
4388
4724
|
decodePaymentRequestQR,
|
|
4725
|
+
decodeUnverifiedConsumerSettlementReceiptQR,
|
|
4726
|
+
derToRawP256Signature,
|
|
4389
4727
|
encodeArtifactUri,
|
|
4390
4728
|
encodeAuthorizationQR,
|
|
4391
4729
|
encodeBase45,
|
|
4730
|
+
encodeConsumerSettlementReceiptQR,
|
|
4392
4731
|
encodeNQR,
|
|
4393
4732
|
encodeOfflineClaimSmsMessage,
|
|
4733
|
+
encodeOfflineSmsSettleToken,
|
|
4394
4734
|
encodePaymentRequestQR,
|
|
4395
4735
|
extractOfflineClaimSmsToken,
|
|
4736
|
+
extractOfflineSmsSettleToken,
|
|
4396
4737
|
formatAmount,
|
|
4397
4738
|
generateDynamicQR,
|
|
4398
4739
|
generateStaticQR,
|
|
4399
4740
|
init,
|
|
4741
|
+
isConsumerPaymentRequestExpired,
|
|
4400
4742
|
isHardenedArtifactType,
|
|
4401
4743
|
isKnownArtifactType,
|
|
4402
4744
|
isPassWithinValidity,
|
|
@@ -4409,6 +4751,7 @@ export {
|
|
|
4409
4751
|
routingHint,
|
|
4410
4752
|
signArtifact,
|
|
4411
4753
|
signAuthorization,
|
|
4754
|
+
signConsumerPaymentRequest,
|
|
4412
4755
|
signOAC,
|
|
4413
4756
|
signPartnerRequest,
|
|
4414
4757
|
signPass,
|
|
@@ -4420,7 +4763,11 @@ export {
|
|
|
4420
4763
|
verifyArtifactUri,
|
|
4421
4764
|
verifyAuthorization,
|
|
4422
4765
|
verifyClaimSignature,
|
|
4766
|
+
verifyConsumerPaymentRequest,
|
|
4767
|
+
verifyConsumerSettlement,
|
|
4768
|
+
verifyConsumerSettlementReceiptQR,
|
|
4423
4769
|
verifyOAC,
|
|
4770
|
+
verifyOfflineSmsSettleToken,
|
|
4424
4771
|
verifyPass,
|
|
4425
4772
|
verifyPaymentRequest,
|
|
4426
4773
|
verifyReceipt,
|