@siglume/direct-request-payment 0.3.0 → 0.3.1

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,406 @@
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
+ ## 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 recurring product tag ONCE by signing a recurring
267
+ challenge (a distinct scheme, so one-time challenges and recurring approvals can
268
+ never be replayed as each other); after that, recurring charges are
269
+ challenge-free by design. Subscriptions are bounded by the buyer's mandate;
270
+ scheduled autopay is bounded by the buyer's per-run, daily, and monthly
271
+ auto-pay budget.
272
+
273
+ - **Subscription** (`cadence: "monthly"`): Siglume charges the buyer's wallet
274
+ monthly and pays your merchant wallet automatically. First month is charged at
275
+ setup. The buyer can cancel from their Siglume wallet at any time.
276
+ - **Scheduled autopay** (`cadence: "daily"`): `daily` is the approval tag for
277
+ merchant-triggered scheduled autopay, not a run-count limiter. The
278
+ buyer authorizes the per-run amount and budget envelope, then hands you a
279
+ `schedule_token`; YOUR scheduler triggers each occurrence with that token.
280
+
281
+ ```ts
282
+ import { createDirectRequestPaymentRecurringChallenge } from "@siglume/direct-request-payment";
283
+
284
+ // Merchant server: approve a JPY 980 monthly subscription once.
285
+ const recurring = await createDirectRequestPaymentRecurringChallenge({
286
+ merchant: "example_merchant",
287
+ amount_minor: 980,
288
+ currency: "JPY",
289
+ cadence: "monthly",
290
+ secret: process.env.SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET!,
291
+ nonce: "subscription_setup_4711",
292
+ });
293
+
294
+ // Hand recurring.challenge to the buyer-facing page. The buyer creates the
295
+ // subscription with their Siglume token:
296
+ // POST /v1/market/api-store/direct-payments/subscriptions
297
+ // { merchant, amount_minor, currency, cadence: "monthly", challenge }
298
+ // For scheduled autopay, the buyer instead creates a scheduled auto-pay
299
+ // authorization (mode: "external_402") and gives you the schedule_token.
300
+ ```
301
+
302
+ ```py
303
+ import os
304
+
305
+ from siglume_direct_request_payment import create_direct_request_payment_recurring_challenge
306
+
307
+ # Merchant server: approve a JPY 980 monthly subscription once.
308
+ recurring = create_direct_request_payment_recurring_challenge(
309
+ merchant="example_merchant",
310
+ amount_minor=980,
311
+ currency="JPY",
312
+ cadence="monthly",
313
+ secret=os.environ["SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET"],
314
+ nonce="subscription_setup_4711",
315
+ )
316
+
317
+ # Hand recurring["challenge"] to the buyer-facing page, as in the TS example.
318
+ print(recurring["challenge"])
319
+ ```
320
+
321
+ Each recurring challenge is single-use: it authorizes exactly one subscription
322
+ or schedule, bound to the first buyer who redeems it. Issue a fresh challenge
323
+ per setup. The platform fee on recurring charges is your plan's payment fee
324
+ (with the per-payment minimum), frozen at setup.
325
+
326
+ Merchant-facing webhook events: `subscription.created`, `subscription.renewed`
327
+ (each monthly charge), `payment.failed` (renewal failure, with `will_retry` /
328
+ `final_failure` flags), `subscription.cancelled`, and — for each scheduled
329
+ autopay occurrence — the usual `direct_payment.confirmed`.
330
+
331
+ ## Webhooks
332
+
333
+ Your merchant system should treat Siglume webhooks as the durable delivery
334
+ signal. Always verify the signature against the raw request body before trusting
335
+ the payload. Create a marketplace webhook subscription with
336
+ `POST /v1/market/webhooks/subscriptions`; the response returns the `whsec_`
337
+ signing secret once.
338
+
339
+ ```ts
340
+ import { verifyDirectRequestPaymentWebhook } from "@siglume/direct-request-payment";
341
+
342
+ const { event } = await verifyDirectRequestPaymentWebhook(
343
+ process.env.SIGLUME_WEBHOOK_SECRET!,
344
+ rawRequestBody,
345
+ request.headers["siglume-signature"],
346
+ );
347
+
348
+ if (event.type === "direct_payment.confirmed") {
349
+ // Mark the order paid if event.data.challenge_hash/order mapping matches.
350
+ }
351
+ ```
352
+
353
+ ```py
354
+ import os
355
+
356
+ from siglume_direct_request_payment import verify_direct_request_payment_webhook
357
+
358
+ verified = verify_direct_request_payment_webhook(
359
+ os.environ["SIGLUME_WEBHOOK_SECRET"],
360
+ raw_request_body,
361
+ siglume_signature_header,
362
+ )
363
+
364
+ if verified["event"]["type"] == "direct_payment.confirmed":
365
+ # Mark the order paid if event.data.challenge_hash/order mapping matches.
366
+ pass
367
+ ```
368
+
369
+ ## Security Rules
370
+
371
+ - Keep the challenge secret on the merchant server only.
372
+ - Keep merchant order amount and currency server-authored.
373
+ - Use one nonce per order payment attempt.
374
+ - Store `challenge_hash` with the order and reject mismatches.
375
+ - Make order fulfillment idempotent by `requirement_id` and order id.
376
+ - Verify webhook signatures against the raw body.
377
+ - Do not use a merchant token to charge a customer wallet.
378
+ - Do not treat Direct Request Payment as stored value, prepaid points, escrow, or
379
+ a platform balance.
380
+
381
+ Read [docs/security.md](./docs/security.md) before going live.
382
+
383
+ ## Go-Live Checklist
384
+
385
+ - Run `setupCheckout` with the merchant Siglume JWT.
386
+ - Complete the merchant billing mandate wallet approval if required.
387
+ - Store `SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET` only on the merchant server.
388
+ - Store the returned `SIGLUME_WEBHOOK_SECRET` only on the merchant server.
389
+ - Persist `challenge_hash`, `requirement_id`, and fulfillment state per order.
390
+ - Fulfill orders only from verified webhook data, with idempotency.
391
+ - Treat `fee_bps` returned by Siglume as the runtime fee source of truth.
392
+
393
+ ## Documentation
394
+
395
+ - [Merchant quickstart](./docs/merchant-quickstart.md)
396
+ - [API reference](./docs/api-reference.md)
397
+ - [Pricing](./docs/pricing.md)
398
+ - [Security guide](./docs/security.md)
399
+ - [Merchant setup example](./examples/setup-merchant.ts)
400
+ - [Express checkout example](./examples/express-checkout.ts)
401
+ - [Japanese launch announcement draft](./docs/announcement-ja.md)
402
+ - [Changelog](./CHANGELOG.md)
403
+
404
+ ## License
405
+
406
+ MIT