@nokinc-flur/sdk 1.0.0 → 1.0.2

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.js CHANGED
@@ -1426,6 +1426,111 @@ function decodeAuthorizationQR(s) {
1426
1426
  return OfflinePaymentAuthorizationSchema.parse(JSON.parse(json));
1427
1427
  }
1428
1428
 
1429
+ // src/offline/settlements.ts
1430
+ import { z as z6 } from "zod";
1431
+ var OfflineTokenSchema = z6.object({
1432
+ tokenId: z6.string().uuid(),
1433
+ tokenSerial: z6.string(),
1434
+ issuerAccountId: z6.string().uuid(),
1435
+ payerUserId: z6.string().uuid(),
1436
+ maxAmountKobo: z6.number().int().positive(),
1437
+ currency: z6.string().length(3),
1438
+ issuedAtMs: z6.number().int().nonnegative(),
1439
+ expiresAtMs: z6.number().int().nonnegative(),
1440
+ issuerSig: z6.string()
1441
+ });
1442
+ var PaymentClaimSchema = z6.object({
1443
+ encounterId: z6.string().regex(/^[0-9a-f]{64}$/i).optional(),
1444
+ tokenSerial: z6.string(),
1445
+ payerUserId: z6.string().uuid(),
1446
+ payeeUserId: z6.string().uuid(),
1447
+ payerNonce: z6.string(),
1448
+ payeeNonce: z6.string(),
1449
+ amountKobo: z6.number().int().positive(),
1450
+ currency: z6.string().length(3).default("NGN"),
1451
+ occurredAtMs: z6.number().int().nonnegative(),
1452
+ completedAtMs: z6.number().int().nonnegative().optional(),
1453
+ contextId: z6.string().optional(),
1454
+ payerPubkey: z6.string().regex(/^[0-9a-f]{64}$/i),
1455
+ payerSignature: z6.string().regex(/^[0-9a-f]+$/i),
1456
+ payeePubkey: z6.string().regex(/^[0-9a-f]{64}$/i).optional(),
1457
+ payeeSignature: z6.string().regex(/^[0-9a-f]+$/i).optional()
1458
+ });
1459
+ var SettlementSchema = z6.object({
1460
+ settlementId: z6.string().uuid(),
1461
+ settlementKey: z6.string().regex(/^[0-9a-f]{64}$/i),
1462
+ encounterId: z6.string().regex(/^[0-9a-f]{64}$/i),
1463
+ issuerAccountId: z6.string().uuid(),
1464
+ tokenSerial: z6.string(),
1465
+ payerUserId: z6.string().uuid(),
1466
+ payeeUserId: z6.string().uuid(),
1467
+ amountKobo: z6.number().int().nonnegative(),
1468
+ currency: z6.string().length(3),
1469
+ receiptId: z6.string().nullable(),
1470
+ status: z6.enum(["SETTLED", "REVIEW", "REJECTED"]),
1471
+ issuerSig: z6.string(),
1472
+ createdAtMs: z6.number().int().nonnegative()
1473
+ });
1474
+ var SettleResponseSchema = z6.object({
1475
+ settlement: SettlementSchema,
1476
+ encounterId: z6.string().regex(/^[0-9a-f]{64}$/i),
1477
+ replayed: z6.boolean()
1478
+ });
1479
+ var ENCOUNTER_DOMAIN = "offline:v1:encounter";
1480
+ async function sha256Hex(input) {
1481
+ const subtle = globalThis.crypto?.subtle;
1482
+ const enc2 = new TextEncoder().encode(input);
1483
+ if (subtle) {
1484
+ const buf = await subtle.digest("SHA-256", enc2);
1485
+ return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
1486
+ }
1487
+ const mod = await import("crypto");
1488
+ return mod.createHash("sha256").update(input).digest("hex");
1489
+ }
1490
+ async function computeEncounterId(input) {
1491
+ return sha256Hex(
1492
+ ENCOUNTER_DOMAIN + "|" + [
1493
+ input.payerUserId,
1494
+ input.payeeUserId,
1495
+ input.payerNonce,
1496
+ input.payeeNonce,
1497
+ input.tokenSerial
1498
+ ].join("|")
1499
+ );
1500
+ }
1501
+ function createOfflineSettlementsClient(partner) {
1502
+ return {
1503
+ issueOfflineToken: (input) => partner.request({
1504
+ method: "POST",
1505
+ path: "/v1/offline/tokens",
1506
+ body: {
1507
+ payerUserId: input.payerUserId,
1508
+ maxAmountKobo: input.maxAmountKobo,
1509
+ currency: input.currency ?? "NGN",
1510
+ ttlMs: input.ttlMs,
1511
+ ...input.tokenSerial ? { tokenSerial: input.tokenSerial } : {}
1512
+ }
1513
+ }),
1514
+ getOfflineToken: (serial) => partner.request({
1515
+ method: "GET",
1516
+ path: `/v1/offline/tokens/${encodeURIComponent(serial)}`
1517
+ }),
1518
+ settle: (claim) => partner.request({
1519
+ method: "POST",
1520
+ path: "/v1/offline/settlements",
1521
+ body: claim
1522
+ }),
1523
+ getSettlement: (id) => partner.request({
1524
+ method: "GET",
1525
+ path: `/v1/offline/settlements/${encodeURIComponent(id)}`
1526
+ }),
1527
+ listConflicts: () => partner.request({
1528
+ method: "GET",
1529
+ path: "/v1/offline/conflicts"
1530
+ })
1531
+ };
1532
+ }
1533
+
1429
1534
  // src/auth/hmac.ts
