@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
|
@@ -392,7 +392,10 @@ signer and verifier only.
|
|
|
392
392
|
Use the webhook as the durable signal, not just the browser return path.
|
|
393
393
|
|
|
394
394
|
```ts
|
|
395
|
-
import {
|
|
395
|
+
import {
|
|
396
|
+
classifyDirectPaymentConfirmation,
|
|
397
|
+
verifyDirectRequestPaymentWebhook,
|
|
398
|
+
} from "@siglume/direct-request-payment";
|
|
396
399
|
|
|
397
400
|
const { event } = await verifyDirectRequestPaymentWebhook(
|
|
398
401
|
process.env.SIGLUME_WEBHOOK_SECRET!,
|
|
@@ -404,57 +407,57 @@ if (event.type !== "direct_payment.confirmed") {
|
|
|
404
407
|
return new Response(null, { status: 204 });
|
|
405
408
|
}
|
|
406
409
|
|
|
407
|
-
const
|
|
410
|
+
const confirmation = classifyDirectPaymentConfirmation(event);
|
|
408
411
|
|
|
409
|
-
if (
|
|
412
|
+
if (confirmation.kind === "metered_batch_settled") {
|
|
410
413
|
// Aggregated Micro/Nano settlement events do not carry an order challenge.
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
});
|
|
418
|
-
}
|
|
414
|
+
await orders.reconcileMeteredSettlementOnce({
|
|
415
|
+
settlement_batch_id: confirmation.settlement_batch_id,
|
|
416
|
+
chain_receipt_id: confirmation.chain_receipt_id,
|
|
417
|
+
usage_event_digest: confirmation.usage_event_digest,
|
|
418
|
+
settled_at: confirmation.settled_at ?? null,
|
|
419
|
+
});
|
|
419
420
|
return new Response(null, { status: 204 });
|
|
420
421
|
}
|
|
421
422
|
|
|
422
|
-
if (
|
|
423
|
-
|
|
424
|
-
data.finality === "per_payment_onchain" &&
|
|
425
|
-
data.settlement_status === "settled"
|
|
426
|
-
) {
|
|
427
|
-
const order = await orders.findByChallengeHash(String(data.challenge_hash ?? ""));
|
|
423
|
+
if (confirmation.kind === "standard_settled") {
|
|
424
|
+
const order = await orders.findByChallengeHash(confirmation.challenge_hash);
|
|
428
425
|
if (!order) {
|
|
429
426
|
await orders.flagForPaymentStateReview({
|
|
430
427
|
reason: "unknown_challenge_hash",
|
|
431
|
-
requirement_id:
|
|
428
|
+
requirement_id: confirmation.requirement_id,
|
|
432
429
|
});
|
|
433
430
|
return new Response(null, { status: 204 });
|
|
434
431
|
}
|
|
435
432
|
await orders.markPaidOnce(order.id, {
|
|
436
|
-
siglume_requirement_id:
|
|
437
|
-
chain_receipt_id:
|
|
433
|
+
siglume_requirement_id: confirmation.requirement_id,
|
|
434
|
+
chain_receipt_id: confirmation.chain_receipt_id,
|
|
438
435
|
});
|
|
439
436
|
return new Response(null, { status: 204 });
|
|
440
437
|
}
|
|
441
438
|
|
|
442
|
-
if (
|
|
443
|
-
const order = await orders.findByChallengeHash(
|
|
444
|
-
if (order) {
|
|
445
|
-
await orders.
|
|
446
|
-
|
|
447
|
-
|
|
439
|
+
if (confirmation.kind === "metered_usage_accepted") {
|
|
440
|
+
const order = await orders.findByChallengeHash(confirmation.challenge_hash);
|
|
441
|
+
if (!order) {
|
|
442
|
+
await orders.flagForPaymentStateReview({
|
|
443
|
+
reason: "unknown_metered_challenge_hash",
|
|
444
|
+
requirement_id: confirmation.requirement_id,
|
|
448
445
|
});
|
|
446
|
+
return new Response(null, { status: 204 });
|
|
449
447
|
}
|
|
448
|
+
await orders.markFulfilledButUnsettledOnce(order.id, {
|
|
449
|
+
siglume_requirement_id: confirmation.requirement_id,
|
|
450
|
+
pricing_band: confirmation.pricing_band,
|
|
451
|
+
});
|
|
450
452
|
return new Response(null, { status: 204 });
|
|
451
453
|
}
|
|
452
454
|
|
|
453
455
|
// Missing or unknown machine fields: do not mark the order paid from the event
|
|
454
456
|
// name alone. Fetch the requirement or route it to manual review.
|
|
455
457
|
await orders.flagForPaymentStateReview({
|
|
456
|
-
reason:
|
|
457
|
-
requirement_id:
|
|
458
|
+
reason: confirmation.reason,
|
|
459
|
+
requirement_id: confirmation.requirement_id ?? "",
|
|
460
|
+
settlement_batch_id: confirmation.settlement_batch_id ?? null,
|
|
458
461
|
});
|
|
459
462
|
return new Response(null, { status: 204 });
|
|
460
463
|
```
|
|
@@ -464,7 +467,10 @@ Python:
|
|
|
464
467
|
```py
|
|
465
468
|
import os
|
|
466
469
|
|
|
467
|
-
from siglume_direct_request_payment import
|
|
470
|
+
from siglume_direct_request_payment import (
|
|
471
|
+
classify_direct_payment_confirmation,
|
|
472
|
+
verify_direct_request_payment_webhook,
|
|
473
|
+
)
|
|
468
474
|
|
|
469
475
|
verified = verify_direct_request_payment_webhook(
|
|
470
476
|
os.environ["SIGLUME_WEBHOOK_SECRET"],
|
|
@@ -475,53 +481,54 @@ verified = verify_direct_request_payment_webhook(
|
|
|
475
481
|
if verified["event"]["type"] != "direct_payment.confirmed":
|
|
476
482
|
return "", 204
|
|
477
483
|
|
|
478
|
-
|
|
484
|
+
confirmation = classify_direct_payment_confirmation(verified["event"])
|
|
479
485
|
|
|
480
|
-
if
|
|
486
|
+
if confirmation["kind"] == "metered_batch_settled":
|
|
481
487
|
# Aggregated Micro/Nano settlement events do not carry an order challenge.
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
)
|
|
488
|
+
orders.reconcile_metered_settlement_once(
|
|
489
|
+
settlement_batch_id=confirmation["settlement_batch_id"],
|
|
490
|
+
chain_receipt_id=confirmation["chain_receipt_id"],
|
|
491
|
+
usage_event_digest=confirmation["usage_event_digest"],
|
|
492
|
+
settled_at=confirmation.get("settled_at"),
|
|
493
|
+
)
|
|
489
494
|
return "", 204
|
|
490
495
|
|
|
491
|
-
if
|
|
492
|
-
|
|
493
|
-
and data.get("finality") == "per_payment_onchain"
|
|
494
|
-
and data.get("settlement_status") == "settled"
|
|
495
|
-
):
|
|
496
|
-
order = orders.find_by_challenge_hash(str(data.get("challenge_hash") or ""))
|
|
496
|
+
if confirmation["kind"] == "standard_settled":
|
|
497
|
+
order = orders.find_by_challenge_hash(confirmation["challenge_hash"])
|
|
497
498
|
if not order:
|
|
498
499
|
orders.flag_for_payment_state_review(
|
|
499
500
|
reason="unknown_challenge_hash",
|
|
500
|
-
requirement_id=
|
|
501
|
+
requirement_id=confirmation["requirement_id"],
|
|
501
502
|
)
|
|
502
503
|
return "", 204
|
|
503
504
|
orders.mark_paid_once(
|
|
504
505
|
order["id"],
|
|
505
|
-
siglume_requirement_id=
|
|
506
|
-
chain_receipt_id=
|
|
506
|
+
siglume_requirement_id=confirmation["requirement_id"],
|
|
507
|
+
chain_receipt_id=confirmation["chain_receipt_id"],
|
|
507
508
|
)
|
|
508
509
|
return "", 204
|
|
509
510
|
|
|
510
|
-
if
|
|
511
|
-
order = orders.find_by_challenge_hash(
|
|
512
|
-
if order:
|
|
513
|
-
orders.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
pricing_band=str(data.get("pricing_band") or ""),
|
|
511
|
+
if confirmation["kind"] == "metered_usage_accepted":
|
|
512
|
+
order = orders.find_by_challenge_hash(confirmation["challenge_hash"])
|
|
513
|
+
if not order:
|
|
514
|
+
orders.flag_for_payment_state_review(
|
|
515
|
+
reason="unknown_metered_challenge_hash",
|
|
516
|
+
requirement_id=confirmation["requirement_id"],
|
|
517
517
|
)
|
|
518
|
+
return "", 204
|
|
519
|
+
orders.mark_fulfilled_but_unsettled_once(
|
|
520
|
+
order["id"],
|
|
521
|
+
siglume_requirement_id=confirmation["requirement_id"],
|
|
522
|
+
pricing_band=confirmation["pricing_band"],
|
|
523
|
+
)
|
|
518
524
|
return "", 204
|
|
519
525
|
|
|
520
526
|
# Missing or unknown machine fields: do not mark the order paid from the event
|
|
521
527
|
# name alone. Fetch the requirement or route it to manual review.
|
|
522
528
|
orders.flag_for_payment_state_review(
|
|
523
|
-
reason="
|
|
524
|
-
requirement_id=
|
|
529
|
+
reason=confirmation["reason"],
|
|
530
|
+
requirement_id=confirmation.get("requirement_id") or "",
|
|
531
|
+
settlement_batch_id=confirmation.get("settlement_batch_id"),
|
|
525
532
|
)
|
|
526
533
|
return "", 204
|
|
527
534
|
```
|
|
@@ -529,11 +536,12 @@ return "", 204
|
|
|
529
536
|
## Reconcile Micro / Nano Statements
|
|
530
537
|
|
|
531
538
|
Standard Payment can be marked paid from the verified `direct_payment.confirmed`
|
|
532
|
-
webhook only when `
|
|
533
|
-
`
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
539
|
+
webhook only when `classifyDirectPaymentConfirmation(event)` returns
|
|
540
|
+
`standard_settled`, which requires Standard pricing, per-payment on-chain
|
|
541
|
+
finality, settled status, a challenge hash, a requirement id, and a chain
|
|
542
|
+
receipt id. Micro Payment and Nano Payment are different: they are automatic
|
|
543
|
+
amount bands and are settled later in aggregated on-chain batches. Use the
|
|
544
|
+
statement APIs to answer:
|
|
537
545
|
|
|
538
546
|
- how much Micro / Nano usage is open this week or month,
|
|
539
547
|
- when the buyer's assigned period closes,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
import {
|
|
3
|
+
classifyDirectPaymentConfirmation,
|
|
3
4
|
DirectRequestPaymentMerchantClient,
|
|
4
5
|
verifyDirectRequestPaymentWebhook,
|
|
5
6
|
} from "@siglume/direct-request-payment";
|
|
@@ -22,45 +23,54 @@ app.use((req, res, next) => {
|
|
|
22
23
|
|
|
23
24
|
const orders = new Map<string, any>();
|
|
24
25
|
|
|
25
|
-
async function
|
|
26
|
-
|
|
26
|
+
async function flagForPaymentStateReview(payload: Record<string, any>): Promise<void> {
|
|
27
|
+
console.warn("payment state review required", payload);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function handleDirectPaymentConfirmed(event: any): Promise<void> {
|
|
31
|
+
const classification = classifyDirectPaymentConfirmation(event);
|
|
32
|
+
|
|
33
|
+
if (classification.kind === "metered_batch_settled") {
|
|
27
34
|
// Aggregated Micro/Nano settlement events do not carry an order challenge.
|
|
28
35
|
// Reconcile them against statement / settlement batch data instead.
|
|
29
|
-
|
|
30
|
-
console.log("settled metered batch", data.settlement_batch_id || data.usage_event_digest);
|
|
31
|
-
}
|
|
36
|
+
console.log("settled metered batch", classification.settlement_batch_id, classification.chain_receipt_id);
|
|
32
37
|
return;
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
if (
|
|
36
|
-
|
|
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);
|
|
40
|
+
if (classification.kind === "standard_settled") {
|
|
41
|
+
const order = [...orders.values()].find((item) => item.siglume_challenge_hash === classification.challenge_hash);
|
|
42
42
|
if (order) {
|
|
43
43
|
order.siglume_payment_status = "paid";
|
|
44
|
-
order.siglume_requirement_id =
|
|
45
|
-
order.siglume_chain_receipt_id =
|
|
44
|
+
order.siglume_requirement_id = classification.requirement_id;
|
|
45
|
+
order.siglume_chain_receipt_id = classification.chain_receipt_id;
|
|
46
|
+
} else {
|
|
47
|
+
await flagForPaymentStateReview({
|
|
48
|
+
reason: "unknown_challenge_hash",
|
|
49
|
+
requirement_id: classification.requirement_id,
|
|
50
|
+
});
|
|
46
51
|
}
|
|
47
52
|
return;
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
if (
|
|
51
|
-
const
|
|
52
|
-
const order = [...orders.values()].find((item) => item.siglume_challenge_hash === challengeHash);
|
|
55
|
+
if (classification.kind === "metered_usage_accepted") {
|
|
56
|
+
const order = [...orders.values()].find((item) => item.siglume_challenge_hash === classification.challenge_hash);
|
|
53
57
|
if (order) {
|
|
54
58
|
order.siglume_payment_status = "fulfilled_unsettled";
|
|
55
|
-
order.siglume_requirement_id =
|
|
59
|
+
order.siglume_requirement_id = classification.requirement_id;
|
|
60
|
+
} else {
|
|
61
|
+
await flagForPaymentStateReview({
|
|
62
|
+
reason: "unknown_metered_challenge_hash",
|
|
63
|
+
requirement_id: classification.requirement_id,
|
|
64
|
+
});
|
|
56
65
|
}
|
|
57
66
|
return;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
// Unknown or older payload shape: do not mark paid from the event name alone.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
requirement_id:
|
|
70
|
+
await flagForPaymentStateReview({
|
|
71
|
+
reason: classification.reason,
|
|
72
|
+
requirement_id: classification.requirement_id,
|
|
73
|
+
settlement_batch_id: classification.settlement_batch_id,
|
|
64
74
|
});
|
|
65
75
|
}
|
|
66
76
|
|
|
@@ -111,7 +121,7 @@ app.post("/siglume/webhook", express.raw({ type: "application/json" }), asyncRou
|
|
|
111
121
|
);
|
|
112
122
|
|
|
113
123
|
if (event.type === "direct_payment.confirmed") {
|
|
114
|
-
await handleDirectPaymentConfirmed(event
|
|
124
|
+
await handleDirectPaymentConfirmed(event);
|
|
115
125
|
}
|
|
116
126
|
|
|
117
127
|
res.status(204).send();
|