@siglume/direct-request-payment 0.4.3 → 0.4.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.5 - 2026-06-19
4
+
5
+ Patch release for the public beta re-review.
6
+
7
+ - Added `cursor` support to TypeScript and Python metered statement list
8
+ helpers, with tests that fetch a second page.
9
+ - Added missing TypeScript settlement batch retry fields
10
+ (`attempt_count`, `next_attempt_at`) and narrowed metered minor amount fields
11
+ to decimal strings.
12
+ - Clarified the public idempotency contract: one-time requirement creation uses
13
+ the challenge nonce / `challenge_hash` / `request_hash_v2`; the SDK does not
14
+ expose an unsupported requirement `idempotency_key`.
15
+ - Clarified Micro / Nano rounding, usage CSV `rounding_delta_minor`, provider
16
+ statement auth roles, and Standard vs aggregated on-chain receipt wording.
17
+
18
+ ## 0.4.4 - 2026-06-19
19
+
20
+ Correctness and security hardening release for the SDRP Direct Request Payment
21
+ SDK manual and public helpers.
22
+
23
+ - Clarified that Standard / Micro / Nano are selected by amount, removed the
24
+ unsupported "force Standard for immediate finality" implication, and changed
25
+ Hosted Checkout Standard examples to Standard-band amounts.
26
+ - Removed the Express sample route that accepted a buyer `Authorization` header
27
+ on the merchant server; human web checkout now redirects through Hosted
28
+ Checkout, while agent payment remains buyer-side direct API/tool work.
29
+ - Documented Micro / Nano decimal fee rounding, `rounding_delta_minor`, budget
30
+ reservation versus token locking, no guaranteed `past_due` collection, HTTP
31
+ result accounting, and operational status handling.
32
+ - Added typed TypeScript and named Python helpers for Micro / Nano statement
33
+ APIs.
34
+ - Added `request_hash_v2` helpers/docs and documented the new
35
+ requirement/webhook machine fields for pricing band, settlement cadence,
36
+ finality, protocol fee, and settlement status.
37
+ - Hardened TS/Python integer and `checkout_allowed_origins` validation.
38
+
3
39
  ## 0.4.3 - 2026-06-19
4
40
 
5
41
  Documentation-only release for SDRP Micro / Nano operations.
package/README.md CHANGED
@@ -22,8 +22,9 @@ buyer-facing Siglume payment flow creates and pays the requirement.
22
22
 
23
23
  `DirectRequestPaymentMerchantClient` requires the merchant's Siglume bearer
24
24
  token for setup. `DirectRequestPaymentClient` requires the buyer's Siglume
25
- bearer token for payment requirements. Do not use a Developer Portal `cli_` API
26
- key with this package.
25
+ bearer token for payment requirements and buyer statements, or the provider /
26
+ merchant user's Siglume bearer token for provider statements. Do not use a
27
+ Developer Portal `cli_` API key with this package.
27
28
 
28
29
  ## Two Kinds of Buyer
29
30
 