1430
1535
  import { hmac } from "@noble/hashes/hmac";
1431
1536
  import { sha256 } from "@noble/hashes/sha256";
@@ -1550,7 +1655,7 @@ function createHmacFetch(opts) {
1550
1655
  }
1551
1656
 
1552
1657
  // src/passes/pass.ts
1553
- import { z as z6 } from "zod";
1658
+ import { z as z7 } from "zod";
1554
1659
  var PASS_KINDS = [
1555
1660
  "ride-ticket",
1556
1661
  "transit-pass",
@@ -1566,41 +1671,41 @@ var PASS_STATES = [
1566
1671
  "expired",
1567
1672
  "revoked"
1568
1673
  ];
1569
- var HexString2 = (length) => z6.string().regex(
1674
+ var HexString2 = (length) => z7.string().regex(
1570
1675
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1571
1676
  `expected ${length}-byte hex string`
1572
1677
  );
1573
- var PassMetadataSchema = z6.record(
1574
- z6.union([z6.string(), z6.number(), z6.boolean(), z6.null()])
1678
+ var PassMetadataSchema = z7.record(
1679
+ z7.union([z7.string(), z7.number(), z7.boolean(), z7.null()])
1575
1680
  );
1576
- var PassSchema = z6.object({
1577
- passId: z6.string().min(1),
1681
+ var PassSchema = z7.object({
1682
+ passId: z7.string().min(1),
1578
1683
  /** Optional client/template grouping id (server may omit). */
1579
- templateId: z6.string().min(1).optional(),
1684
+ templateId: z7.string().min(1).optional(),
1580
1685
  /** Optional human-facing holder identity (server may omit). The cryptographic binding
1581
1686
  * is `holderDevicePubkey` below. */
1582
- holderUserId: z6.string().min(1).optional(),
1583
- kind: z6.enum(PASS_KINDS),
1584
- issuerId: z6.string().min(1),
1585
- issuedAtMs: z6.number().int().nonnegative(),
1586
- validFromMs: z6.number().int().nonnegative(),
1587
- validUntilMs: z6.number().int().positive(),
1588
- state: z6.enum(PASS_STATES),
1687
+ holderUserId: z7.string().min(1).optional(),
1688
+ kind: z7.enum(PASS_KINDS),
1689
+ issuerId: z7.string().min(1),
1690
+ issuedAtMs: z7.number().int().nonnegative(),
1691
+ validFromMs: z7.number().int().nonnegative(),
1692
+ validUntilMs: z7.number().int().positive(),
1693
+ state: z7.enum(PASS_STATES),
1589
1694
  metadata: PassMetadataSchema,
1590
- nonce: z6.string().min(1),
1695
+ nonce: z7.string().min(1),
1591
1696
  /** Device id this pass is bound to (FK to backend `device_keys`). */
1592
- holderDeviceId: z6.string().min(1),
1697
+ holderDeviceId: z7.string().min(1),
1593
1698
  /** 32-byte hex Ed25519 public key of the bound device. The redemption signature
1594
1699
  * is verified against this key — it is the security-critical binding. */
1595
1700
  holderDevicePubkey: HexString2(32),
1596
1701
  /** Optional fixed amount for monetary passes (vouchers, gift cards) in kobo. */
1597
- amountKobo: z6.number().int().nonnegative().optional(),
1702
+ amountKobo: z7.number().int().nonnegative().optional(),
1598
1703
  /** ISO-4217-ish currency code; required on the wire. SDK builders default to NGN. */
1599
- currency: z6.string().min(3).max(8),
1704
+ currency: z7.string().min(3).max(8),
1600
1705
  /** Monotonic redemption counter floor. Redemption.counter MUST be > counterSeed. */
1601
- counterSeed: z6.number().int().nonnegative(),
1706
+ counterSeed: z7.number().int().nonnegative(),
1602
1707
  /** Optional cumulative spend cap in kobo across all redemptions of this pass. */
1603
- cumulativeCapKobo: z6.number().int().nonnegative().optional(),
1708
+ cumulativeCapKobo: z7.number().int().nonnegative().optional(),
1604
1709
  issuerSig: HexString2(64)
1605
1710
  }).refine((v) => v.validUntilMs > v.validFromMs, {
1606
1711
  message: "validUntilMs must be greater than validFromMs"
@@ -1657,18 +1762,18 @@ function isPassWithinValidity(pass, nowMs) {
1657
1762
  }
1658
1763
 
1659
1764
  // src/passes/redemption.ts
1660
- import { z as z7 } from "zod";
1661
- var HexSig2 = z7.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
1662
- var RedemptionSchema = z7.object({
1765
+ import { z as z8 } from "zod";
1766
+ var HexSig2 = z8.string().regex(/^[0-9a-fA-F]{128}$/, "expected 64-byte hex signature");
1767
+ var RedemptionSchema = z8.object({
1663
1768
  pass: PassSchema,
1664
- redeemerId: z7.string().min(1),
1665
- redeemedAtMs: z7.number().int().nonnegative(),
1769
+ redeemerId: z8.string().min(1),
1770
+ redeemedAtMs: z8.number().int().nonnegative(),
1666
1771
  /** Strictly monotonic counter scoped to a single pass. Must be > pass.counterSeed
1667
1772
  * and > the redeemer's lastSeenCounter for this pass. */
1668
- counter: z7.number().int().positive(),
1773
+ counter: z8.number().int().positive(),
1669
1774
  /** Amount being redeemed in kobo (0 for non-monetary passes like ride tickets). */
1670
- amountKobo: z7.number().int().nonnegative(),
1671
- nonce: z7.string().min(1),
1775
+ amountKobo: z8.number().int().nonnegative(),
1776
+ nonce: z8.string().min(1),
1672
1777
  holderSig: HexSig2
1673
1778
  });
1674
1779
  var REDEEMABLE_STATES = /* @__PURE__ */ new Set(["issued", "active"]);
@@ -1753,118 +1858,44 @@ function verifyRedemption(r, issuerPublicKey) {
1753
1858
  }
1754
1859
  }
1755
1860
 
1756
- // src/passes/client.ts
1757
- function createPassesClient(opts) {
1758
- const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
1759
- if (!fetchImpl)
1760
- throw new Error("createPassesClient: no fetch implementation available");
1761
- const baseUrl = opts.baseUrl.replace(/\/$/, "");
1762
- async function call(method, path, body, parser) {
1763
- const init2 = {
1764
- method,
1765
- headers: {
1766
- "content-type": "application/json",
1767
- accept: "application/json"
1768
- }
1769
- };
1770
- if (body !== void 0) init2.body = JSON.stringify(body);
1771
- const resp = await fetchImpl(`${baseUrl}${path}`, init2);
1772
- const text = await resp.text();
1773
- let raw = void 0;
1774
- if (text) {
1775
- try {
1776
- raw = JSON.parse(text);
1777
- } catch {
1778
- }
1779
- }
1780
- if (!resp.ok) {
1781
- const code = (raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : void 0) ?? `http_${resp.status}`;
1782
- const message = (raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : void 0) ?? `request failed with status ${resp.status}`;
1783
- throw new FlurApiError(resp.status, code, message, raw);
1784
- }
1785
- return parser(raw);
1786
- }
1787
- return {
1788
- issuePass: (input) => call("POST", "/v1/passes", input, (raw) => PassSchema.parse(raw)),
1789
- listPasses: (input) => {
1790
- const qs = new URLSearchParams();
1791
- if (input.holderDeviceId) qs.set("holderDeviceId", input.holderDeviceId);
1792
- if (input.holderUserId) qs.set("holderUserId", input.holderUserId);
1793
- if (input.state) qs.set("state", input.state);
1794
- if (input.kind) qs.set("kind", input.kind);
1795
- if (input.templateId) qs.set("templateId", input.templateId);
1796
- if (typeof input.limit === "number") qs.set("limit", String(input.limit));
1797
- if (input.cursor) qs.set("cursor", input.cursor);
1798
- const path = `/v1/passes${qs.size > 0 ? `?${qs.toString()}` : ""}`;
1799
- return call("GET", path, void 0, (raw) => {
1800
- const obj = raw;
1801
- const items = obj.items.map(
1802
- (it) => PassSchema.parse(it)
1803
- );
1804
- const nextCursor = typeof obj.nextCursor === "string" ? obj.nextCursor : null;
1805
- return { items, nextCursor };
1806
- });
1807
- },
1808
- getPass: (passId) => call(
1809
- "GET",
1810
- `/v1/passes/${encodeURIComponent(passId)}`,
1811
- void 0,
1812
- (raw) => PassSchema.parse(raw)
1813
- ),
1814
- redeemPass: (passId, redemption) => call(
1815
- "POST",
1816
- `/v1/passes/${encodeURIComponent(passId)}/redeem`,
1817
- RedemptionSchema.parse(redemption),
1818
- (raw) => PassSchema.parse(raw)
1819
- ),
1820
- revokePass: (passId, input) => call(
1821
- "POST",
1822
- `/v1/passes/${encodeURIComponent(passId)}/revoke`,
1823
- input,
1824
- (raw) => PassSchema.parse(raw)
1825
- ),
1826
- verifyPass: (pass, issuerPublicKey) => verifyPass(pass, issuerPublicKey)
1827
- };
1828
- }
1829
-
1830
1861
  // src/receipts/receipt.ts
1831
- import { z as z8 } from "zod";
1862
+ import { z as z9 } from "zod";
1832
1863
  var RECEIPT_CHANNELS = ["cash", "pass"];
1833
1864
  var RECEIPT_KINDS = RECEIPT_CHANNELS;
1834
- var HexString3 = (length) => z8.string().regex(
1865
+ var HexString3 = (length) => z9.string().regex(
1835
1866
  new RegExp(`^[0-9a-fA-F]{${length * 2}}$`),
1836
1867
  `expected ${length}-byte hex string`
1837
1868
  );
1838
- var ReceiptPayloadSchema = z8.record(
1839
- z8.union([z8.string(), z8.number(), z8.boolean(), z8.null()])
1869
+ var ReceiptPayloadSchema = z9.record(
1870
+ z9.union([z9.string(), z9.number(), z9.boolean(), z9.null()])
1840
1871
  );
1841
- var ReceiptSchema = z8.object({
1842
- receiptId: z8.string().min(1),
1843
- channel: z8.enum(RECEIPT_CHANNELS),
1872
+ var ReceiptSchema = z9.object({
1873
+ receiptId: z9.string().min(1),
1874
+ channel: z9.enum(RECEIPT_CHANNELS),
1844
1875
  /** Cash-channel: send_intents.id. Required when channel === 'cash'. */
1845
- intentId: z8.string().min(1).optional(),
1876
+ intentId: z9.string().min(1).optional(),
1846
1877
  /** Pass-channel: pass_redemptions.id. Required when channel === 'pass'. */
1847
- passRedemptionId: z8.string().min(1).optional(),
1848
- payerUserId: z8.string().min(1),
1849
- payeeUserId: z8.string().min(1),
1850
- amountKobo: z8.number().int().nonnegative(),
1851
- currency: z8.string().min(3).max(8),
1852
- issuedAtMs: z8.number().int().nonnegative(),
1853
- issuerId: z8.string().min(1),
1878
+ passRedemptionId: z9.string().min(1).optional(),
1879
+ payerUserId: z9.string().min(1),
1880
+ payeeUserId: z9.string().min(1),
1881
+ amountKobo: z9.number().int().nonnegative(),
1882
+ currency: z9.string().min(3).max(8),
1883
+ issuedAtMs: z9.number().int().nonnegative(),
1884
+ issuerId: z9.string().min(1),
1854
1885
  payload: ReceiptPayloadSchema,
1855
1886
  issuerSig: HexString3(64)
1856
1887
  }).superRefine((v, ctx) => {
1857
1888
  if (v.channel === "cash") {
1858
1889
  if (!v.intentId) {
1859
1890
  ctx.addIssue({
1860
- code: z8.ZodIssueCode.custom,
1891
+ code: z9.ZodIssueCode.custom,
1861
1892
  message: "cash receipts require intentId",
1862
1893
  path: ["intentId"]
1863
1894
  });
1864
1895
  }
1865
1896
  if (v.passRedemptionId) {
1866
1897
  ctx.addIssue({
1867
- code: z8.ZodIssueCode.custom,
1898
+ code: z9.ZodIssueCode.custom,
1868
1899
  message: "cash receipts must not carry passRedemptionId",
1869
1900
  path: ["passRedemptionId"]
1870
1901
  });
@@ -1872,14 +1903,14 @@ var ReceiptSchema = z8.object({
1872
1903
  } else if (v.channel === "pass") {
1873
1904
  if (!v.passRedemptionId) {
1874
1905
  ctx.addIssue({
1875
- code: z8.ZodIssueCode.custom,
1906
+ code: z9.ZodIssueCode.custom,
1876
1907
  message: "pass receipts require passRedemptionId",
1877
1908
  path: ["passRedemptionId"]
1878
1909
  });
1879
1910
  }
1880
1911
  if (v.intentId) {
1881
1912
  ctx.addIssue({
1882
- code: z8.ZodIssueCode.custom,
1913
+ code: z9.ZodIssueCode.custom,
1883
1914
  message: "pass receipts must not carry intentId",
1884
1915
  path: ["intentId"]
1885
1916
  });
@@ -1937,6 +1968,114 @@ function verifyReceipt(r, issuerPublicKey) {
1937
1968
  }
1938
1969
  }
1939
1970
 
1971
+ // src/passes/client.ts
1972
+ function createPassesClient(opts) {
1973
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
1974
+ if (!fetchImpl)
1975
+ throw new Error("createPassesClient: no fetch implementation available");
1976
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
1977
+ async function call(method, path, body, parser) {
1978
+ const init2 = {
1979
+ method,
1980
+ headers: {
1981
+ "content-type": "application/json",
1982
+ accept: "application/json"
1983
+ }
1984
+ };
1985
+ if (body !== void 0) init2.body = JSON.stringify(body);
1986
+ const resp = await fetchImpl(`${baseUrl}${path}`, init2);
1987
+ const text = await resp.text();
1988
+ let raw = void 0;
1989
+ if (text) {
1990
+ try {
1991
+ raw = JSON.parse(text);
1992
+ } catch {
1993
+ }
1994
+ }
1995
+ if (!resp.ok) {
1996
+ const code = (raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : void 0) ?? `http_${resp.status}`;
1997
+ const message = (raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : void 0) ?? `request failed with status ${resp.status}`;
1998
+ throw new FlurApiError(resp.status, code, message, raw);
1999
+ }
2000
+ return parser(raw);
2001
+ }
2002
+ return {
2003
+ issuePass: (input) => call("POST", "/v1/passes", input, (raw) => PassSchema.parse(raw)),
2004
+ listPasses: (input) => {
2005
+ const qs = new URLSearchParams();
2006
+ if (input.holderDeviceId) qs.set("holderDeviceId", input.holderDeviceId);
2007
+ if (input.holderUserId) qs.set("holderUserId", input.holderUserId);
2008
+ if (input.state) qs.set("state", input.state);
2009
+ if (input.kind) qs.set("kind", input.kind);
2010
+ if (input.templateId) qs.set("templateId", input.templateId);
2011
+ if (typeof input.limit === "number") qs.set("limit", String(input.limit));
2012
+ if (input.cursor) qs.set("cursor", input.cursor);
2013
+ const path = `/v1/passes${qs.size > 0 ? `?${qs.toString()}` : ""}`;
2014
+ return call("GET", path, void 0, (raw) => {
2015
+ const obj = raw;
2016
+ const items = obj.items.map(
2017
+ (it) => PassSchema.parse(it)
2018
+ );
2019
+ const nextCursor = typeof obj.nextCursor === "string" ? obj.nextCursor : null;
2020
+ return { items, nextCursor };
2021
+ });
2022
+ },
2023
+ getPass: (passId) => call(
2024
+ "GET",
2025
+ `/v1/passes/${encodeURIComponent(passId)}`,
2026
+ void 0,
2027
+ (raw) => PassSchema.parse(raw)
2028
+ ),
2029
+ redeemPass: (passId, redemption) => call(
2030
+ "POST",
2031
+ `/v1/passes/${encodeURIComponent(passId)}/redeem`,
2032
+ RedemptionSchema.parse(redemption),
2033
+ (raw) => {
2034
+ const obj = raw;
2035
+ return {
2036
+ pass: PassSchema.parse(raw),
2037
+ redemptionId: typeof obj.redemptionId === "string" ? obj.redemptionId : ""
2038
+ };
2039
+ }
2040
+ ).then((result) => result.pass),
2041
+ redeemPassDetailed: (passId, redemption) => call(
2042
+ "POST",
2043
+ `/v1/passes/${encodeURIComponent(passId)}/redeem`,
2044
+ RedemptionSchema.parse(redemption),
2045
+ (raw) => {
2046
+ const obj = raw;
2047
+ return {
2048
+ pass: PassSchema.parse(raw),
2049
+ redemptionId: typeof obj.redemptionId === "string" ? obj.redemptionId : ""
2050
+ };
2051
+ }
2052
+ ),
2053
+ redeemPassWithReceipt: (passId, redemption, receipt) => call(
2054
+ "POST",
2055
+ `/v1/passes/${encodeURIComponent(passId)}/redeem-with-receipt`,
2056
+ {
2057
+ redemption: RedemptionSchema.parse(redemption),
2058
+ receipt
2059
+ },
2060
+ (raw) => {
2061
+ const obj = raw;
2062
+ return {
2063
+ pass: PassSchema.parse(obj.pass),
2064
+ redemptionId: typeof obj.redemptionId === "string" ? obj.redemptionId : "",
2065
+ receipt: ReceiptSchema.parse(obj.receipt)
2066
+ };
2067
+ }
2068
+ ),
2069
+ revokePass: (passId, input) => call(
2070
+ "POST",
2071
+ `/v1/passes/${encodeURIComponent(passId)}/revoke`,
2072
+ input,
2073
+ (raw) => PassSchema.parse(raw)
2074
+ ),
2075
+ verifyPass: (pass, issuerPublicKey) => verifyPass(pass, issuerPublicKey)
2076
+ };
2077
+ }
2078
+
1940
2079
  // src/receipts/client.ts
1941
2080
  function createReceiptsClient(opts) {
1942
2081
  const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
@@ -2093,8 +2232,315 @@ function init(opts) {
2093
2232
  receipts
2094
2233
  };
2095
2234
  }
2235
+
2236
+ // src/accounts/client.ts
2237
+ import { z as z10 } from "zod";
2238
+ var ACCOUNT_TYPES = ["personal", "business", "partner"];
2239
+ var ACCOUNT_STATUSES = ["active", "suspended", "closed"];
2240
+ var MEMBERSHIP_ROLES = ["owner", "admin", "driver", "staff"];
2241
+ var AccountSchema = z10.object({
2242
+ accountId: z10.string().uuid(),
2243
+ type: z10.enum(ACCOUNT_TYPES),
2244
+ displayName: z10.string().min(1),
2245
+ status: z10.enum(ACCOUNT_STATUSES),
2246
+ ownerUserId: z10.string().uuid().nullable(),
2247
+ createdAtMs: z10.number().int().nonnegative()
2248
+ });
2249
+ var AccountMembershipSchema = z10.object({
2250
+ accountId: z10.string().uuid(),
2251
+ userId: z10.string().uuid(),
2252
+ role: z10.enum(MEMBERSHIP_ROLES),
2253
+ createdAtMs: z10.number().int().nonnegative()
2254
+ });
2255
+ function createAccountsClient(opts) {
2256
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
2257
+ if (!fetchImpl) {
2258
+ throw new Error("createAccountsClient: no fetch implementation available");
2259
+ }
2260
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
2261
+ async function call(method, path, body, parser) {
2262
+ const init2 = {
2263
+ method,
2264
+ headers: {
2265
+ "content-type": "application/json",
2266
+ accept: "application/json"
2267
+ }
2268
+ };
2269
+ if (body !== void 0) init2.body = JSON.stringify(body);
2270
+ const resp = await fetchImpl(`${baseUrl}${path}`, init2);
2271
+ const text = await resp.text();
2272
+ let raw = void 0;
2273
+ if (text) {
2274
+ try {
2275
+ raw = JSON.parse(text);
2276
+ } catch {
2277
+ }
2278
+ }
2279
+ if (!resp.ok) {
2280
+ const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
2281
+ const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
2282
+ throw new FlurApiError(resp.status, code, message, raw);
2283
+ }
2284
+ return parser(raw);
2285
+ }
2286
+ const itemsSchema = z10.object({ items: z10.array(AccountSchema) });
2287
+ const memberItemsSchema = z10.object({
2288
+ items: z10.array(AccountMembershipSchema)
2289
+ });
2290
+ return {
2291
+ listMyAccounts: () => call(
2292
+ "GET",
2293
+ "/v1/accounts/me",
2294
+ void 0,
2295
+ (raw) => itemsSchema.parse(raw)
2296
+ ),
2297
+ getAccount: (accountId) => call(
2298
+ "GET",
2299
+ `/v1/accounts/${encodeURIComponent(accountId)}`,
2300
+ void 0,
2301
+ (raw) => AccountSchema.parse(raw)
2302
+ ),
2303
+ listMembers: (accountId) => call(
2304
+ "GET",
2305
+ `/v1/accounts/${encodeURIComponent(accountId)}/members`,
2306
+ void 0,
2307
+ (raw) => memberItemsSchema.parse(raw)
2308
+ ),
2309
+ createBusinessAccount: (input) => call("POST", "/v1/accounts", input, (raw) => AccountSchema.parse(raw)),
2310
+ addMember: (accountId, input) => call(
2311
+ "POST",
2312
+ `/v1/accounts/${encodeURIComponent(accountId)}/members`,
2313
+ input,
2314
+ (raw) => AccountMembershipSchema.parse(raw)
2315
+ )
2316
+ };
2317
+ }
2318
+
2319
+ // src/partner/client.ts
2320
+ import { z as z11 } from "zod";
2321
+ var PARTNER_SCOPES = [
2322
+ "passes:write",
2323
+ "passes:read",
2324
+ "passes:redeem",
2325
+ "receipts:write",
2326
+ "receipts:read",
2327
+ "offline:issue",
2328
+ "offline:settle",
2329
+ "offline:read"
2330
+ ];
2331
+ var ApiCredentialPublicSchema = z11.object({
2332
+ id: z11.string().uuid(),
2333
+ accountId: z11.string().uuid(),
2334
+ keyId: z11.string(),
2335
+ scopes: z11.array(z11.enum(PARTNER_SCOPES)),
2336
+ label: z11.string().nullable(),
2337
+ createdAtMs: z11.number().int().nonnegative(),
2338
+ lastUsedAtMs: z11.number().int().nonnegative().nullable(),
2339
+ revokedAtMs: z11.number().int().nonnegative().nullable()
2340
+ });
2341
+ var MintedApiCredentialSchema = ApiCredentialPublicSchema.extend({
2342
+ secret: z11.string().min(1)
2343
+ });
2344
+ async function getSubtle() {
2345
+ const c = globalThis.crypto;
2346
+ if (c?.subtle) return c.subtle;
2347
+ const mod = await import("crypto");
2348
+ return mod.webcrypto.subtle;
2349
+ }
2350
+ var enc = new TextEncoder();
2351
+ function bytesToHex3(buf) {
2352
+ const u = new Uint8Array(buf);
2353
+ let s = "";
2354
+ for (let i = 0; i < u.length; i++) {
2355
+ s += u[i].toString(16).padStart(2, "0");
2356
+ }
2357
+ return s;
2358
+ }
2359
+ async function sha256Hex2(input) {
2360
+ const subtle = await getSubtle();
2361
+ const data = typeof input === "string" ? enc.encode(input) : input;
2362
+ const buffer = data.byteOffset === 0 && data.byteLength === data.buffer.byteLength ? data.buffer : data.slice().buffer;
2363
+ const buf = await subtle.digest("SHA-256", buffer);
2364
+ return bytesToHex3(buf);
2365
+ }
2366
+ async function hmacSha256Hex(keyHex, message) {
2367
+ const subtle = await getSubtle();
2368
+ const key = await subtle.importKey(
2369
+ "raw",
2370
+ enc.encode(keyHex),
2371
+ { name: "HMAC", hash: "SHA-256" },
2372
+ false,
2373
+ ["sign"]
2374
+ );
2375
+ const sig = await subtle.sign("HMAC", key, enc.encode(message));
2376
+ return bytesToHex3(sig);
2377
+ }
2378
+ function defaultNonce2() {
2379
+ const c = globalThis.crypto;
2380
+ if (c?.randomUUID) return c.randomUUID();
2381
+ return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`;
2382
+ }
2383
+ function canonical(params) {
2384
+ return [
2385
+ params.method.toUpperCase(),
2386
+ params.path,
2387
+ params.ts,
2388
+ params.nonce,
2389
+ params.bodyHash
2390
+ ].join("\n");
2391
+ }
2392
+ async function signPartnerRequest(params) {
2393
+ const ts = String(Math.floor((params.nowMs ?? Date.now()) / 1e3));
2394
+ const nonce = params.nonce ?? defaultNonce2();
2395
+ const bodyData = typeof params.body === "string" ? enc.encode(params.body) : params.body ?? new Uint8Array();
2396
+ const bodyHash = await sha256Hex2(bodyData);
2397
+ const message = canonical({
2398
+ method: params.method,
2399
+ path: params.path,
2400
+ ts,
2401
+ nonce,
2402
+ bodyHash
2403
+ });
2404
+ const signingKey = await sha256Hex2(params.secret);
2405
+ const sig = await hmacSha256Hex(signingKey, message);
2406
+ return {
2407
+ authorization: `Flur-Hmac key=${params.keyId}, ts=${ts}, nonce=${nonce}, sig=${sig}`,
2408
+ ts,
2409
+ nonce,
2410
+ bodyHash
2411
+ };
2412
+ }
2413
+ function createFlurPartnerClient(opts) {
2414
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
2415
+ if (!fetchImpl) {
2416
+ throw new Error(
2417
+ "createFlurPartnerClient: no fetch implementation available"
2418
+ );
2419
+ }
2420
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
2421
+ async function makeSignedInit(method, path, body) {
2422
+ const sig = await signPartnerRequest({
2423
+ keyId: opts.keyId,
2424
+ secret: opts.secret,
2425
+ method,
2426
+ path,
2427
+ body: body ?? "",
2428
+ nowMs: opts.nowMs?.(),
2429
+ nonce: opts.nonce?.()
2430
+ });
2431
+ const headers = {
2432
+ authorization: sig.authorization,
2433
+ accept: "application/json"
2434
+ };
2435
+ if (body !== void 0) headers["content-type"] = "application/json";
2436
+ const init2 = { method, headers };
2437
+ if (body !== void 0) init2.body = body;
2438
+ return init2;
2439
+ }
2440
+ async function request(opts2) {
2441
+ const bodyStr = opts2.body === void 0 ? void 0 : JSON.stringify(opts2.body);
2442
+ const init2 = await makeSignedInit(opts2.method, opts2.path, bodyStr);
2443
+ const resp = await fetchImpl(`${baseUrl}${opts2.path}`, init2);
2444
+ const text = await resp.text();
2445
+ let raw;
2446
+ if (text) {
2447
+ try {
2448
+ raw = JSON.parse(text);
2449
+ } catch {
2450
+ }
2451
+ }
2452
+ if (!resp.ok) {
2453
+ const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
2454
+ const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
2455
+ throw new FlurApiError(resp.status, code, message, raw);
2456
+ }
2457
+ return raw;
2458
+ }
2459
+ const fetchLike = async (input, init2) => {
2460
+ const url = typeof input === "string" ? input : input.toString();
2461
+ const u = new URL(url, baseUrl);
2462
+ const path = `${u.pathname}${u.search}`;
2463
+ const method = (init2?.method ?? "GET").toUpperCase();
2464
+ let bodyStr;
2465
+ if (init2?.body) {
2466
+ bodyStr = typeof init2.body === "string" ? init2.body : init2.body.toString();
2467
+ }
2468
+ const signed = await makeSignedInit(method, path, bodyStr);
2469
+ return fetchImpl(`${baseUrl}${path}`, {
2470
+ ...init2,
2471
+ ...signed,
2472
+ headers: {
2473
+ ...init2?.headers,
2474
+ ...signed.headers
2475
+ }
2476
+ });
2477
+ };
2478
+ return { request, fetch: fetchLike };
2479
+ }
2480
+ function createApiCredentialsAdminClient(opts) {
2481
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
2482
+ if (!fetchImpl) {
2483
+ throw new Error(
2484
+ "createApiCredentialsAdminClient: no fetch implementation available"
2485
+ );
2486
+ }
2487
+ const baseUrl = opts.baseUrl.replace(/\/$/, "");
2488
+ async function call(method, path, body, parser) {
2489
+ const init2 = {
2490
+ method,
2491
+ headers: {
2492
+ "content-type": "application/json",
2493
+ accept: "application/json"
2494
+ }
2495
+ };
2496
+ if (body !== void 0) init2.body = JSON.stringify(body);
2497
+ const resp = await fetchImpl(`${baseUrl}${path}`, init2);
2498
+ const text = await resp.text();
2499
+ let raw;
2500
+ if (text) {
2501
+ try {
2502
+ raw = JSON.parse(text);
2503
+ } catch {
2504
+ }
2505
+ }
2506
+ if (!resp.ok) {
2507
+ const code = raw && typeof raw === "object" && "code" in raw && typeof raw.code === "string" ? raw.code : `http_${resp.status}`;
2508
+ const message = raw && typeof raw === "object" && "message" in raw && typeof raw.message === "string" ? raw.message : `request failed with status ${resp.status}`;
2509
+ throw new FlurApiError(resp.status, code, message, raw);
2510
+ }
2511
+ return parser(raw);
2512
+ }
2513
+ const listSchema = z11.object({
2514
+ items: z11.array(ApiCredentialPublicSchema)
2515
+ });
2516
+ return {
2517
+ list: (accountId) => call(
2518
+ "GET",
2519
+ `/v1/accounts/${accountId}/api-credentials`,
2520
+ void 0,
2521
+ (raw) => listSchema.parse(raw)
2522
+ ),
2523
+ mint: (accountId, input) => call(
2524
+ "POST",
2525
+ `/v1/accounts/${accountId}/api-credentials`,
2526
+ input,
2527
+ (raw) => MintedApiCredentialSchema.parse(raw)
2528
+ ),
2529
+ revoke: (accountId, credentialId) => call(
2530
+ "DELETE",
2531
+ `/v1/accounts/${accountId}/api-credentials/${credentialId}`,
2532
+ void 0,
2533
+ (raw) => ApiCredentialPublicSchema.parse(raw)
2534
+ )
2535
+ };
2536
+ }
2096
2537
  export {
2538
+ ACCOUNT_STATUSES,
2539
+ ACCOUNT_TYPES,
2097
2540
  ADDITIONAL_DATA_SUBFIELD,
2541
+ AccountMembershipSchema,
2542
+ AccountSchema,
2543
+ ApiCredentialPublicSchema,
2098
2544
  FIELD,
2099
2545
  FlurApiError,
2100
2546
  FlurCapExceededError,
@@ -2102,6 +2548,8 @@ export {
2102
2548
  FlurError,
2103
2549
  FlurExpiredError,
2104
2550
  FlurReplayError,
2551
+ MEMBERSHIP_ROLES,
2552
+ MintedApiCredentialSchema,
2105
2553
  NGN_CURRENCY_CODE,
2106
2554
  NG_COUNTRY_CODE,
2107
2555
  NQRParseError,
@@ -2111,18 +2559,23 @@ export {
2111
2559
  OAC_DEFAULT_VALIDITY_MS,
2112
2560
  OfflinePaymentAuthorizationSchema,
2113
2561
  OfflinePaymentRequestSchema,
2562
+ OfflineTokenSchema,
2563
+ PARTNER_SCOPES,
2114
2564
  PASS_KINDS,
2115
2565
  PASS_STATES,
2116
2566
  PAYLOAD_FORMAT_INDICATOR_VALUE,
2117
2567
  POINT_OF_INITIATION,
2118
2568
  PassMetadataSchema,
2119
2569
  PassSchema,
2570
+ PaymentClaimSchema,
2120
2571
  RECEIPT_CHANNELS,
2121
2572
  RECEIPT_KINDS,
2122
2573
  REPLAY_WINDOW_MS,
2123
2574
  ReceiptPayloadSchema,
2124
2575
  ReceiptSchema,
2125
2576
  RedemptionSchema,
2577
+ SettleResponseSchema,
2578
+ SettlementSchema,
2126
2579
  bodySha256Hex,
2127
2580
  buildAuthorization,
2128
2581
  buildOAC,
@@ -2133,10 +2586,15 @@ export {
2133
2586
  canonicalJSONBytes,
2134
2587
  canonicalJSONStringify,
2135
2588
  canonicalRequestString,
2589
+ computeEncounterId,
2136
2590
  constantTimeEqual,
2137
2591
  crc16ccitt,
2138
2592
  crc16ccittHex,
2593
+ createAccountsClient,
2594
+ createApiCredentialsAdminClient,
2595
+ createFlurPartnerClient,
2139
2596
  createHmacFetch,
2597
+ createOfflineSettlementsClient,
2140
2598
  createPassesClient,
2141
2599
  createReceiptsClient,
2142
2600
  decodeAuthorizationQR,
@@ -2164,6 +2622,7 @@ export {
2164
2622
  signAuthorization,
2165
2623
  signCanonical,
2166
2624
  signOAC,
2625
+ signPartnerRequest,
2167
2626
  signPass,
2168
2627
  signPaymentRequest,
2169
2628
  signReceipt,