@nokinc-flur/sdk 1.0.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,12 +17,25 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
22
32
  __export(index_exports, {
33
+ ACCOUNT_STATUSES: () => ACCOUNT_STATUSES,
34
+ ACCOUNT_TYPES: () => ACCOUNT_TYPES,
23
35
  ADDITIONAL_DATA_SUBFIELD: () => ADDITIONAL_DATA_SUBFIELD,
36
+ AccountMembershipSchema: () => AccountMembershipSchema,
37
+ AccountSchema: () => AccountSchema,
38
+ ApiCredentialPublicSchema: () => ApiCredentialPublicSchema,
24
39
  FIELD: () => FIELD,
25
40
  FlurApiError: () => FlurApiError,
26
41
  FlurCapExceededError: () => FlurCapExceededError,
@@ -28,6 +43,8 @@ __export(index_exports, {
28
43
  FlurError: () => FlurError,
29
44
  FlurExpiredError: () => FlurExpiredError,
30
45
  FlurReplayError: () => FlurReplayError,
46
+ MEMBERSHIP_ROLES: () => MEMBERSHIP_ROLES,
47
+ MintedApiCredentialSchema: () => MintedApiCredentialSchema,
31
48
  NGN_CURRENCY_CODE: () => NGN_CURRENCY_CODE,
32
49
  NG_COUNTRY_CODE: () => NG_COUNTRY_CODE,
33
50
  NQRParseError: () => NQRParseError,
@@ -37,18 +54,23 @@ __export(index_exports, {
37
54
  OAC_DEFAULT_VALIDITY_MS: () => OAC_DEFAULT_VALIDITY_MS,
38
55
  OfflinePaymentAuthorizationSchema: () => OfflinePaymentAuthorizationSchema,
39
56
  OfflinePaymentRequestSchema: () => OfflinePaymentRequestSchema,
57
+ OfflineTokenSchema: () => OfflineTokenSchema,
58
+ PARTNER_SCOPES: () => PARTNER_SCOPES,
40
59
  PASS_KINDS: () => PASS_KINDS,
41
60
  PASS_STATES: () => PASS_STATES,
42
61
  PAYLOAD_FORMAT_INDICATOR_VALUE: () => PAYLOAD_FORMAT_INDICATOR_VALUE,
43
62
  POINT_OF_INITIATION: () => POINT_OF_INITIATION,
44
63
  PassMetadataSchema: () => PassMetadataSchema,
45
64
  PassSchema: () => PassSchema,
65
+ PaymentClaimSchema: () => PaymentClaimSchema,
46
66
  RECEIPT_CHANNELS: () => RECEIPT_CHANNELS,
47
67
  RECEIPT_KINDS: () => RECEIPT_KINDS,
48
68
  REPLAY_WINDOW_MS: () => REPLAY_WINDOW_MS,
49
69
  ReceiptPayloadSchema: () => ReceiptPayloadSchema,
50
70
  ReceiptSchema: () => ReceiptSchema,
51
71
  RedemptionSchema: () => RedemptionSchema,
72
+ SettleResponseSchema: () => SettleResponseSchema,
73
+ SettlementSchema: () => SettlementSchema,
52
74
  bodySha256Hex: () => bodySha256Hex,
53
75
  buildAuthorization: () => buildAuthorization,
54
76
  buildOAC: () => buildOAC,
@@ -59,10 +81,15 @@ __export(index_exports, {
59
81
  canonicalJSONBytes: () => canonicalJSONBytes,
60
82
  canonicalJSONStringify: () => canonicalJSONStringify,
61
83
  canonicalRequestString: () => canonicalRequestString,
84
+ computeEncounterId: () => computeEncounterId,
62
85
  constantTimeEqual: () => constantTimeEqual,
63
86
  crc16ccitt: () => crc16ccitt,
64
87
  crc16ccittHex: () => crc16ccittHex,
88
+ createAccountsClient: () => createAccountsClient,
89
+ createApiCredentialsAdminClient: () => createApiCredentialsAdminClient,
90
+ createFlurPartnerClient: () => createFlurPartnerClient,
65
91
  createHmacFetch: () => createHmacFetch,
92
+ createOfflineSettlementsClient: () => createOfflineSettlementsClient,
66
93
  createPassesClient: () => createPassesClient,
67
94
  createReceiptsClient: () => createReceiptsClient,
68
95
  decodeAuthorizationQR: () => decodeAuthorizationQR,
@@ -90,6 +117,7 @@ __export(index_exports, {
90
117
  signAuthorization: () => signAuthorization,
91
118
  signCanonical: () => signCanonical,
92
119
  signOAC: () => signOAC,
120
+ signPartnerRequest: () => signPartnerRequest,
93
121
  signPass: () => signPass,
94
122
  signPaymentRequest: () => signPaymentRequest,
95
123
  signReceipt: () => signReceipt,
@@ -1536,6 +1564,111 @@ function decodeAuthorizationQR(s) {
1536
1564
  return OfflinePaymentAuthorizationSchema.parse(JSON.parse(json));
1537
1565
  }
1538
1566
 
1567
+ // src/offline/settlements.ts
1568
+ var import_zod6 = require("zod");
1569
+ var OfflineTokenSchema = import_zod6.z.object({
1570
+ tokenId: import_zod6.z.string().uuid(),
1571
+ tokenSerial: import_zod6.z.string(),
1572
+ issuerAccountId: import_zod6.z.string().uuid(),
1573
+ payerUserId: import_zod6.z.string().uuid(),
1574
+ maxAmountKobo: import_zod6.z.number().int().positive(),
1575
+ currency: import_zod6.z.string().length(3),
1576
+ issuedAtMs: import_zod6.z.number().int().nonnegative(),
1577
+ expiresAtMs: import_zod6.z.number().int().nonnegative(),
1578
+ issuerSig: import_zod6.z.string()
1579
+ });
1580
+ var PaymentClaimSchema = import_zod6.z.object({
1581
+ encounterId: import_zod6.z.string().regex(/^[0-9a-f]{64}$/i).optional(),
1582
+ tokenSerial: import_zod6.z.string(),
1583
+ payerUserId: import_zod6.z.string().uuid(),
1584
+ payeeUserId: import_zod6.z.string().uuid(),
1585
+ payerNonce: import_zod6.z.string(),
1586
+ payeeNonce: import_zod6.z.string(),
1587
+ amountKobo: import_zod6.z.number().int().positive(),
1588
+ currency: import_zod6.z.string().length(3).default("NGN"),
1589
+ occurredAtMs: import_zod6.z.number().int().nonnegative(),
1590
+ completedAtMs: import_zod6.z.number().int().nonnegative().optional(),
1591
+ contextId: import_zod6.z.string().optional(),
1592
+ payerPubkey: import_zod6.z.string().regex(/^[0-9a-f]{64}$/i),
1593
+ payerSignature: import_zod6.z.string().regex(/^[0-9a-f]+$/i),
1594
+ payeePubkey: import_zod6.z.string().regex(/^[0-9a-f]{64}$/i).optional(),
1595
+ payeeSignature: import_zod6.z.string().regex(/^[0-9a-f]+$/i).optional()
1596
+ });
1597
+ var SettlementSchema = import_zod6.z.object({
1598
+ settlementId: import_zod6.z.string().uuid(),
1599
+ settlementKey: import_zod6.z.string().regex(/^[0-9a-f]{64}$/i),
1600
+ encounterId: import_zod6.z.string().regex(/^[0-9a-f]{64}$/i),
1601
+ issuerAccountId: import_zod6.z.string().uuid(),
1602
+ tokenSerial: import_zod6.z.string(),
1603
+ payerUserId: import_zod6.z.string().uuid(),
1604
+ payeeUserId: import_zod6.z.string().uuid(),
1605
+ amountKobo: import_zod6.z.number().int().nonnegative(),
1606
+ currency: import_zod6.z.string().length(3),
1607
+ receiptId: import_zod6.z.string().nullable(),
1608
+ status: import_zod6.z.enum(["SETTLED", "REVIEW", "REJECTED"]),
1609
+ issuerSig: import_zod6.z.string(),
1610
+ createdAtMs: import_zod6.z.number().int().nonnegative()
1611
+ });
1612
+ var SettleResponseSchema = import_zod6.z.object({
1613
+ settlement: SettlementSchema,
1614
+ encounterId: import_zod6.z.string().regex(/^[0-9a-f]{64}$/i),
1615
+ replayed: import_zod6.z.boolean()
1616
+ });
1617
+ var ENCOUNTER_DOMAIN = "offline:v1:encounter";
1618
+ async function sha256Hex(input) {
1619
+ const subtle = globalThis.crypto?.subtle;
1620
+ const enc2 = new TextEncoder().encode(input);
1621
+ if (subtle) {
1622
+ const buf = await subtle.digest("SHA-256", enc2);
1623
+ return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
1624
+ }
1625
+ const mod = await import("crypto");
1626
+ return mod.createHash("sha256").update(input).digest("hex");
1627
+ }
1628
+ async function computeEncounterId(input) {
1629
+ return sha256Hex(
1630
+ ENCOUNTER_DOMAIN + "|" + [
1631
+ input.payerUserId,
1632
+ input.payeeUserId,
1633
+ input.payerNonce,
1634
+ input.payeeNonce,
1635
+ input.tokenSerial
1636
+ ].join("|")
1637
+ );
1638
+ }
1639
+ function createOfflineSettlementsClient(partner) {
1640
+ return {
1641
+ issueOfflineToken: (input) => partner.request({
1642
+ method: "POST",
1643
+ path: "/v1/offline/tokens",
1644
+ body: {
1645
+ payerUserId: input.payerUserId,
1646
+ maxAmountKobo: input.maxAmountKobo,
1647
+ currency: input.currency ?? "NGN",
1648
+ ttlMs: input.ttlMs,
1649
+ ...input.tokenSerial ? { tokenSerial: input.tokenSerial } : {}
1650
+ }
1651
+ }),
1652
+ getOfflineToken: (serial) => partner.request({
1653
+ method: "GET",
1654
+ path: `/v1/offline/tokens/${encodeURIComponent(serial)}`
1655
+ }),
1656
+ settle: (claim) => partner.request({
1657
+ method: "POST",
1658
+ path: "/v1/offline/settlements",
1659
+ body: claim
1660
+ }),
1661
+ getSettlement: (id) => partner.request({
1662
+ method: "GET",
1663
+ path: `/v1/offline/settlements/${encodeURIComponent(id)}`
1664
+ }),
1665
+ listConflicts: () => partner.request({
1666
+ method: "GET",
1667
+ path: "/v1/offline/conflicts"
1668
+ })
1669
+ };
1670
+ }
1671
+
1539
1672
  // src/auth/hmac.ts
1540
1673
  var import_hmac = require("@noble/hashes/hmac");
1541
1674
  var import_sha256 = require("@noble/hashes/sha256");
@@ -1660,7 +1793,7 @@ function createHmacFetch(opts) {
1660
1793
  }
1661
1794
 
1662
1795
  // src/passes/pass.ts
1663
- var import_zod6 = require("zod");
1796
+ var import_zod7 = require("zod");
1664
1797
  var PASS_KINDS = [
1665
1798
  "ride-ticket",
1666
1799
  "transit-pass",
@@ -1676,41 +1809,41 @@ var PASS_STATES = [
1676
1809
  "expired",
1677
1810
  "revoked"
1678
1811
  ];
1679
- var HexString2 = (length) => import_zod6.z.string().regex(
1812
+ var HexString2 = (length) => import_zod7.z.string().regex(
1680
1813
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1681
1814
  `expected ${length}-byte hex string`
1682
1815
  );
1683
- var PassMetadataSchema = import_zod6.z.record(
1684
- import_zod6.z.union([import_zod6.z.string(), import_zod6.z.number(), import_zod6.z.boolean(), import_zod6.z.null()])
1816
+ var PassMetadataSchema = import_zod7.z.record(
1817
+ import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number(), import_zod7.z.boolean(), import_zod7.z.null()])
1685
1818
  );
1686
- var PassSchema = import_zod6.z.object({
1687
- passId: import_zod6.z.string().min(1),
1819
+ var PassSchema = import_zod7.z.object({
1820
+ passId: import_zod7.z.string().min(1),
1688
1821
  /** Optional client/template grouping id (server may omit). */
1689
- templateId: import_zod6.z.string().min(1).optional(),
1822
+ templateId: import_zod7.z.string().min(1).optional(),
1690
1823
  /** Optional human-facing holder identity (server may omit). The cryptographic binding
1691
1824
  * is `holderDevicePubkey` below. */
1692
- holderUserId: import_zod6.z.string().min(1).optional(),
1693
- kind: import_zod6.z.enum(PASS_KINDS),
1694
- issuerId: import_zod6.z.string().min(1),
1695
- issuedAtMs: import_zod6.z.number().int().nonnegative(),
1696
- validFromMs: import_zod6.z.number().int().nonnegative(),
1697
- validUntilMs: import_zod6.z.number().int().positive(),
1698
- state: import_zod6.z.enum(PASS_STATES),
1825
+ holderUserId: import_zod7.z.string().min(1).optional(),
1826
+ kind: import_zod7.z.enum(PASS_KINDS),
1827
+ issuerId: import_zod7.z.string().min(1),
1828
+ issuedAtMs: import_zod7.z.number().int().nonnegative(),
1829
+ validFromMs: import_zod7.z.number().int().nonnegative(),
1830
+ validUntilMs: import_zod7.z.number().int().positive(),
1831
+ state: import_zod7.z.enum(PASS_STATES),
1699
1832
  metadata: PassMetadataSchema,
1700
- nonce: import_zod6.z.string().min(1),
1833
+ nonce: import_zod7.z.string().min(1),
1701
1834
  /** Device id this pass is bound to (FK to backend `device_keys`). */
1702
- holderDeviceId: import_zod6.z.string().min(1),
1835
+ holderDeviceId: import_zod7.z.string().min(1),
1703
1836
  /** 32-byte hex Ed25519 public key of the bound device. The redemption signature
1704
1837
  * is verified against this key — it is the security-critical binding. */
1705
1838
  holderDevicePubkey: HexString2(32),
1706
1839
  /** Optional fixed amount for monetary passes (vouchers, gift cards) in kobo. */
1707
- amountKobo: import_zod6.z.number().int().nonnegative().optional(),
1840
+ amountKobo: import_zod7.z.number().int().nonnegative().optional(),
1708
1841
  /** ISO-4217-ish currency code; required on the wire. SDK builders default to NGN. */
1709
- currency: import_zod6.z.string().min(3).max(8),
1842
+ currency: import_zod7.z.string().min(3).max(8),
1710
1843
  /** Monotonic redemption counter floor. Redemption.counter MUST be > counterSeed. */
1711
- counterSeed: import_zod6.z.number().int().nonnegative(),
1844
+ counterSeed: import_zod7.z.number().int().nonnegative(),
1712
1845
  /** Optional cumulative spend cap in kobo across all redemptions of this pass. */
1713
- cumulativeCapKobo: import_zod6.z.number().int().nonnegative().optional(),
1846
+ cumulativeCapKobo: import_zod7.z.number().int().nonnegative().optional(),
1714
1847
  issuerSig: HexString2(64)
1715
1848
  }).refine((v) => v.validUntilMs > v.validFromMs, {
1716
1849
  message: "validUntilMs must be greater than validFromMs"
@@ -1767,18 +1900,18 @@ function isPassWithinValidity(pass, nowMs) {
1767
1900
  }
1768
1901
 
1769
1902
  // src/passes/redemption.ts
1770
- var import_zod7 = require("zod");
1771
- var HexSig2 = import_zod7.z.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
1772
- var RedemptionSchema = import_zod7.z.object({
1903
+ var import_zod8 = require("zod");
1904
+ var HexSig2 = import_zod8.z.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
1905
+ var RedemptionSchema = import_zod8.z.object({
1773
1906
  pass: PassSchema,
1774
- redeemerId: import_zod7.z.string().min(1),
1775
- redeemedAtMs: import_zod7.z.number().int().nonnegative(),
1907
+ redeemerId: import_zod8.z.string().min(1),
1908
+ redeemedAtMs: import_zod8.z.number().int().nonnegative(),
1776
1909
  /** Strictly monotonic counter scoped to a single pass. Must be > pass.counterSeed
1777
1910
  * and > the redeemer's lastSeenCounter for this pass. */
1778
- counter: import_zod7.z.number().int().positive(),
1911
+ counter: import_zod8.z.number().int().positive(),
1779
1912
  /** Amount being redeemed in kobo (0 for non-monetary passes like ride tickets). */
1780
- amountKobo: import_zod7.z.number().int().nonnegative(),
1781
- nonce: import_zod7.z.string().min(1),
1913
+ amountKobo: import_zod8.z.number().int().nonnegative(),
1914
+ nonce: import_zod8.z.string().min(1),
1782
1915
  holderSig: HexSig2
1783
1916
  });
1784
1917
  var REDEEMABLE_STATES = /* @__PURE__ */ new Set(["issued", "active"]);
@@ -1863,118 +1996,44 @@ function verifyRedemption(r, issuerPublicKey) {
1863
1996
  }
1864
1997
  }
1865
1998
 
1866
- // src/passes/client.ts
1867
- function createPassesClient(opts) {
1868
- const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
1869
- if (!fetchImpl)
1870
- throw new Error("createPassesClient: no fetch implementation available");
1871
- const baseUrl = opts.baseUrl.replace(/\/$/, "");
1872
- async function call(method, path, body, parser) {
1873
- const init2 = {
1874
- method,
1875
- headers: {
1876
- "content-type": "application/json",
1877
- accept: "application/json"
1878
- }
1879
- };
1880
- if (body !== void 0) init2.body = JSON.stringify(body);
1881
- const resp = await fetchImpl(`${baseUrl}${path}`, init2);
1882
- const text = await resp.text();
1883
- let raw = void 0;
1884
- if (text) {
1885
- try {
1886
- raw = JSON.parse(text);
1887
- } catch {
1888
- }
1889
- }
1890
- if (!resp.ok) {
1891
- const code = (raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : void 0) ?? `http_${resp.status}`;
1892
- const message = (raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : void 0) ?? `request failed with status ${resp.status}`;
1893
- throw new FlurApiError(resp.status, code, message, raw);
1894
- }
1895
- return parser(raw);
1896
- }
1897
- return {
1898
- issuePass: (input) => call("POST", "/v1/passes", input, (raw) => PassSchema.parse(raw)),
1899
- listPasses: (input) => {
1900
- const qs = new URLSearchParams();
1901
- if (input.holderDeviceId) qs.set("holderDeviceId", input.holderDeviceId);
1902
- if (input.holderUserId) qs.set("holderUserId", input.holderUserId);
1903
- if (input.state) qs.set("state", input.state);
1904
- if (input.kind) qs.set("kind", input.kind);
1905
- if (input.templateId) qs.set("templateId", input.templateId);
1906
- if (typeof input.limit === "number") qs.set("limit", String(input.limit));
1907
- if (input.cursor) qs.set("cursor", input.cursor);
1908
- const path = `/v1/passes${qs.size > 0 ? `?${qs.toString()}` : ""}`;
1909
- return call("GET", path, void 0, (raw) => {
1910
- const obj = raw;
1911
- const items = obj.items.map(
1912
- (it) => PassSchema.parse(it)
1913
- );
1914
- const nextCursor = typeof obj.nextCursor === "string" ? obj.nextCursor : null;
1915
- return { items, nextCursor };
1916
- });
1917
- },
1918
- getPass: (passId) => call(
1919
- "GET",
1920
- `/v1/passes/${encodeURIComponent(passId)}`,
1921
- void 0,
1922
- (raw) => PassSchema.parse(raw)
1923
- ),
1924
- redeemPass: (passId, redemption) => call(
1925
- "POST",
1926
- `/v1/passes/${encodeURIComponent(passId)}/redeem`,
1927
- RedemptionSchema.parse(redemption),
1928
- (raw) => PassSchema.parse(raw)
1929
- ),
1930
- revokePass: (passId, input) => call(
1931
- "POST",
1932
- `/v1/passes/${encodeURIComponent(passId)}/revoke`,
1933
- input,
1934
- (raw) => PassSchema.parse(raw)
1935
- ),
1936
- verifyPass: (pass, issuerPublicKey) => verifyPass(pass, issuerPublicKey)
1937
- };
1938
- }
1939
-
1940
1999
  // src/receipts/receipt.ts
1941
- var import_zod8 = require("zod");
2000
+ var import_zod9 = require("zod");
1942
2001
  var RECEIPT_CHANNELS = ["cash", "pass"];
1943
2002
  var RECEIPT_KINDS = RECEIPT_CHANNELS;
1944
- var HexString3 = (length) => import_zod8.z.string().regex(
2003
+ var HexString3 = (length) => import_zod9.z.string().regex(
1945
2004
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1946
2005
  `expected ${length}-byte hex string`
1947
2006
  );
1948
- var ReceiptPayloadSchema = import_zod8.z.record(
1949
- import_zod8.z.union([import_zod8.z.string(), import_zod8.z.number(), import_zod8.z.boolean(), import_zod8.z.null()])
2007
+ var ReceiptPayloadSchema = import_zod9.z.record(
2008
+ import_zod9.z.union([import_zod9.z.string(), import_zod9.z.number(), import_zod9.z.boolean(), import_zod9.z.null()])
1950
2009
  );
1951
- var ReceiptSchema = import_zod8.z.object({
1952
- receiptId: import_zod8.z.string().min(1),
1953
- channel: import_zod8.z.enum(RECEIPT_CHANNELS),
2010
+ var ReceiptSchema = import_zod9.z.object({
2011
+ receiptId: import_zod9.z.string().min(1),
2012
+ channel: import_zod9.z.enum(RECEIPT_CHANNELS),
1954
2013
  /** Cash-channel: send_intents.id. Required when channel === 'cash'. */
1955
- intentId: import_zod8.z.string().min(1).optional(),
2014
+ intentId: import_zod9.z.string().min(1).optional(),
1956
2015
  /** Pass-channel: pass_redemptions.id. Required when channel === 'pass'. */
1957
- passRedemptionId: import_zod8.z.string().min(1).optional(),
1958
- payerUserId: import_zod8.z.string().min(1),
1959
- payeeUserId: import_zod8.z.string().min(1),
1960
- amountKobo: import_zod8.z.number().int().nonnegative(),
1961
- currency: import_zod8.z.string().min(3).max(8),
1962
- issuedAtMs: import_zod8.z.number().int().nonnegative(),
1963
- issuerId: import_zod8.z.string().min(1),
2016
+ passRedemptionId: import_zod9.z.string().min(1).optional(),
2017
+ payerUserId: import_zod9.z.string().min(1),
2018
+ payeeUserId: import_zod9.z.string().min(1),
2019
+ amountKobo: import_zod9.z.number().int().nonnegative(),
2020
+ currency: import_zod9.z.string().min(3).max(8),
2021
+ issuedAtMs: import_zod9.z.number().int().nonnegative(),
2022
+ issuerId: import_zod9.z.string().min(1),
1964
2023
  payload: ReceiptPayloadSchema,
1965
2024
  issuerSig: HexString3(64)
1966
2025
  }).superRefine((v, ctx) => {
1967
2026
  if (v.channel === "cash") {
1968
2027
  if (!v.intentId) {
1969
2028
  ctx.addIssue({
1970
- code: import_zod8.z.ZodIssueCode.custom,
2029
+ code: import_zod9.z.ZodIssueCode.custom,
1971
2030
  message: "cash receipts require intentId",
1972
2031
  path: ["intentId"]
1973
2032
  });
1974
2033
  }
1975
2034
  if (v.passRedemptionId) {
1976
2035
  ctx.addIssue({
1977
- code: import_zod8.z.ZodIssueCode.custom,
2036
+ code: import_zod9.z.ZodIssueCode.custom,
1978
2037
  message: "cash receipts must not carry passRedemptionId",
1979
2038
  path: ["passRedemptionId"]
1980
2039
  });
@@ -1982,14 +2041,14 @@ var ReceiptSchema = import_zod8.z.object({
1982
2041
  } else if (v.channel === "pass") {
1983
2042
  if (!v.passRedemptionId) {
1984
2043
  ctx.addIssue({
1985
- code: import_zod8.z.ZodIssueCode.custom,
2044
+ code: import_zod9.z.ZodIssueCode.custom,
1986
2045
  message: "pass receipts require passRedemptionId",
1987
2046
  path: ["passRedemptionId"]
1988
2047
  });
1989
2048
  }
1990
2049
  if (v.intentId) {
1991
2050
  ctx.addIssue({
1992
- code: import_zod8.z.ZodIssueCode.custom,
2051
+ code: import_zod9.z.ZodIssueCode.custom,
1993
2052
  message: "pass receipts must not carry intentId",
1994
2053
  path: ["intentId"]
1995
2054
  });
@@ -2047,6 +2106,114 @@ function verifyReceipt(r, issuerPublicKey) {
2047
2106
  }
2048
2107
  }
2049
2108
 
2109
+ // src/passes/client.ts
2110
+ function createPassesClient(opts) {
2111
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
2112
+ if (!fetchImpl)
2113
+ throw new Error("createPassesClient: no fetch implementation available");
2114
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
2115
+ async function call(method, path, body, parser) {
2116
+ const init2 = {
2117
+ method,
2118
+ headers: {
2119
+ "content-type": "application/json",
2120
+ accept: "application/json"
2121
+ }
2122
+ };
2123
+ if (body !== void 0) init2.body = JSON.stringify(body);
2124
+ const resp = await fetchImpl(`${baseUrl}${path}`, init2);
2125
+ const text = await resp.text();
2126
+ let raw = void 0;
2127
+ if (text) {
2128
+ try {
2129
+ raw = JSON.parse(text);
2130
+ } catch {
2131
+ }
2132
+ }
2133
+ if (!resp.ok) {
2134
+ const code = (raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : void 0) ?? `http_${resp.status}`;
2135
+ const message = (raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : void 0) ?? `request failed with status ${resp.status}`;
2136
+ throw new FlurApiError(resp.status, code, message, raw);
2137
+ }
2138
+ return parser(raw);
2139
+ }
2140
+ return {
2141
+ issuePass: (input) => call("POST", "/v1/passes", input, (raw) => PassSchema.parse(raw)),
2142
+ listPasses: (input) => {
2143
+ const qs = new URLSearchParams();
2144
+ if (input.holderDeviceId) qs.set("holderDeviceId", input.holderDeviceId);
2145
+ if (input.holderUserId) qs.set("holderUserId", input.holderUserId);
2146
+ if (input.state) qs.set("state", input.state);
2147
+ if (input.kind) qs.set("kind", input.kind);
2148
+ if (input.templateId) qs.set("templateId", input.templateId);
2149
+ if (typeof input.limit === "number") qs.set("limit", String(input.limit));
2150
+ if (input.cursor) qs.set("cursor", input.cursor);
2151
+ const path = `/v1/passes${qs.size > 0 ? `?${qs.toString()}` : ""}`;
2152
+ return call("GET", path, void 0, (raw) => {
2153
+ const obj = raw;
2154
+ const items = obj.items.map(
2155
+ (it) => PassSchema.parse(it)
2156
+ );
2157
+ const nextCursor = typeof obj.nextCursor === "string" ? obj.nextCursor : null;
2158
+ return { items, nextCursor };
2159
+ });
2160
+ },
2161
+ getPass: (passId) => call(
2162
+ "GET",
2163
+ `/v1/passes/${encodeURIComponent(passId)}`,
2164
+ void 0,
2165
+ (raw) => PassSchema.parse(raw)
2166
+ ),
2167
+ redeemPass: (passId, redemption) => call(
2168
+ "POST",
2169
+ `/v1/passes/${encodeURIComponent(passId)}/redeem`,
2170
+ RedemptionSchema.parse(redemption),
2171
+ (raw) => {
2172
+ const obj = raw;
2173
+ return {
2174
+ pass: PassSchema.parse(raw),
2175
+ redemptionId: typeof obj.redemptionId === "string" ? obj.redemptionId : ""
2176
+ };
2177
+ }
2178
+ ).then((result) => result.pass),
2179
+ redeemPassDetailed: (passId, redemption) => call(
2180
+ "POST",
2181
+ `/v1/passes/${encodeURIComponent(passId)}/redeem`,
2182
+ RedemptionSchema.parse(redemption),
2183
+ (raw) => {
2184
+ const obj = raw;
2185
+ return {
2186
+ pass: PassSchema.parse(raw),
2187
+ redemptionId: typeof obj.redemptionId === "string" ? obj.redemptionId : ""
2188
+ };
2189
+ }
2190
+ ),
2191
+ redeemPassWithReceipt: (passId, redemption, receipt) => call(
2192
+ "POST",
2193
+ `/v1/passes/${encodeURIComponent(passId)}/redeem-with-receipt`,
2194
+ {
2195
+ redemption: RedemptionSchema.parse(redemption),
2196
+ receipt
2197
+ },
2198
+ (raw) => {
2199
+ const obj = raw;
2200
+ return {
2201
+ pass: PassSchema.parse(obj.pass),
2202
+ redemptionId: typeof obj.redemptionId === "string" ? obj.redemptionId : "",
2203
+ receipt: ReceiptSchema.parse(obj.receipt)
2204
+ };
2205
+ }
2206
+ ),
2207
+ revokePass: (passId, input) => call(
2208
+ "POST",
2209
+ `/v1/passes/${encodeURIComponent(passId)}/revoke`,
2210
+ input,
2211
+ (raw) => PassSchema.parse(raw)
2212
+ ),
2213
+ verifyPass: (pass, issuerPublicKey) => verifyPass(pass, issuerPublicKey)
2214
+ };
2215
+ }
2216
+
2050
2217
  // src/receipts/client.ts
2051
2218
  function createReceiptsClient(opts) {
2052
2219
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
@@ -2203,9 +2370,316 @@ function init(opts) {
2203
2370
  receipts
2204
2371
  };
2205
2372
  }
2373
+
2374
+ // src/accounts/client.ts
2375
+ var import_zod10 = require("zod");
2376
+ var ACCOUNT_TYPES = ["personal", "business", "partner"];
2377
+ var ACCOUNT_STATUSES = ["active", "suspended", "closed"];
2378
+ var MEMBERSHIP_ROLES = ["owner", "admin", "driver", "staff"];
2379
+ var AccountSchema = import_zod10.z.object({
2380
+ accountId: import_zod10.z.string().uuid(),
2381
+ type: import_zod10.z.enum(ACCOUNT_TYPES),
2382
+ displayName: import_zod10.z.string().min(1),
2383
+ status: import_zod10.z.enum(ACCOUNT_STATUSES),
2384
+ ownerUserId: import_zod10.z.string().uuid().nullable(),
2385
+ createdAtMs: import_zod10.z.number().int().nonnegative()
2386
+ });
2387
+ var AccountMembershipSchema = import_zod10.z.object({
2388
+ accountId: import_zod10.z.string().uuid(),
2389
+ userId: import_zod10.z.string().uuid(),
2390
+ role: import_zod10.z.enum(MEMBERSHIP_ROLES),
2391
+ createdAtMs: import_zod10.z.number().int().nonnegative()
2392
+ });
2393
+ function createAccountsClient(opts) {
2394
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
2395
+ if (!fetchImpl) {
2396
+ throw new Error("createAccountsClient: no fetch implementation available");
2397
+ }
2398
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
2399
+ async function call(method, path, body, parser) {
2400
+ const init2 = {
2401
+ method,
2402
+ headers: {
2403
+ "content-type": "application/json",
2404
+ accept: "application/json"
2405
+ }
2406
+ };
2407
+ if (body !== void 0) init2.body = JSON.stringify(body);
2408
+ const resp = await fetchImpl(`${baseUrl}${path}`, init2);
2409
+ const text = await resp.text();
2410
+ let raw = void 0;
2411
+ if (text) {
2412
+ try {
2413
+ raw = JSON.parse(text);
2414
+ } catch {
2415
+ }
2416
+ }
2417
+ if (!resp.ok) {
2418
+ const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
2419
+ const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
2420
+ throw new FlurApiError(resp.status, code, message, raw);
2421
+ }
2422
+ return parser(raw);
2423
+ }
2424
+ const itemsSchema = import_zod10.z.object({ items: import_zod10.z.array(AccountSchema) });
2425
+ const memberItemsSchema = import_zod10.z.object({
2426
+ items: import_zod10.z.array(AccountMembershipSchema)
2427
+ });
2428
+ return {
2429
+ listMyAccounts: () => call(
2430
+ "GET",
2431
+ "/v1/accounts/me",
2432
+ void 0,
2433
+ (raw) => itemsSchema.parse(raw)
2434
+ ),
2435
+ getAccount: (accountId) => call(
2436
+ "GET",
2437
+ `/v1/accounts/${encodeURIComponent(accountId)}`,
2438
+ void 0,
2439
+ (raw) => AccountSchema.parse(raw)
2440
+ ),
2441
+ listMembers: (accountId) => call(
2442
+ "GET",
2443
+ `/v1/accounts/${encodeURIComponent(accountId)}/members`,
2444
+ void 0,
2445
+ (raw) => memberItemsSchema.parse(raw)
2446
+ ),
2447
+ createBusinessAccount: (input) => call("POST", "/v1/accounts", input, (raw) => AccountSchema.parse(raw)),
2448
+ addMember: (accountId, input) => call(
2449
+ "POST",
2450
+ `/v1/accounts/${encodeURIComponent(accountId)}/members`,
2451
+ input,
2452
+ (raw) => AccountMembershipSchema.parse(raw)
2453
+ )
2454
+ };
2455
+ }
2456
+
2457
+ // src/partner/client.ts
2458
+ var import_zod11 = require("zod");
2459
+ var PARTNER_SCOPES = [
2460
+ "passes:write",
2461
+ "passes:read",
2462
+ "passes:redeem",
2463
+ "receipts:write",
2464
+ "receipts:read",
2465
+ "offline:issue",
2466
+ "offline:settle",
2467
+ "offline:read"
2468
+ ];
2469
+ var ApiCredentialPublicSchema = import_zod11.z.object({
2470
+ id: import_zod11.z.string().uuid(),
2471
+ accountId: import_zod11.z.string().uuid(),
2472
+ keyId: import_zod11.z.string(),
2473
+ scopes: import_zod11.z.array(import_zod11.z.enum(PARTNER_SCOPES)),
2474
+ label: import_zod11.z.string().nullable(),
2475
+ createdAtMs: import_zod11.z.number().int().nonnegative(),
2476
+ lastUsedAtMs: import_zod11.z.number().int().nonnegative().nullable(),
2477
+ revokedAtMs: import_zod11.z.number().int().nonnegative().nullable()
2478
+ });
2479
+ var MintedApiCredentialSchema = ApiCredentialPublicSchema.extend({
2480
+ secret: import_zod11.z.string().min(1)
2481
+ });
2482
+ async function getSubtle() {
2483
+ const c = globalThis.crypto;
2484
+ if (c?.subtle) return c.subtle;
2485
+ const mod = await import("crypto");
2486
+ return mod.webcrypto.subtle;
2487
+ }
2488
+ var enc = new TextEncoder();
2489
+ function bytesToHex3(buf) {
2490
+ const u = new Uint8Array(buf);
2491
+ let s = "";
2492
+ for (let i = 0; i < u.length; i++) {
2493
+ s += u[i].toString(16).padStart(2, "0");
2494
+ }
2495
+ return s;
2496
+ }
2497
+ async function sha256Hex2(input) {
2498
+ const subtle = await getSubtle();
2499
+ const data = typeof input === "string" ? enc.encode(input) : input;
2500
+ const buffer = data.byteOffset === 0 && data.byteLength === data.buffer.byteLength ? data.buffer : data.slice().buffer;
2501
+ const buf = await subtle.digest("SHA-256", buffer);
2502
+ return bytesToHex3(buf);
2503
+ }
2504
+ async function hmacSha256Hex(keyHex, message) {
2505
+ const subtle = await getSubtle();
2506
+ const key = await subtle.importKey(
2507
+ "raw",
2508
+ enc.encode(keyHex),
2509
+ { name: "HMAC", hash: "SHA-256" },
2510
+ false,
2511
+ ["sign"]
2512
+ );
2513
+ const sig = await subtle.sign("HMAC", key, enc.encode(message));
2514
+ return bytesToHex3(sig);
2515
+ }
2516
+ function defaultNonce2() {
2517
+ const c = globalThis.crypto;
2518
+ if (c?.randomUUID) return c.randomUUID();
2519
+ return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`;
2520
+ }
2521
+ function canonical(params) {
2522
+ return [
2523
+ params.method.toUpperCase(),
2524
+ params.path,
2525
+ params.ts,
2526
+ params.nonce,
2527
+ params.bodyHash
2528
+ ].join("\n");
2529
+ }
2530
+ async function signPartnerRequest(params) {
2531
+ const ts = String(Math.floor((params.nowMs ?? Date.now()) / 1e3));
2532
+ const nonce = params.nonce ?? defaultNonce2();
2533
+ const bodyData = typeof params.body === "string" ? enc.encode(params.body) : params.body ?? new Uint8Array();
2534
+ const bodyHash = await sha256Hex2(bodyData);
2535
+ const message = canonical({
2536
+ method: params.method,
2537
+ path: params.path,
2538
+ ts,
2539
+ nonce,
2540
+ bodyHash
2541
+ });
2542
+ const signingKey = await sha256Hex2(params.secret);
2543
+ const sig = await hmacSha256Hex(signingKey, message);
2544
+ return {
2545
+ authorization: `Flur-Hmac key=${params.keyId}, ts=${ts}, nonce=${nonce}, sig=${sig}`,
2546
+ ts,
2547
+ nonce,
2548
+ bodyHash
2549
+ };
2550
+ }
2551
+ function createFlurPartnerClient(opts) {
2552
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
2553
+ if (!fetchImpl) {
2554
+ throw new Error(
2555
+ "createFlurPartnerClient: no fetch implementation available"
2556
+ );
2557
+ }
2558
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
2559
+ async function makeSignedInit(method, path, body) {
2560
+ const sig = await signPartnerRequest({
2561
+ keyId: opts.keyId,
2562
+ secret: opts.secret,
2563
+ method,
2564
+ path,
2565
+ body: body ?? "",
2566
+ nowMs: opts.nowMs?.(),
2567
+ nonce: opts.nonce?.()
2568
+ });
2569
+ const headers = {
2570
+ authorization: sig.authorization,
2571
+ accept: "application/json"
2572
+ };
2573
+ if (body !== void 0) headers["content-type"] = "application/json";
2574
+ const init2 = { method, headers };
2575
+ if (body !== void 0) init2.body = body;
2576
+ return init2;
2577
+ }
2578
+ async function request(opts2) {
2579
+ const bodyStr = opts2.body === void 0 ? void 0 : JSON.stringify(opts2.body);
2580
+ const init2 = await makeSignedInit(opts2.method, opts2.path, bodyStr);
2581
+ const resp = await fetchImpl(`${baseUrl}${opts2.path}`, init2);
2582
+ const text = await resp.text();
2583
+ let raw;
2584
+ if (text) {
2585
+ try {
2586
+ raw = JSON.parse(text);
2587
+ } catch {
2588
+ }
2589
+ }
2590
+ if (!resp.ok) {
2591
+ const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
2592
+ const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
2593
+ throw new FlurApiError(resp.status, code, message, raw);
2594
+ }
2595
+ return raw;
2596
+ }
2597
+ const fetchLike = async (input, init2) => {
2598
+ const url = typeof input === "string" ? input : input.toString();
2599
+ const u = new URL(url, baseUrl);
2600
+ const path = `${u.pathname}${u.search}`;
2601
+ const method = (init2?.method ?? "GET").toUpperCase();
2602
+ let bodyStr;
2603
+ if (init2?.body) {
2604
+ bodyStr = typeof init2.body === "string" ? init2.body : init2.body.toString();
2605
+ }
2606
+ const signed = await makeSignedInit(method, path, bodyStr);
2607
+ return fetchImpl(`${baseUrl}${path}`, {
2608
+ ...init2,
2609
+ ...signed,
2610
+ headers: {
2611
+ ...init2?.headers,
2612
+ ...signed.headers
2613
+ }
2614
+ });
2615
+ };
2616
+ return { request, fetch: fetchLike };
2617
+ }
2618
+ function createApiCredentialsAdminClient(opts) {
2619
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
2620
+ if (!fetchImpl) {
2621
+ throw new Error(
2622
+ "createApiCredentialsAdminClient: no fetch implementation available"
2623
+ );
2624
+ }
2625
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
2626
+ async function call(method, path, body, parser) {
2627
+ const init2 = {
2628
+ method,
2629
+ headers: {
2630
+ "content-type": "application/json",
2631
+ accept: "application/json"
2632
+ }
2633
+ };
2634
+ if (body !== void 0) init2.body = JSON.stringify(body);
2635
+ const resp = await fetchImpl(`${baseUrl}${path}`, init2);
2636
+ const text = await resp.text();
2637
+ let raw;
2638
+ if (text) {
2639
+ try {
2640
+ raw = JSON.parse(text);
2641
+ } catch {
2642
+ }
2643
+ }
2644
+ if (!resp.ok) {
2645
+ const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
2646
+ const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
2647
+ throw new FlurApiError(resp.status, code, message, raw);
2648
+ }
2649
+ return parser(raw);
2650
+ }
2651
+ const listSchema = import_zod11.z.object({
2652
+ items: import_zod11.z.array(ApiCredentialPublicSchema)
2653
+ });
2654
+ return {
2655
+ list: (accountId) => call(
2656
+ "GET",
2657
+ `/v1/accounts/${accountId}/api-credentials`,
2658
+ void 0,
2659
+ (raw) => listSchema.parse(raw)
2660
+ ),
2661
+ mint: (accountId, input) => call(
2662
+ "POST",
2663
+ `/v1/accounts/${accountId}/api-credentials`,
2664
+ input,
2665
+ (raw) => MintedApiCredentialSchema.parse(raw)
2666
+ ),
2667
+ revoke: (accountId, credentialId) => call(
2668
+ "DELETE",
2669
+ `/v1/accounts/${accountId}/api-credentials/${credentialId}`,
2670
+ void 0,
2671
+ (raw) => ApiCredentialPublicSchema.parse(raw)
2672
+ )
2673
+ };
2674
+ }
2206
2675
  // Annotate the CommonJS export names for ESM import in node:
2207
2676
  0 && (module.exports = {
2677
+ ACCOUNT_STATUSES,
2678
+ ACCOUNT_TYPES,
2208
2679
  ADDITIONAL_DATA_SUBFIELD,
2680
+ AccountMembershipSchema,
2681
+ AccountSchema,
2682
+ ApiCredentialPublicSchema,
2209
2683
  FIELD,
2210
2684
  FlurApiError,
2211
2685
  FlurCapExceededError,
@@ -2213,6 +2687,8 @@ function init(opts) {
2213
2687
  FlurError,
2214
2688
  FlurExpiredError,
2215
2689
  FlurReplayError,
2690
+ MEMBERSHIP_ROLES,
2691
+ MintedApiCredentialSchema,
2216
2692
  NGN_CURRENCY_CODE,
2217
2693
  NG_COUNTRY_CODE,
2218
2694
  NQRParseError,
@@ -2222,18 +2698,23 @@ function init(opts) {
2222
2698
  OAC_DEFAULT_VALIDITY_MS,
2223
2699
  OfflinePaymentAuthorizationSchema,
2224
2700
  OfflinePaymentRequestSchema,
2701
+ OfflineTokenSchema,
2702
+ PARTNER_SCOPES,
2225
2703
  PASS_KINDS,
2226
2704
  PASS_STATES,
2227
2705
  PAYLOAD_FORMAT_INDICATOR_VALUE,
2228
2706
  POINT_OF_INITIATION,
2229
2707
  PassMetadataSchema,
2230
2708
  PassSchema,
2709
+ PaymentClaimSchema,
2231
2710
  RECEIPT_CHANNELS,
2232
2711
  RECEIPT_KINDS,
2233
2712
  REPLAY_WINDOW_MS,
2234
2713
  ReceiptPayloadSchema,
2235
2714
  ReceiptSchema,
2236
2715
  RedemptionSchema,
2716
+ SettleResponseSchema,
2717
+ SettlementSchema,
2237
2718
  bodySha256Hex,
2238
2719
  buildAuthorization,
2239
2720
  buildOAC,
@@ -2244,10 +2725,15 @@ function init(opts) {
2244
2725
  canonicalJSONBytes,
2245
2726
  canonicalJSONStringify,
2246
2727
  canonicalRequestString,
2728
+ computeEncounterId,
2247
2729
  constantTimeEqual,
2248
2730
  crc16ccitt,
2249
2731
  crc16ccittHex,
2732
+ createAccountsClient,
2733
+ createApiCredentialsAdminClient,
2734
+ createFlurPartnerClient,
2250
2735
  createHmacFetch,
2736
+ createOfflineSettlementsClient,
2251
2737
  createPassesClient,
2252
2738
  createReceiptsClient,
2253
2739
  decodeAuthorizationQR,
@@ -2275,6 +2761,7 @@ function init(opts) {
2275
2761
  signAuthorization,
2276
2762
  signCanonical,
2277
2763
  signOAC,
2764
+ signPartnerRequest,
2278
2765
  signPass,
2279
2766
  signPaymentRequest,
2280
2767
  signReceipt,