@siglume/direct-request-payment 0.3.6 → 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.
@@ -4,6 +4,32 @@ The TypeScript package is `@siglume/direct-request-payment`. The Python package
4
4
  is `siglume-direct-request-payment` and imports as
5
5
  `siglume_direct_request_payment`.
6
6
 
7
+ ## Two Buyer Systems
8
+
9
+ SDRP serves two kinds of buyer, and you integrate each differently. In both
10
+ cases the buyer pays from a Siglume wallet (JPYC for JPY, USDC for USD) — not a
11
+ card — and the merchant SDK never authenticates the buyer.
12
+
13
+ - **Human web shopper → Hosted Checkout.** Call
14
+ [`createCheckoutSession`](#createcheckoutsessioninput--create_checkout_session)
15
+ on `DirectRequestPaymentMerchantClient` and redirect the shopper to the
16
+ returned `checkout_url`. The shopper signs into Siglume on the hosted page,
17
+ approves, and pays from their own wallet.
18
+ - **AI agent / agent-to-agent (AtoA) → direct API / tools.** An autonomous
19
+ buyer agent pays through `DirectRequestPaymentClient` (your app holds the
20
+ buyer's Siglume JWT) or through the Siglume marketplace tool
21
+ `market_confirm_direct_payment_and_execute` (MCP). Agent payment assumes the
22
+ buyer agent is **already connected to Siglume before the payment**: an AI
23
+ client (Claude / ChatGPT / Cursor) connects through the Siglume MCP server
24
+ (OAuth authorization with a consent screen), or a custom app holds the buyer's
25
+ Siglume bearer token. The merchant SDK does not log the buyer in. Unattended
26
+ runs are bounded by Siglume's approval gates / spending budgets (per-run /
27
+ daily / monthly auto-pay budgets, or Works approval).
28
+
29
+ In both systems the merchant fulfills on the same signed
30
+ `direct_payment.confirmed` webhook. Hosted Checkout adds no new money movement
31
+ and no new webhook.
32
+
7
33
  ## Environment Variables
8
34
 
9
35
  | Name | Used by | Purpose |
@@ -158,6 +184,12 @@ Input:
158
184
  - `billing_currency`: `JPY`; `USD` requires agreed USD/USDC billing terms
159
185
  - `webhook_callback_url`: HTTPS callback URL for signed payment events
160
186
  - `max_amount_minor`: optional billing mandate cap
187
+ - `checkout_allowed_origins`: optional `string[]` return-URL origin allowlist for
188
+ Hosted Checkout (open-redirect defense). A Hosted Checkout `success_url` /
189
+ `cancel_url` must be on a registered origin; the origin of
190
+ `webhook_callback_url` is auto-allowed in addition. Each entry must be an
191
+ absolute origin such as `https://shop.example.com`; entries are normalized to
192
+ bare, lowercased origins and deduped.
161
193
 
162
194
  Returns:
163
195
 
@@ -178,6 +210,97 @@ POST /v1/sdrp/direct-payments/merchants
178
210
  ```
179
211
 
180
212
  Creates or updates the merchant account for the authenticated merchant user.
213
+ Accepts the optional `checkout_allowed_origins: string[]` return-URL origin
214
+ allowlist described under `setupCheckout` above; the same normalization and
215
+ webhook-origin auto-allow apply.
216
+
217
+ ### `createCheckoutSession(input)` / `create_checkout_session(...)`
218
+
219
+ Creates a single-use, expiring Hosted Checkout session for a human web shopper
220
+ and returns the URL to redirect them to. Requires the merchant's Siglume bearer
221
+ token. The server authors the challenge from the merchant's challenge secret —
222
+ the browser never sees or supplies it.
223
+
224
+ Calls:
225
+
226
+ ```text
227
+ POST /v1/sdrp/direct-payments/checkout-sessions
228
+ ```
229
+
230
+ ```ts
231
+ const session = await merchant.createCheckoutSession({
232
+ merchant: "example_merchant",
233
+ amount_minor: 500,
234
+ currency: "JPY",
235
+ nonce: order.id,
236
+ success_url: "https://www.your-shop.com/thanks",
237
+ cancel_url: "https://www.your-shop.com/cart",
238
+ metadata: { order_id: order.id },
239
+ });
240
+ ```
241
+
242
+ ```py
243
+ session = merchant.create_checkout_session(
244
+ merchant="example_merchant",
245
+ amount_minor=500,
246
+ currency="JPY",
247
+ nonce=order["id"],
248
+ success_url="https://www.your-shop.com/thanks",
249
+ cancel_url="https://www.your-shop.com/cart",
250
+ metadata={"order_id": order["id"]},
251
+ )
252
+ ```
253
+
254
+ Input:
255
+
256
+ - `merchant`: Siglume merchant key
257
+ - `amount_minor`: positive integer in minor currency units (server-fixed; the
258
+ browser cannot change it)
259
+ - `currency`: `JPY`, or `USD` when enabled for the merchant account
260
+ - `nonce`: unique per order; must not contain `:`
261
+ - `success_url`: return URL after a completed payment; must be on a registered
262
+ `checkout_allowed_origins` origin (or the webhook origin)
263
+ - `cancel_url`: return URL after the shopper cancels; same origin rule
264
+ - `metadata`: optional JSON object
265
+
266
+ Returns:
267
+
268
+ - `checkout_url`: hosted page to redirect the shopper to
269
+ (`https://siglume.com/pay/<session_id>`)
270
+ - `session_id`
271
+ - `challenge_hash`: store this on the order to map the later webhook back
272
+ - `status`
273
+ - `expires_at`: the session is single-use and expires (~30 minutes)
274
+
275
+ ### `getCheckoutSession(session_id)` / `get_checkout_session(session_id)`
276
+
277
+ Returns the current status of a Hosted Checkout session. Useful if you want to
278
+ show progress in your own UI; the signed `direct_payment.confirmed` webhook
279
+ remains the source of truth for fulfillment. Never exposes the raw challenge or
280
+ buyer PII.
281
+
282
+ Calls:
283
+
284
+ ```text
285
+ GET /v1/sdrp/direct-payments/checkout-sessions/{session_id}
286
+ ```
287
+
288
+ Returns a session status object with:
289
+
290
+ - `session_id`
291
+ - `merchant`
292
+ - `currency`
293
+ - `token_symbol`
294
+ - `amount_minor`
295
+ - `status`: one of `open`, `authenticated`, `paid`, `expired`, `cancelled`,
296
+ `failed`
297
+ - `challenge_hash`
298
+ - `requirement_id`
299
+ - `success_url`
300
+ - `cancel_url`
301
+ - `expires_at`
302
+ - `paid_at`
303
+ - `metadata_jsonb`
181
304
 
182
305
  ### `getMerchant(merchant)` / `get_merchant(merchant)`
183
306
 
@@ -20,6 +20,124 @@ a weekly / monthly cadence (see [Pricing](./pricing.md#settlement-schedule)); th
20
20
  are not browser checkout requirements you create with this SDK. Their provider
21
21
  revenue remains unsettled until the later on-chain settlement succeeds.
22
22
 
23
+ ## Two Buyer Systems
24
+
25
+ There are two ways a buyer reaches you, and you integrate each differently:
26
+
27
+ - **Human web shopper → Hosted Checkout.** Create a checkout session and
28
+ redirect the shopper to the Siglume-hosted page (the
29
+ [section below](#hosted-checkout-human-web-shoppers)). This is the path that
30
+ resembles a Stripe-style hosted checkout.
31
+ - **AI agent / agent-to-agent (AtoA) → direct API / tools.** An autonomous
32
+ buyer pays through `DirectRequestPaymentClient` or the marketplace tool
33
+ `market_confirm_direct_payment_and_execute`, as in sections 2-4 below.
34
+
35
+ In both cases the buyer pays from a Siglume wallet (JPYC / USDC, not a card),
36
+ the merchant SDK never authenticates the buyer, and you fulfill on the same
37
+ `direct_payment.confirmed` webhook.
38
+
39
+ ## Hosted Checkout (Human Web Shoppers)
40
+
41
+ When a person clicks "Pay with Siglume" on your site, create a session and
42
+ redirect them to the returned `checkout_url`. They sign into Siglume on the
43
+ hosted page, approve, and pay from their own wallet, then return to your
44
+ `success_url`. Siglume fixes the amount, currency, challenge, and return URLs
45
+ server-side, so the browser cannot tamper with the price or the redirect target.
46
+
47
+ Register your return-URL origins once (open-redirect defense). The origin of
48
+ your `webhook_callback_url` is auto-allowed in addition to these.
49
+
50
+ TypeScript:
51
+
52
+ ```ts
53
+ import { DirectRequestPaymentMerchantClient } from "@siglume/direct-request-payment";
54
+
55
+ const merchant = new DirectRequestPaymentMerchantClient({
56
+ auth_token: process.env.SIGLUME_MERCHANT_AUTH_TOKEN!,
57
+ });
58
+
59
+ // Once, at setup: register the return-URL origin allowlist.
60
+ await merchant.setupCheckout({
61
+ merchant: "example_merchant",
62
+ display_name: "Example Merchant",
63
+ billing_plan: "launch",
64
+ billing_currency: "JPY",
65
+ webhook_callback_url: "https://merchant.example/siglume/webhook",
66
+ checkout_allowed_origins: ["https://www.example.com"],
67
+ });
68
+
69
+ // Per order: create a session and redirect the shopper to checkout_url.
70
+ const session = await merchant.createCheckoutSession({
71
+ merchant: "example_merchant",
72
+ amount_minor: 500, // server-fixed; the browser cannot change it
73
+ currency: "JPY",
74
+ nonce: order.id, // unique per order
75
+ success_url: "https://www.example.com/thanks",
76
+ cancel_url: "https://www.example.com/cart",
77
+ metadata: { order_id: order.id },
78
+ });
79
+
80
+ await orders.update(order.id, {
81
+ siglume_challenge_hash: session.challenge_hash,
82
+ siglume_payment_status: "pending",
83
+ });
84
+
85
+ redirect(session.checkout_url); // -> https://siglume.com/pay/<session_id>
86
+ ```
87
+
88
+ Python:
89
+
90
+ ```py
91
+ import os
92
+
93
+ from siglume_direct_request_payment import DirectRequestPaymentMerchantClient
94
+
95
+ merchant = DirectRequestPaymentMerchantClient(
96
+ auth_token=os.environ["SIGLUME_MERCHANT_AUTH_TOKEN"],
97
+ )
98
+
99
+ # Once, at setup: register the return-URL origin allowlist.
100
+ merchant.setup_checkout(
101
+ merchant="example_merchant",
102
+ display_name="Example Merchant",
103
+ billing_plan="launch",
104
+ billing_currency="JPY",
105
+ webhook_callback_url="https://merchant.example/siglume/webhook",
106
+ checkout_allowed_origins=["https://www.example.com"],
107
+ )
108
+
109
+ # Per order: create a session and redirect the shopper to checkout_url.
110
+ session = merchant.create_checkout_session(
111
+ merchant="example_merchant",
112
+ amount_minor=500, # server-fixed; the browser cannot change it
113
+ currency="JPY",
114
+ nonce=order["id"], # unique per order
115
+ success_url="https://www.example.com/thanks",
116
+ cancel_url="https://www.example.com/cart",
117
+ metadata={"order_id": order["id"]},
118
+ )
119
+
120
+ orders.update(
121
+ order["id"],
122
+ {
123
+ "siglume_challenge_hash": session["challenge_hash"],
124
+ "siglume_payment_status": "pending",
125
+ },
126
+ )
127
+
128
+ # Redirect the shopper to session["checkout_url"]
129
+ # -> https://siglume.com/pay/<session_id>
130
+ ```
131
+
132
+ Fulfill exactly as in [section 4](#4-fulfill-from-webhook): on the signed
133
+ `direct_payment.confirmed` webhook, look up the order by `challenge_hash` and
134
+ mark it paid once. The session is single-use and expires (~30 minutes); you can
135
+ poll `getCheckoutSession` / `get_checkout_session` if you also want to show
136
+ status in your own UI, but the webhook is the source of truth. Honest framing:
137
+ the merchant plumbing integrates quickly, but human web payment still requires
138
+ the shopper to have — or create — a Siglume wallet and pay from it; it is not a
139
+ card-style "instant" checkout for first-time buyers.
140
+
23
141
  ## 1. Run Merchant Setup
24
142
 
25
143
  Run setup from the merchant server, CI, or an integration agent with the
@@ -162,6 +280,18 @@ The nonce must be unique per order payment attempt and must not contain `:`.
162
280
 
163
281
  ## 3. Buyer Creates and Pays the Requirement
164
282
 
283
+ This is the AI agent / AtoA path: the buyer pays directly through
284
+ `DirectRequestPaymentClient` (or the marketplace tool
285
+ `market_confirm_direct_payment_and_execute`), rather than through Hosted
286
+ Checkout. It assumes the buyer agent is **already connected to Siglume before
287
+ the payment**: an AI client (Claude / ChatGPT / Cursor) connects through the
288
+ Siglume MCP server (OAuth authorization with a consent screen), or a custom app
289
+ holds the buyer's Siglume bearer token (JWT). Either way a Siglume
290
+ authentication context is established first — the merchant SDK does not log the
291
+ buyer in. Unattended runs are bounded by Siglume's approval gates / spending
292
+ budgets (per-run / daily / monthly auto-pay budgets, or Works approval), not by
293
+ the merchant.
294
+
165
295
  After the buyer authenticates with Siglume, create the payment requirement with
166
296
  the buyer's Siglume bearer token. Do not use a Developer Portal `cli_` API key
167
297
  or merchant API key here.
package/docs/security.md CHANGED
@@ -49,6 +49,23 @@ autopay approval tag; it does not itself limit occurrences to once per day.
49
49
  Scheduled autopay execution is bounded by the buyer-approved per-run, daily, and
50
50
  monthly auto-pay budget.
51
51
 
52
+ ## Hosted Checkout Return URLs
53
+
54
+ Hosted Checkout adds a return-URL origin allowlist as open-redirect defense.
55
+ Register your allowed origins once via `checkout_allowed_origins` on
56
+ `setupCheckout` / `setupMerchant`. A checkout session's `success_url` and
57
+ `cancel_url` must be on a registered origin; the origin of your
58
+ `webhook_callback_url` is auto-allowed in addition. Each entry must be an
59
+ absolute origin such as `https://shop.example.com`; entries are normalized to
60
+ bare, lowercased origins and deduped. A return URL that is not on an allowed
61
+ origin is rejected, so an attacker cannot point a session at an arbitrary
62
+ redirect target.
63
+
64
+ For a Hosted Checkout session, Siglume authors the amount, currency, challenge,
65
+ and return URLs server-side at session creation. The browser cannot tamper with
66
+ the price or the redirect target, and the raw challenge is never exposed to the
67
+ browser or returned by `getCheckoutSession`.
68
+
52
69
  ## Do Not Trust Browser Amounts
53
70
 
54
71
  The merchant server owns:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siglume/direct-request-payment",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "SDK for the Siglume Direct Request Payment SDRP payment protocol",
5
5
  "keywords": [
6
6
  "siglume",