@siglume/direct-request-payment 0.4.15 → 0.4.17

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,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.17 - 2026-06-19
4
+
5
+ Public-surface cleanup for the v0.4.15 external review.
6
+
7
+ - Classified signed `direct_payment.confirmed` webhooks with unsupported
8
+ `data.mode` as `unknown` with `unsupported_confirmation_mode`, instead of
9
+ failing raw webhook parsing.
10
+ - Added stricter TypeScript settlement/open-period summary types and public
11
+ webhook amount/threshold fields.
12
+ - Rejected unsafe API base URLs and webhook callback URLs in both TypeScript
13
+ and Python helpers.
14
+ - Added release workflow checks that tag names match package versions before
15
+ publishing.
16
+ - Aligned README and docs wording around the buyer / provider / token / pricing
17
+ band exposure scope and fixed JPY / USD threshold wording.
18
+
19
+ ## 0.4.16 - 2026-06-19
20
+
21
+ SDRP Micro / Nano terminal-risk and idempotency hardening release.
22
+
23
+ - Added public settlement batch fields for terminal provider accounting:
24
+ `terminal_provider_receivable_minor`,
25
+ `uncollectible_provider_receivable_minor`,
26
+ `written_off_provider_receivable_minor`, `terminal_status`,
27
+ `terminal_marked_at`, and `terminal_reason_code`.
28
+ - Documented operator terminal states `uncollectible` and `written_off` after
29
+ past-due manual review. These amounts are not settled, unsettled, or past-due
30
+ receivable.
31
+ - Documented merchant setup risk acceptance receipt
32
+ `merchant_account.metadata_jsonb.metered_risk_acceptance`.
33
+ - Documented fail-closed idempotency behavior:
34
+ `IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD` for reused keys with a
35
+ different metered input payload.
36
+
3
37
  ## 0.4.15 - 2026-06-19
4
38
 
5
39
  Corrective SDRP Micro / Nano public-surface release.
package/README.md CHANGED
@@ -24,7 +24,7 @@ reflects this 402 lineage.
24
24
  Use this package when an external EC site, booking service, membership service,
25
25
  or paid API wants to accept Siglume wallet payments without taking custody of
26
26
  customer funds. The SDK creates and verifies one-time and recurring wallet
27
- payments; it does not hold customer funds or wallets.
27
+ payment authorizations; it does not hold customer funds or wallets.
28
28
 
29
29
  **Current public beta scope.** SDRP currently settles JPYC / USDC on **Polygon
30
30
  PoS only**. The public SDK does not expose chain selection, cross-chain payment,
@@ -199,11 +199,11 @@ setup, and after that the applied fee and the settlement timing follow the
199
199
  - **Standard Payment** — most payments. Your selected plan's percentage fee,
200
200
  settled on-chain immediately after each payment confirms.
201
201
  - **Micro Payment** — small payments, applied automatically by amount. A flat
202
- per-SDRP-Tx protocol fee, settled weekly or earlier when the buyer/payee
203
- batch reaches JPY 10,000 / USD 100.00.
202
+ per-SDRP-Tx protocol fee, settled weekly or earlier when the same buyer /
203
+ provider / token / pricing band reaches JPY 10,000 / USD 100.00.
204
204
  - **Nano Payment** — very small payments, applied automatically by amount. A
205
- flat per-SDRP-Tx protocol fee, settled monthly or earlier when the buyer/payee
206
- batch reaches JPY 10,000 / USD 100.00.
205
+ flat per-SDRP-Tx protocol fee, settled monthly or earlier when the same buyer
206
+ / provider / token / pricing band reaches JPY 10,000 / USD 100.00.
207
207
 
208
208
  Here, `Tx` means one accepted SDRP payment, not the later on-chain settlement
209
209
  transaction. Micro / Nano settlement batches are aggregated on-chain after the
@@ -223,6 +223,10 @@ aggregated settlement model whenever they offer amounts in these bands. If a
223
223
  product cannot fulfill before provider revenue is settled, keep the price in the
