@siglume/direct-request-payment 0.4.0 → 0.4.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.
@@ -10,7 +10,7 @@ SDRP serves two kinds of buyer, and you integrate each differently. In both
10
10
  cases the buyer pays from a Siglume wallet (JPYC for JPY, USDC for USD) — not a
11
11
  card — and the merchant SDK never authenticates the buyer.
12
12
 
13
- - **Human web shopper → Hosted Checkout.** Call
13
+ - **Human web shopper → Hosted Checkout (Beta; server rollout in progress).** Call
14
14
  [`createCheckoutSession`](#createcheckoutsessioninput--create_checkout_session)
15
15
  on `DirectRequestPaymentMerchantClient` and redirect the shopper to the
16
16
  returned `checkout_url`. The shopper signs into Siglume on the hosted page,
@@ -80,10 +80,79 @@ Returns:
80
80
  `nonce` must not contain `:` because the platform challenge string is delimited
81
81
  as `scheme:nonce:signature`.
82
82
 
83
+ ## `createDirectRequestPaymentChallengeSignature(secret, input)` / `create_direct_request_payment_challenge_signature(...)`
84
+
85
+ Returns just the HMAC-SHA256 hex digest (no `scheme:nonce:signature` wrapper),
86
+ for callers who assemble the challenge string themselves. This is the primitive
87
+ that `createDirectRequestPaymentChallenge` calls internally. The HMAC material is
88
+ `merchant:amount_minor:currency:nonce`.
89
+
90
+ ```ts
91
+ const signature = await createDirectRequestPaymentChallengeSignature(secret, {
92
+ merchant: "example_merchant",
93
+ amount_minor: 1200,
94
+ currency: "JPY",
95
+ nonce: "order_123-attempt_1",
96
+ });
97
+ ```
98
+
99
+ ```py
100
+ signature = create_direct_request_payment_challenge_signature(
101
+ secret=secret,
102
+ merchant="example_merchant",
103
+ amount_minor=1200,
104
+ currency="JPY",
105
+ nonce="order_123-attempt_1",
106
+ )
107
+ ```
108
+
109
+ In TypeScript the secret is the first positional argument and the rest are an
110
+ object. In Python every argument is keyword-only:
111
+ `create_direct_request_payment_challenge_signature(*, secret, merchant, amount_minor, currency, nonce)`.
112
+ Returns a `string` (TS) / `str` (Py).
113
+
83
114
  ## `verifyDirectRequestPaymentChallenge(secret, input)` / `verify_direct_request_payment_challenge(...)`
84
115
 
85
116
  Verifies a challenge against merchant, amount, currency, and secret. This is
86
- useful in tests and internal checkout assertions.
117
+ useful in tests and internal checkout assertions. Returns `boolean` (TS) /
118
+ `bool` (Py) — `true` only when the challenge scheme matches and the recomputed
119
+ signature is a timing-safe match.
120
+
121
+ ```ts
122
+ const ok = await verifyDirectRequestPaymentChallenge(secret, {
123
+ merchant: "example_merchant",
124
+ amount_minor: 1200,
125
+ currency: "JPY",
126
+ challenge: challengeString,
127
+ });
128
+ ```
129
+
130
+ In TypeScript the secret is positional and the rest is an object
131
+ (`verifyDirectRequestPaymentChallenge(secret, { merchant, amount_minor, currency, challenge })`).
132
+ In Python every argument is keyword-only:
133
+
134
+ ```py
135
+ ok = verify_direct_request_payment_challenge(
136
+ secret=secret,
137
+ merchant="example_merchant",
138
+ amount_minor=1200,
139
+ currency="JPY",
140
+ challenge=challenge_string,
141
+ )
142
+ ```
143
+
144
+ ## `parseDirectRequestPaymentChallenge(challenge)` / `parse_direct_request_payment_challenge(challenge)`
145
+
146
+ Splits a `scheme:nonce:signature` challenge string into its parts. Throws
147
+ `SiglumeDirectRequestPaymentError` (TS) / `DirectRequestPaymentError` (Py) when
148
+ the string is not exactly three non-empty colon-delimited parts. The `challenge`
149
+ argument is positional in both languages.
150
+
151
+ Returns:
152
+
153
+ - `scheme`
154
+ - `nonce`
155
+ - `signature`
87
156
 
88
157
  ## `createDirectRequestPaymentRecurringChallenge(input)` / `create_direct_request_payment_recurring_challenge(...)`
89
158
 
@@ -137,10 +206,59 @@ monthly auto-pay budget.
137
206
 
138
207
  Returns the same fields as the one-time challenge helper, plus `cadence`.
139
208
 
209
+ ## `createDirectRequestPaymentRecurringChallengeSignature(secret, input)` / `create_direct_request_payment_recurring_challenge_signature(...)`
210
+
211
+ Returns just the HMAC-SHA256 hex digest for a recurring approval, for callers who
212
+ assemble the challenge string themselves. This is the primitive that
213
+ `createDirectRequestPaymentRecurringChallenge` calls internally. The HMAC
214
+ material is `merchant:amount_minor:currency:cadence:nonce` and must stay
215
+ byte-identical to the server's recurring-challenge signer.
216
+
217
+ ```ts
218
+ const signature = await createDirectRequestPaymentRecurringChallengeSignature(secret, {
219
+ merchant: "example_merchant",
220
+ amount_minor: 980,
221
+ currency: "JPY",
222
+ cadence: "monthly",
223
+ nonce: "subscription_setup_4711",
224
+ });
225
+ ```
226
+
227
+ ```py
228
+ signature = create_direct_request_payment_recurring_challenge_signature(
229
+ secret=secret,
230
+ merchant="example_merchant",
231
+ amount_minor=980,
232
+ currency="JPY",
233
+ cadence="monthly",
234
+ nonce="subscription_setup_4711",
235
+ )
236
+ ```
237
+
238
+ In TypeScript the secret is positional and the rest is an object. In Python every
239
+ argument is keyword-only:
240
+ `create_direct_request_payment_recurring_challenge_signature(*, secret, merchant, amount_minor, currency, cadence, nonce)`.
241
+ Returns a `string` (TS) / `str` (Py).
242
+
140
243
  ## `verifyDirectRequestPaymentRecurringChallenge(secret, input)` / `verify_direct_request_payment_recurring_challenge(...)`
141
244
 
142
245
  Verifies a recurring approval challenge against merchant, amount, currency,
143
- cadence, and secret.
246
+ cadence, and secret. Returns `boolean` (TS) / `bool` (Py).
247
+
248
+ In TypeScript the secret is positional and the rest is an object
249
+ (`verifyDirectRequestPaymentRecurringChallenge(secret, { merchant, amount_minor, currency, cadence, challenge })`).
250
+ In Python every argument is keyword-only:
251
+
252
+ ```py
253
+ ok = verify_direct_request_payment_recurring_challenge(
254
+ secret=secret,
255
+ merchant="example_merchant",
256
+ amount_minor=980,
257
+ currency="JPY",
258
+ cadence="monthly",
259
+ challenge=challenge_string,
260
+ )
261
+ ```
144
262
 
145
263
  ## `directRequestPaymentChallengeHash(challenge)` / `direct_request_payment_challenge_hash(...)`
146
264
 
@@ -180,7 +298,9 @@ Input:
180
298
  - `merchant`: self-service merchant key, 3-64 chars using lowercase letters,
181
299
  numbers, `_`, or `-`
182
300
  - `display_name`: optional public merchant name
183
- - `billing_plan`: `launch`, `starter`, `growth`, or `pro`
301
+ - `billing_plan`: `launch`, `starter`, `growth`, or `pro` (default `launch`). The
302
+ legacy key `free` is also accepted as a compatibility input and maps to the
303
+ Launch tier; prefer `launch` in new code.
184
304
  - `billing_currency`: `JPY`; `USD` requires agreed USD/USDC billing terms
185
305
  - `webhook_callback_url`: HTTPS callback URL for signed payment events
186
306
  - `max_amount_minor`: optional billing mandate cap
@@ -191,6 +311,21 @@ Input:
191
311
  absolute origin such as `https://shop.example.com`; entries are normalized to
192
312
  bare, lowercased origins and deduped.
193
313
 
314
+ In addition to the `setupMerchant` inputs above, `setupCheckout` accepts these
315
+ orchestration toggles:
316
+
317
+ - `prepare_billing_mandate`: default `true`. When `false`, the billing mandate
318
+ step is skipped and `billing_mandate` in the result is `null`.
319
+ - `create_webhook_subscription`: optional. When omitted, a webhook subscription
320
+ is created only if `webhook_callback_url` is set. Set `false` to skip webhook
321
+ creation even when a callback URL is present (TS uses `?? Boolean(webhook_callback_url)`;
322
+ Py defaults to `bool(webhook_callback_url)`).
323
+ - `webhook_event_types`: optional `string[]` of event types for the created
324
+ subscription. When omitted the subscription defaults to
325
+ `direct_payment.confirmed` and `direct_payment.spent`.
326
+ - `webhook_description`: optional description for the created subscription;
327
+ defaults to `"<merchant> Direct Request Payment"`.
328
+
194
329
  Returns:
195
330
 
196
331
  - `merchant`: merchant account setup response
@@ -216,6 +351,12 @@ webhook-origin auto-allow apply.
216
351
 
217
352
  ### `createCheckoutSession(input)` / `create_checkout_session(...)`
218
353
 
354
+ Beta / server rollout: Hosted Checkout is rolling out account by account. If the
355
+ server endpoint is not enabled for the merchant yet, the SDK raises
356
+ `HostedCheckoutNotAvailableError` (TS + Py) rather than leaking a raw rollout
357
+ 404/409. Fulfillment must still key off the signed `direct_payment.confirmed`
358
+ webhook.
359
+
219
360
  Creates a single-use, expiring Hosted Checkout session for a human web shopper
220
361
  and returns the URL to redirect them to. Requires the merchant's Siglume bearer
221
362
  token. The server authors the challenge from the merchant's challenge secret —
@@ -285,7 +426,7 @@ Calls:
285
426
  GET /v1/sdrp/direct-payments/checkout-sessions/{session_id}
286
427
  ```
287
428
 
288
- Returns a session status object with:
429
+ Returns a `HostedCheckoutSession` status object with:
289
430
 
290
431
  - `session_id`
291
432
  - `merchant`
@@ -295,13 +436,19 @@ Returns a session status object with:
295
436
  - `status`: one of `open`, `authenticated`, `paid`, `expired`, `cancelled`,
296
437
  `failed`
297
438
  - `challenge_hash`
298
- - `requirement_id`
439
+ - `requirement_id` (nullable until a requirement is created)
299
440
  - `success_url`
300
441
  - `cancel_url`
301
- - `expires_at`
302
- - `paid_at`
442
+ - `expires_at` (nullable)
443
+ - `authenticated_at` (nullable; set when the shopper signs into Siglume)
444
+ - `paid_at` (nullable; set when the payment confirms)
445
+ - `cancelled_at` (nullable; set when the shopper cancels)
446
+ - `created_at` (nullable)
303
447
  - `metadata_jsonb`
304
448
 
449
+ The TS `HostedCheckoutSession` interface also carries an index signature, so the
450
+ server may include additional pass-through fields.
451
+
305
452
  ### `getMerchant(merchant)` / `get_merchant(merchant)`
306
453
 
307
454
  Calls:
@@ -395,6 +542,61 @@ Input:
395
542
  - `allowance_amount_minor`: optional positive integer
396
543
  - `metadata`: optional JSON object
397
544
 
545
+ ### `getPaymentRequirement(requirement_id)` / `get_payment_requirement(requirement_id)`
546
+
547
+ Calls:
548
+
549
+ ```text
550
+ GET /v1/sdrp/direct-payments/requirements/{requirement_id}
551
+ ```
552
+
553
+ Fetches the current state of a payment requirement (status, `transaction_request`,
554
+ `approve_transaction_request`, `chain_receipt_id`, etc.) by id. The
555
+ `requirement_id` argument is positional in both languages. Returns the same
556
+ requirement object shape as `createPaymentRequirement`.
557
+
558
+ ```ts
559
+ const requirement = await siglume.getPaymentRequirement(requirementId);
560
+ ```
561
+
562
+ ```py
563
+ requirement = siglume.get_payment_requirement(requirement_id)
564
+ ```
565
+
566
+ ### `executePreparedTransaction(payload)` / `execute_prepared_transaction(payload)`
567
+
568
+ Calls:
569
+
570
+ ```text
571
+ POST /v1/market/web3/transactions/execute-prepared
572
+ ```
573
+
574
+ The raw prepared-transaction executor. It posts a prepared-transaction payload
575
+ (`transaction_request`, `receipt_kind`, `reference_type`, `reference_id`,
576
+ `metadata`, `await_finality`) to the marketplace web3 route and returns the
577
+ execution result (`{ receipt?, finalization?, ... }`). The `payload` argument is
578
+ positional in both languages.
579
+
580
+ `executePaymentTransaction` / `execute_payment_transaction` and
581
+ `executeAllowanceTransaction` / `execute_allowance_transaction` are convenience
582
+ wrappers over this method: they build the payload from the requirement (via
583
+ [`buildPaymentExecutionPayload`](#payload-builders) /
584
+ [`buildAllowanceExecutionPayload`](#payload-builders)) and call
585
+ `executePreparedTransaction` for you. Call `executePreparedTransaction` directly
586
+ only when you build the payload yourself.
587
+
588
+ ```ts
589
+ const result = await siglume.executePreparedTransaction(
590
+ buildPaymentExecutionPayload(requirement, { await_finality: true }),
591
+ );
592
+ ```
593
+
594
+ ```py
595
+ result = siglume.execute_prepared_transaction(
596
+ build_payment_execution_payload(requirement, await_finality=True),
597
+ )
598
+ ```
599
+
398
600
  ### `executeAllowanceTransaction(requirement)` / `execute_allowance_transaction(...)`
399
601
 
400
602
  Executes `requirement.approve_transaction_request` through:
@@ -427,19 +629,139 @@ Input may include:
427
629
  - `await_timeout_seconds`
428
630
  - `await_poll_seconds`
429
631
 
632
+ ## Payload Builders
633
+
634
+ These pure functions build the `execute-prepared` payload from a payment
635
+ requirement, for callers who execute the transaction themselves (rather than via
636
+ the `executePaymentTransaction` / `executeAllowanceTransaction` wrappers). They
637
+ make no network calls.
638
+
639
+ ### `buildPaymentExecutionPayload(requirement, options)` / `build_payment_execution_payload(...)`
640
+
641
+ Builds the payment-transaction payload from `requirement.transaction_request`
642
+ with `receipt_kind = "sdrp_direct_payment"`.
643
+
644
+ In TypeScript `options` is an optional object `{ await_finality?, metadata? }`.
645
+ In Python the options are keyword-only:
646
+ `build_payment_execution_payload(requirement, *, await_finality=False, metadata=None)`.
647
+ Returns the prepared-transaction payload object.
648
+
649
+ ### `buildAllowanceExecutionPayload(requirement, options)` / `build_allowance_execution_payload(...)`
650
+
651
+ Builds the allowance/approval-transaction payload from
652
+ `requirement.approve_transaction_request` with
653
+ `receipt_kind = "sdrp_direct_payment_allowance"`. Throws
654
+ `SiglumeDirectRequestPaymentError` (TS) / `DirectRequestPaymentError` (Py) when
655
+ the requirement carries no allowance approval transaction. Same options shape as
656
+ `buildPaymentExecutionPayload`.
657
+
658
+ ### `buildPreparedTransactionExecutionPayload(requirement, transaction_request, options)` / `build_prepared_transaction_execution_payload(...)`
659
+
660
+ The lower-level builder both of the above call. It merges
661
+ `transaction_request.metadata_jsonb` with any `options.metadata`, and sets
662
+ `reference_type = "sdrp_direct_payment_requirement"` and `reference_id =
663
+ requirement.requirement_id`.
664
+
665
+ In TypeScript `options` is required and must include `receipt_kind`:
666
+ `{ receipt_kind, await_finality?, metadata? }`. In Python the third argument is
667
+ the `transaction_request` and the rest are keyword-only:
668
+ `build_prepared_transaction_execution_payload(requirement, transaction_request, *, receipt_kind, await_finality=False, metadata=None)`.
669
+ Returns the prepared-transaction payload object.
670
+
430
671
  ## Webhook Helpers
431
672
 
432
- - `buildWebhookSignatureHeader(secret, body)` for tests
433
- - `verifyWebhookSignature(secret, body, header)`
434
- - `verifyDirectRequestPaymentWebhook(secret, body, header)`
435
- - `parseDirectRequestPaymentWebhookEvent(payload)`
436
- - Python equivalents use snake_case:
437
- `build_webhook_signature_header`, `verify_webhook_signature`,
438
- `verify_direct_request_payment_webhook`, and
439
- `parse_direct_request_payment_webhook_event`.
673
+ ### `computeWebhookSignature(secret, body, options)` / `compute_webhook_signature(secret, body, *, timestamp)`
674
+
675
+ Returns the bare HMAC-SHA256 hex digest over `"<timestamp>.<body>"`. This is the
676
+ primitive `buildWebhookSignatureHeader` / `verifyWebhookSignature` use. In
677
+ TypeScript `options` is `{ timestamp: number }`; in Python `timestamp` is a
678
+ keyword-only `int`. `body` may be raw bytes, a string, or a JSON object.
679
+
680
+ ### `buildWebhookSignatureHeader(secret, body, options)` / `build_webhook_signature_header(secret, body, *, timestamp=None)`
681
+
682
+ Returns a `t=<timestamp>,v1=<signature>` header string. Mainly for tests /
683
+ mocking inbound webhooks. In TypeScript `options` is an optional
684
+ `{ timestamp?: number }` (defaults to now); in Python `timestamp` is a
685
+ keyword-only optional `int`.
686
+
687
+ ### `verifyWebhookSignature(secret, body, signature_header, options)` / `verify_webhook_signature(secret, body, signature_header, *, tolerance_seconds=300, now=None)`
688
+
689
+ Verifies the `Siglume-Signature` header against the raw `body`. Throws
690
+ `SiglumeWebhookSignatureError` (TS) / `SiglumeWebhookSignatureError` (Py) when
691
+ the timestamp is outside tolerance or the signature does not match. In TypeScript
692
+ `options` is `{ tolerance_seconds?, now? }`; in Python those are keyword-only
693
+ (`tolerance_seconds` defaults to `DEFAULT_WEBHOOK_TOLERANCE_SECONDS` = 300).
694
+ Returns `{ timestamp, signature }`.
695
+
696
+ ### `parseDirectRequestPaymentWebhookEvent(payload)` / `parse_direct_request_payment_webhook_event(payload)`
697
+
698
+ Validates and normalizes a parsed webhook event object (requires `id`, `type`,
699
+ `api_version`, `occurred_at`, and an object `data`). Throws
700
+ `SiglumeWebhookPayloadError` on a malformed event, or when a
701
+ `direct_payment.confirmed` event does not carry `data.mode = "external_402"`. The
702
+ `payload` argument is positional in both languages.
703
+
704
+ ### `verifyDirectRequestPaymentWebhook(secret, body, signature_header, options)` / `verify_direct_request_payment_webhook(secret, body, signature_header, *, tolerance_seconds=300, now=None)`
705
+
706
+ Verifies the signature and parses the event in one call. Returns
707
+ `{ event, verification }` (TS) / `{"event": ..., "verification": ...}` (Py). Same
708
+ options shape as `verifyWebhookSignature` (keyword-only in Python).
440
709
 
441
- `verifyDirectRequestPaymentWebhook` verifies the signature and parses the event
442
- in one call.
710
+ Webhook-verification trio (typical inbound webhook handler):
711
+
712
+ ```ts
713
+ import { verifyDirectRequestPaymentWebhook } from "@siglume/direct-request-payment";
714
+
715
+ const { event, verification } = await verifyDirectRequestPaymentWebhook(
716
+ process.env.SIGLUME_WEBHOOK_SECRET!,
717
+ rawRequestBody, // the RAW body bytes/string, not re-stringified JSON
718
+ request.headers["siglume-signature"],
719
+ );
720
+ // event.type === "direct_payment.confirmed" -> fulfill once; verification.timestamp is the signed time
721
+ ```
722
+
723
+ ```py
724
+ from siglume_direct_request_payment import verify_direct_request_payment_webhook
725
+
726
+ verified = verify_direct_request_payment_webhook(
727
+ os.environ["SIGLUME_WEBHOOK_SECRET"],
728
+ raw_request_body, # the RAW body bytes/string
729
+ siglume_signature_header,
730
+ )
731
+ event = verified["event"]
732
+ # event["type"] == "direct_payment.confirmed" -> fulfill once
733
+ ```
734
+
735
+ ## Exported Constants
736
+
737
+ Both packages export these importable constants:
738
+
739
+ | Constant | Value |
740
+ | --- | --- |
741
+ | `DEFAULT_SIGLUME_API_BASE` | `https://siglume.com/v1` |
742
+ | `DIRECT_REQUEST_PAYMENT_CHALLENGE_SCHEME` | `siglume-external-402-v1` |
743
+ | `DIRECT_REQUEST_PAYMENT_RECURRING_CHALLENGE_SCHEME` | `siglume-external-402-recurring-v1` |
744
+ | `DIRECT_REQUEST_PAYMENT_MODE` | `external_402` |
745
+ | `DIRECT_REQUEST_PAYMENT_RECEIPT_KIND` | `sdrp_direct_payment` |
746
+ | `DIRECT_REQUEST_PAYMENT_ALLOWANCE_RECEIPT_KIND` | `sdrp_direct_payment_allowance` |
747
+ | `DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE` | `sdrp_direct_payment_requirement` |
748
+ | `DEFAULT_WEBHOOK_TOLERANCE_SECONDS` | `300` |
749
+
750
+ The `external_402` / `siglume-external-402-*` values are legacy wire-compat
751
+ identifiers, not public product names (see the README "Compatibility Notes").
752
+
753
+ ## Aliases
754
+
755
+ For legacy wire-compat naming, the following exported names are aliases of the
756
+ preferred `DirectRequestPayment*` functions. They are identical functions; new
757
+ code should prefer the `DirectRequestPayment*` names.
758
+
759
+ | Alias (TS) | Alias (Py) | Preferred function |
760
+ | --- | --- | --- |
761
+ | `createExternal402Challenge` | `create_external_402_challenge` | `createDirectRequestPaymentChallenge` / `create_direct_request_payment_challenge` |
762
+ | `verifyExternal402Challenge` | `verify_external_402_challenge` | `verifyDirectRequestPaymentChallenge` / `verify_direct_request_payment_challenge` |
763
+ | `createExternal402RecurringChallenge` | `create_external_402_recurring_challenge` | `createDirectRequestPaymentRecurringChallenge` / `create_direct_request_payment_recurring_challenge` |
764
+ | `verifyExternal402RecurringChallenge` | `verify_external_402_recurring_challenge` | `verifyDirectRequestPaymentRecurringChallenge` / `verify_direct_request_payment_recurring_challenge` |
443
765
 
444
766
  ## Errors
445
767
 
@@ -447,6 +769,7 @@ TypeScript exports:
447
769
 
448
770
  - `SiglumeDirectRequestPaymentError`
449
771
  - `SiglumeApiError`
772
+ - `HostedCheckoutNotAvailableError`
450
773
  - `SiglumeWebhookSignatureError`
451
774
  - `SiglumeWebhookPayloadError`
452
775
 
@@ -454,8 +777,11 @@ Python exports:
454
777
 
455
778
  - `DirectRequestPaymentError`
456
779
  - `SiglumeApiError`
780
+ - `HostedCheckoutNotAvailableError`
457
781
  - `SiglumeWebhookSignatureError`
458
782
  - `SiglumeWebhookPayloadError`
459
783
 
460
784
  `SiglumeApiError` includes the HTTP status, platform error code, and parsed
461
785
  response data where available.
786
+ `HostedCheckoutNotAvailableError` is raised when the Hosted Checkout server
787
+ surface is not enabled for the account yet during the rollout.
@@ -24,7 +24,7 @@ revenue remains unsettled until the later on-chain settlement succeeds.
24
24
 
25
25
  There are two ways a buyer reaches you, and you integrate each differently:
26
26
 
27
- - **Human web shopper → Hosted Checkout.** Create a checkout session and
27
+ - **Human web shopper → Hosted Checkout (Beta; server rollout in progress).** Create a checkout session and
28
28
  redirect the shopper to the Siglume-hosted page (the
29
29
  [section below](#hosted-checkout-human-web-shoppers)). This is the path that
30
30
  resembles a Stripe-style hosted checkout.
@@ -38,6 +38,10 @@ the merchant SDK never authenticates the buyer, and you fulfill on the same
38
38
 
39
39
  ## Hosted Checkout (Human Web Shoppers)
40
40
 
41
+ **Beta / server rollout:** Hosted Checkout is rolling out account by account.
42
+ Some merchant accounts may not have the server endpoint enabled yet. The SDK
43
+ raises `HostedCheckoutNotAvailableError` for rollout 404/409 responses.
44
+
41
45
  When a person clicks "Pay with Siglume" on your site, create a session and
42
46
  redirect them to the returned `checkout_url`. They sign into Siglume on the
43
47
  hosted page, approve, and pay from their own wallet, then return to your
@@ -357,6 +361,26 @@ siglume.verify_payment_requirement(
357
361
  )
358
362
  ```
359
363
 
364
+ ### Subscriptions and scheduled autopay (no SDK method)
365
+
366
+ The SDK signs the merchant-side recurring approval challenge
367
+ (`createDirectRequestPaymentRecurringChallenge` /
368
+ `create_direct_request_payment_recurring_challenge`), but there is **no SDK
369
+ method for subscription creation**. After you hand the buyer the recurring
370
+ challenge, the subscription itself is created over **raw HTTP** with the buyer's
371
+ Siglume bearer token:
372
+
373
+ ```text
374
+ POST /v1/sdrp/direct-payments/subscriptions
375
+ { merchant, amount_minor, currency, cadence: "monthly", challenge }
376
+ ```
377
+
378
+ For scheduled autopay (`cadence: "daily"`), the buyer instead creates a scheduled
379
+ auto-pay authorization and hands you a `schedule_token`; your scheduler triggers
380
+ each occurrence with that token. Neither of these calls is wrapped by
381
+ `DirectRequestPaymentClient` today — the SDK's recurring surface is the challenge
382
+ signer and verifier only.
383
+
360
384
  ## 4. Fulfill from Webhook
361
385
 
362
386
  Use the webhook as the durable signal, not just the browser return path.
@@ -421,6 +445,13 @@ if verified["event"]["type"] == "direct_payment.confirmed":
421
445
  - `EXTERNAL_402_MERCHANT_BILLING_PAST_DUE` or
422
446
  `EXTERNAL_402_MERCHANT_BILLING_SUSPENDED`: merchant billing must be fixed
423
447
  before new payments can be accepted.
448
+ - `METERED_SETTLEMENT_PAST_DUE` (Micro / Nano only): a previous Micro / Nano
449
+ metered settlement for this buyer is unresolved, so new Micro / Nano usage in
450
+ the same fee band is paused until it settles. Siglume retries settlement
451
+ automatically every 6 hours, up to 28 attempts, before it requires manual
452
+ resolution. The provider's Micro / Nano revenue stays unsettled until the
453
+ settlement succeeds. This is a settlement-side state, not a per-request
454
+ challenge error.
424
455
 
425
456
  ## Go-Live Checklist
426
457
 
package/docs/pricing.md CHANGED
@@ -59,34 +59,38 @@ confirmed payment turns into money in your settlement wallet.
59
59
  | Band | Cadence | Period | You are paid |
60
60
  | --- | --- | --- | --- |
61
61
  | Standard Payment | Per payment | n/a | On-chain, immediately after each payment confirms |
62
- | Micro Payment | Weekly | Buyer settlement timezone Monday 00:00 to the next Monday 00:00; default timezone is UTC | After the week closes, in aggregated on-chain settlement(s) grouped per buyer, payee, token, and period |
63
- | Nano Payment | Monthly | Buyer settlement timezone 1st 00:00 to the 1st of the next month 00:00; default timezone is UTC | After the month closes, in aggregated on-chain settlement(s) grouped per buyer, payee, token, and period |
62
+ | Micro Payment | Weekly | Fixed weekly slot assigned per account | After the period closes, after the final notice and an approximately 3-day pre-debit notice site, in aggregated on-chain settlement(s) grouped per buyer, payee, token, and period |
63
+ | Nano Payment | Monthly | Fixed monthly slot assigned per account | After the period closes, after the final notice and an approximately 3-day pre-debit notice site, in aggregated on-chain settlement(s) grouped per buyer, payee, token, and period |
64
64
 
65
65
  ### Micro weekly settlement
66
66
 
67
- - **Closing period.** Micro-band payments accrue across one calendar week:
68
- Monday 00:00 to the following Monday 00:00 in the buyer settlement timezone.
67
+ - **Closing period.** Micro-band payments accrue across one weekly period. The
68
+ specific closing weekday and time are assigned as a fixed slot per account to
69
+ spread settlement load.
69
70
  - **Timezone.** Period boundaries are evaluated in the buyer's configured
70
- settlement timezone, defaulting to UTC, so different buyers can close on
71
- slightly different local boundaries.
71
+ settlement timezone, defaulting to UTC. Assigned slots are persisted and are
72
+ not recalculated on the fly.
72
73
  - **Settlement.** After the week closes, Siglume aggregates that week's Micro
73
74
  payments — grouped per buyer, payee, token, and period — into on-chain
74
- settlement(s). Aggregation and payment run automatically on the next settlement
75
- pass after the period closes; there is a short, platform-managed lag between
76
- the close and the on-chain transaction.
75
+ settlement(s). Siglume sends the final debit notice first; the on-chain debit
76
+ is not attempted until the scheduled attempt time after an approximately
77
+ 3-day pre-debit notice site (`not_before_attempt_at`).
77
78
  - **Revenue recognition.** A Micro payment is final only once its weekly
78
79
  settlement confirms on-chain. Until then it is accrued, not settled.
79
80
 
80
81
  ### Nano monthly settlement
81
82
 
82
- - **Closing period.** Nano-band payments accrue across one calendar month:
83
- the 1st at 00:00 to the 1st of the next month at 00:00 in the buyer
84
- settlement timezone.
83
+ - **Closing period.** Nano-band payments accrue across one monthly period. The
84
+ specific closing day and time are assigned as a fixed slot per account to
85
+ spread settlement load.
85
86
  - **Timezone.** As with Micro, period boundaries use the buyer's configured
86
- settlement timezone, defaulting to UTC.
87
+ settlement timezone, defaulting to UTC. Assigned slots are persisted and are
88
+ not recalculated on the fly.
87
89
  - **Settlement.** After the month closes, Siglume aggregates that month's Nano
88
90
  payments — grouped per buyer, payee, token, and period — into on-chain
89
- settlement(s), on the next settlement pass after the period closes.
91
+ settlement(s). Siglume sends the final debit notice first; the on-chain debit
92
+ is not attempted until the scheduled attempt time after an approximately
93
+ 3-day pre-debit notice site (`not_before_attempt_at`).
90
94
  - **Revenue recognition.** A Nano payment is final only once its monthly
91
95
  settlement confirms on-chain.
92
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siglume/direct-request-payment",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "SDK for the Siglume Direct Request Payment SDRP payment protocol",
5
5
  "keywords": [
6
6
  "siglume",