@@ -89,7 +90,7 @@ await merchant.setupMerchant({
89
90
  // 2. Per order: create a session and redirect the shopper to checkout_url.
90
91
  const session = await merchant.createCheckoutSession({
91
92
  merchant: "your_merchant_key",
92
- amount_minor: 500, // server-fixed; the browser cannot change it
93
+ amount_minor: 1200, // server-fixed; the browser cannot change it
93
94
  currency: "JPY",
94
95
  nonce: order.id, // unique per order
95
96
  success_url: "https://www.your-shop.com/thanks",
@@ -121,7 +122,7 @@ merchant.setup_merchant(
121
122
  # 2. Per order: create a session and redirect the shopper to checkout_url.
122
123
  session = merchant.create_checkout_session(
123
124
  merchant="your_merchant_key",
124
- amount_minor=500, # server-fixed; the browser cannot change it
125
+ amount_minor=1200, # server-fixed; the browser cannot change it
125
126
  currency="JPY",
126
127
  nonce=order["id"], # unique per order
127
128
  success_url="https://www.your-shop.com/thanks",
@@ -183,6 +184,16 @@ Provider revenue in the Micro and Nano bands is not settled revenue until the
183
184
  weekly or monthly on-chain settlement succeeds. Siglume keeps outstanding failed
184
185
  settlements for retry under the published policy, but does not advance or
185
186
  guarantee provider revenue before settlement succeeds.
187
+ Micro / Nano budget checks reserve spending capacity only; they do not lock,
188
+ escrow, or guarantee the buyer's wallet balance, allowance, or settlement funds.
189
+ Sub-minor-unit Nano fees are accumulated with decimal precision and rounded only
190
+ when a settlement batch is created; see [Pricing](./docs/pricing.md) for the
191
+ rounding formula and `rounding_delta_minor` semantics.
192
+ For low-count Nano batches, integer-token settlement can make the effective
193
+ buyer burden per usage higher than the headline USD 0.001 protocol fee; the
194
+ difference is reported as batch `rounding_delta_minor`. Treat Micro / Nano
195
+ minor amounts as decimal strings and use a decimal library or `Decimal` for
196
+ accounting.
186
197
  For operational reconciliation, expected revenue, settled revenue, retry state,
187
198
  and CSV exports, see
188
199
  [docs/metered-statements.md](./docs/metered-statements.md).
@@ -230,14 +241,20 @@ amounts differ.
230
241
 
231
242
  | Payment amount | Applied automatically | What you select | Fee | Settlement |
232
243
  | --- | --- | --- | --- | --- |
233
- | Over JPY 500 / over USD 3.00, or whenever immediate finality is required | Standard Payment | Select one Standard plan: Launch, Starter, Growth, or Pro | Launch: JPY 0 / USD 0 monthly, 1.8%; Starter: JPY 980 / USD 6 monthly, 1.0%; Growth: JPY 2,980 / USD 18 monthly, 0.7%; Pro: JPY 9,800 / USD 60 monthly, 0.5%. Minimum JPY 30 / USD 0.20 per payment. | Settled on-chain immediately after the payment confirms |
244
+ | Over JPY 500 / over USD 3.00 | Standard Payment | Select one Standard plan: Launch, Starter, Growth, or Pro | Launch: JPY 0 / USD 0 monthly, 1.8%; Starter: JPY 980 / USD 6 monthly, 1.0%; Growth: JPY 2,980 / USD 18 monthly, 0.7%; Pro: JPY 9,800 / USD 60 monthly, 0.5%. Minimum JPY 30 / USD 0.20 per payment. | Settled on-chain immediately after the payment confirms |
234
245
  | JPY 50-500 / over USD 0.30 and up to USD 3.00 | Micro Payment | Applied automatically by amount | USD 0.01 / Tx, about JPY 2 | Weekly settlement - see [Settlement schedule](./docs/pricing.md#settlement-schedule) |
235
246
  | Under JPY 50 / up to USD 0.30 | Nano Payment | Applied automatically by amount | USD 0.001 / usage, about JPY 0.2 | Monthly settlement - see [Settlement schedule](./docs/pricing.md#settlement-schedule) |
236
247
 
237
248
  A merchant billing mandate is required before accepting payments, even on the
238
- Launch plan. `fee_bps` returned on a payment requirement is the authoritative
239
- fee rate for that payment in the merchant's settlement currency. The full fee
240
- table and the weekly / monthly settlement schedule live in
249
+ Launch plan. The current public API does not expose a flag that forces a
250
+ JPY 500-and-under / USD 3-and-under payment into Standard immediate settlement.
251
+ If immediate on-chain settlement is a hard requirement, price the item in the
252
+ Standard band or confirm a merchant-specific contract with Siglume before
253
+ launch. For Standard Payment, `fee_bps` returned on a payment requirement is the
254
+ authoritative fee rate for that payment in the merchant's settlement currency.
255
+ For Micro / Nano, the statement APIs expose `protocol_fee_minor`,
256
+ `gross_buyer_debit_minor`, `buyer_debit_minor`, and `rounding_delta_minor`.
257
+ The full fee table and the weekly / monthly settlement schedule live in
241
258
  [docs/pricing.md](./docs/pricing.md). Statement APIs for "how much was used,
242
259
  when will it close, when can it debit, and what is settled" are documented in
243
260
  [docs/metered-statements.md](./docs/metered-statements.md).
@@ -347,7 +364,7 @@ The nonce must not contain `:` because the current platform challenge format is
347
364
 
348
365
  ## Buyer Payment Flow
349
366
 
350
- Use `DirectRequestPaymentClient` only with the authenticated buyer's Siglume
367
+ Use `DirectRequestPaymentClient` here with the authenticated buyer's Siglume
351
368
  bearer token. `SIGLUME_AUTH_TOKEN` may be used in server-side payment-confirmation
352
369
  helpers; `SIGLUME_API_KEY` and Developer Portal `cli_` keys are not accepted.
353
370
 
@@ -516,6 +533,11 @@ if verified["event"]["type"] == "direct_payment.confirmed":
516
533
  pass
517
534
  ```
518
535
 
536
+ New `direct_payment.confirmed` payloads include `pricing_band`,
537
+ `settlement_cadence`, `finality`, `protocol_fee_minor`, `settlement_status`, and
538
+ when available `request_hash_v2`. Use these machine fields instead of inferring
539
+ settlement semantics from the event name alone.
540
+
519
541
  ## Security Rules
520
542
 
521
543
  - Keep the challenge secret on the merchant server only.
@@ -538,7 +560,8 @@ Read [docs/security.md](./docs/security.md) before going live.
538
560
  - Store the returned `SIGLUME_WEBHOOK_SECRET` only on the merchant server.
539
561
  - Persist `challenge_hash`, `requirement_id`, and fulfillment state per order.
540
562
  - Fulfill orders only from verified webhook data, with idempotency.
541
- - Treat `fee_bps` returned by Siglume as the runtime fee source of truth.
563
+ - Treat `fee_bps` returned by Siglume as the Standard Payment runtime fee source
564
+ of truth; use statement API amount fields for Micro / Nano.
542
565
 
543
566
  ## Compatibility Notes
544
567
 
package/dist/index.cjs CHANGED
@@ -38,6 +38,7 @@ __export(src_exports, {
38
38
  DIRECT_REQUEST_PAYMENT_RECEIPT_KIND: () => DIRECT_REQUEST_PAYMENT_RECEIPT_KIND,
39
39
  DIRECT_REQUEST_PAYMENT_RECURRING_CHALLENGE_SCHEME: () => DIRECT_REQUEST_PAYMENT_RECURRING_CHALLENGE_SCHEME,
40
40
  DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE: () => DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE,
41
+ DIRECT_REQUEST_PAYMENT_SDK_VERSION: () => DIRECT_REQUEST_PAYMENT_SDK_VERSION,
41
42
  DirectRequestPaymentClient: () => DirectRequestPaymentClient,
42
43
  DirectRequestPaymentMerchantClient: () => DirectRequestPaymentMerchantClient,
43
44
  HostedCheckoutNotAvailableError: () => HostedCheckoutNotAvailableError,
@@ -58,6 +59,7 @@ __export(src_exports, {
58
59
  createExternal402RecurringChallenge: () => createExternal402RecurringChallenge,
59
60
  directRequestPaymentChallengeHash: () => directRequestPaymentChallengeHash,
60
61
  directRequestPaymentRequestHash: () => directRequestPaymentRequestHash,
62
+ directRequestPaymentRequestHashV2: () => directRequestPaymentRequestHashV2,
61
63
  parseDirectRequestPaymentChallenge: () => parseDirectRequestPaymentChallenge,
62
64
  parseDirectRequestPaymentWebhookEvent: () => parseDirectRequestPaymentWebhookEvent,
63
65
  verifyDirectRequestPaymentChallenge: () => verifyDirectRequestPaymentChallenge,
@@ -76,6 +78,8 @@ var DIRECT_REQUEST_PAYMENT_RECEIPT_KIND = "sdrp_direct_payment";
76
78
  var DIRECT_REQUEST_PAYMENT_ALLOWANCE_RECEIPT_KIND = "sdrp_direct_payment_allowance";
77
79
  var DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE = "sdrp_direct_payment_requirement";
78
80
  var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
81
+ var DIRECT_REQUEST_PAYMENT_SDK_VERSION = "0.4.5";
82
+ var DIRECT_REQUEST_PAYMENT_CONFIRMED_WEBHOOK_MODES = /* @__PURE__ */ new Set([DIRECT_REQUEST_PAYMENT_MODE, "metered_settlement_batch"]);
79
83
  var SiglumeDirectRequestPaymentError = class extends Error {
80
84
  constructor(message) {
81
85
  super(message);
@@ -122,7 +126,7 @@ var DirectRequestPaymentClient = class {
122
126
  const authToken = options.auth_token ?? envValue("SIGLUME_AUTH_TOKEN");
123
127
  if (!authToken) {
124
128
  throw new SiglumeDirectRequestPaymentError(
125
- "A buyer Siglume bearer token is required for Direct Request Payment API calls. Developer Portal API keys are not accepted."
129
+ "A buyer or provider Siglume user bearer token is required for Direct Request Payment API calls. Developer Portal API keys are not accepted."
126
130
  );
127
131
  }
128
132
  const fetchImpl = options.fetch ?? globalThis.fetch;
@@ -132,7 +136,7 @@ var DirectRequestPaymentClient = class {
132
136
  this.auth_token = authToken;
133
137
  this.base_url = (options.base_url ?? envValue("SIGLUME_API_BASE") ?? DEFAULT_SIGLUME_API_BASE).replace(/\/+$/, "");
134
138
  this.timeout_ms = Math.max(1, Math.trunc(options.timeout_ms ?? 15e3));
135
- this.user_agent = options.user_agent ?? "@siglume/direct-request-payment/0.4.3";
139
+ this.user_agent = options.user_agent ?? `@siglume/direct-request-payment/${DIRECT_REQUEST_PAYMENT_SDK_VERSION}`;
136
140
  this.fetch_impl = fetchImpl;
137
141
  }
138
142
  async createPaymentRequirement(input) {
@@ -180,6 +184,53 @@ var DirectRequestPaymentClient = class {
180
184
  async executeAllowanceTransaction(requirement, options = {}) {
181
185
  return this.executePreparedTransaction(buildAllowanceExecutionPayload(requirement, options));
182
186
  }
187
+ async getBuyerMeteredSummary(input = {}) {
188
+ return this.request(
189
+ "GET",
190
+ meteredQueryPath("/sdrp/metered/my-summary", input)
191
+ );
192
+ }
193
+ async listBuyerUsageEvents(input = {}) {
194
+ return this.request(
195
+ "GET",
196
+ meteredQueryPath("/sdrp/metered/my-usage-events", input)
197
+ );
198
+ }
199
+ async listBuyerSettlementBatches(input = {}) {
200
+ return this.request(
201
+ "GET",
202
+ meteredQueryPath("/sdrp/metered/my-settlement-batches", input)
203
+ );
204
+ }
205
+ async getProviderMeteredSummary(input = {}) {
206
+ return this.request(
207
+ "GET",
208
+ meteredQueryPath("/sdrp/metered/provider/summary", input)
209
+ );
210
+ }
211
+ async listProviderUsageEvents(input = {}) {
212
+ return this.request(
213
+ "GET",
214
+ meteredQueryPath("/sdrp/metered/provider/usage-events", input)
215
+ );
216
+ }
217
+ async listProviderSettlementBatches(input = {}) {
218
+ return this.request(
219
+ "GET",
220
+ meteredQueryPath("/sdrp/metered/provider/settlement-batches", input)
221
+ );
222
+ }
223
+ async getProviderSettlementBatch(settlement_batch_id, input = {}) {
224
+ return this.request(
225
+ "GET",
226
+ meteredQueryPath(
227
+ `/sdrp/metered/provider/settlement-batches/${encodeURIComponent(
228
+ requireNonEmpty(settlement_batch_id, "settlement_batch_id")
229
+ )}`,
230
+ input
231
+ )
232
+ );
233
+ }
183
234
  async request(method, path, json_body) {
184
235
  const controller = new AbortController();
185
236
  const timeout = setTimeout(() => controller.abort(), this.timeout_ms);
@@ -237,7 +288,7 @@ var DirectRequestPaymentMerchantClient = class {
237
288
  this.auth_token = authToken;
238
289
  this.base_url = (options.base_url ?? envValue("SIGLUME_API_BASE") ?? DEFAULT_SIGLUME_API_BASE).replace(/\/+$/, "");
239
290
  this.timeout_ms = Math.max(1, Math.trunc(options.timeout_ms ?? 15e3));
240
- this.user_agent = options.user_agent ?? "@siglume/direct-request-payment/0.4.3";
291
+ this.user_agent = options.user_agent ?? `@siglume/direct-request-payment/${DIRECT_REQUEST_PAYMENT_SDK_VERSION}`;
241
292
  this.fetch_impl = fetchImpl;
242
293
  }
243
294
  async setupMerchant(input) {
@@ -530,6 +581,16 @@ async function directRequestPaymentRequestHash(input) {
530
581
  const material = `${normalizeMerchant(input.merchant)}${positiveInteger(input.amount_minor, "amount_minor")}${normalizeCurrency(input.currency)}${requireNonEmpty(input.challenge, "challenge")}`;
531
582
  return sha256Prefixed(material);
532
583
  }
584
+ async function directRequestPaymentRequestHashV2(input) {
585
+ const material = JSON.stringify({
586
+ amount_minor: positiveInteger(input.amount_minor, "amount_minor"),
587
+ challenge: requireNonEmpty(input.challenge, "challenge"),
588
+ currency: normalizeCurrency(input.currency),
589
+ merchant: normalizeMerchant(input.merchant),
590
+ version: 2
591
+ });
592
+ return sha256Prefixed(material);
593
+ }
533
594
  function buildPaymentExecutionPayload(requirement, options = {}) {
534
595
  return buildPreparedTransactionExecutionPayload(requirement, requirement.transaction_request, {
535
596
  receipt_kind: DIRECT_REQUEST_PAYMENT_RECEIPT_KIND,
@@ -603,8 +664,10 @@ function parseDirectRequestPaymentWebhookEvent(payload) {
603
664
  occurred_at: requireNonEmpty(stringOrNull(event.occurred_at) ?? "", "webhook occurred_at"),
604
665
  data: { ...data }
605
666
  };
606
- if (parsed.type === "direct_payment.confirmed" && parsed.data.mode !== DIRECT_REQUEST_PAYMENT_MODE) {
607
- throw new SiglumeWebhookPayloadError("direct_payment.confirmed webhook must carry data.mode='external_402'.");
667
+ if (parsed.type === "direct_payment.confirmed" && !DIRECT_REQUEST_PAYMENT_CONFIRMED_WEBHOOK_MODES.has(String(parsed.data.mode ?? ""))) {
668
+ throw new SiglumeWebhookPayloadError(
669
+ "direct_payment.confirmed webhook must carry a supported Direct Request Payment mode."
670
+ );
608
671
  }
609
672
  return parsed;
610
673
  }
@@ -658,6 +721,13 @@ function normalizeToken(value) {
658
721
  }
659
722
  return token;
660
723
  }
724
+ function normalizeMeteredPlanType(value) {
725
+ const planType = requireNonEmpty(value, "plan_type").toLowerCase();
726
+ if (planType === "micro" || planType === "nano") {
727
+ return planType;
728
+ }
729
+ throw new SiglumeDirectRequestPaymentError("plan_type must be micro or nano.");
730
+ }
661
731
  function normalizeAllowedCurrencies(value) {
662
732
  const normalized = {};
663
733
  if (Array.isArray(value)) {
@@ -695,7 +765,15 @@ function normalizeOriginList(value) {
695
765
  "each checkout_allowed_origins entry must be an absolute origin such as https://shop.example.com."
696
766
  );
697
767
  }
698
- const origin = `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}`;
768
+ if (url.username || url.password) {
769
+ throw new SiglumeDirectRequestPaymentError("checkout_allowed_origins entries must not include userinfo.");
770
+ }
771
+ if (!isAllowedCheckoutOriginScheme(url)) {
772
+ throw new SiglumeDirectRequestPaymentError(
773
+ "checkout_allowed_origins entries must use https, except http is allowed for localhost, 127.0.0.1, or [::1]."
774
+ );
775
+ }
776
+ const origin = url.origin.toLowerCase();
699
777
  if (!seen.has(origin)) {
700
778
  seen.add(origin);
701
779
  origins.push(origin);
@@ -703,8 +781,47 @@ function normalizeOriginList(value) {
703
781
  }
704
782
  return origins;
705
783
  }
784
+ function isAllowedCheckoutOriginScheme(url) {
785
+ if (url.protocol === "https:") {
786
+ return Boolean(url.hostname);
787
+ }
788
+ if (url.protocol !== "http:") {
789
+ return false;
790
+ }
791
+ const hostname = url.hostname.toLowerCase();
792
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
793
+ }
794
+ function meteredQueryPath(path, input) {
795
+ const params = new URLSearchParams();
796
+ if (input.plan_type !== void 0) {
797
+ params.set("plan_type", normalizeMeteredPlanType(input.plan_type));
798
+ }
799
+ if (input.token_symbol !== void 0) {
800
+ params.set("token_symbol", normalizeToken(input.token_symbol));
801
+ }
802
+ if ("status" in input && input.status !== void 0) {
803
+ params.set("status", requireNonEmpty(input.status, "status"));
804
+ }
805
+ if ("listing_id" in input && input.listing_id !== void 0) {
806
+ params.set("listing_id", requireNonEmpty(input.listing_id, "listing_id"));
807
+ }
808
+ if ("capability_key" in input && input.capability_key !== void 0) {
809
+ params.set("capability_key", requireNonEmpty(input.capability_key, "capability_key"));
810
+ }
811
+ if ("limit" in input && input.limit !== void 0) {
812
+ params.set("limit", String(positiveInteger(input.limit, "limit")));
813
+ }
814
+ if ("cursor" in input && input.cursor !== void 0) {
815
+ params.set("cursor", requireNonEmpty(input.cursor, "cursor"));
816
+ }
817
+ const query = params.toString();
818
+ return query ? `${path}?${query}` : path;
819
+ }
706
820
  function positiveInteger(value, name) {
707
- const parsed = Number(value);
821
+ if (typeof value !== "number") {
822
+ throw new SiglumeDirectRequestPaymentError(`${name} must be a positive safe integer.`);
823
+ }
824
+ const parsed = value;
708
825
  if (!Number.isSafeInteger(parsed) || parsed <= 0) {
709
826
  throw new SiglumeDirectRequestPaymentError(`${name} must be a positive safe integer.`);
710
827
  }