@siglume/direct-request-payment 0.4.1 → 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.
@@ -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
@@ -291,7 +426,7 @@ Calls:
291
426
  GET /v1/sdrp/direct-payments/checkout-sessions/{session_id}
292
427
  ```
293
428
 
294
- Returns a session status object with:
429
+ Returns a `HostedCheckoutSession` status object with:
295
430
 
296
431
  - `session_id`
297
432
  - `merchant`
@@ -301,13 +436,19 @@ Returns a session status object with:
301
436
  - `status`: one of `open`, `authenticated`, `paid`, `expired`, `cancelled`,
302
437
  `failed`
303
438
  - `challenge_hash`
304
- - `requirement_id`
439
+ - `requirement_id` (nullable until a requirement is created)
305
440
  - `success_url`
306
441
  - `cancel_url`
307
- - `expires_at`
308
- - `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)
309
447
  - `metadata_jsonb`
310
448
 
449
+ The TS `HostedCheckoutSession` interface also carries an index signature, so the
450
+ server may include additional pass-through fields.
451
+
311
452
  ### `getMerchant(merchant)` / `get_merchant(merchant)`
312
453
 
313
454
  Calls:
@@ -401,6 +542,61 @@ Input:
401
542
  - `allowance_amount_minor`: optional positive integer
402
543
  - `metadata`: optional JSON object
403
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
+
404
600
  ### `executeAllowanceTransaction(requirement)` / `execute_allowance_transaction(...)`
405
601
 
406
602
  Executes `requirement.approve_transaction_request` through:
@@ -433,19 +629,139 @@ Input may include:
433
629
  - `await_timeout_seconds`
434
630
  - `await_poll_seconds`
435
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
+
436
671
  ## Webhook Helpers
437
672
 
438
- - `buildWebhookSignatureHeader(secret, body)` for tests
439
- - `verifyWebhookSignature(secret, body, header)`
440
- - `verifyDirectRequestPaymentWebhook(secret, body, header)`
441
- - `parseDirectRequestPaymentWebhookEvent(payload)`
442
- - Python equivalents use snake_case:
443
- `build_webhook_signature_header`, `verify_webhook_signature`,
444
- `verify_direct_request_payment_webhook`, and
445
- `parse_direct_request_payment_webhook_event`.
446
-
447
- `verifyDirectRequestPaymentWebhook` verifies the signature and parses the event
448
- in one call.
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).
709
+
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` |
449
765
 
450
766
  ## Errors
451
767
 
@@ -361,6 +361,26 @@ siglume.verify_payment_requirement(
361
361
  )
362
362
  ```
363
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
+
364
384
  ## 4. Fulfill from Webhook
365
385
 
366
386
  Use the webhook as the durable signal, not just the browser return path.
@@ -425,6 +445,13 @@ if verified["event"]["type"] == "direct_payment.confirmed":
425
445
  - `EXTERNAL_402_MERCHANT_BILLING_PAST_DUE` or
426
446
  `EXTERNAL_402_MERCHANT_BILLING_SUSPENDED`: merchant billing must be fixed
427
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.
428
455
 
429
456
  ## Go-Live Checklist
430
457
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siglume/direct-request-payment",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "SDK for the Siglume Direct Request Payment SDRP payment protocol",
5
5
  "keywords": [
6
6
  "siglume",