@siglume/direct-request-payment 0.4.7 → 0.4.9
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 +36 -32
- package/dist/index.cjs +124 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -2
- package/dist/index.d.ts +51 -2
- package/dist/index.js +124 -1
- package/dist/index.js.map +1 -1
- package/docs/announcement-ja.md +1 -1
- package/docs/api-reference.md +41 -13
- package/docs/merchant-quickstart.md +69 -61
- package/examples/express-checkout.ts +32 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.9 - 2026-06-19
|
|
4
|
+
|
|
5
|
+
Classifier consistency patch release.
|
|
6
|
+
|
|
7
|
+
- Required Micro / Nano settlement batch confirmations to carry aggregated
|
|
8
|
+
on-chain finality, a valid Micro/Nano pricing band, and the expected weekly or
|
|
9
|
+
monthly settlement cadence before returning `metered_batch_settled`.
|
|
10
|
+
- Added TypeScript and Python tests for missing finality, wrong finality,
|
|
11
|
+
missing pricing band, and Micro/Nano cadence mismatches.
|
|
12
|
+
|
|
13
|
+
## 0.4.8 - 2026-06-19
|
|
14
|
+
|
|
15
|
+
Webhook sample hardening release.
|
|
16
|
+
|
|
17
|
+
- Added TypeScript and Python confirmation classifiers for Standard settled
|
|
18
|
+
payments, Micro / Nano accepted usage, and Micro / Nano settled batches.
|
|
19
|
+
- Updated public webhook samples to require finality, settlement status, and
|
|
20
|
+
non-empty settlement identifiers before fulfilling or reconciling.
|
|
21
|
+
- Routed unknown Micro / Nano challenge hashes and malformed settlement batch
|
|
22
|
+
confirmations to manual review in copy-paste samples.
|
|
23
|
+
|
|
3
24
|
## 0.4.7 - 2026-06-19
|
|
4
25
|
|
|
5
26
|
Documentation-only patch release.
|
package/README.md
CHANGED
|
@@ -103,10 +103,10 @@ const session = await merchant.createCheckoutSession({
|
|
|
103
103
|
});
|
|
104
104
|
redirect(session.checkout_url); // -> https://siglume.com/pay/<session_id>
|
|
105
105
|
|
|
106
|
-
// 3. Handle the signed direct_payment.confirmed webhook.
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
// until the later
|
|
106
|
+
// 3. Handle the signed direct_payment.confirmed webhook. Use
|
|
107
|
+
// classifyDirectPaymentConfirmation(event). Fulfill Standard only for
|
|
108
|
+
// standard_settled; treat metered_usage_accepted as fulfilled-unsettled
|
|
109
|
+
// until the later metered_batch_settled event arrives.
|
|
110
110
|
// Poll merchant.getCheckoutSession(session.session_id) if you also want to
|
|
111
111
|
// show status in your own UI.
|
|
112
112
|
```
|
|
@@ -138,10 +138,10 @@ session = merchant.create_checkout_session(
|
|
|
138
138
|
)
|
|
139
139
|
redirect(session["checkout_url"]) # -> https://siglume.com/pay/<session_id>
|
|
140
140
|
|
|
141
|
-
# 3. Handle the signed direct_payment.confirmed webhook.
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
# until the later
|
|
141
|
+
# 3. Handle the signed direct_payment.confirmed webhook. Use
|
|
142
|
+
# classify_direct_payment_confirmation(event). Fulfill Standard only for
|
|
143
|
+
# standard_settled; treat metered_usage_accepted as fulfilled-unsettled
|
|
144
|
+
# until the later metered_batch_settled event arrives.
|
|
145
145
|
# Poll merchant.get_checkout_session(session["session_id"]) if you also want
|
|
146
146
|
# to show status in your own UI.
|
|
147
147
|
```
|
|
@@ -525,7 +525,10 @@ the payload. Create a marketplace webhook subscription with
|
|
|
525
525
|
signing secret once.
|
|
526
526
|
|
|
527
527
|
```ts
|
|
528
|
-
import {
|
|
528
|
+
import {
|
|
529
|
+
classifyDirectPaymentConfirmation,
|
|
530
|
+
verifyDirectRequestPaymentWebhook,
|
|
531
|
+
} from "@siglume/direct-request-payment";
|
|
529
532
|
|
|
530
533
|
const { event } = await verifyDirectRequestPaymentWebhook(
|
|
531
534
|
process.env.SIGLUME_WEBHOOK_SECRET!,
|
|
@@ -534,18 +537,16 @@ const { event } = await verifyDirectRequestPaymentWebhook(
|
|
|
534
537
|
);
|
|
535
538
|
|
|
536
539
|
if (event.type === "direct_payment.confirmed") {
|
|
537
|
-
|
|
540
|
+
const confirmation = classifyDirectPaymentConfirmation(event);
|
|
541
|
+
if (confirmation.kind === "metered_batch_settled") {
|
|
538
542
|
// Reconcile settled Micro / Nano batches by settlement_batch_id /
|
|
539
543
|
// usage_event_digest; these events do not carry an order challenge hash.
|
|
540
|
-
} else if (
|
|
541
|
-
event.data.pricing_band === "standard" &&
|
|
542
|
-
event.data.finality === "per_payment_onchain" &&
|
|
543
|
-
event.data.settlement_status === "settled"
|
|
544
|
-
) {
|
|
544
|
+
} else if (confirmation.kind === "standard_settled") {
|
|
545
545
|
// Mark the order paid once if event.data.challenge_hash/order mapping matches.
|
|
546
|
-
} else if (
|
|
547
|
-
// Mark fulfilled-but-unsettled
|
|
548
|
-
|
|
546
|
+
} else if (confirmation.kind === "metered_usage_accepted") {
|
|
547
|
+
// Mark fulfilled-but-unsettled after matching confirmation.challenge_hash.
|
|
548
|
+
} else {
|
|
549
|
+
// Route confirmation.reason to manual review. Do not mark paid or fulfilled.
|
|
549
550
|
}
|
|
550
551
|
}
|
|
551
552
|
```
|
|
@@ -553,7 +554,10 @@ if (event.type === "direct_payment.confirmed") {
|
|
|
553
554
|
```py
|
|
554
555
|
import os
|
|
555
556
|
|
|
556
|
-
from siglume_direct_request_payment import
|
|
557
|
+
from siglume_direct_request_payment import (
|
|
558
|
+
classify_direct_payment_confirmation,
|
|
559
|
+
verify_direct_request_payment_webhook,
|
|
560
|
+
)
|
|
557
561
|
|
|
558
562
|
verified = verify_direct_request_payment_webhook(
|
|
559
563
|
os.environ["SIGLUME_WEBHOOK_SECRET"],
|
|
@@ -562,30 +566,30 @@ verified = verify_direct_request_payment_webhook(
|
|
|
562
566
|
)
|
|
563
567
|
|
|
564
568
|
if verified["event"]["type"] == "direct_payment.confirmed":
|
|
565
|
-
|
|
566
|
-
if
|
|
569
|
+
confirmation = classify_direct_payment_confirmation(verified["event"])
|
|
570
|
+
if confirmation["kind"] == "metered_batch_settled":
|
|
567
571
|
# Reconcile settled Micro / Nano batches by settlement_batch_id /
|
|
568
572
|
# usage_event_digest; these events do not carry an order challenge hash.
|
|
569
573
|
pass
|
|
570
|
-
elif
|
|
571
|
-
data.get("pricing_band") == "standard"
|
|
572
|
-
and data.get("finality") == "per_payment_onchain"
|
|
573
|
-
and data.get("settlement_status") == "settled"
|
|
574
|
-
):
|
|
574
|
+
elif confirmation["kind"] == "standard_settled":
|
|
575
575
|
# Mark the order paid once if event.data.challenge_hash/order mapping matches.
|
|
576
576
|
pass
|
|
577
|
-
elif
|
|
578
|
-
# Mark fulfilled-but-unsettled
|
|
579
|
-
|
|
577
|
+
elif confirmation["kind"] == "metered_usage_accepted":
|
|
578
|
+
# Mark fulfilled-but-unsettled after matching confirmation["challenge_hash"].
|
|
579
|
+
pass
|
|
580
|
+
else:
|
|
581
|
+
# Route confirmation["reason"] to manual review. Do not mark paid or fulfilled.
|
|
580
582
|
pass
|
|
581
583
|
```
|
|
582
584
|
|
|
583
585
|
New `direct_payment.confirmed` payloads include `pricing_band`,
|
|
584
586
|
`settlement_cadence`, `finality`, `protocol_fee_minor`, `settlement_status`,
|
|
585
587
|
`settlement_batch_id`, `chain_receipt_id`, `usage_event_digest`, `settled_at`,
|
|
586
|
-
and when available `request_hash_v2`. Use
|
|
587
|
-
|
|
588
|
-
|
|
588
|
+
and when available `request_hash_v2`. Use
|
|
589
|
+
`classifyDirectPaymentConfirmation(event)` /
|
|
590
|
+
`classify_direct_payment_confirmation(event)` or the same machine-field checks
|
|
591
|
+
instead of inferring settlement semantics from the event name alone. Do not mark
|
|
592
|
+
an order paid from the event type alone.
|
|
589
593
|
|
|
590
594
|
## Security Rules
|
|
591
595
|
|
package/dist/index.cjs
CHANGED
|
@@ -34,11 +34,15 @@ __export(src_exports, {
|
|
|
34
34
|
DEFAULT_WEBHOOK_TOLERANCE_SECONDS: () => DEFAULT_WEBHOOK_TOLERANCE_SECONDS,
|
|
35
35
|
DIRECT_REQUEST_PAYMENT_ALLOWANCE_RECEIPT_KIND: () => DIRECT_REQUEST_PAYMENT_ALLOWANCE_RECEIPT_KIND,
|
|
36
36
|
DIRECT_REQUEST_PAYMENT_CHALLENGE_SCHEME: () => DIRECT_REQUEST_PAYMENT_CHALLENGE_SCHEME,
|
|
37
|
+
DIRECT_REQUEST_PAYMENT_METERED_ACCEPTED_STATUS: () => DIRECT_REQUEST_PAYMENT_METERED_ACCEPTED_STATUS,
|
|
38
|
+
DIRECT_REQUEST_PAYMENT_METERED_FINALITY: () => DIRECT_REQUEST_PAYMENT_METERED_FINALITY,
|
|
37
39
|
DIRECT_REQUEST_PAYMENT_MODE: () => DIRECT_REQUEST_PAYMENT_MODE,
|
|
38
40
|
DIRECT_REQUEST_PAYMENT_RECEIPT_KIND: () => DIRECT_REQUEST_PAYMENT_RECEIPT_KIND,
|
|
39
41
|
DIRECT_REQUEST_PAYMENT_RECURRING_CHALLENGE_SCHEME: () => DIRECT_REQUEST_PAYMENT_RECURRING_CHALLENGE_SCHEME,
|
|
40
42
|
DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE: () => DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE,
|
|
41
43
|
DIRECT_REQUEST_PAYMENT_SDK_VERSION: () => DIRECT_REQUEST_PAYMENT_SDK_VERSION,
|
|
44
|
+
DIRECT_REQUEST_PAYMENT_STANDARD_FINALITY: () => DIRECT_REQUEST_PAYMENT_STANDARD_FINALITY,
|
|
45
|
+
DIRECT_REQUEST_PAYMENT_STANDARD_SETTLED_STATUS: () => DIRECT_REQUEST_PAYMENT_STANDARD_SETTLED_STATUS,
|
|
42
46
|
DirectRequestPaymentClient: () => DirectRequestPaymentClient,
|
|
43
47
|
DirectRequestPaymentMerchantClient: () => DirectRequestPaymentMerchantClient,
|
|
44
48
|
HostedCheckoutNotAvailableError: () => HostedCheckoutNotAvailableError,
|
|
@@ -50,6 +54,7 @@ __export(src_exports, {
|
|
|
50
54
|
buildPaymentExecutionPayload: () => buildPaymentExecutionPayload,
|
|
51
55
|
buildPreparedTransactionExecutionPayload: () => buildPreparedTransactionExecutionPayload,
|
|
52
56
|
buildWebhookSignatureHeader: () => buildWebhookSignatureHeader,
|
|
57
|
+
classifyDirectPaymentConfirmation: () => classifyDirectPaymentConfirmation,
|
|
53
58
|
computeWebhookSignature: () => computeWebhookSignature,
|
|
54
59
|
createDirectRequestPaymentChallenge: () => createDirectRequestPaymentChallenge,
|
|
55
60
|
createDirectRequestPaymentChallengeSignature: () => createDirectRequestPaymentChallengeSignature,
|
|
@@ -78,7 +83,11 @@ var DIRECT_REQUEST_PAYMENT_RECEIPT_KIND = "sdrp_direct_payment";
|
|
|
78
83
|
var DIRECT_REQUEST_PAYMENT_ALLOWANCE_RECEIPT_KIND = "sdrp_direct_payment_allowance";
|
|
79
84
|
var DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE = "sdrp_direct_payment_requirement";
|
|
80
85
|
var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
81
|
-
var DIRECT_REQUEST_PAYMENT_SDK_VERSION = "0.4.
|
|
86
|
+
var DIRECT_REQUEST_PAYMENT_SDK_VERSION = "0.4.9";
|
|
87
|
+
var DIRECT_REQUEST_PAYMENT_STANDARD_SETTLED_STATUS = "settled";
|
|
88
|
+
var DIRECT_REQUEST_PAYMENT_METERED_ACCEPTED_STATUS = "pending_settlement";
|
|
89
|
+
var DIRECT_REQUEST_PAYMENT_STANDARD_FINALITY = "per_payment_onchain";
|
|
90
|
+
var DIRECT_REQUEST_PAYMENT_METERED_FINALITY = "aggregated_onchain_settlement";
|
|
82
91
|
var DIRECT_REQUEST_PAYMENT_CONFIRMED_WEBHOOK_MODES = /* @__PURE__ */ new Set([DIRECT_REQUEST_PAYMENT_MODE, "metered_settlement_batch"]);
|
|
83
92
|
var SiglumeDirectRequestPaymentError = class extends Error {
|
|
84
93
|
constructor(message) {
|
|
@@ -671,6 +680,120 @@ function parseDirectRequestPaymentWebhookEvent(payload) {
|
|
|
671
680
|
}
|
|
672
681
|
return parsed;
|
|
673
682
|
}
|
|
683
|
+
function classifyDirectPaymentConfirmation(event) {
|
|
684
|
+
const data = event.data;
|
|
685
|
+
const requirementId = stringOrNull(data.requirement_id) ?? stringOrNull(data.direct_payment_requirement_id);
|
|
686
|
+
const challengeHash = stringOrNull(data.challenge_hash);
|
|
687
|
+
const pricingBand = stringOrNull(data.pricing_band);
|
|
688
|
+
const settlementCadence = stringOrNull(data.settlement_cadence);
|
|
689
|
+
const finality = stringOrNull(data.finality);
|
|
690
|
+
const settlementStatus = stringOrNull(data.settlement_status);
|
|
691
|
+
if (event.type !== "direct_payment.confirmed") {
|
|
692
|
+
return {
|
|
693
|
+
kind: "unknown",
|
|
694
|
+
event,
|
|
695
|
+
data,
|
|
696
|
+
reason: "not_direct_payment_confirmed",
|
|
697
|
+
requirement_id: requirementId,
|
|
698
|
+
settlement_batch_id: stringOrNull(data.settlement_batch_id),
|
|
699
|
+
pricing_band: pricingBand,
|
|
700
|
+
settlement_cadence: settlementCadence,
|
|
701
|
+
settlement_status: settlementStatus,
|
|
702
|
+
finality
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
if (data.mode === "metered_settlement_batch") {
|
|
706
|
+
const settlementBatchId = stringOrNull(data.settlement_batch_id);
|
|
707
|
+
const chainReceiptId = stringOrNull(data.chain_receipt_id);
|
|
708
|
+
const usageEventDigest = stringOrNull(data.usage_event_digest);
|
|
709
|
+
if (settlementStatus === DIRECT_REQUEST_PAYMENT_STANDARD_SETTLED_STATUS && finality === DIRECT_REQUEST_PAYMENT_METERED_FINALITY && (pricingBand === "micro" || pricingBand === "nano") && settlementCadence === (pricingBand === "micro" ? "weekly" : "monthly") && settlementBatchId && chainReceiptId && usageEventDigest) {
|
|
710
|
+
return {
|
|
711
|
+
kind: "metered_batch_settled",
|
|
712
|
+
event,
|
|
713
|
+
data,
|
|
714
|
+
pricing_band: pricingBand,
|
|
715
|
+
settlement_cadence: pricingBand === "micro" ? "weekly" : "monthly",
|
|
716
|
+
settlement_batch_id: settlementBatchId,
|
|
717
|
+
chain_receipt_id: chainReceiptId,
|
|
718
|
+
usage_event_digest: usageEventDigest,
|
|
719
|
+
settled_at: stringOrNull(data.settled_at)
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
kind: "unknown",
|
|
724
|
+
event,
|
|
725
|
+
data,
|
|
726
|
+
reason: "invalid_metered_settlement_confirmation",
|
|
727
|
+
requirement_id: requirementId,
|
|
728
|
+
settlement_batch_id: settlementBatchId,
|
|
729
|
+
pricing_band: pricingBand,
|
|
730
|
+
settlement_cadence: settlementCadence,
|
|
731
|
+
settlement_status: settlementStatus,
|
|
732
|
+
finality
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
if (pricingBand === "standard") {
|
|
736
|
+
const chainReceiptId = stringOrNull(data.chain_receipt_id);
|
|
737
|
+
if (finality === DIRECT_REQUEST_PAYMENT_STANDARD_FINALITY && settlementStatus === DIRECT_REQUEST_PAYMENT_STANDARD_SETTLED_STATUS && requirementId && challengeHash && chainReceiptId) {
|
|
738
|
+
return {
|
|
739
|
+
kind: "standard_settled",
|
|
740
|
+
event,
|
|
741
|
+
data,
|
|
742
|
+
requirement_id: requirementId,
|
|
743
|
+
challenge_hash: challengeHash,
|
|
744
|
+
chain_receipt_id: chainReceiptId,
|
|
745
|
+
request_hash_v2: stringOrNull(data.request_hash_v2)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
kind: "unknown",
|
|
750
|
+
event,
|
|
751
|
+
data,
|
|
752
|
+
reason: "missing_standard_settlement_fields",
|
|
753
|
+
requirement_id: requirementId,
|
|
754
|
+
pricing_band: pricingBand,
|
|
755
|
+
settlement_cadence: settlementCadence,
|
|
756
|
+
settlement_status: settlementStatus,
|
|
757
|
+
finality
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if (pricingBand === "micro" || pricingBand === "nano") {
|
|
761
|
+
if (finality === DIRECT_REQUEST_PAYMENT_METERED_FINALITY && settlementStatus === DIRECT_REQUEST_PAYMENT_METERED_ACCEPTED_STATUS && requirementId && challengeHash) {
|
|
762
|
+
return {
|
|
763
|
+
kind: "metered_usage_accepted",
|
|
764
|
+
event,
|
|
765
|
+
data,
|
|
766
|
+
pricing_band: pricingBand,
|
|
767
|
+
requirement_id: requirementId,
|
|
768
|
+
challenge_hash: challengeHash,
|
|
769
|
+
request_hash_v2: stringOrNull(data.request_hash_v2)
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
kind: "unknown",
|
|
774
|
+
event,
|
|
775
|
+
data,
|
|
776
|
+
reason: "missing_metered_usage_fields",
|
|
777
|
+
requirement_id: requirementId,
|
|
778
|
+
pricing_band: pricingBand,
|
|
779
|
+
settlement_cadence: settlementCadence,
|
|
780
|
+
settlement_status: settlementStatus,
|
|
781
|
+
finality
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
kind: "unknown",
|
|
786
|
+
event,
|
|
787
|
+
data,
|
|
788
|
+
reason: "unknown_confirmation_shape",
|
|
789
|
+
requirement_id: requirementId,
|
|
790
|
+
settlement_batch_id: stringOrNull(data.settlement_batch_id),
|
|
791
|
+
pricing_band: pricingBand,
|
|
792
|
+
settlement_cadence: settlementCadence,
|
|
793
|
+
settlement_status: settlementStatus,
|
|
794
|
+
finality
|
|
795
|
+
};
|
|
796
|
+
}
|
|
674
797
|
async function verifyDirectRequestPaymentWebhook(signing_secret, body, signature_header, options = {}) {
|
|
675
798
|
const verification = await verifyWebhookSignature(signing_secret, body, signature_header, options);
|
|
676
799
|
const text = new TextDecoder().decode(bodyBytes(body));
|