@siglume/direct-request-payment 0.3.0 → 0.3.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/README.md CHANGED
@@ -1,404 +1,424 @@
1
- # @siglume/direct-request-payment
2
-
3
- [![npm version](https://img.shields.io/npm/v/@siglume/direct-request-payment.svg)](https://www.npmjs.com/package/@siglume/direct-request-payment)
4
- [![PyPI version](https://img.shields.io/pypi/v/siglume-direct-request-payment.svg)](https://pypi.org/project/siglume-direct-request-payment/)
5
-
6
- Merchant SDK for Siglume Direct Request Payment checkout integrations.
7
-
8
- Use this package when an external EC site, booking service, membership service,
9
- or paid API wants to accept Siglume wallet payments without taking custody of
10
- customer funds.
11
-
12
- This SDK is intentionally separate from `@siglume/api-sdk`:
13
-
14
- - `@siglume/api-sdk` is for publishing agent-facing APIs to the Siglume API Store.
15
- - `@siglume/direct-request-payment` is for external merchants integrating
16
- Siglume Direct Request Payment into their own checkout.
17
-
18
- ## What This SDK Covers
19
-
20
- - merchant self-service setup with a Siglume merchant JWT
21
- - challenge secret creation and rotation
22
- - merchant billing mandate preparation
23
- - webhook subscription creation
24
- - merchant-signed payment challenges
25
- - buyer-authenticated payment requirement creation
26
- - prepared wallet transaction execution payloads
27
- - payment requirement verification
28
- - signed webhook verification
29
-
30
- It does not custody funds or manage customer wallets. Merchant setup runs through
31
- Siglume APIs with the merchant's Siglume JWT; buyer payment creation runs with
32
- the buyer's Siglume JWT.
33
-
34
- ## Install
35
-
36
- ```bash
37
- npm install @siglume/direct-request-payment
38
- ```
39
-
40
- ```bash
41
- pip install siglume-direct-request-payment
42
- ```
43
-
44
- Node.js 18 or later is required for the TypeScript SDK. Python 3.11 or later is
45
- required for the Python SDK.
46
-
47
- ## Current Platform Contract
48
-
49
- The public product name is **Siglume Direct Request Payment**. The current
50
- platform payload still uses the internal mode name `external_402`; this SDK sets
51
- that value for you when creating a payment requirement.
52
-
53
- Payment requirement creation must run in the authenticated buyer's Siglume
54
- context. Your merchant server must not use a merchant secret or API key to
55
- charge a customer wallet. The merchant server creates the signed challenge; the
56
- buyer-facing Siglume payment flow creates and pays the requirement.
57
-
58
- `DirectRequestPaymentMerchantClient` requires the merchant's Siglume bearer
59
- token for setup. `DirectRequestPaymentClient` requires the buyer's Siglume
60
- bearer token for payment requirements. Do not use a Developer Portal `cli_` API
61
- key with this package.
62
-
63
- Current HTTP endpoints live under Siglume's market/API Store route namespace for
64
- compatibility with the existing platform contract. That does not make this SDK an
65
- API Store publishing SDK.
66
-
67
- ## Trial Pricing
68
-
69
- Siglume Direct Request Payment is currently offered with trial-phase merchant
70
- pricing designed for small EC sites, booking services, membership services, paid
71
- APIs, and agent-to-agent payment experiments.
72
-
73
- Both launch settlement currencies are first-class: JPY settled in JPYC, and USD
74
- settled in USDC. A merchant settles in one currency, chosen at onboarding. The
75
- settlement fee percentage is identical in both currencies; only the flat
76
- amounts differ.
77
-
78
- | Plan | Monthly fee (JPY / USD) | Payment fee |
79
- | --- | ---: | ---: |
80
- | Launch | JPY 0 / USD 0 | 1.8% |
81
- | Starter | JPY 980 / USD 6.00 | 1.0% |
82
- | Growth | JPY 2,980 / USD 18.00 | 0.7% |
83
- | Pro | JPY 9,800 / USD 60.00 | 0.5% |
84
-
85
- Every payment is fee-bearing at the plan rate. The minimum fee is JPY 30
86
- (USD merchants: USD 0.20) per payment — it recovers the per-payment settlement
87
- cost (an on-chain signature plus network gas) on small payments; the percentage
88
- rate applies on larger payments. A merchant billing
89
- mandate is required before accepting payments, even on the Launch plan. The API
90
- and merchant registry may still expose the internal plan key `free` for this
91
- tier. See [docs/pricing.md](./docs/pricing.md) for details.
92
-
93
- Per-payment fees are deducted at payment settlement time, so the merchant
94
- receives the net amount. Monthly base fees are collected through the merchant
95
- billing mandate. `fee_bps` returned on a payment requirement is the authoritative
96
- per-payment rate for that payment in the merchant's settlement currency.
97
-
98
- ## Merchant Setup: One SDK Call
99
-
100
- Run this once from the merchant server or an integration agent with the
101
- merchant's Siglume JWT. It reserves the merchant key, creates the challenge
102
- secret, prepares the billing mandate, and creates the webhook subscription.
103
-
104
- ```ts
105
- import { DirectRequestPaymentMerchantClient } from "@siglume/direct-request-payment";
106
-
107
- const merchant = new DirectRequestPaymentMerchantClient({
108
- auth_token: process.env.SIGLUME_MERCHANT_AUTH_TOKEN!,
109
- });
110
-
111
- const setup = await merchant.setupCheckout({
112
- merchant: "example_merchant",
113
- display_name: "Example Merchant",
114
- billing_plan: "launch",
115
- billing_currency: "JPY",
116
- webhook_callback_url: "https://merchant.example/siglume/webhook",
117
- max_amount_minor: 100000,
118
- });
119
-
120
- console.log(setup.env);
121
- // {
122
- // SIGLUME_DIRECT_PAYMENT_MERCHANT: "example_merchant",
123
- // SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET: "edrp_...",
124
- // SIGLUME_WEBHOOK_SECRET: "whsec_..."
125
- // }
126
- ```
127
-
128
- ```py
129
- import os
130
-
131
- from siglume_direct_request_payment import DirectRequestPaymentMerchantClient
132
-
133
- merchant = DirectRequestPaymentMerchantClient(
134
- auth_token=os.environ["SIGLUME_MERCHANT_AUTH_TOKEN"],
135
- )
136
-
137
- setup = merchant.setup_checkout(
138
- merchant="example_merchant",
139
- display_name="Example Merchant",
140
- billing_plan="launch",
141
- billing_currency="JPY",
142
- webhook_callback_url="https://merchant.example/siglume/webhook",
143
- max_amount_minor=100000,
144
- )
145
-
146
- print(setup["env"])
147
- ```
148
-
149
- Store returned secrets on the merchant server. `challenge_secret` and
150
- `signing_secret` are returned only when they are created or rotated. If a billing
151
- mandate response requires wallet approval, complete that Siglume wallet step
152
- before accepting production payments.
153
-
154
- ## Merchant Server: Create a Challenge
155
-
156
- ```ts
157
- import { createDirectRequestPaymentChallenge } from "@siglume/direct-request-payment";
158
-
159
- const challenge = await createDirectRequestPaymentChallenge({
160
- merchant: "example_merchant",
161
- amount_minor: 1200,
162
- currency: "JPY",
163
- secret: process.env.SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET!,
164
- nonce: "order_123-attempt_1",
165
- });
166
-
167
- // Return only challenge.challenge to the buyer-facing checkout.
168
- // Never return the challenge secret to the browser.
169
- console.log(challenge.challenge);
170
- ```
171
-
172
- ```py
173
- import os
174
-
175
- from siglume_direct_request_payment import create_direct_request_payment_challenge
176
-
177
- challenge = create_direct_request_payment_challenge(
178
- merchant="example_merchant",
179
- amount_minor=1200,
180
- currency="JPY",
181
- secret=os.environ["SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET"],
182
- nonce="order_123-attempt_1",
183
- )
184
-
185
- print(challenge["challenge"])
186
- ```
187
-
188
- The signed challenge binds:
189
-
190
- - merchant key
191
- - amount in minor units
192
- - currency
193
- - nonce
194
-
195
- Changing any of those values invalidates the challenge.
196
- The nonce must not contain `:` because the current platform challenge format is
197
- `scheme:nonce:signature`.
198
-
199
- ## Buyer Payment Flow
200
-
201
- Use `DirectRequestPaymentClient` only with the authenticated buyer's Siglume
202
- bearer token. `SIGLUME_AUTH_TOKEN` may be used in server-side payment-confirmation
203
- helpers; `SIGLUME_API_KEY` and Developer Portal `cli_` keys are not accepted.
204
-
205
- ```ts
206
- import { DirectRequestPaymentClient } from "@siglume/direct-request-payment";
207
-
208
- const siglume = new DirectRequestPaymentClient({
209
- auth_token: buyerSiglumeBearerToken,
210
- });
211
-
212
- const requirement = await siglume.createPaymentRequirement({
213
- merchant: "example_merchant",
214
- amount_minor: 1200,
215
- currency: "JPY",
216
- challenge: challengeFromMerchantServer,
217
- });
218
-
219
- if (requirement.approve_transaction_request) {
220
- await siglume.executeAllowanceTransaction(requirement, { await_finality: true });
221
- }
222
-
223
- const payment = await siglume.executePaymentTransaction(requirement, {
224
- await_finality: true,
225
- });
226
-
227
- const receiptId = String(payment.receipt?.receipt_id ?? "");
228
- const verified = await siglume.verifyPaymentRequirement(requirement.requirement_id, {
229
- receipt_id: receiptId,
230
- await_finality: false,
231
- });
232
-
233
- console.log(verified.status);
234
- ```
235
-
236
- ```py
237
- from siglume_direct_request_payment import DirectRequestPaymentClient
238
-
239
- siglume = DirectRequestPaymentClient(auth_token=buyer_siglume_bearer_token)
240
-
241
- requirement = siglume.create_payment_requirement(
242
- merchant="example_merchant",
243
- amount_minor=1200,
244
- currency="JPY",
245
- challenge=challenge_from_merchant_server,
246
- )
247
-
248
- if requirement.get("approve_transaction_request"):
249
- siglume.execute_allowance_transaction(requirement, await_finality=True)
250
-
251
- payment = siglume.execute_payment_transaction(requirement, await_finality=True)
252
- receipt_id = str((payment.get("receipt") or {}).get("receipt_id") or "")
253
-
254
- verified = siglume.verify_payment_requirement(
255
- requirement["requirement_id"],
256
- receipt_id=receipt_id,
257
- await_finality=False,
258
- )
259
-
260
- print(verified["status"])
261
- ```
262
-
263
- ## Recurring Payments: Subscription and Scheduled Autopay
264
-
265
- Beyond one-time checkout, a buyer can authorize recurring payments. The merchant
266
- approves the price and cadence ONCE by signing a recurring challenge (a distinct
267
- scheme, so one-time challenges and recurring approvals can never be replayed as
268
- each other); after that, recurring charges are challenge-free by design — the
269
- buyer's on-chain payment mandate (frozen payee, amount cap, cadence) is the
270
- per-charge integrity check.
271
-
272
- - **Subscription** (`cadence: "monthly"`): Siglume charges the buyer's wallet
273
- monthly and pays your merchant wallet automatically. First month is charged at
274
- setup. The buyer can cancel from their Siglume wallet at any time.
275
- - **Scheduled autopay** (`cadence: "daily"`): the buyer authorizes at most one
276
- charge per day at a fixed amount and hands you a `schedule_token`; YOUR
277
- scheduler triggers each occurrence with that token.
278
-
279
- ```ts
280
- import { createDirectRequestPaymentRecurringChallenge } from "@siglume/direct-request-payment";
281
-
282
- // Merchant server: approve a JPY 980 monthly subscription once.
283
- const recurring = await createDirectRequestPaymentRecurringChallenge({
284
- merchant: "example_merchant",
285
- amount_minor: 980,
286
- currency: "JPY",
287
- cadence: "monthly",
288
- secret: process.env.SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET!,
289
- nonce: "subscription_setup_4711",
290
- });
291
-
292
- // Hand recurring.challenge to the buyer-facing page. The buyer creates the
293
- // subscription with their Siglume token:
294
- // POST /v1/market/api-store/direct-payments/subscriptions
295
- // { merchant, amount_minor, currency, cadence: "monthly", challenge }
296
- // For scheduled autopay, the buyer instead creates a scheduled auto-pay
297
- // authorization (mode: "external_402") and gives you the schedule_token.
298
- ```
299
-
300
- ```py
301
- import os
302
-
303
- from siglume_direct_request_payment import create_direct_request_payment_recurring_challenge
304
-
305
- # Merchant server: approve a JPY 980 monthly subscription once.
306
- recurring = create_direct_request_payment_recurring_challenge(
307
- merchant="example_merchant",
308
- amount_minor=980,
309
- currency="JPY",
310
- cadence="monthly",
311
- secret=os.environ["SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET"],
312
- nonce="subscription_setup_4711",
313
- )
314
-
315
- # Hand recurring["challenge"] to the buyer-facing page, as in the TS example.
316
- print(recurring["challenge"])
317
- ```
318
-
319
- Each recurring challenge is single-use: it authorizes exactly one subscription
320
- or schedule, bound to the first buyer who redeems it. Issue a fresh challenge
321
- per setup. The platform fee on recurring charges is your plan's payment fee
322
- (with the per-payment minimum), frozen at setup.
323
-
324
- Merchant-facing webhook events: `subscription.created`, `subscription.renewed`
325
- (each monthly charge), `payment.failed` (renewal failure, with `will_retry` /
326
- `final_failure` flags), `subscription.cancelled`, and — for each scheduled
327
- autopay occurrence — the usual `direct_payment.confirmed`.
328
-
329
- ## Webhooks
330
-
331
- Your merchant system should treat Siglume webhooks as the durable delivery
332
- signal. Always verify the signature against the raw request body before trusting
333
- the payload. Create a marketplace webhook subscription with
334
- `POST /v1/market/webhooks/subscriptions`; the response returns the `whsec_`
335
- signing secret once.
336
-
337
- ```ts
338
- import { verifyDirectRequestPaymentWebhook } from "@siglume/direct-request-payment";
339
-
340
- const { event } = await verifyDirectRequestPaymentWebhook(
341
- process.env.SIGLUME_WEBHOOK_SECRET!,
342
- rawRequestBody,
343
- request.headers["siglume-signature"],
344
- );
345
-
346
- if (event.type === "direct_payment.confirmed") {
347
- // Mark the order paid if event.data.challenge_hash/order mapping matches.
348
- }
349
- ```
350
-
351
- ```py
352
- import os
353
-
354
- from siglume_direct_request_payment import verify_direct_request_payment_webhook
355
-
356
- verified = verify_direct_request_payment_webhook(
357
- os.environ["SIGLUME_WEBHOOK_SECRET"],
358
- raw_request_body,
359
- siglume_signature_header,
360
- )
361
-
362
- if verified["event"]["type"] == "direct_payment.confirmed":
363
- # Mark the order paid if event.data.challenge_hash/order mapping matches.
364
- pass
365
- ```
366
-
367
- ## Security Rules
368
-
369
- - Keep the challenge secret on the merchant server only.
370
- - Keep merchant order amount and currency server-authored.
371
- - Use one nonce per order payment attempt.
372
- - Store `challenge_hash` with the order and reject mismatches.
373
- - Make order fulfillment idempotent by `requirement_id` and order id.
374
- - Verify webhook signatures against the raw body.
375
- - Do not use a merchant token to charge a customer wallet.
376
- - Do not treat Direct Request Payment as stored value, prepaid points, escrow, or
377
- a platform balance.
378
-
379
- Read [docs/security.md](./docs/security.md) before going live.
380
-
381
- ## Go-Live Checklist
382
-
383
- - Run `setupCheckout` with the merchant Siglume JWT.
384
- - Complete the merchant billing mandate wallet approval if required.
385
- - Store `SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET` only on the merchant server.
386
- - Store the returned `SIGLUME_WEBHOOK_SECRET` only on the merchant server.
387
- - Persist `challenge_hash`, `requirement_id`, and fulfillment state per order.
388
- - Fulfill orders only from verified webhook data, with idempotency.
389
- - Treat `fee_bps` returned by Siglume as the runtime fee source of truth.
390
-
391
- ## Documentation
392
-
393
- - [Merchant quickstart](./docs/merchant-quickstart.md)
394
- - [API reference](./docs/api-reference.md)
395
- - [Pricing](./docs/pricing.md)
396
- - [Security guide](./docs/security.md)
397
- - [Merchant setup example](./examples/setup-merchant.ts)
398
- - [Express checkout example](./examples/express-checkout.ts)
399
- - [Japanese launch announcement draft](./docs/announcement-ja.md)
400
- - [Changelog](./CHANGELOG.md)
401
-
402
- ## License
403
-
404
- MIT
1
+ # @siglume/direct-request-payment
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@siglume/direct-request-payment.svg)](https://www.npmjs.com/package/@siglume/direct-request-payment)
4
+ [![PyPI version](https://img.shields.io/pypi/v/siglume-direct-request-payment.svg)](https://pypi.org/project/siglume-direct-request-payment/)
5
+
6
+ Merchant SDK for Siglume Direct Request Payment checkout integrations.
7
+
8
+ Use this package when an external EC site, booking service, membership service,
9
+ or paid API wants to accept Siglume wallet payments without taking custody of
10
+ customer funds.
11
+
12
+ This SDK is intentionally separate from `@siglume/api-sdk`:
13
+
14
+ - `@siglume/api-sdk` is for publishing agent-facing APIs to the Siglume API Store.
15
+ - `@siglume/direct-request-payment` is for external merchants integrating
16
+ Siglume Direct Request Payment into their own checkout.
17
+
18
+ ## What This SDK Covers
19
+
20
+ - merchant self-service setup with a Siglume merchant JWT
21
+ - challenge secret creation and rotation
22
+ - merchant billing mandate preparation
23
+ - webhook subscription creation
24
+ - merchant-signed payment challenges
25
+ - buyer-authenticated payment requirement creation
26
+ - prepared wallet transaction execution payloads
27
+ - payment requirement verification
28
+ - signed webhook verification
29
+
30
+ It does not custody funds or manage customer wallets. Merchant setup runs through
31
+ Siglume APIs with the merchant's Siglume JWT; buyer payment creation runs with
32
+ the buyer's Siglume JWT.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ npm install @siglume/direct-request-payment
38
+ ```
39
+
40
+ ```bash
41
+ pip install siglume-direct-request-payment
42
+ ```
43
+
44
+ Node.js 18 or later is required for the TypeScript SDK. Python 3.11 or later is
45
+ required for the Python SDK.
46
+
47
+ ## Current Platform Contract
48
+
49
+ The public product name is **Siglume Direct Request Payment**. The current
50
+ platform payload still uses the internal mode name `external_402`; this SDK sets
51
+ that value for you when creating a payment requirement.
52
+
53
+ Payment requirement creation must run in the authenticated buyer's Siglume
54
+ context. Your merchant server must not use a merchant secret or API key to
55
+ charge a customer wallet. The merchant server creates the signed challenge; the
56
+ buyer-facing Siglume payment flow creates and pays the requirement.
57
+
58
+ `DirectRequestPaymentMerchantClient` requires the merchant's Siglume bearer
59
+ token for setup. `DirectRequestPaymentClient` requires the buyer's Siglume
60
+ bearer token for payment requirements. Do not use a Developer Portal `cli_` API
61
+ key with this package.
62
+
63
+ Current HTTP endpoints live under Siglume's market/API Store route namespace for
64
+ compatibility with the existing platform contract. That does not make this SDK an
65
+ API Store publishing SDK.
66
+
67
+ ## SDRP Payment Menu Boundary
68
+
69
+ SDRP is the overall protocol name. This SDK covers **Standard Payment** for
70
+ external merchants: checkout, subscription, and scheduled-autopay flows that
71
+ settle through the ordinary DirectPaymentHub wallet-payment rail.
72
+
73
+ The new API Store small-payment menus are separate from this SDK:
74
+
75
+ | SDRP menu | Amount band | Settlement behavior | SDK boundary |
76
+ | --- | --- | --- | --- |
77
+ | Standard Payment | Over JPY 500 / over USD 3.00, or immediate finality required | Buyer confirms payment; DirectPaymentHub settles on-chain immediately | Covered by `@siglume/direct-request-payment` |
78
+ | Micro Payment | JPY 50-500 / about USD 0.30-3.00 | API Store meter gate before provider execution; weekly delayed settlement | Use the API Store flow, not this merchant checkout SDK |
79
+ | Nano Payment | Under JPY 1 to JPY 49 / under USD 0.01 to about USD 0.30 | API Store meter gate before provider execution; monthly delayed settlement | Use the API Store flow, not this merchant checkout SDK |
80
+
81
+ Micro Payment and Nano Payment do not execute an on-chain payment during the
82
+ provider API call. If the buyer has no valid metered budget, scope, or remaining
83
+ limit, Siglume records `rejected_no_charge` and does not call the provider API.
84
+
85
+ ## Standard Payment Merchant Pricing
86
+
87
+ Siglume Direct Request Payment is currently offered with trial-phase merchant
88
+ pricing designed for small EC sites, booking services, membership services, paid
89
+ APIs, and external scheduled-payment experiments.
90
+
91
+ Both launch settlement currencies are first-class: JPY settled in JPYC, and USD
92
+ settled in USDC. A merchant settles in one currency, chosen at onboarding. The
93
+ settlement fee percentage is identical in both currencies; only the flat
94
+ amounts differ.
95
+
96
+ | Plan | Monthly fee (JPY / USD) | Payment fee |
97
+ | --- | ---: | ---: |
98
+ | Launch | JPY 0 / USD 0 | 1.8% |
99
+ | Starter | JPY 980 / USD 6.00 | 1.0% |
100
+ | Growth | JPY 2,980 / USD 18.00 | 0.7% |
101
+ | Pro | JPY 9,800 / USD 60.00 | 0.5% |
102
+
103
+ Every payment is fee-bearing at the plan rate. The minimum fee is JPY 30
104
+ (USD merchants: USD 0.20) per payment — it recovers the per-payment settlement
105
+ cost (an on-chain signature plus network gas) on small payments; the percentage
106
+ rate applies on larger payments. A merchant billing
107
+ mandate is required before accepting payments, even on the Launch plan. The API
108
+ and merchant registry may still expose the internal plan key `free` for this
109
+ tier. See [docs/pricing.md](./docs/pricing.md) for details.
110
+
111
+ Per-payment fees are deducted at payment settlement time, so the merchant
112
+ receives the net amount. Monthly base fees are collected through the merchant
113
+ billing mandate. `fee_bps` returned on a payment requirement is the authoritative
114
+ per-payment rate for that payment in the merchant's settlement currency.
115
+
116
+ ## Merchant Setup: One SDK Call
117
+
118
+ Run this once from the merchant server or an integration agent with the
119
+ merchant's Siglume JWT. It reserves the merchant key, creates the challenge
120
+ secret, prepares the billing mandate, and creates the webhook subscription.
121
+
122
+ ```ts
123
+ import { DirectRequestPaymentMerchantClient } from "@siglume/direct-request-payment";
124
+
125
+ const merchant = new DirectRequestPaymentMerchantClient({
126
+ auth_token: process.env.SIGLUME_MERCHANT_AUTH_TOKEN!,
127
+ });
128
+
129
+ const setup = await merchant.setupCheckout({
130
+ merchant: "example_merchant",
131
+ display_name: "Example Merchant",
132
+ billing_plan: "launch",
133
+ billing_currency: "JPY",
134
+ webhook_callback_url: "https://merchant.example/siglume/webhook",
135
+ max_amount_minor: 100000,
136
+ });
137
+
138
+ console.log(setup.env);
139
+ // {
140
+ // SIGLUME_DIRECT_PAYMENT_MERCHANT: "example_merchant",
141
+ // SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET: "edrp_...",
142
+ // SIGLUME_WEBHOOK_SECRET: "whsec_..."
143
+ // }
144
+ ```
145
+
146
+ ```py
147
+ import os
148
+
149
+ from siglume_direct_request_payment import DirectRequestPaymentMerchantClient
150
+
151
+ merchant = DirectRequestPaymentMerchantClient(
152
+ auth_token=os.environ["SIGLUME_MERCHANT_AUTH_TOKEN"],
153
+ )
154
+
155
+ setup = merchant.setup_checkout(
156
+ merchant="example_merchant",
157
+ display_name="Example Merchant",
158
+ billing_plan="launch",
159
+ billing_currency="JPY",
160
+ webhook_callback_url="https://merchant.example/siglume/webhook",
161
+ max_amount_minor=100000,
162
+ )
163
+
164
+ print(setup["env"])
165
+ ```
166
+
167
+ Store returned secrets on the merchant server. `challenge_secret` and
168
+ `signing_secret` are returned only when they are created or rotated. If a billing
169
+ mandate response requires wallet approval, complete that Siglume wallet step
170
+ before accepting production payments.
171
+
172
+ ## Merchant Server: Create a Challenge
173
+
174
+ ```ts
175
+ import { createDirectRequestPaymentChallenge } from "@siglume/direct-request-payment";
176
+
177
+ const challenge = await createDirectRequestPaymentChallenge({
178
+ merchant: "example_merchant",
179
+ amount_minor: 1200,
180
+ currency: "JPY",
181
+ secret: process.env.SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET!,
182
+ nonce: "order_123-attempt_1",
183
+ });
184
+
185
+ // Return only challenge.challenge to the buyer-facing checkout.
186
+ // Never return the challenge secret to the browser.
187
+ console.log(challenge.challenge);
188
+ ```
189
+
190
+ ```py
191
+ import os
192
+
193
+ from siglume_direct_request_payment import create_direct_request_payment_challenge
194
+
195
+ challenge = create_direct_request_payment_challenge(
196
+ merchant="example_merchant",
197
+ amount_minor=1200,
198
+ currency="JPY",
199
+ secret=os.environ["SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET"],
200
+ nonce="order_123-attempt_1",
201
+ )
202
+
203
+ print(challenge["challenge"])
204
+ ```
205
+
206
+ The signed challenge binds:
207
+
208
+ - merchant key
209
+ - amount in minor units
210
+ - currency
211
+ - nonce
212
+
213
+ Changing any of those values invalidates the challenge.
214
+ The nonce must not contain `:` because the current platform challenge format is
215
+ `scheme:nonce:signature`.
216
+
217
+ ## Buyer Payment Flow
218
+
219
+ Use `DirectRequestPaymentClient` only with the authenticated buyer's Siglume
220
+ bearer token. `SIGLUME_AUTH_TOKEN` may be used in server-side payment-confirmation
221
+ helpers; `SIGLUME_API_KEY` and Developer Portal `cli_` keys are not accepted.
222
+
223
+ ```ts
224
+ import { DirectRequestPaymentClient } from "@siglume/direct-request-payment";
225
+
226
+ const siglume = new DirectRequestPaymentClient({
227
+ auth_token: buyerSiglumeBearerToken,
228
+ });
229
+
230
+ const requirement = await siglume.createPaymentRequirement({
231
+ merchant: "example_merchant",
232
+ amount_minor: 1200,
233
+ currency: "JPY",
234
+ challenge: challengeFromMerchantServer,
235
+ });
236
+
237
+ if (requirement.approve_transaction_request) {
238
+ await siglume.executeAllowanceTransaction(requirement, { await_finality: true });
239
+ }
240
+
241
+ const payment = await siglume.executePaymentTransaction(requirement, {
242
+ await_finality: true,
243
+ });
244
+
245
+ const receiptId = String(payment.receipt?.receipt_id ?? "");
246
+ const verified = await siglume.verifyPaymentRequirement(requirement.requirement_id, {
247
+ receipt_id: receiptId,
248
+ await_finality: false,
249
+ });
250
+
251
+ console.log(verified.status);
252
+ ```
253
+
254
+ ```py
255
+ from siglume_direct_request_payment import DirectRequestPaymentClient
256
+
257
+ siglume = DirectRequestPaymentClient(auth_token=buyer_siglume_bearer_token)
258
+
259
+ requirement = siglume.create_payment_requirement(
260
+ merchant="example_merchant",
261
+ amount_minor=1200,
262
+ currency="JPY",
263
+ challenge=challenge_from_merchant_server,
264
+ )
265
+
266
+ if requirement.get("approve_transaction_request"):
267
+ siglume.execute_allowance_transaction(requirement, await_finality=True)
268
+
269
+ payment = siglume.execute_payment_transaction(requirement, await_finality=True)
270
+ receipt_id = str((payment.get("receipt") or {}).get("receipt_id") or "")
271
+
272
+ verified = siglume.verify_payment_requirement(
273
+ requirement["requirement_id"],
274
+ receipt_id=receipt_id,
275
+ await_finality=False,
276
+ )
277
+
278
+ print(verified["status"])
279
+ ```
280
+
281
+ ## Recurring Payments: Subscription and Scheduled Autopay
282
+
283
+ Beyond one-time checkout, a buyer can authorize recurring payments. The merchant
284
+ approves the price and recurring product tag ONCE by signing a recurring
285
+ challenge (a distinct scheme, so one-time challenges and recurring approvals can
286
+ never be replayed as each other); after that, recurring charges are
287
+ challenge-free by design. Subscriptions are bounded by the buyer's mandate;
288
+ scheduled autopay is bounded by the buyer's per-run, daily, and monthly
289
+ auto-pay budget.
290
+
291
+ - **Subscription** (`cadence: "monthly"`): Siglume charges the buyer's wallet
292
+ monthly and pays your merchant wallet automatically. First month is charged at
293
+ setup. The buyer can cancel from their Siglume wallet at any time.
294
+ - **Scheduled autopay** (`cadence: "daily"`): `daily` is the approval tag for
295
+ merchant-triggered scheduled autopay, not a run-count limiter. The
296
+ buyer authorizes the per-run amount and budget envelope, then hands you a
297
+ `schedule_token`; YOUR scheduler triggers each occurrence with that token.
298
+
299
+ ```ts
300
+ import { createDirectRequestPaymentRecurringChallenge } from "@siglume/direct-request-payment";
301
+
302
+ // Merchant server: approve a JPY 980 monthly subscription once.
303
+ const recurring = await createDirectRequestPaymentRecurringChallenge({
304
+ merchant: "example_merchant",
305
+ amount_minor: 980,
306
+ currency: "JPY",
307
+ cadence: "monthly",
308
+ secret: process.env.SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET!,
309
+ nonce: "subscription_setup_4711",
310
+ });
311
+
312
+ // Hand recurring.challenge to the buyer-facing page. The buyer creates the
313
+ // subscription with their Siglume token:
314
+ // POST /v1/market/api-store/direct-payments/subscriptions
315
+ // { merchant, amount_minor, currency, cadence: "monthly", challenge }
316
+ // For scheduled autopay, the buyer instead creates a scheduled auto-pay
317
+ // authorization (mode: "external_402") and gives you the schedule_token.
318
+ ```
319
+
320
+ ```py
321
+ import os
322
+
323
+ from siglume_direct_request_payment import create_direct_request_payment_recurring_challenge
324
+
325
+ # Merchant server: approve a JPY 980 monthly subscription once.
326
+ recurring = create_direct_request_payment_recurring_challenge(
327
+ merchant="example_merchant",
328
+ amount_minor=980,
329
+ currency="JPY",
330
+ cadence="monthly",
331
+ secret=os.environ["SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET"],
332
+ nonce="subscription_setup_4711",
333
+ )
334
+
335
+ # Hand recurring["challenge"] to the buyer-facing page, as in the TS example.
336
+ print(recurring["challenge"])
337
+ ```
338
+
339
+ Each recurring challenge is single-use: it authorizes exactly one subscription
340
+ or schedule, bound to the first buyer who redeems it. Issue a fresh challenge
341
+ per setup. The platform fee on recurring charges is your plan's payment fee
342
+ (with the per-payment minimum), frozen at setup.
343
+
344
+ Merchant-facing webhook events: `subscription.created`, `subscription.renewed`
345
+ (each monthly charge), `payment.failed` (renewal failure, with `will_retry` /
346
+ `final_failure` flags), `subscription.cancelled`, and for each scheduled
347
+ autopay occurrence the usual `direct_payment.confirmed`.
348
+
349
+ ## Webhooks
350
+
351
+ Your merchant system should treat Siglume webhooks as the durable delivery
352
+ signal. Always verify the signature against the raw request body before trusting
353
+ the payload. Create a marketplace webhook subscription with
354
+ `POST /v1/market/webhooks/subscriptions`; the response returns the `whsec_`
355
+ signing secret once.
356
+
357
+ ```ts
358
+ import { verifyDirectRequestPaymentWebhook } from "@siglume/direct-request-payment";
359
+
360
+ const { event } = await verifyDirectRequestPaymentWebhook(
361
+ process.env.SIGLUME_WEBHOOK_SECRET!,
362
+ rawRequestBody,
363
+ request.headers["siglume-signature"],
364
+ );
365
+
366
+ if (event.type === "direct_payment.confirmed") {
367
+ // Mark the order paid if event.data.challenge_hash/order mapping matches.
368
+ }
369
+ ```
370
+
371
+ ```py
372
+ import os
373
+
374
+ from siglume_direct_request_payment import verify_direct_request_payment_webhook
375
+
376
+ verified = verify_direct_request_payment_webhook(
377
+ os.environ["SIGLUME_WEBHOOK_SECRET"],
378
+ raw_request_body,
379
+ siglume_signature_header,
380
+ )
381
+
382
+ if verified["event"]["type"] == "direct_payment.confirmed":
383
+ # Mark the order paid if event.data.challenge_hash/order mapping matches.
384
+ pass
385
+ ```
386
+
387
+ ## Security Rules
388
+
389
+ - Keep the challenge secret on the merchant server only.
390
+ - Keep merchant order amount and currency server-authored.
391
+ - Use one nonce per order payment attempt.
392
+ - Store `challenge_hash` with the order and reject mismatches.
393
+ - Make order fulfillment idempotent by `requirement_id` and order id.
394
+ - Verify webhook signatures against the raw body.
395
+ - Do not use a merchant token to charge a customer wallet.
396
+ - Do not treat Direct Request Payment as stored value, prepaid points, escrow, or
397
+ a platform balance.
398
+
399
+ Read [docs/security.md](./docs/security.md) before going live.
400
+
401
+ ## Go-Live Checklist
402
+
403
+ - Run `setupCheckout` with the merchant Siglume JWT.
404
+ - Complete the merchant billing mandate wallet approval if required.
405
+ - Store `SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET` only on the merchant server.
406
+ - Store the returned `SIGLUME_WEBHOOK_SECRET` only on the merchant server.
407
+ - Persist `challenge_hash`, `requirement_id`, and fulfillment state per order.
408
+ - Fulfill orders only from verified webhook data, with idempotency.
409
+ - Treat `fee_bps` returned by Siglume as the runtime fee source of truth.
410
+
411
+ ## Documentation
412
+
413
+ - [Merchant quickstart](./docs/merchant-quickstart.md)
414
+ - [API reference](./docs/api-reference.md)
415
+ - [Pricing](./docs/pricing.md)
416
+ - [Security guide](./docs/security.md)
417
+ - [Merchant setup example](./examples/setup-merchant.ts)
418
+ - [Express checkout example](./examples/express-checkout.ts)
419
+ - [Japanese launch announcement draft](./docs/announcement-ja.md)
420
+ - [Changelog](./CHANGELOG.md)
421
+
422
+ ## License
423
+
424
+ MIT