@siglume/direct-request-payment 0.4.4 → 0.4.6
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/CHANGELOG.md +27 -0
- package/README.md +74 -23
- package/dist/index.cjs +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/docs/announcement-ja.md +4 -2
- package/docs/api-reference.md +80 -22
- package/docs/merchant-quickstart.md +102 -10
- package/docs/metered-statements.md +42 -4
- package/docs/pricing.md +15 -0
- package/docs/security.md +22 -10
- package/examples/express-checkout.ts +43 -6
- package/package.json +1 -1
package/docs/api-reference.md
CHANGED
|
@@ -26,9 +26,11 @@ card — and the merchant SDK never authenticates the buyer.
|
|
|
26
26
|
runs are bounded by Siglume's approval gates / spending budgets (per-run /
|
|
27
27
|
daily / monthly auto-pay budgets, or Works approval).
|
|
28
28
|
|
|
29
|
-
In both systems the merchant
|
|
30
|
-
|
|
31
|
-
and
|
|
29
|
+
In both systems the merchant handles the same signed `direct_payment.confirmed`
|
|
30
|
+
webhook. Hosted Checkout adds no new money movement and no new webhook. Inspect
|
|
31
|
+
`pricing_band`, `finality`, and `settlement_status`: Standard can be marked paid
|
|
32
|
+
only after settled per-payment finality, while Micro / Nano usage is accepted
|
|
33
|
+
before the later aggregated settlement.
|
|
32
34
|
|
|
33
35
|
## Environment Variables
|
|
34
36
|
|
|
@@ -36,7 +38,7 @@ and no new webhook.
|
|
|
36
38
|
| --- | --- | --- |
|
|
37
39
|
| `SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET` | merchant server | HMAC secret for order challenges |
|
|
38
40
|
| `SIGLUME_MERCHANT_AUTH_TOKEN` | merchant setup helper | merchant Siglume bearer token for self-service setup |
|
|
39
|
-
| `SIGLUME_AUTH_TOKEN` |
|
|
41
|
+
| `SIGLUME_AUTH_TOKEN` | user-authenticated helper | buyer Siglume bearer token for payment / buyer statements, or provider Siglume bearer token for provider statements |
|
|
40
42
|
| `SIGLUME_API_BASE` | optional | API base URL override; defaults to `https://siglume.com/v1` |
|
|
41
43
|
| `SIGLUME_WEBHOOK_SECRET` | merchant server | webhook signing secret returned as `whsec_...` |
|
|
42
44
|
|
|
@@ -364,8 +366,9 @@ webhook-origin auto-allow apply.
|
|
|
364
366
|
Beta / server rollout: Hosted Checkout is rolling out account by account. If the
|
|
365
367
|
server endpoint is not enabled for the merchant yet, the SDK raises
|
|
366
368
|
`HostedCheckoutNotAvailableError` (TS + Py) rather than leaking a raw rollout
|
|
367
|
-
404/409.
|
|
368
|
-
webhook
|
|
369
|
+
404/409. Payment handling must still key off the signed
|
|
370
|
+
`direct_payment.confirmed` webhook and its settlement machine fields, not the
|
|
371
|
+
event name alone.
|
|
369
372
|
|
|
370
373
|
Creates a single-use, expiring Hosted Checkout session for a human web shopper
|
|
371
374
|
and returns the URL to redirect them to. Requires the merchant's Siglume bearer
|
|
@@ -447,11 +450,24 @@ Returns a `HostedCheckoutSession` status object with:
|
|
|
447
450
|
`failed`
|
|
448
451
|
- `challenge_hash`
|
|
449
452
|
- `requirement_id` (nullable until a requirement is created)
|
|
453
|
+
- `pricing_band` (nullable until a requirement is created): `standard`,
|
|
454
|
+
`micro`, or `nano`
|
|
455
|
+
- `settlement_cadence` (nullable until a requirement is created):
|
|
456
|
+
`per_payment`, `weekly`, or `monthly`
|
|
457
|
+
- `finality` (nullable until a requirement is created), for example
|
|
458
|
+
`per_payment_onchain` or `aggregated_onchain_settlement`
|
|
459
|
+
- `protocol_fee_minor` (nullable; decimal string for Micro / Nano)
|
|
460
|
+
- `settlement_status` (nullable until a requirement is created), for example
|
|
461
|
+
`pending_payment`, `provisional`, `settled`, or `pending_settlement`
|
|
462
|
+
- `chain_receipt_id` (nullable)
|
|
450
463
|
- `success_url`
|
|
451
464
|
- `cancel_url`
|
|
452
465
|
- `expires_at` (nullable)
|
|
453
466
|
- `authenticated_at` (nullable; set when the shopper signs into Siglume)
|
|
454
|
-
- `paid_at` (nullable; set when the
|
|
467
|
+
- `paid_at` (nullable; set when Hosted Checkout has accepted the wallet
|
|
468
|
+
payment flow. For Micro / Nano, this is not the same as final provider
|
|
469
|
+
settlement; use `pricing_band`, `finality`, `settlement_status`, and the
|
|
470
|
+
statement APIs.)
|
|
455
471
|
- `cancelled_at` (nullable; set when the shopper cancels)
|
|
456
472
|
- `created_at` (nullable)
|
|
457
473
|
- `metadata_jsonb`
|
|
@@ -505,8 +521,10 @@ or rotation.
|
|
|
505
521
|
## `DirectRequestPaymentClient`
|
|
506
522
|
|
|
507
523
|
Thin wrapper around the current Siglume Direct Request Payment HTTP contract.
|
|
508
|
-
Use it with the authenticated
|
|
509
|
-
|
|
524
|
+
Use it with the authenticated Siglume user bearer token for the route you call:
|
|
525
|
+
buyer tokens for buyer payment and buyer statement routes, provider / merchant
|
|
526
|
+
tokens for provider statement routes. Developer Portal `cli_` API keys are not
|
|
527
|
+
accepted by these user-authenticated routes.
|
|
510
528
|
|
|
511
529
|
This client creates SDRP Standard Payment requirements for external merchant
|
|
512
530
|
checkout flows. Micro Payment and Nano Payment are applied automatically by
|
|
@@ -522,14 +540,14 @@ statement API amount fields (`protocol_fee_minor`, `gross_buyer_debit_minor`,
|
|
|
522
540
|
|
|
523
541
|
```ts
|
|
524
542
|
const siglume = new DirectRequestPaymentClient({
|
|
525
|
-
auth_token:
|
|
543
|
+
auth_token: buyerOrProviderSiglumeBearerToken,
|
|
526
544
|
base_url: "https://siglume.com/v1",
|
|
527
545
|
});
|
|
528
546
|
```
|
|
529
547
|
|
|
530
548
|
```py
|
|
531
549
|
siglume = DirectRequestPaymentClient(
|
|
532
|
-
auth_token=
|
|
550
|
+
auth_token=buyer_or_provider_siglume_bearer_token,
|
|
533
551
|
base_url="https://siglume.com/v1",
|
|
534
552
|
)
|
|
535
553
|
```
|
|
@@ -556,6 +574,20 @@ Input:
|
|
|
556
574
|
- `allowance_amount_minor`: optional positive integer
|
|
557
575
|
- `metadata`: optional JSON object
|
|
558
576
|
|
|
577
|
+
There is no public `idempotency_key` field on this requirement-create request.
|
|
578
|
+
For one-time external checkout, idempotency is the merchant-signed challenge
|
|
579
|
+
nonce: derive `nonce` from a durable order payment attempt, store the returned
|
|
580
|
+
`challenge_hash`, and use `request_hash_v2` when you need a canonical machine
|
|
581
|
+
hash for the same payment request. Do not retry the same order by minting a new
|
|
582
|
+
nonce unless you intentionally want a new payment attempt.
|
|
583
|
+
|
|
584
|
+
For Siglume Marketplace paid capability execution / MCP tools, `idempotency_key`
|
|
585
|
+
is a separate top-level JSON field on the execution payload or tool arguments.
|
|
586
|
+
Use one stable key per logical paid operation, up to 128 characters, and do not
|
|
587
|
+
reuse it for a different payload. A retry with the same key returns or reconciles
|
|
588
|
+
the first recorded outcome instead of creating another chargeable usage event.
|
|
589
|
+
The HTTP `Idempotency-Key` header is not the public requirement-create contract.
|
|
590
|
+
|
|
559
591
|
The returned requirement includes both compatibility and machine-readable
|
|
560
592
|
settlement fields:
|
|
561
593
|
|
|
@@ -723,8 +755,9 @@ SDK methods:
|
|
|
723
755
|
- TypeScript: `listBuyerUsageEvents(...)`, `listBuyerSettlementBatches(...)`
|
|
724
756
|
- Python: `list_buyer_usage_events(...)`, `list_buyer_settlement_batches(...)`
|
|
725
757
|
|
|
726
|
-
Each accepts `plan_type`, `token_symbol`, `status`, and `
|
|
727
|
-
`{items, next_cursor}`.
|
|
758
|
+
Each accepts `plan_type`, `token_symbol`, `status`, `limit`, and `cursor`, and
|
|
759
|
+
returns `{items, next_cursor}`. When `next_cursor` is non-null, pass it back as
|
|
760
|
+
`cursor` to fetch the next page.
|
|
728
761
|
|
|
729
762
|
Buyer-facing amount names are centered on the debit:
|
|
730
763
|
|
|
@@ -783,9 +816,10 @@ SDK methods:
|
|
|
783
816
|
`list_provider_settlement_batches(...)`, `get_provider_settlement_batch(...)`
|
|
784
817
|
|
|
785
818
|
Provider list methods accept `plan_type`, `token_symbol`, `status`,
|
|
786
|
-
`listing_id`, `capability_key`, and `
|
|
819
|
+
`listing_id`, `capability_key`, `limit`, and `cursor`. Detail accepts
|
|
787
820
|
`settlement_batch_id`, plus optional `listing_id` and `capability_key`. List
|
|
788
|
-
methods return `{items, next_cursor}`.
|
|
821
|
+
methods return `{items, next_cursor}`. When `next_cursor` is non-null, pass it
|
|
822
|
+
back as `cursor` to fetch the next page.
|
|
789
823
|
|
|
790
824
|
Provider-facing amount names:
|
|
791
825
|
|
|
@@ -843,6 +877,13 @@ metered_usage_id,created_at,plan_type,settlement_cadence,period_start,period_end
|
|
|
843
877
|
```
|
|
844
878
|
|
|
845
879
|
The CSV uses `buyer_period_ref`, not raw buyer account identifiers.
|
|
880
|
+
The CSV keeps the `rounding_delta_minor` column for schema stability, but usage
|
|
881
|
+
rows report `0`; the authoritative rounding adjustment is the settlement batch
|
|
882
|
+
field `rounding_delta_minor`.
|
|
883
|
+
|
|
884
|
+
Micro / Nano amount fields are decimal minor-unit strings. In JavaScript, do
|
|
885
|
+
not aggregate them with `number`; parse them with a decimal library. In Python,
|
|
886
|
+
use `Decimal` for accounting and reconciliation.
|
|
846
887
|
|
|
847
888
|
## Payload Builders
|
|
848
889
|
|
|
@@ -919,11 +960,26 @@ argument is positional in both languages.
|
|
|
919
960
|
|
|
920
961
|
For `direct_payment.confirmed`, inspect `event.data.pricing_band`,
|
|
921
962
|
`event.data.settlement_cadence`, `event.data.finality`,
|
|
922
|
-
`event.data.protocol_fee_minor`,
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
963
|
+
`event.data.protocol_fee_minor`, `event.data.settlement_status`,
|
|
964
|
+
`event.data.settlement_batch_id`, `event.data.chain_receipt_id`,
|
|
965
|
+
`event.data.usage_event_digest`, and `event.data.settled_at` instead of
|
|
966
|
+
inferring whether the event means per-payment on-chain confirmation, Micro /
|
|
967
|
+
Nano accepted-but-unsettled usage, or an aggregated Micro / Nano settlement
|
|
968
|
+
confirmation. `event.data.request_hash_v2` is present on new challenge-backed
|
|
969
|
+
requirements; keep accepting `event.data.request_hash` for historical payloads.
|
|
970
|
+
|
|
971
|
+
Recommended branch:
|
|
972
|
+
|
|
973
|
+
- `mode === "metered_settlement_batch"`: no order `challenge_hash` is expected.
|
|
974
|
+
Reconcile the batch only when `settlement_status === "settled"`.
|
|
975
|
+
- `pricing_band === "standard"`, `finality === "per_payment_onchain"`, and
|
|
976
|
+
`settlement_status === "settled"`: mark the mapped order paid once.
|
|
977
|
+
- `pricing_band === "micro" || pricing_band === "nano"`: treat the usage as
|
|
978
|
+
accepted but unsettled. Fulfill only if your business accepts delayed
|
|
979
|
+
settlement risk, and reconcile final revenue from statement APIs / settlement
|
|
980
|
+
batches.
|
|
981
|
+
- Missing machine fields: do not mark paid from the event type alone; fetch the
|
|
982
|
+
requirement or route the event to manual review.
|
|
927
983
|
|
|
928
984
|
### `verifyDirectRequestPaymentWebhook(secret, body, signature_header, options)` / `verify_direct_request_payment_webhook(secret, body, signature_header, *, tolerance_seconds=300, now=None)`
|
|
929
985
|
|
|
@@ -941,7 +997,8 @@ const { event, verification } = await verifyDirectRequestPaymentWebhook(
|
|
|
941
997
|
rawRequestBody, // the RAW body bytes/string, not re-stringified JSON
|
|
942
998
|
request.headers["siglume-signature"],
|
|
943
999
|
);
|
|
944
|
-
// event.type === "direct_payment.confirmed"
|
|
1000
|
+
// event.type === "direct_payment.confirmed"; inspect pricing_band/finality/
|
|
1001
|
+
// settlement_status before marking an order paid.
|
|
945
1002
|
```
|
|
946
1003
|
|
|
947
1004
|
```py
|
|
@@ -953,7 +1010,8 @@ verified = verify_direct_request_payment_webhook(
|
|
|
953
1010
|
siglume_signature_header,
|
|
954
1011
|
)
|
|
955
1012
|
event = verified["event"]
|
|
956
|
-
# event["type"] == "direct_payment.confirmed"
|
|
1013
|
+
# event["type"] == "direct_payment.confirmed"; inspect pricing_band/finality/
|
|
1014
|
+
# settlement_status before marking an order paid.
|
|
957
1015
|
```
|
|
958
1016
|
|
|
959
1017
|
## Exported Constants
|
|
@@ -396,16 +396,63 @@ const { event } = await verifyDirectRequestPaymentWebhook(
|
|
|
396
396
|
siglumeSignatureHeader,
|
|
397
397
|
);
|
|
398
398
|
|
|
399
|
-
if (event.type
|
|
400
|
-
|
|
399
|
+
if (event.type !== "direct_payment.confirmed") {
|
|
400
|
+
return new Response(null, { status: 204 });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const data = event.data;
|
|
404
|
+
|
|
405
|
+
if (data.mode === "metered_settlement_batch") {
|
|
406
|
+
// Aggregated Micro/Nano settlement events do not carry an order challenge.
|
|
407
|
+
if (data.settlement_status === "settled") {
|
|
408
|
+
await orders.reconcileMeteredSettlementOnce({
|
|
409
|
+
settlement_batch_id: String(data.settlement_batch_id ?? ""),
|
|
410
|
+
chain_receipt_id: String(data.chain_receipt_id ?? ""),
|
|
411
|
+
usage_event_digest: String(data.usage_event_digest ?? ""),
|
|
412
|
+
settled_at: String(data.settled_at ?? ""),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
return new Response(null, { status: 204 });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (
|
|
419
|
+
data.pricing_band === "standard" &&
|
|
420
|
+
data.finality === "per_payment_onchain" &&
|
|
421
|
+
data.settlement_status === "settled"
|
|
422
|
+
) {
|
|
401
423
|
const order = await orders.findByChallengeHash(String(data.challenge_hash ?? ""));
|
|
402
424
|
if (!order) {
|
|
403
|
-
|
|
425
|
+
await orders.flagForPaymentStateReview({
|
|
426
|
+
reason: "unknown_challenge_hash",
|
|
427
|
+
requirement_id: String(data.requirement_id ?? data.direct_payment_requirement_id ?? ""),
|
|
428
|
+
});
|
|
429
|
+
return new Response(null, { status: 204 });
|
|
404
430
|
}
|
|
405
431
|
await orders.markPaidOnce(order.id, {
|
|
406
432
|
siglume_requirement_id: String(data.requirement_id ?? data.direct_payment_requirement_id ?? ""),
|
|
433
|
+
chain_receipt_id: String(data.chain_receipt_id ?? ""),
|
|
407
434
|
});
|
|
435
|
+
return new Response(null, { status: 204 });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (data.pricing_band === "micro" || data.pricing_band === "nano") {
|
|
439
|
+
const order = await orders.findByChallengeHash(String(data.challenge_hash ?? ""));
|
|
440
|
+
if (order) {
|
|
441
|
+
await orders.markFulfilledButUnsettledOnce(order.id, {
|
|
442
|
+
siglume_requirement_id: String(data.requirement_id ?? data.direct_payment_requirement_id ?? ""),
|
|
443
|
+
pricing_band: String(data.pricing_band),
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return new Response(null, { status: 204 });
|
|
408
447
|
}
|
|
448
|
+
|
|
449
|
+
// Missing or unknown machine fields: do not mark the order paid from the event
|
|
450
|
+
// name alone. Fetch the requirement or route it to manual review.
|
|
451
|
+
await orders.flagForPaymentStateReview({
|
|
452
|
+
reason: "missing_settlement_machine_fields",
|
|
453
|
+
requirement_id: String(data.requirement_id ?? data.direct_payment_requirement_id ?? ""),
|
|
454
|
+
});
|
|
455
|
+
return new Response(null, { status: 204 });
|
|
409
456
|
```
|
|
410
457
|
|
|
411
458
|
Python:
|
|
@@ -421,23 +468,68 @@ verified = verify_direct_request_payment_webhook(
|
|
|
421
468
|
siglume_signature_header,
|
|
422
469
|
)
|
|
423
470
|
|
|
424
|
-
if verified["event"]["type"]
|
|
425
|
-
|
|
471
|
+
if verified["event"]["type"] != "direct_payment.confirmed":
|
|
472
|
+
return "", 204
|
|
473
|
+
|
|
474
|
+
data = verified["event"]["data"]
|
|
475
|
+
|
|
476
|
+
if data.get("mode") == "metered_settlement_batch":
|
|
477
|
+
# Aggregated Micro/Nano settlement events do not carry an order challenge.
|
|
478
|
+
if data.get("settlement_status") == "settled":
|
|
479
|
+
orders.reconcile_metered_settlement_once(
|
|
480
|
+
settlement_batch_id=str(data.get("settlement_batch_id") or ""),
|
|
481
|
+
chain_receipt_id=str(data.get("chain_receipt_id") or ""),
|
|
482
|
+
usage_event_digest=str(data.get("usage_event_digest") or ""),
|
|
483
|
+
settled_at=str(data.get("settled_at") or ""),
|
|
484
|
+
)
|
|
485
|
+
return "", 204
|
|
486
|
+
|
|
487
|
+
if (
|
|
488
|
+
data.get("pricing_band") == "standard"
|
|
489
|
+
and data.get("finality") == "per_payment_onchain"
|
|
490
|
+
and data.get("settlement_status") == "settled"
|
|
491
|
+
):
|
|
426
492
|
order = orders.find_by_challenge_hash(str(data.get("challenge_hash") or ""))
|
|
427
493
|
if not order:
|
|
428
|
-
|
|
494
|
+
orders.flag_for_payment_state_review(
|
|
495
|
+
reason="unknown_challenge_hash",
|
|
496
|
+
requirement_id=str(data.get("requirement_id") or data.get("direct_payment_requirement_id") or ""),
|
|
497
|
+
)
|
|
498
|
+
return "", 204
|
|
429
499
|
orders.mark_paid_once(
|
|
430
500
|
order["id"],
|
|
431
501
|
siglume_requirement_id=str(data.get("requirement_id") or data.get("direct_payment_requirement_id") or ""),
|
|
502
|
+
chain_receipt_id=str(data.get("chain_receipt_id") or ""),
|
|
432
503
|
)
|
|
504
|
+
return "", 204
|
|
505
|
+
|
|
506
|
+
if data.get("pricing_band") in ("micro", "nano"):
|
|
507
|
+
order = orders.find_by_challenge_hash(str(data.get("challenge_hash") or ""))
|
|
508
|
+
if order:
|
|
509
|
+
orders.mark_fulfilled_but_unsettled_once(
|
|
510
|
+
order["id"],
|
|
511
|
+
siglume_requirement_id=str(data.get("requirement_id") or data.get("direct_payment_requirement_id") or ""),
|
|
512
|
+
pricing_band=str(data.get("pricing_band") or ""),
|
|
513
|
+
)
|
|
514
|
+
return "", 204
|
|
515
|
+
|
|
516
|
+
# Missing or unknown machine fields: do not mark the order paid from the event
|
|
517
|
+
# name alone. Fetch the requirement or route it to manual review.
|
|
518
|
+
orders.flag_for_payment_state_review(
|
|
519
|
+
reason="missing_settlement_machine_fields",
|
|
520
|
+
requirement_id=str(data.get("requirement_id") or data.get("direct_payment_requirement_id") or ""),
|
|
521
|
+
)
|
|
522
|
+
return "", 204
|
|
433
523
|
```
|
|
434
524
|
|
|
435
525
|
## Reconcile Micro / Nano Statements
|
|
436
526
|
|
|
437
|
-
Standard Payment can be
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
527
|
+
Standard Payment can be marked paid from the verified `direct_payment.confirmed`
|
|
528
|
+
webhook only when `pricing_band === "standard"`,
|
|
529
|
+
`finality === "per_payment_onchain"`, and `settlement_status === "settled"`.
|
|
530
|
+
Micro Payment and Nano Payment are different: they are automatic amount bands
|
|
531
|
+
and are settled later in aggregated on-chain batches. Use the statement APIs to
|
|
532
|
+
answer:
|
|
441
533
|
|
|
442
534
|
- how much Micro / Nano usage is open this week or month,
|
|
443
535
|
- when the buyer's assigned period closes,
|
|
@@ -106,12 +106,24 @@ buyer_debit_minor = ceil(gross_buyer_debit_minor)
|
|
|
106
106
|
rounding_delta_minor = buyer_debit_minor - gross_buyer_debit_minor
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
For low-count Nano batches, the ceiling can make the effective buyer burden per
|
|
110
|
+
usage higher than the headline "USD 0.001 / usage" protocol fee. The protocol
|
|
111
|
+
fee remains the decimal statement amount; the extra integer-minor-unit
|
|
112
|
+
adjustment is recorded as `rounding_delta_minor` on the settlement batch. Each
|
|
113
|
+
settlement batch can add a positive rounding adjustment of less than 1 token
|
|
114
|
+
minor unit; if a buyer uses many providers / payees in one period, that
|
|
115
|
+
adjustment can occur once per settlement batch.
|
|
116
|
+
|
|
109
117
|
`rounding_delta_minor` belongs to the buyer debit and Siglume's rounding
|
|
110
118
|
adjustment accounting for that batch. It is not provider revenue. Provider
|
|
111
119
|
reports should use `provider_receivable_minor`,
|
|
112
120
|
`settled_provider_receivable_minor`, `unsettled_provider_receivable_minor`, and
|
|
113
121
|
`past_due_provider_receivable_minor`.
|
|
114
122
|
|
|
123
|
+
Micro / Nano amount fields are decimal minor-unit strings. JavaScript
|
|
124
|
+
integrations should aggregate them with a decimal library, not `number`. Python
|
|
125
|
+
integrations should use `Decimal` for accounting.
|
|
126
|
+
|
|
115
127
|
### Usage Events
|
|
116
128
|
|
|
117
129
|
```text
|
|
@@ -123,6 +135,7 @@ Query parameters:
|
|
|
123
135
|
- `plan_type`: `micro` or `nano`
|
|
124
136
|
- `token_symbol`: `JPYC` or `USDC`
|
|
125
137
|
- `status`: for example `pending_settlement`, `settled`, `failed_chargeable`
|
|
138
|
+
- `cursor`: pass the previous response's `next_cursor` to fetch the next page
|
|
126
139
|
- `limit`: 1 to 500
|
|
127
140
|
|
|
128
141
|
SDK methods: `listBuyerUsageEvents(...)` / `list_buyer_usage_events(...)`.
|
|
@@ -148,6 +161,7 @@ Query parameters:
|
|
|
148
161
|
- `token_symbol`: `JPYC` or `USDC`
|
|
149
162
|
- `status`: `notice_pending`, `ready`, `submitted`, `settled`, `failed`,
|
|
150
163
|
`past_due`, or `notice_delivery_failed`
|
|
164
|
+
- `cursor`: pass the previous response's `next_cursor` to fetch the next page
|
|
151
165
|
- `limit`: 1 to 200
|
|
152
166
|
|
|
153
167
|
SDK methods: `listBuyerSettlementBatches(...)` /
|
|
@@ -233,6 +247,7 @@ Query parameters:
|
|
|
233
247
|
- `status`
|
|
234
248
|
- `listing_id`
|
|
235
249
|
- `capability_key`
|
|
250
|
+
- `cursor`: pass the previous response's `next_cursor` to fetch the next page
|
|
236
251
|
- `limit`: 1 to 500
|
|
237
252
|
|
|
238
253
|
SDK methods: `listProviderUsageEvents(...)` /
|
|
@@ -266,6 +281,7 @@ Query parameters for the list endpoint:
|
|
|
266
281
|
- `status`
|
|
267
282
|
- `listing_id`
|
|
268
283
|
- `capability_key`
|
|
284
|
+
- `cursor`: pass the previous response's `next_cursor` to fetch the next page
|
|
269
285
|
- `limit`: 1 to 200
|
|
270
286
|
|
|
271
287
|
The detail endpoint also accepts `listing_id` and `capability_key`.
|
|
@@ -317,6 +333,10 @@ metered_usage_id,created_at,plan_type,settlement_cadence,period_start,period_end
|
|
|
317
333
|
```
|
|
318
334
|
|
|
319
335
|
The CSV uses `buyer_period_ref`, not `buyer_user_id`.
|
|
336
|
+
`rounding_delta_minor` is present for a stable usage-event schema, but per-row
|
|
337
|
+
values are `0`. The authoritative rounding adjustment is the settlement batch
|
|
338
|
+
`rounding_delta_minor`; do not allocate that adjustment to provider revenue
|
|
339
|
+
from individual CSV rows.
|
|
320
340
|
|
|
321
341
|
## Notifications
|
|
322
342
|
|
|
@@ -360,9 +380,20 @@ or support staff. Do not depend on raw platform failure messages.
|
|
|
360
380
|
|
|
361
381
|
## Usage Accounting by Result
|
|
362
382
|
|
|
363
|
-
Use idempotency keys for every paid operation. Siglume
|
|
364
|
-
|
|
365
|
-
|
|
383
|
+
Use idempotency keys for every paid operation. For Siglume Marketplace paid
|
|
384
|
+
capability execution and MCP tools, pass `idempotency_key` as a top-level JSON
|
|
385
|
+
field on the execution payload / tool arguments. Do not rely on an HTTP
|
|
386
|
+
`Idempotency-Key` header for this public paid-operation contract; Siglume may
|
|
387
|
+
also use that header internally when it calls providers.
|
|
388
|
+
|
|
389
|
+
The key should be a stable retry key for one logical paid operation, such as
|
|
390
|
+
`order:<order_id>:attempt:<n>` or `<provider_event_id>`, and should not be reused
|
|
391
|
+
for a different payload. The current public contract stores up to 128
|
|
392
|
+
characters. Siglume records one chargeable usage event per idempotency key within
|
|
393
|
+
the same buyer / listing / capability scope; a retry with the same key returns or
|
|
394
|
+
reconciles the first recorded outcome rather than creating another chargeable
|
|
395
|
+
event. If a provider times out after doing work, retry or reconcile with the
|
|
396
|
+
same key before repeating side effects.
|
|
366
397
|
|
|
367
398
|
| Case | Provider API executed? | Usage counted? | Integration rule |
|
|
368
399
|
| --- | --- | --- | --- |
|
|
@@ -392,6 +423,11 @@ using stable idempotency keys and provider-side completion records.
|
|
|
392
423
|
| `past_due` | Buyer has an unresolved settlement block; provider sees past-due revenue | New Micro / Nano usage for the same buyer / plan / token is paused | Operator requeue or manual resolution only | Do not promise collection or provider payment. Ask the buyer to repair balance / allowance / BudgetVault / caps and reference `support_reference`. |
|
|
393
424
|
| `failed_chargeable` | Usage is still chargeable because provider work was accepted or completed | Included in later settlement attempts | Review if the provider disputes completion | Keep fulfillment idempotent and preserve evidence keyed by idempotency key. |
|
|
394
425
|
|
|
426
|
+
Future platform versions may add explicit terminal states such as
|
|
427
|
+
`closed_unpaid`, `uncollectible`, or `written_off`. Treat unknown terminal
|
|
428
|
+
settlement states as not settled unless `status === "settled"` and
|
|
429
|
+
`chain_receipt_id` is present.
|
|
430
|
+
|
|
395
431
|
## Operational Recipes
|
|
396
432
|
|
|
397
433
|
### "How much did we use this week or month?"
|
|
@@ -424,7 +460,9 @@ separate from settled revenue.
|
|
|
424
460
|
## Go-Live Checklist
|
|
425
461
|
|
|
426
462
|
- Your order fulfillment is idempotent by order id and requirement id.
|
|
427
|
-
- Standard Payment fulfillment still uses verified `direct_payment.confirmed
|
|
463
|
+
- Standard Payment fulfillment still uses verified `direct_payment.confirmed`
|
|
464
|
+
only when `pricing_band`, `finality`, and `settlement_status` show settled
|
|
465
|
+
per-payment finality.
|
|
428
466
|
- Micro / Nano accounting uses statement APIs or CSV, not only webhooks.
|
|
429
467
|
- Your dashboard separates settled, unsettled, and past-due provider amounts.
|
|
430
468
|
- Your support UI shows sanitized failure fields and `support_reference`.
|
package/docs/pricing.md
CHANGED
|
@@ -40,6 +40,11 @@ The current public API chooses the band from `amount_minor`; it does not expose
|
|
|
40
40
|
`settlement_mode: "immediate"` or `require_immediate_finality: true`. If a
|
|
41
41
|
merchant needs immediate on-chain finality, the payment amount must be in the
|
|
42
42
|
Standard band or the merchant must have a separately agreed platform contract.
|
|
43
|
+
For public one-time Direct Payment / Hosted Checkout, `amount_minor` is a
|
|
44
|
+
positive integer in minor currency units. That means the smallest public
|
|
45
|
+
one-time checkout amount is JPY 1 or USD 0.01. Nano Payment on this public path
|
|
46
|
+
therefore means JPY 1-49 or USD 0.01-0.30. Sub-minor Nano protocol fees are
|
|
47
|
+
settlement-accounting amounts, not externally submitted one-time item prices.
|
|
43
48
|
|
|
44
49
|
For the operational statement APIs, CSV export, buyer past-due blocks, and the
|
|
45
50
|
field-by-field meaning of `scheduled_debit_at`, `not_before_attempt_at`,
|
|
@@ -179,6 +184,16 @@ revenue. Providers should reconcile their revenue with
|
|
|
179
184
|
`unsettled_provider_receivable_minor`, and
|
|
180
185
|
`past_due_provider_receivable_minor`, not with `buyer_debit_minor`.
|
|
181
186
|
|
|
187
|
+
For low-count Nano batches, the integer ceiling can make the effective buyer
|
|
188
|
+
burden per usage higher than the headline USD 0.001 / usage protocol fee. The
|
|
189
|
+
decimal protocol fee remains visible as `protocol_fee_minor`; the difference
|
|
190
|
+
created by integer-token settlement is visible as `rounding_delta_minor` on the
|
|
191
|
+
batch. Each settlement batch can add a positive rounding adjustment of less than
|
|
192
|
+
1 token minor unit. If a buyer uses many providers / payees in one period, that
|
|
193
|
+
adjustment can occur once per settlement batch. JavaScript integrations should
|
|
194
|
+
not sum Micro / Nano minor amounts with `number`; use a decimal library. Python
|
|
195
|
+
integrations should use `Decimal`.
|
|
196
|
+
|
|
182
197
|
## Statement APIs and Notices
|
|
183
198
|
|
|
184
199
|
Micro and Nano require operational reconciliation after usage is accepted. The
|
package/docs/security.md
CHANGED
|
@@ -119,6 +119,19 @@ Fulfill exactly once per order. Store at least:
|
|
|
119
119
|
Duplicate webhook deliveries and manual redelivery can occur. A duplicate
|
|
120
120
|
webhook with the same requirement id must not ship the order twice.
|
|
121
121
|
|
|
122
|
+
The public requirement-create API does not accept an `idempotency_key` field.
|
|
123
|
+
For one-time external checkout, the durable idempotency material is the
|
|
124
|
+
merchant-authored challenge nonce plus the returned `challenge_hash` /
|
|
125
|
+
`request_hash_v2`. Reuse the same order-attempt nonce when reconciling a retry;
|
|
126
|
+
mint a new nonce only for a new payment attempt.
|
|
127
|
+
|
|
128
|
+
For Micro / Nano paid capability execution through Siglume Marketplace or MCP
|
|
129
|
+
tools, use the top-level JSON field `idempotency_key` on the execution payload /
|
|
130
|
+
tool arguments. Treat it as a stable retry key for one logical paid operation
|
|
131
|
+
and do not reuse it for a different payload. The HTTP `Idempotency-Key` header is
|
|
132
|
+
not the public contract for this requirement-create API; Siglume may use headers
|
|
133
|
+
internally when calling providers.
|
|
134
|
+
|
|
122
135
|
## Micro / Nano Statement Privacy
|
|
123
136
|
|
|
124
137
|
Micro Payment and Nano Payment introduce operational statement APIs and CSV
|
|
@@ -154,16 +167,15 @@ Direct Request Payment is not:
|
|
|
154
167
|
- a platform balance
|
|
155
168
|
- a card payment fallback
|
|
156
169
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
wallet
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
authorization, cap, or on-chain transaction fails.
|
|
170
|
+
Standard Payment is settled individually with its own on-chain receipt. Micro
|
|
171
|
+
and Nano usage events are included in an aggregated settlement batch, and the
|
|
172
|
+
batch is backed by an on-chain receipt. They are still wallet payments, not a
|
|
173
|
+
stored balance. Before a small payment is fulfilled, Siglume checks the buyer's
|
|
174
|
+
wallet budget and fails closed when it is invalid, so a rejected request is
|
|
175
|
+
never charged. Provider revenue for Micro and Nano remains unsettled until the
|
|
176
|
+
aggregated on-chain settlement succeeds; Siglume does not advance or guarantee
|
|
177
|
+
revenue when a buyer's balance, allowance, BudgetVault authorization, cap, or
|
|
178
|
+
on-chain transaction fails.
|
|
167
179
|
|
|
168
180
|
A Micro / Nano budget reservation is not a token lock, escrow, or payment
|
|
169
181
|
guarantee. It reserves room against Siglume spending limits only. A later
|
|
@@ -22,6 +22,48 @@ app.use((req, res, next) => {
|
|
|
22
22
|
|
|
23
23
|
const orders = new Map<string, any>();
|
|
24
24
|
|
|
25
|
+
async function handleDirectPaymentConfirmed(data: Record<string, any>): Promise<void> {
|
|
26
|
+
if (data.mode === "metered_settlement_batch") {
|
|
27
|
+
// Aggregated Micro/Nano settlement events do not carry an order challenge.
|
|
28
|
+
// Reconcile them against statement / settlement batch data instead.
|
|
29
|
+
if (data.settlement_status === "settled") {
|
|
30
|
+
console.log("settled metered batch", data.settlement_batch_id || data.usage_event_digest);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
data.pricing_band === "standard" &&
|
|
37
|
+
data.finality === "per_payment_onchain" &&
|
|
38
|
+
data.settlement_status === "settled"
|
|
39
|
+
) {
|
|
40
|
+
const challengeHash = String(data.challenge_hash || "");
|
|
41
|
+
const order = [...orders.values()].find((item) => item.siglume_challenge_hash === challengeHash);
|
|
42
|
+
if (order) {
|
|
43
|
+
order.siglume_payment_status = "paid";
|
|
44
|
+
order.siglume_requirement_id = data.requirement_id || data.direct_payment_requirement_id;
|
|
45
|
+
order.siglume_chain_receipt_id = data.chain_receipt_id || null;
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (data.pricing_band === "micro" || data.pricing_band === "nano") {
|
|
51
|
+
const challengeHash = String(data.challenge_hash || "");
|
|
52
|
+
const order = [...orders.values()].find((item) => item.siglume_challenge_hash === challengeHash);
|
|
53
|
+
if (order) {
|
|
54
|
+
order.siglume_payment_status = "fulfilled_unsettled";
|
|
55
|
+
order.siglume_requirement_id = data.requirement_id || data.direct_payment_requirement_id;
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Unknown or older payload shape: do not mark paid from the event name alone.
|
|
61
|
+
console.warn("direct_payment.confirmed missing settlement machine fields", {
|
|
62
|
+
id: data.id,
|
|
63
|
+
requirement_id: data.requirement_id || data.direct_payment_requirement_id,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
25
67
|
const asyncRoute =
|
|
26
68
|
(handler: express.RequestHandler): express.RequestHandler =>
|
|
27
69
|
(req, res, next) => {
|
|
@@ -69,12 +111,7 @@ app.post("/siglume/webhook", express.raw({ type: "application/json" }), asyncRou
|
|
|
69
111
|
);
|
|
70
112
|
|
|
71
113
|
if (event.type === "direct_payment.confirmed") {
|
|
72
|
-
|
|
73
|
-
const order = [...orders.values()].find((item) => item.siglume_challenge_hash === challengeHash);
|
|
74
|
-
if (order) {
|
|
75
|
-
order.siglume_payment_status = "paid";
|
|
76
|
-
order.siglume_requirement_id = event.data.requirement_id || event.data.direct_payment_requirement_id;
|
|
77
|
-
}
|
|
114
|
+
await handleDirectPaymentConfirmed(event.data);
|
|
78
115
|
}
|
|
79
116
|
|
|
80
117
|
res.status(204).send();
|