224
224
  Standard band; in practice, do not offer JPY 500-and-under or USD 3-and-under
225
225
  items for that product.
226
+ Self-service setup records this acceptance in
227
+ `merchant_account.metadata_jsonb.metered_risk_acceptance`, including
228
+ `terms_version`, `accepted_at`, `principal_user_id`, `receipt_id`, and fixed
229
+ market thresholds `JPY: 10000` / `USD: 10000`.
226
230
  Micro / Nano budget checks reserve spending capacity only; they do not lock,
227
231
  escrow, or guarantee the buyer's wallet balance, allowance, or settlement funds.
228
232
  Sub-minor-unit Nano fees are accumulated with decimal precision, but they are
@@ -273,10 +277,11 @@ Pricing has one structure: choose a Standard Payment plan, then Siglume applies
273
277
  the fee for each payment by amount. Micro / Nano are automatic amount bands, not
274
278
  extra setup choices.
275
279
 
276
- Both launch settlement currencies are first-class: JPY settled in JPYC, and USD
277
- settled in USDC. A merchant settles in one currency, chosen at onboarding. The
278
- settlement fee percentage is identical in both currencies; only the flat
279
- amounts differ.
280
+ Both launch settlement currencies are first-class where enabled: JPY settled in
281
+ JPYC, and USD settled in USDC. Some accounts may require agreed USD/USDC terms
282
+ before USD is enabled. A merchant settles in one currency, chosen at
283
+ onboarding. The settlement fee percentage is identical in both currencies; only
284
+ the flat amounts differ.
280
285
 
281
286
  | Public one-time payment amount | Applied automatically | What you select | Fee | Settlement |
282
287
  | --- | --- | --- | --- | --- |
package/dist/index.cjs CHANGED
@@ -83,7 +83,7 @@ var DIRECT_REQUEST_PAYMENT_RECEIPT_KIND = "sdrp_direct_payment";
83
83
  var DIRECT_REQUEST_PAYMENT_ALLOWANCE_RECEIPT_KIND = "sdrp_direct_payment_allowance";
84
84
  var DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE = "sdrp_direct_payment_requirement";
85
85
  var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
86
- var DIRECT_REQUEST_PAYMENT_SDK_VERSION = "0.4.15";
86
+ var DIRECT_REQUEST_PAYMENT_SDK_VERSION = "0.4.17";
87
87
  var DIRECT_REQUEST_PAYMENT_STANDARD_SETTLED_STATUS = "settled";
88
88
  var DIRECT_REQUEST_PAYMENT_METERED_ACCEPTED_STATUS = "pending_settlement";
89
89
  var DIRECT_REQUEST_PAYMENT_STANDARD_FINALITY = "per_payment_onchain";
@@ -126,7 +126,7 @@ var SiglumeWebhookPayloadError = class extends SiglumeDirectRequestPaymentError
126
126
  }
127
127
  };
