@siglume/direct-request-payment 0.4.5 → 0.4.7
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 +21 -0
- package/README.md +76 -26
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/docs/announcement-ja.md +4 -2
- package/docs/api-reference.md +53 -13
- package/docs/merchant-quickstart.md +109 -13
- package/docs/metered-statements.md +21 -5
- package/docs/pricing.md +19 -7
- package/docs/security.md +7 -0
- package/examples/express-checkout.ts +43 -6
- package/package.json +1 -1
|
@@ -16,11 +16,15 @@ order challenge; the buyer-facing Siglume payment flow pays it.
|
|
|
16
16
|
|
|
17
17
|
This quickstart uses Standard-band example amounts. Micro Payment and Nano
|
|
18
18
|
Payment are applied automatically by amount through the same Hosted Checkout or
|
|
19
|
-
agent/API flow; you do not create a separate Micro/Nano object or
|
|
20
|
-
|
|
19
|
+
agent/API flow; you do not create a separate Micro/Nano object or manually
|
|
20
|
+
select the amount band. Micro/Nano are settled on account-assigned weekly /
|
|
21
21
|
monthly slots after the final notice and close-plus-3-day window (see
|
|
22
22
|
[Pricing](./pricing.md#settlement-schedule)), and provider revenue remains
|
|
23
|
-
unsettled until the later on-chain settlement succeeds.
|
|
23
|
+
unsettled until the later on-chain settlement succeeds. Completing merchant
|
|
24
|
+
setup and the billing mandate means accepting this Micro/Nano delayed aggregated
|
|
25
|
+
settlement model for low-price items. If your product requires immediate
|
|
26
|
+
on-chain settlement, keep its price above the Micro/Nano thresholds instead of
|
|
27
|
+
offering JPY 500-and-under or USD 3-and-under amounts.
|
|
24
28
|
|
|
25
29
|
## Two Buyer Systems
|
|
26
30
|
|
|
@@ -396,16 +400,63 @@ const { event } = await verifyDirectRequestPaymentWebhook(
|
|
|
396
400
|
siglumeSignatureHeader,
|
|
397
401
|
);
|
|
398
402
|
|
|
399
|
-
if (event.type
|
|
400
|
-
|
|
403
|
+
if (event.type !== "direct_payment.confirmed") {
|
|
404
|
+
return new Response(null, { status: 204 });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const data = event.data;
|
|
408
|
+
|
|
409
|
+
if (data.mode === "metered_settlement_batch") {
|
|
410
|
+
// Aggregated Micro/Nano settlement events do not carry an order challenge.
|
|
411
|
+
if (data.settlement_status === "settled") {
|
|
412
|
+
await orders.reconcileMeteredSettlementOnce({
|
|
413
|
+
settlement_batch_id: String(data.settlement_batch_id ?? ""),
|
|
414
|
+
chain_receipt_id: String(data.chain_receipt_id ?? ""),
|
|
415
|
+
usage_event_digest: String(data.usage_event_digest ?? ""),
|
|
416
|
+
settled_at: String(data.settled_at ?? ""),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return new Response(null, { status: 204 });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (
|
|
423
|
+
data.pricing_band === "standard" &&
|
|
424
|
+
data.finality === "per_payment_onchain" &&
|
|
425
|
+
data.settlement_status === "settled"
|
|
426
|
+
) {
|
|
401
427
|
const order = await orders.findByChallengeHash(String(data.challenge_hash ?? ""));
|
|
402
428
|
if (!order) {
|
|
403
|
-
|
|
429
|
+
await orders.flagForPaymentStateReview({
|
|
430
|
+
reason: "unknown_challenge_hash",
|
|
431
|
+
requirement_id: String(data.requirement_id ?? data.direct_payment_requirement_id ?? ""),
|
|
432
|
+
});
|
|
433
|
+
return new Response(null, { status: 204 });
|
|
404
434
|
}
|
|
405
435
|
await orders.markPaidOnce(order.id, {
|
|
406
436
|
siglume_requirement_id: String(data.requirement_id ?? data.direct_payment_requirement_id ?? ""),
|
|
437
|
+
chain_receipt_id: String(data.chain_receipt_id ?? ""),
|
|
407
438
|
});
|
|
439
|
+
return new Response(null, { status: 204 });
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (data.pricing_band === "micro" || data.pricing_band === "nano") {
|
|
443
|
+
const order = await orders.findByChallengeHash(String(data.challenge_hash ?? ""));
|
|
444
|
+
if (order) {
|
|
445
|
+
await orders.markFulfilledButUnsettledOnce(order.id, {
|
|
446
|
+
siglume_requirement_id: String(data.requirement_id ?? data.direct_payment_requirement_id ?? ""),
|
|
447
|
+
pricing_band: String(data.pricing_band),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
return new Response(null, { status: 204 });
|
|
408
451
|
}
|
|
452
|
+
|
|
453
|
+
// Missing or unknown machine fields: do not mark the order paid from the event
|
|
454
|
+
// name alone. Fetch the requirement or route it to manual review.
|
|
455
|
+
await orders.flagForPaymentStateReview({
|
|
456
|
+
reason: "missing_settlement_machine_fields",
|
|
457
|
+
requirement_id: String(data.requirement_id ?? data.direct_payment_requirement_id ?? ""),
|
|
458
|
+
});
|
|
459
|
+
return new Response(null, { status: 204 });
|
|
409
460
|
```
|
|
410
461
|
|
|
411
462
|
Python:
|
|
@@ -421,23 +472,68 @@ verified = verify_direct_request_payment_webhook(
|
|
|
421
472
|
siglume_signature_header,
|
|
422
473
|
)
|
|
423
474
|
|
|
424
|
-
if verified["event"]["type"]
|
|
425
|
-
|
|
475
|
+
if verified["event"]["type"] != "direct_payment.confirmed":
|
|
476
|
+
return "", 204
|
|
477
|
+
|
|
478
|
+
data = verified["event"]["data"]
|
|
479
|
+
|
|
480
|
+
if data.get("mode") == "metered_settlement_batch":
|
|
481
|
+
# Aggregated Micro/Nano settlement events do not carry an order challenge.
|
|
482
|
+
if data.get("settlement_status") == "settled":
|
|
483
|
+
orders.reconcile_metered_settlement_once(
|
|
484
|
+
settlement_batch_id=str(data.get("settlement_batch_id") or ""),
|
|
485
|
+
chain_receipt_id=str(data.get("chain_receipt_id") or ""),
|
|
486
|
+
usage_event_digest=str(data.get("usage_event_digest") or ""),
|
|
487
|
+
settled_at=str(data.get("settled_at") or ""),
|
|
488
|
+
)
|
|
489
|
+
return "", 204
|
|
490
|
+
|
|
491
|
+
if (
|
|
492
|
+
data.get("pricing_band") == "standard"
|
|
493
|
+
and data.get("finality") == "per_payment_onchain"
|
|
494
|
+
and data.get("settlement_status") == "settled"
|
|
495
|
+
):
|
|
426
496
|
order = orders.find_by_challenge_hash(str(data.get("challenge_hash") or ""))
|
|
427
497
|
if not order:
|
|
428
|
-
|
|
498
|
+
orders.flag_for_payment_state_review(
|
|
499
|
+
reason="unknown_challenge_hash",
|
|
500
|
+
requirement_id=str(data.get("requirement_id") or data.get("direct_payment_requirement_id") or ""),
|
|
501
|
+
)
|
|
502
|
+
return "", 204
|
|
429
503
|
orders.mark_paid_once(
|
|
430
504
|
order["id"],
|
|
431
505
|
siglume_requirement_id=str(data.get("requirement_id") or data.get("direct_payment_requirement_id") or ""),
|
|
506
|
+
chain_receipt_id=str(data.get("chain_receipt_id") or ""),
|
|
432
507
|
)
|
|
508
|
+
return "", 204
|
|
509
|
+
|
|
510
|
+
if data.get("pricing_band") in ("micro", "nano"):
|
|
511
|
+
order = orders.find_by_challenge_hash(str(data.get("challenge_hash") or ""))
|
|
512
|
+
if order:
|
|
513
|
+
orders.mark_fulfilled_but_unsettled_once(
|
|
514
|
+
order["id"],
|
|
515
|
+
siglume_requirement_id=str(data.get("requirement_id") or data.get("direct_payment_requirement_id") or ""),
|
|
516
|
+
pricing_band=str(data.get("pricing_band") or ""),
|
|
517
|
+
)
|
|
518
|
+
return "", 204
|
|
519
|
+
|
|
520
|
+
# Missing or unknown machine fields: do not mark the order paid from the event
|
|
521
|
+
# name alone. Fetch the requirement or route it to manual review.
|
|
522
|
+
orders.flag_for_payment_state_review(
|
|
523
|
+
reason="missing_settlement_machine_fields",
|
|
524
|
+
requirement_id=str(data.get("requirement_id") or data.get("direct_payment_requirement_id") or ""),
|
|
525
|
+
)
|
|
526
|
+
return "", 204
|
|
433
527
|
```
|
|
434
528
|
|
|
435
529
|
## Reconcile Micro / Nano Statements
|
|
436
530
|
|
|
437
|
-
Standard Payment can be
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
531
|
+
Standard Payment can be marked paid from the verified `direct_payment.confirmed`
|
|
532
|
+
webhook only when `pricing_band === "standard"`,
|
|
533
|
+
`finality === "per_payment_onchain"`, and `settlement_status === "settled"`.
|
|
534
|
+
Micro Payment and Nano Payment are different: they are automatic amount bands
|
|
535
|
+
and are settled later in aggregated on-chain batches. Use the statement APIs to
|
|
536
|
+
answer:
|
|
441
537
|
|
|
442
538
|
- how much Micro / Nano usage is open this week or month,
|
|
443
539
|
- when the buyer's assigned period closes,
|
|
@@ -109,7 +109,10 @@ rounding_delta_minor = buyer_debit_minor - gross_buyer_debit_minor
|
|
|
109
109
|
For low-count Nano batches, the ceiling can make the effective buyer burden per
|
|
110
110
|
usage higher than the headline "USD 0.001 / usage" protocol fee. The protocol
|
|
111
111
|
fee remains the decimal statement amount; the extra integer-minor-unit
|
|
112
|
-
adjustment is recorded as `rounding_delta_minor` on the settlement batch.
|
|
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.
|
|
113
116
|
|
|
114
117
|
`rounding_delta_minor` belongs to the buyer debit and Siglume's rounding
|
|
115
118
|
adjustment accounting for that batch. It is not provider revenue. Provider
|
|
@@ -377,9 +380,20 @@ or support staff. Do not depend on raw platform failure messages.
|
|
|
377
380
|
|
|
378
381
|
## Usage Accounting by Result
|
|
379
382
|
|
|
380
|
-
Use idempotency keys for every paid operation. Siglume
|
|
381
|
-
|
|
382
|
-
|
|
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.
|
|
383
397
|
|
|
384
398
|
| Case | Provider API executed? | Usage counted? | Integration rule |
|
|
385
399
|
| --- | --- | --- | --- |
|
|
@@ -446,7 +460,9 @@ separate from settled revenue.
|
|
|
446
460
|
## Go-Live Checklist
|
|
447
461
|
|
|
448
462
|
- Your order fulfillment is idempotent by order id and requirement id.
|
|
449
|
-
- 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.
|
|
450
466
|
- Micro / Nano accounting uses statement APIs or CSV, not only webhooks.
|
|
451
467
|
- Your dashboard separates settled, unsettled, and past-due provider amounts.
|
|
452
468
|
- Your support UI shows sanitized failure fields and `support_reference`.
|
package/docs/pricing.md
CHANGED
|
@@ -7,7 +7,9 @@ fee data returned at runtime.
|
|
|
7
7
|
|
|
8
8
|
Pricing has one structure: a merchant selects the Standard Payment plan during
|
|
9
9
|
setup, then Siglume applies the fee for each payment by amount. Micro Payment
|
|
10
|
-
and Nano Payment are automatic amount bands, not separate choices.
|
|
10
|
+
and Nano Payment are automatic amount bands, not separate choices. Merchant
|
|
11
|
+
setup and the billing mandate terms assume the merchant accepts Micro / Nano
|
|
12
|
+
delayed aggregated settlement whenever they offer amounts in those bands.
|
|
11
13
|
|
|
12
14
|
## Settlement Currencies
|
|
13
15
|
|
|
@@ -36,10 +38,17 @@ aggregated and settled in account-assigned weekly / monthly slots - see
|
|
|
36
38
|
pre-debit notice window elapses, when revenue becomes settled, and how rejected
|
|
37
39
|
requests behave.
|
|
38
40
|
|
|
39
|
-
The current public API chooses the band from `amount_minor`;
|
|
40
|
-
|
|
41
|
-
merchant needs immediate on-chain finality, the payment amount
|
|
42
|
-
|
|
41
|
+
The current public API chooses the band from `amount_minor`; JPY 500-and-under /
|
|
42
|
+
USD 3-and-under payments are routed to Micro / Nano delayed aggregated
|
|
43
|
+
settlement. If a merchant needs immediate on-chain finality, the payment amount
|
|
44
|
+
must be in the Standard band. In practice, do not offer JPY 500-and-under or
|
|
45
|
+
USD 3-and-under items for a product that cannot accept Micro / Nano delayed
|
|
46
|
+
aggregated settlement. For public one-time Direct Payment / Hosted Checkout,
|
|
47
|
+
`amount_minor` is a positive integer in minor currency units. That means the
|
|
48
|
+
smallest public one-time checkout amount is JPY 1 or USD 0.01. Nano Payment on
|
|
49
|
+
this public path therefore means JPY 1-49 or USD 0.01-0.30. Sub-minor Nano
|
|
50
|
+
protocol fees are settlement-accounting amounts, not externally submitted
|
|
51
|
+
one-time item prices.
|
|
43
52
|
|
|
44
53
|
For the operational statement APIs, CSV export, buyer past-due blocks, and the
|
|
45
54
|
field-by-field meaning of `scheduled_debit_at`, `not_before_attempt_at`,
|
|
@@ -183,8 +192,11 @@ For low-count Nano batches, the integer ceiling can make the effective buyer
|
|
|
183
192
|
burden per usage higher than the headline USD 0.001 / usage protocol fee. The
|
|
184
193
|
decimal protocol fee remains visible as `protocol_fee_minor`; the difference
|
|
185
194
|
created by integer-token settlement is visible as `rounding_delta_minor` on the
|
|
186
|
-
batch.
|
|
187
|
-
|
|
195
|
+
batch. Each settlement batch can add a positive rounding adjustment of less than
|
|
196
|
+
1 token minor unit. If a buyer uses many providers / payees in one period, that
|
|
197
|
+
adjustment can occur once per settlement batch. JavaScript integrations should
|
|
198
|
+
not sum Micro / Nano minor amounts with `number`; use a decimal library. Python
|
|
199
|
+
integrations should use `Decimal`.
|
|
188
200
|
|
|
189
201
|
## Statement APIs and Notices
|
|
190
202
|
|
package/docs/security.md
CHANGED
|
@@ -125,6 +125,13 @@ merchant-authored challenge nonce plus the returned `challenge_hash` /
|
|
|
125
125
|
`request_hash_v2`. Reuse the same order-attempt nonce when reconciling a retry;
|
|
126
126
|
mint a new nonce only for a new payment attempt.
|
|
127
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
|
+
|
|
128
135
|
## Micro / Nano Statement Privacy
|
|
129
136
|
|
|
130
137
|
Micro Payment and Nano Payment introduce operational statement APIs and CSV
|
|
@@ -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();
|