@siglume/direct-request-payment 0.4.2 → 0.4.4
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 +33 -0
- package/README.md +34 -10
- package/dist/index.cjs +120 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +142 -1
- package/dist/index.d.ts +142 -1
- package/dist/index.js +120 -6
- package/dist/index.js.map +1 -1
- package/docs/announcement-ja.md +12 -12
- package/docs/api-reference.md +236 -11
- package/docs/merchant-quickstart.md +65 -7
- package/docs/metered-statements.md +433 -0
- package/docs/pricing.md +87 -27
- package/docs/security.md +44 -8
- package/examples/express-checkout.ts +12 -30
- package/package.json +1 -1
package/docs/security.md
CHANGED
|
@@ -61,6 +61,11 @@ bare, lowercased origins and deduped. A return URL that is not on an allowed
|
|
|
61
61
|
origin is rejected, so an attacker cannot point a session at an arbitrary
|
|
62
62
|
redirect target.
|
|
63
63
|
|
|
64
|
+
Production allowlist entries must use `https`. Development `http` entries are
|
|
65
|
+
accepted only for `http://localhost`, `http://127.0.0.1`, or `http://[::1]`
|
|
66
|
+
(with optional ports). Userinfo such as `https://user@shop.example.com` is
|
|
67
|
+
rejected so an attacker cannot rely on origin-spoofing URL forms.
|
|
68
|
+
|
|
64
69
|
For a Hosted Checkout session, Siglume authors the amount, currency, challenge,
|
|
65
70
|
and return URLs server-side at session creation. The browser cannot tamper with
|
|
66
71
|
the price or the redirect target, and the raw challenge is never exposed to the
|
|
@@ -114,6 +119,31 @@ Fulfill exactly once per order. Store at least:
|
|
|
114
119
|
Duplicate webhook deliveries and manual redelivery can occur. A duplicate
|
|
115
120
|
webhook with the same requirement id must not ship the order twice.
|
|
116
121
|
|
|
122
|
+
## Micro / Nano Statement Privacy
|
|
123
|
+
|
|
124
|
+
Micro Payment and Nano Payment introduce operational statement APIs and CSV
|
|
125
|
+
exports because revenue is settled later in aggregated on-chain batches.
|
|
126
|
+
|
|
127
|
+
Provider-facing statement APIs intentionally do not expose raw `buyer_user_id`,
|
|
128
|
+
buyer email, buyer wallet address, relayer id, nonce, gas data, raw RPC errors,
|
|
129
|
+
or raw platform failure messages. Use `buyer_period_ref` for provider-side
|
|
130
|
+
reconciliation within a statement period, and show only the sanitized public
|
|
131
|
+
failure fields:
|
|
132
|
+
|
|
133
|
+
- `failure_reason_code`
|
|
134
|
+
- `failure_reason_label`
|
|
135
|
+
- `failure_reason_help`
|
|
136
|
+
- `support_reference`
|
|
137
|
+
|
|
138
|
+
Buyer-facing APIs may include past-due block reasons and balance / allowance /
|
|
139
|
+
BudgetVault sufficiency indicators for the buyer's own account. Do not forward
|
|
140
|
+
those buyer-account details to providers.
|
|
141
|
+
|
|
142
|
+
Webhooks remain required for fulfillment, but webhooks alone are not a complete
|
|
143
|
+
Micro / Nano revenue ledger. Use the statement APIs or CSV in
|
|
144
|
+
[Micro / Nano Statements and Notices](./metered-statements.md) to separate
|
|
145
|
+
settled, unsettled, and past-due provider amounts.
|
|
146
|
+
|
|
117
147
|
## What Direct Request Payment Is Not
|
|
118
148
|
|
|
119
149
|
Direct Request Payment is not:
|
|
@@ -125,12 +155,18 @@ Direct Request Payment is not:
|
|
|
125
155
|
- a card payment fallback
|
|
126
156
|
|
|
127
157
|
Each payment is an individual wallet payment backed by an on-chain receipt. Small
|
|
128
|
-
payments in the Micro and Nano amount bands are aggregated and settled on
|
|
129
|
-
weekly / monthly
|
|
130
|
-
[pricing guide](./pricing.md#settlement-schedule)), but they are still
|
|
131
|
-
payments, not a stored balance. Before a small payment is fulfilled,
|
|
132
|
-
checks the buyer's wallet budget and fails closed when it is invalid, so
|
|
133
|
-
rejected request is never charged. Provider revenue for Micro and Nano remains
|
|
134
|
-
unsettled until the
|
|
135
|
-
|
|
158
|
+
payments in the Micro and Nano amount bands are aggregated and settled on
|
|
159
|
+
account-assigned weekly / monthly slots instead of one transaction at a time
|
|
160
|
+
(see the [pricing guide](./pricing.md#settlement-schedule)), but they are still
|
|
161
|
+
wallet payments, not a stored balance. Before a small payment is fulfilled,
|
|
162
|
+
Siglume checks the buyer's wallet budget and fails closed when it is invalid, so
|
|
163
|
+
a rejected request is never charged. Provider revenue for Micro and Nano remains
|
|
164
|
+
unsettled until the aggregated on-chain settlement succeeds; Siglume does not
|
|
165
|
+
advance or guarantee revenue when a buyer's balance, allowance, BudgetVault
|
|
136
166
|
authorization, cap, or on-chain transaction fails.
|
|
167
|
+
|
|
168
|
+
A Micro / Nano budget reservation is not a token lock, escrow, or payment
|
|
169
|
+
guarantee. It reserves room against Siglume spending limits only. A later
|
|
170
|
+
settlement can still fail if the buyer no longer has sufficient balance,
|
|
171
|
+
allowance, BudgetVault authorization, or cap room; `past_due` records the issue
|
|
172
|
+
but does not guarantee eventual collection or provider payment.
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
DirectRequestPaymentClient,
|
|
3
|
+
DirectRequestPaymentMerchantClient,
|
|
5
4
|
verifyDirectRequestPaymentWebhook,
|
|
6
5
|
} from "@siglume/direct-request-payment";
|
|
7
6
|
|
|
8
7
|
const app = express();
|
|
9
8
|
const port = Number(process.env.PORT || 3000);
|
|
10
9
|
const merchantKey = process.env.SIGLUME_DIRECT_PAYMENT_MERCHANT || "example_merchant";
|
|
10
|
+
const siglumeMerchant = new DirectRequestPaymentMerchantClient({
|
|
11
|
+
auth_token: process.env.SIGLUME_MERCHANT_AUTH_TOKEN,
|
|
12
|
+
});
|
|
11
13
|
|
|
12
14
|
// Use JSON for normal routes. Use raw body only on the webhook route.
|
|
13
15
|
app.use((req, res, next) => {
|
|
@@ -35,49 +37,29 @@ app.post("/checkout/siglume/start", asyncRoute(async (req, res) => {
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
order.payment_attempt = Number(order.payment_attempt || 0) + 1;
|
|
38
|
-
const
|
|
40
|
+
const session = await siglumeMerchant.createCheckoutSession({
|
|
39
41
|
merchant: merchantKey,
|
|
40
42
|
amount_minor: order.amount_minor,
|
|
41
43
|
currency: order.currency,
|
|
42
|
-
secret: process.env.SIGLUME_DIRECT_PAYMENT_CHALLENGE_SECRET!,
|
|
43
44
|
nonce: `${order.id}-attempt_${order.payment_attempt}`,
|
|
45
|
+
success_url: `${process.env.SHOP_PUBLIC_ORIGIN || "https://shop.example.com"}/thanks`,
|
|
46
|
+
cancel_url: `${process.env.SHOP_PUBLIC_ORIGIN || "https://shop.example.com"}/cart`,
|
|
47
|
+
metadata: { order_id: order.id },
|
|
44
48
|
});
|
|
45
49
|
|
|
46
|
-
order.siglume_challenge_hash =
|
|
50
|
+
order.siglume_challenge_hash = session.challenge_hash;
|
|
51
|
+
order.siglume_checkout_session_id = session.session_id;
|
|
47
52
|
order.siglume_payment_status = "pending";
|
|
48
53
|
|
|
49
54
|
res.json({
|
|
50
55
|
order_id: order.id,
|
|
51
56
|
amount_minor: order.amount_minor,
|
|
52
57
|
currency: order.currency,
|
|
53
|
-
|
|
58
|
+
checkout_url: session.checkout_url,
|
|
59
|
+
session_id: session.session_id,
|
|
54
60
|
});
|
|
55
61
|
}));
|
|
56
62
|
|
|
57
|
-
app.post("/checkout/siglume/pay", asyncRoute(async (req, res) => {
|
|
58
|
-
const order = orders.get(String(req.body.order_id || ""));
|
|
59
|
-
if (!order) {
|
|
60
|
-
res.status(404).json({ error: "order_not_found" });
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// In production, obtain this from the authenticated buyer's Siglume session
|
|
65
|
-
// or a hosted Siglume payment confirmation flow. Do not use a merchant secret
|
|
66
|
-
// to charge a customer wallet.
|
|
67
|
-
const siglume = new DirectRequestPaymentClient({
|
|
68
|
-
auth_token: String(req.headers.authorization || "").replace(/^Bearer\s+/i, ""),
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const requirement = await siglume.createPaymentRequirement({
|
|
72
|
-
merchant: merchantKey,
|
|
73
|
-
amount_minor: order.amount_minor,
|
|
74
|
-
currency: order.currency,
|
|
75
|
-
challenge: String(req.body.siglume_challenge || ""),
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
res.json({ requirement });
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
63
|
app.post("/siglume/webhook", express.raw({ type: "application/json" }), asyncRoute(async (req, res) => {
|
|
82
64
|
const header = String(req.headers["siglume-signature"] || "");
|
|
83
65
|
const { event } = await verifyDirectRequestPaymentWebhook(
|