128
128
  var DirectRequestPaymentClient = class {
129
- auth_token;
129
+ #authToken;
130
130
  base_url;
131
131
  timeout_ms;
132
132
  user_agent;
@@ -142,8 +142,8 @@ var DirectRequestPaymentClient = class {
142
142
  if (!fetchImpl) {
143
143
  throw new SiglumeDirectRequestPaymentError("A fetch implementation is required in this runtime.");
144
144
  }
145
- this.auth_token = authToken;
146
- this.base_url = (options.base_url ?? envValue("SIGLUME_API_BASE") ?? DEFAULT_SIGLUME_API_BASE).replace(/\/+$/, "");
145
+ this.#authToken = authToken;
146
+ this.base_url = normalizeApiBaseUrl(options.base_url ?? envValue("SIGLUME_API_BASE") ?? DEFAULT_SIGLUME_API_BASE);
147
147
  this.timeout_ms = Math.max(1, Math.trunc(options.timeout_ms ?? 15e3));
148
148
  this.user_agent = options.user_agent ?? `@siglume/direct-request-payment/${DIRECT_REQUEST_PAYMENT_SDK_VERSION}`;
149
149
  this.fetch_impl = fetchImpl;
@@ -246,7 +246,7 @@ var DirectRequestPaymentClient = class {
246
246
  try {
247
247
  const headers = {
248
248
  "Accept": "application/json",
249
- "Authorization": `Bearer ${this.auth_token}`,
249
+ "Authorization": `Bearer ${this.#authToken}`,
250
250
  "User-Agent": this.user_agent
251
251
  };
252
252
  let body;
@@ -278,7 +278,7 @@ var DirectRequestPaymentClient = class {
278
278
  }
279
279
  };
280
280
  var DirectRequestPaymentMerchantClient = class {
281
- auth_token;
281
+ #authToken;
282
282
  base_url;
283
283
  timeout_ms;
284
284
  user_agent;
@@ -294,8 +294,8 @@ var DirectRequestPaymentMerchantClient = class {
294
294
  if (!fetchImpl) {
295
295
  throw new SiglumeDirectRequestPaymentError("A fetch implementation is required in this runtime.");
296
296
  }
297
- this.auth_token = authToken;
298
- this.base_url = (options.base_url ?? envValue("SIGLUME_API_BASE") ?? DEFAULT_SIGLUME_API_BASE).replace(/\/+$/, "");
297
+ this.#authToken = authToken;
298
+ this.base_url = normalizeApiBaseUrl(options.base_url ?? envValue("SIGLUME_API_BASE") ?? DEFAULT_SIGLUME_API_BASE);
299
299
  this.timeout_ms = Math.max(1, Math.trunc(options.timeout_ms ?? 15e3));
300
300
  this.user_agent = options.user_agent ?? `@siglume/direct-request-payment/${DIRECT_REQUEST_PAYMENT_SDK_VERSION}`;
301
301
  this.fetch_impl = fetchImpl;
@@ -313,7 +313,7 @@ var DirectRequestPaymentMerchantClient = class {
313
313
  payload.allowed_currencies = normalizeAllowedCurrencies(input.allowed_currencies);
314
314
  }
315
315
  if (input.webhook_callback_url !== void 0) {
316
- payload.webhook_callback_url = requireNonEmpty(input.webhook_callback_url, "webhook_callback_url");
316
+ payload.webhook_callback_url = normalizeHttpsUrl(input.webhook_callback_url, "webhook_callback_url");
317
317
  }
318
318
  if (input.billing_mandate_cap_minor !== void 0) {
319
319
  payload.billing_mandate_cap_minor = positiveInteger(input.billing_mandate_cap_minor, "billing_mandate_cap_minor");
@@ -393,7 +393,7 @@ var DirectRequestPaymentMerchantClient = class {
393
393
  }
394
394
  async createWebhookSubscription(input) {
395
395
  const payload = {
396
- callback_url: requireNonEmpty(input.callback_url, "callback_url"),
396
+ callback_url: normalizeHttpsUrl(input.callback_url, "callback_url"),
397
397
  event_types: input.event_types?.length ? input.event_types.map((eventType) => requireNonEmpty(eventType, "event_type")) : ["direct_payment.confirmed", "direct_payment.spent"]
398
398
  };
399
399
  if (input.description !== void 0) {
@@ -436,7 +436,7 @@ var DirectRequestPaymentMerchantClient = class {
436
436
  try {
437
437
  const headers = {
438
438
  "Accept": "application/json",
439
- "Authorization": `Bearer ${this.auth_token}`,
439
+ "Authorization": `Bearer ${this.#authToken}`,
440
440
  "User-Agent": this.user_agent
441
441
  };
442
442
  let body;
@@ -674,11 +674,6 @@ function parseDirectRequestPaymentWebhookEvent(payload) {
674
674
  occurred_at: requireNonEmpty(stringOrNull(event.occurred_at) ?? "", "webhook occurred_at"),
675
675
  data: { ...data }
676
676
  };
677
- if (parsed.type === "direct_payment.confirmed" && !DIRECT_REQUEST_PAYMENT_CONFIRMED_WEBHOOK_MODES.has(String(parsed.data.mode ?? ""))) {
678
- throw new SiglumeWebhookPayloadError(
679
- "direct_payment.confirmed webhook must carry a supported Direct Request Payment mode."
680
- );
681
- }
682
677
  return parsed;
683
678
  }
684
679
  function classifyDirectPaymentConfirmation(event) {
@@ -689,6 +684,7 @@ function classifyDirectPaymentConfirmation(event) {
689
684
  const settlementCadence = stringOrNull(data.settlement_cadence);
690
685
  const finality = stringOrNull(data.finality);
691
686
  const settlementStatus = stringOrNull(data.settlement_status);
687
+ const mode = stringOrNull(data.mode);
692
688
  if (event.type !== "direct_payment.confirmed") {
693
689
  return {
694
690
  kind: "unknown",
@@ -703,7 +699,21 @@ function classifyDirectPaymentConfirmation(event) {
703
699
  finality
704
700
  };
705
701
  }
706
- if (data.mode === "metered_settlement_batch") {
702
+ if (!DIRECT_REQUEST_PAYMENT_CONFIRMED_WEBHOOK_MODES.has(mode ?? "")) {
703
+ return {
704
+ kind: "unknown",
705
+ event,
706
+ data,
707
+ reason: "unsupported_confirmation_mode",
708
+ requirement_id: requirementId,
709
+ settlement_batch_id: stringOrNull(data.settlement_batch_id),
710
+ pricing_band: pricingBand,
711
+ settlement_cadence: settlementCadence,
712
+ settlement_status: settlementStatus,
713
+ finality
714
+ };
715
+ }
716
+ if (mode === "metered_settlement_batch") {
707
717
  const settlementBatchId = stringOrNull(data.settlement_batch_id);
708
718
  const chainReceiptId = stringOrNull(data.chain_receipt_id);
709
719
  const usageEventDigest = stringOrNull(data.usage_event_digest);
@@ -875,6 +885,38 @@ function normalizeAllowedCurrencies(value) {
875
885
  function defaultTokenForCurrency(currency) {
876
886
  return currency === "JPY" ? "JPYC" : "USDC";
877
887
  }
888
+ function normalizeApiBaseUrl(value) {
889
+ let url;
890
+ try {
891
+ url = new URL(requireNonEmpty(value, "base_url"));
892
+ } catch {
893
+ throw new SiglumeDirectRequestPaymentError("base_url must be an absolute URL such as https://siglume.com/v1.");
894
+ }
895
+ if (url.username || url.password) {
896
+ throw new SiglumeDirectRequestPaymentError("base_url must not include userinfo.");
897
+ }
898
+ if (!isAllowedCheckoutOriginScheme(url)) {
899
+ throw new SiglumeDirectRequestPaymentError(
900
+ "base_url must use https, except http is allowed for localhost, 127.0.0.1, or [::1]."
901
+ );
902
+ }
903
+ return url.toString().replace(/\/+$/, "");
904
+ }
905
+ function normalizeHttpsUrl(value, name) {
906
+ let url;
907
+ try {
908
+ url = new URL(requireNonEmpty(value, name));
909
+ } catch {
910
+ throw new SiglumeDirectRequestPaymentError(`${name} must be an absolute https URL.`);
911
+ }
912
+ if (url.username || url.password) {
913
+ throw new SiglumeDirectRequestPaymentError(`${name} must not include userinfo.`);
914
+ }
915
+ if (url.protocol !== "https:" || !url.hostname) {
916
+ throw new SiglumeDirectRequestPaymentError(`${name} must use https.`);
917
+ }
918
+ return url.toString();
919
+ }
878
920
  function normalizeOriginList(value) {
879
921
  if (!Array.isArray(value)) {
880
922
  throw new SiglumeDirectRequestPaymentError("checkout_allowed_origins must be an array of origin URLs.");