@siglume/direct-request-payment 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,80 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 - 2026-06-18
4
+
5
+ Hosted Checkout for human web shoppers ("Pay with Siglume"). The two buyer
6
+ systems are now both first-class: AI agents pay through the API/tools (unchanged)
7
+ and humans pay through a Siglume-hosted checkout page.
8
+
9
+ - **`DirectRequestPaymentMerchantClient.createCheckoutSession(...)`** (TS + Py):
10
+ create a single-use, expiring Hosted Checkout session. Siglume authors the
11
+ challenge server-side and returns a `checkout_url`; redirect the shopper there.
12
+ The shopper logs into Siglume, approves, and pays from their own Siglume wallet
13
+ (JPYC / USDC), then returns to your `success_url`. Fulfill on the existing
14
+ `direct_payment.confirmed` webhook — the source of truth — exactly as with the
15
+ agent flow. The merchant SDK still does not authenticate the buyer.
16
+ - **`getCheckoutSession(session_id)`** (TS + Py): read a session's status
17
+ (`open` / `authenticated` / `paid` / `expired` / `cancelled` / `failed`).
18
+ - **`checkout_allowed_origins`** added to `setupMerchant` / `setupCheckout`: a
19
+ return-URL origin allowlist (open-redirect defense). `success_url` /
20
+ `cancel_url` must be on a registered origin; the `webhook_callback_url` origin
21
+ is auto-allowed.
22
+ - Docs: documented the **two buyer systems** (human Web = Hosted Checkout; AI
23
+ agent / AtoA = direct API / tools), the AtoA **prerequisite** that the buyer
24
+ agent is pre-connected to Siglume (MCP/OAuth, or a custom app holding the
25
+ buyer's Siglume JWT), and the merchant / Siglume / buyer **boundaries** —
26
+ including that the buyer needs a Siglume wallet and this is **not** a card
27
+ payment.
28
+
29
+ No wire-format changes to existing challenges, requirements, or webhooks; 0.3.x
30
+ clients interoperate unchanged. Hosted Checkout is gated server-side and is a
31
+ purely additive surface.
32
+
33
+ ## 0.3.6 - 2026-06-18
34
+
35
+ Documentation and public-surface cleanup release. No wire-format or API changes;
36
+ challenges and clients from 0.3.x interoperate unchanged.
37
+
38
+ - Documented the Micro / Nano **settlement schedule** in the README and the
39
+ pricing guide: Micro settles weekly, Nano settles monthly, with the closing
40
+ period, timezone, revenue-recognition point, retry / carry-over, and
41
+ rejected / no-charge behavior spelled out. The exact close time, default
42
+ timezone, and settlement lag are marked platform-managed — the platform
43
+ response is the source of truth.
44
+ - Clarified that Micro / Nano provider revenue stays unsettled until the weekly /
45
+ monthly on-chain settlement succeeds, that failed settlements are retried and
46
+ may go past due, and that Siglume does not advance, guarantee, or insure unpaid
47
+ amounts.
48
+ - Reframed the docs as a standalone SDRP payment SDK. Internal implementation
49
+ language was removed from the public surface: the legacy `external_402` mode
50
+ value and the `free` Launch-tier key are now isolated in a single
51
+ "Compatibility Notes" section, and internal batch / ledger terms were dropped.
52
+ - Hardened the examples: they no longer print returned secrets (`setup.env`) and
53
+ no longer return raw `error.message` to the client.
54
+ - Removed `external-402` from the npm / PyPI keywords; added `sdrp`,
55
+ `direct-request-payment`, `micropayments`, `metered-billing`, `jpyc`, `usdc`.
56
+ - Hardened `.gitignore` (`.env`, `.env.*`, `.npmrc`, `.pypirc`, `.venv/`,
57
+ `coverage/`) and removed a developer-specific path from `RELEASING.md`.
58
+
59
+ ## 0.3.5 - 2026-06-18
60
+
61
+ - Docs: protocol-first README framing for the SDRP Direct Request Payment SDK.
62
+
63
+ ## 0.3.4 - 2026-06-18
64
+
65
+ - Docs: clarified the SDRP pricing structure — a Standard plan is selected, and
66
+ Micro / Nano are applied automatically by amount — across the README and
67
+ pricing guide.
68
+
69
+ ## 0.3.3 - 2026-06-18
70
+
71
+ - Docs: SDRP direct-payment framing across the README, API reference, merchant
72
+ quickstart, pricing, and security guides.
73
+
74
+ ## 0.3.2 - 2026-06-18
75
+
76
+ - Docs: documented the SDRP Micro / Nano amount-band boundaries.
77
+
3
78
  ## 0.3.1 - 2026-06-12
4
79
 
5
80
  - Docs: scheduled autopay (`cadence: "daily"`) is documented as an approval
package/README.md CHANGED
@@ -5,24 +5,15 @@
5
5
 
6
6
  ## Protocol Overview
7
7
 
8
- Siglume Direct Request Payment is an SDRP payment protocol for products that
9
- want to accept Siglume wallet payments. The merchant fixes the order, amount,
10
- and currency on its server; the buyer pays with a Siglume wallet; Siglume
11
- applies the correct pricing and settlement path from the payment amount and
12
- execution conditions.
13
-
14
- During merchant setup, only the **Standard Payment plan** is selected. Micro
15
- Payment and Nano Payment are not separate choices for the merchant or buyer.
16
- They are applied automatically by amount.
8
+ Siglume Direct Request Payment (SDRP) is a wallet payment protocol for products
9
+ that want to accept Siglume wallet payments. The merchant fixes the order,
10
+ amount, and currency on its server; the buyer pays with a Siglume wallet;
11
+ Siglume applies the correct pricing and settlement path from the payment amount.
17
12
 
18
13
  Use this package when an external EC site, booking service, membership service,
19
14
  or paid API wants to accept Siglume wallet payments without taking custody of
20
- customer funds. This SDK is for external merchants integrating Siglume Direct
21
- Request Payment into their own checkout. It is not the server contract for SDRP
22
- Micro Payment or Nano Payment.
23
-
24
- The current platform payload still uses the internal mode name `external_402`;
25
- this SDK sets that value for you when creating a payment requirement.
15
+ customer funds. The SDK creates and verifies one-time and recurring wallet
16
+ payments; it does not hold customer funds or wallets.
26
17
 
27
18
  Payment requirement creation must run in the authenticated buyer's Siglume
28
19
  context. Your merchant server must not use a merchant secret or API key to
@@ -34,7 +25,114 @@ token for setup. `DirectRequestPaymentClient` requires the buyer's Siglume
34
25
  bearer token for payment requirements. Do not use a Developer Portal `cli_` API
35
26
  key with this package.
36
27
 
37
- The canonical HTTP endpoints live under `/v1/sdrp/direct-payments/...`.
28
+ ## Two Kinds of Buyer
29
+
30
+ SDRP serves two kinds of buyer, and you integrate each differently. In both
31
+ cases the buyer pays from a **Siglume wallet** (JPYC for JPY, USDC for USD) — it
32
+ is **not** a card payment — and your **merchant SDK never authenticates the
33
+ buyer**.
34
+
35
+ 1. **Human web shopper → Hosted Checkout.** When a person clicks "Pay with
36
+ Siglume" on your site, call
37
+ [`createCheckoutSession(...)`](#hosted-checkout-human-web-shoppers) and
38
+ redirect them to the returned `checkout_url`. They sign into Siglume (passkey
39
+ or email code — the login *is* the wallet), review the amount, approve once,
40
+ and pay from their own wallet, then return to your `success_url`. This is the
41
+ Stripe-Checkout-equivalent path.
42
+
43
+ 2. **AI agent / agent-to-agent (AtoA) → direct API / tools.** An autonomous
44
+ buyer agent pays through `DirectRequestPaymentClient` (your app holds the
45
+ buyer's Siglume JWT) or through the Siglume marketplace tool
46
+ `market_confirm_direct_payment_and_execute` (MCP).
47
+
48
+ **Prerequisite (important):** agent payment assumes the buyer agent is
49
+ **already connected to Siglume before the payment**. An AI client
50
+ (Claude / ChatGPT / Cursor, …) connects through the **Siglume MCP server
51
+ (OAuth authorization, with a consent screen)**; a custom app holds the
52
+ buyer's **Siglume bearer token (JWT)**. Either way a Siglume authentication
53
+ context must be established before paying — the merchant SDK does not log the
54
+ buyer in. Unattended runs are bounded by Siglume's **approval gates / spending
55
+ budgets** (per-run / daily / monthly auto-pay budgets, or Works approval), not
56
+ by the merchant.
57
+
58
+ Honest framing: the part that integrates quickly is the **merchant plumbing**
59
+ (challenge or checkout session + webhook). Human web payment still requires the
60
+ shopper to have — or create — a Siglume wallet and pay from it; it is not a
61
+ card-style "instant" checkout for first-time buyers.
62
+
63
+ ## Hosted Checkout (Human Web Shoppers)
64
+
65
+ Hosted Checkout is a Siglume-hosted page that turns a "Pay with Siglume" button
66
+ into a completed wallet payment, then returns the shopper to your store. It
67
+ orchestrates the same rails as the agent flow — there is no new money movement
68
+ and the merchant fulfills on the same `direct_payment.confirmed` webhook.
69
+
70
+ ```ts
71
+ import { DirectRequestPaymentMerchantClient } from "@siglume/direct-request-payment";
72
+
73
+ const merchant = new DirectRequestPaymentMerchantClient({ auth_token: process.env.SIGLUME_MERCHANT_AUTH_TOKEN });
74
+
75
+ // 1. Register the return-URL origins once (open-redirect defense). The origin of
76
+ // your webhook_callback_url is auto-allowed in addition to these.
77
+ await merchant.setupMerchant({
78
+ merchant: "your_merchant_key",
79
+ webhook_callback_url: "https://api.your-shop.com/webhooks/siglume",
80
+ checkout_allowed_origins: ["https://www.your-shop.com"],
81
+ });
82
+
83
+ // 2. Per order: create a session and redirect the shopper to checkout_url.
84
+ const session = await merchant.createCheckoutSession({
85
+ merchant: "your_merchant_key",
86
+ amount_minor: 500, // server-fixed; the browser cannot change it
87
+ currency: "JPY",
88
+ nonce: order.id, // unique per order
89
+ success_url: "https://www.your-shop.com/thanks",
90
+ cancel_url: "https://www.your-shop.com/cart",
91
+ metadata: { order_id: order.id },
92
+ });
93
+ redirect(session.checkout_url); // -> https://siglume.com/pay/<session_id>
94
+
95
+ // 3. Fulfill when the signed direct_payment.confirmed webhook arrives (the
96
+ // source of truth). Poll merchant.getCheckoutSession(session.session_id) if
97
+ // you also want to show status in your own UI.
98
+ ```
99
+
100
+ Siglume fixes the amount, currency, challenge, and return URLs **server-side** at
101
+ session creation, so the browser cannot tamper with the price or the redirect
102
+ target. The shopper's Siglume credentials are never shared with your store.
103
+
104
+ **Who does what.**
105
+
106
+ - **Merchant** — confirms the order; signs the challenge (agent flow) or creates
107
+ a checkout session (web flow); verifies the webhook signature; fulfills
108
+ idempotently. Never sees the buyer's Siglume credentials.
109
+ - **Siglume** — provides the wallet and login, executes the wallet payment,
110
+ applies the fee, settles on-chain, and routes Micro / Nano automatically by
111
+ amount band.
112
+ - **Buyer** — needs a Siglume wallet funded in **JPYC / USDC**. **Not a card
113
+ payment.**
114
+
115
+ ## Amount-Based Pricing and Settlement
116
+
117
+ Pricing has one structure: you choose a **Standard Payment** plan once during
118
+ setup, and after that the applied fee and the settlement timing follow the
119
+ **payment amount** automatically. There is nothing else to choose.
120
+
121
+ - **Standard Payment** — most payments. Your selected plan's percentage fee,
122
+ settled on-chain immediately after each payment confirms.
123
+ - **Micro Payment** — small payments, applied automatically by amount. A flat
124
+ per-transaction protocol fee, **settled weekly**.
125
+ - **Nano Payment** — very small payments, applied automatically by amount. A
126
+ flat per-usage protocol fee, **settled monthly**.
127
+
128
+ Micro Payment and Nano Payment are not separate products you opt into; they are
129
+ amount bands Siglume applies on your behalf. Your integration code is the same
130
+ regardless of which band a payment falls into. The full fee table and the exact
131
+ weekly / monthly settlement schedule are in [docs/pricing.md](./docs/pricing.md).
132
+ Provider revenue in the Micro and Nano bands is not settled revenue until the
133
+ weekly or monthly on-chain settlement succeeds. Siglume keeps outstanding failed
134
+ settlements for retry under the published policy, but does not advance or
135
+ guarantee provider revenue before settlement succeeds.
38
136
 
39
137
  ## What This SDK Covers
40
138
 
@@ -78,15 +176,15 @@ amounts differ.
78
176
 
79
177
  | Payment amount | Applied automatically | What you select | Fee | Settlement |
80
178
  | --- | --- | --- | --- | --- |
81
- | 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. | Immediate on-chain split through DirectPaymentHub after payment confirmation |
82
- | JPY 50-500 / about USD 0.30-3.00 | Micro Payment | No selection. Applied automatically by amount. | USD 0.01 / Tx, about JPY 2 | Meter gate before provider execution; weekly delayed settlement |
83
- | Under JPY 1 to JPY 49 / under USD 0.01 to about USD 0.30 | Nano Payment | No selection. Applied automatically by amount. | USD 0.001 / usage, about JPY 0.2 | Meter gate before provider execution; monthly delayed settlement |
179
+ | 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 |
180
+ | JPY 50-500 / about USD 0.30-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) |
181
+ | Under JPY 1 to JPY 49 / under USD 0.01 to about 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) |
84
182
 
85
183
  A merchant billing mandate is required before accepting payments, even on the
86
- Launch plan. The API and merchant registry may still expose the internal plan key
87
- `free` for the Launch tier; treat it as a compatibility key, not a public plan
88
- name. `fee_bps` returned on a payment requirement is the authoritative fee rate
89
- for that payment in the merchant's settlement currency.
184
+ Launch plan. `fee_bps` returned on a payment requirement is the authoritative
185
+ fee rate for that payment in the merchant's settlement currency. The full fee
186
+ table and the weekly / monthly settlement schedule live in
187
+ [docs/pricing.md](./docs/pricing.md).
90
188
 
91
189
  ## Merchant Setup: One SDK Call
92
190
 
@@ -110,12 +208,12 @@ const setup = await merchant.setupCheckout({
110
208
  max_amount_minor: 100000,
111
209
  });
112
210
 
113
- console.log(setup.env);
114
- // {
115
- // SIGLUME_DIRECT_PAYMENT_MERCHANT: "example_merchant",
116
- // SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET: "edrp_...",
117
- // SIGLUME_WEBHOOK_SECRET: "whsec_..."
118
- // }
211
+ // setup.env holds the merchant key plus the challenge and webhook secrets:
212
+ // SIGLUME_DIRECT_PAYMENT_MERCHANT (not secret)
213
+ // SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET (secret)
214
+ // SIGLUME_WEBHOOK_SECRET (secret)
215
+ // Write these to your server-side secret store. Do NOT log the secret values.
216
+ console.log(`Configured merchant: ${setup.env.SIGLUME_DIRECT_PAYMENT_MERCHANT}`);
119
217
  ```
120
218
 
121
219
  ```py
@@ -136,7 +234,9 @@ setup = merchant.setup_checkout(
136
234
  max_amount_minor=100000,
137
235
  )
138
236
 
139
- print(setup["env"])
237
+ # setup["env"] holds the merchant key plus the challenge and webhook secrets.
238
+ # Persist them to your server-side secret store; do not log the secret values.
239
+ print("Configured merchant:", setup["env"]["SIGLUME_DIRECT_PAYMENT_MERCHANT"])
140
240
  ```
141
241
 
142
242
  Store returned secrets on the merchant server. `challenge_secret` and
@@ -289,7 +389,8 @@ const recurring = await createDirectRequestPaymentRecurringChallenge({
289
389
  // POST /v1/sdrp/direct-payments/subscriptions
290
390
  // { merchant, amount_minor, currency, cadence: "monthly", challenge }
291
391
  // For scheduled autopay, the buyer instead creates a scheduled auto-pay
292
- // authorization (mode: "external_402") and gives you the schedule_token.
392
+ // authorization and hands you the schedule_token; your scheduler triggers
393
+ // each occurrence with that token.
293
394
  ```
294
395
 
295
396
  ```py
@@ -383,6 +484,16 @@ Read [docs/security.md](./docs/security.md) before going live.
383
484
  - Fulfill orders only from verified webhook data, with idempotency.
384
485
  - Treat `fee_bps` returned by Siglume as the runtime fee source of truth.
385
486
 
487
+ ## Compatibility Notes
488
+
489
+ - The Direct Request Payment HTTP endpoints live under
490
+ `/v1/sdrp/direct-payments/...`; the SDK targets them for you.
491
+ - For wire compatibility the platform still tags these payments with the legacy
492
+ mode value `external_402`, and the merchant registry may still expose the
493
+ legacy billing-plan key `free` for the Launch tier. The SDK sets and reads
494
+ these values for you — treat them as compatibility identifiers, not public
495
+ product names.
496
+
386
497
  ## Documentation
387
498
 
388
499
  - [Merchant quickstart](./docs/merchant-quickstart.md)
package/dist/index.cjs CHANGED
@@ -125,7 +125,7 @@ var DirectRequestPaymentClient = class {
125
125
  this.auth_token = authToken;
126
126
  this.base_url = (options.base_url ?? envValue("SIGLUME_API_BASE") ?? DEFAULT_SIGLUME_API_BASE).replace(/\/+$/, "");
127
127
  this.timeout_ms = Math.max(1, Math.trunc(options.timeout_ms ?? 15e3));
128
- this.user_agent = options.user_agent ?? "@siglume/direct-request-payment/0.3.5";
128
+ this.user_agent = options.user_agent ?? "@siglume/direct-request-payment/0.4.0";
129
129
  this.fetch_impl = fetchImpl;
130
130
  }
131
131
  async createPaymentRequirement(input) {
@@ -230,7 +230,7 @@ var DirectRequestPaymentMerchantClient = class {
230
230
  this.auth_token = authToken;
231
231
  this.base_url = (options.base_url ?? envValue("SIGLUME_API_BASE") ?? DEFAULT_SIGLUME_API_BASE).replace(/\/+$/, "");
232
232
  this.timeout_ms = Math.max(1, Math.trunc(options.timeout_ms ?? 15e3));
233
- this.user_agent = options.user_agent ?? "@siglume/direct-request-payment/0.3.5";
233
+ this.user_agent = options.user_agent ?? "@siglume/direct-request-payment/0.4.0";
234
234
  this.fetch_impl = fetchImpl;
235
235
  }
236
236
  async setupMerchant(input) {
@@ -254,8 +254,47 @@ var DirectRequestPaymentMerchantClient = class {
254
254
  if (input.max_amount_minor !== void 0) {
255
255
  payload.max_amount_minor = positiveInteger(input.max_amount_minor, "max_amount_minor");
256
256
  }
257
+ if (input.checkout_allowed_origins !== void 0) {
258
+ payload.checkout_allowed_origins = normalizeOriginList(input.checkout_allowed_origins);
259
+ }
257
260
  return this.request("POST", "/sdrp/direct-payments/merchants", payload);
258
261
  }
262
+ /**
263
+ * Create a Hosted Checkout session (Stripe-Checkout-equivalent for human web
264
+ * shoppers). Siglume authors the challenge server-side, persists a single-use
265
+ * expiring session, and returns a `checkout_url`. Redirect the shopper there;
266
+ * they log into Siglume, approve, and pay from their own wallet, then return
267
+ * to your `success_url`. Fulfill on the `direct_payment.confirmed` webhook
268
+ * (the source of truth), exactly as with the agent flow.
269
+ *
270
+ * `success_url`/`cancel_url` must be on an origin you registered via
271
+ * `checkout_allowed_origins` (or your `webhook_callback_url` origin).
272
+ */
273
+ async createCheckoutSession(input) {
274
+ const payload = {
275
+ merchant: normalizeSelfServiceMerchant(input.merchant),
276
+ amount_minor: positiveInteger(input.amount_minor, "amount_minor"),
277
+ currency: normalizeCurrency(input.currency),
278
+ nonce: requireNonEmpty(input.nonce, "nonce"),
279
+ success_url: requireNonEmpty(input.success_url, "success_url"),
280
+ cancel_url: requireNonEmpty(input.cancel_url, "cancel_url")
281
+ };
282
+ if (input.metadata !== void 0) {
283
+ payload.metadata = cloneJsonObject(input.metadata, "metadata");
284
+ }
285
+ return this.request(
286
+ "POST",
287
+ "/sdrp/direct-payments/checkout-sessions",
288
+ payload
289
+ );
290
+ }
291
+ /** Read a Hosted Checkout session's status (open / authenticated / paid / expired / cancelled / failed). */
292
+ async getCheckoutSession(session_id) {
293
+ return this.request(
294
+ "GET",
295
+ `/sdrp/direct-payments/checkout-sessions/${encodeURIComponent(requireNonEmpty(session_id, "session_id"))}`
296
+ );
297
+ }
259
298
  async getMerchant(merchant) {
260
299
  return this.request(
261
300
  "GET",
@@ -624,6 +663,29 @@ function normalizeAllowedCurrencies(value) {
624
663
  function defaultTokenForCurrency(currency) {
625
664
  return currency === "JPY" ? "JPYC" : "USDC";
626
665
  }
666
+ function normalizeOriginList(value) {
667
+ if (!Array.isArray(value)) {
668
+ throw new SiglumeDirectRequestPaymentError("checkout_allowed_origins must be an array of origin URLs.");
669
+ }
670
+ const seen = /* @__PURE__ */ new Set();
671
+ const origins = [];
672
+ for (const item of value) {
673
+ let url;
674
+ try {
675
+ url = new URL(requireNonEmpty(String(item), "checkout_allowed_origins entry"));
676
+ } catch {
677
+ throw new SiglumeDirectRequestPaymentError(
678
+ "each checkout_allowed_origins entry must be an absolute origin such as https://shop.example.com."
679
+ );
680
+ }
681
+ const origin = `${url.protocol.toLowerCase()}//${url.host.toLowerCase()}`;
682
+ if (!seen.has(origin)) {
683
+ seen.add(origin);
684
+ origins.push(origin);
685
+ }
686
+ }
687
+ return origins;
688
+ }
627
689
  function positiveInteger(value, name) {
628
690
  const parsed = Number(value);
629
691
  if (!Number.isSafeInteger(parsed) || parsed <= 0) {