@peable.to/shared-types 0.2.0 → 0.3.1-oxy-one.0
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/dist/cjs/billing.js +6 -0
- package/dist/cjs/dispute.js +17 -0
- package/dist/cjs/index.js +16 -1
- package/dist/cjs/money.js +48 -8
- package/dist/cjs/paymentIntent.js +185 -9
- package/dist/cjs/settlement.js +2 -0
- package/dist/cjs/social.js +19 -0
- package/dist/esm/billing.js +3 -0
- package/dist/esm/dispute.js +16 -0
- package/dist/esm/index.js +4 -2
- package/dist/esm/money.js +45 -7
- package/dist/esm/package.json +1 -0
- package/dist/esm/paymentIntent.js +182 -8
- package/dist/esm/settlement.js +1 -0
- package/dist/esm/social.js +18 -1
- package/dist/types/billing.d.ts +86 -0
- package/dist/types/checkoutSession.d.ts +12 -3
- package/dist/types/dispute.d.ts +124 -0
- package/dist/types/event.d.ts +122 -9
- package/dist/types/index.d.ts +8 -5
- package/dist/types/merchant.d.ts +21 -2
- package/dist/types/money.d.ts +31 -3
- package/dist/types/paymentIntent.d.ts +75 -8
- package/dist/types/paymentLink.d.ts +19 -4
- package/dist/types/settlement.d.ts +171 -0
- package/dist/types/social.d.ts +110 -0
- package/dist/types/webhookDelivery.d.ts +38 -2
- package/package.json +4 -2
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BILLING_SUBSCRIPTION_STATUSES = void 0;
|
|
4
|
+
/** Recurring billing transport. Store/plan references belong to the authenticated app;
|
|
5
|
+
* they do not identify an Oxy payer or convey entitlements. */
|
|
6
|
+
exports.BILLING_SUBSCRIPTION_STATUSES = ['incomplete', 'incomplete_expired', 'trialing', 'active', 'past_due', 'canceled', 'unpaid', 'paused'];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A dispute — the card network holding a settled payment's money while a
|
|
4
|
+
* cardholder contests it.
|
|
5
|
+
*
|
|
6
|
+
* Published because `payment_intent.disputed` and `payment_intent.dispute_closed`
|
|
7
|
+
* carry it as their `data.object`. It was route-local at first, with the webhook
|
|
8
|
+
* carrying the PaymentIntent and the detail reachable only through
|
|
9
|
+
* `GET /v1/payment_intents/:id/disputes`. That was inconsistent with the reason
|
|
10
|
+
* those two events exist: `disputed` is a DEADLINE, and a merchant who has to
|
|
11
|
+
* make a second call to learn when evidence is due is a merchant who can miss it
|
|
12
|
+
* while their integration is working exactly as documented.
|
|
13
|
+
*
|
|
14
|
+
* It is also what the provider does. Stripe's `charge.dispute.created` carries
|
|
15
|
+
* the dispute, not the charge — the parity was half-applied.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MERCHANT_ENVIRONMENTS = exports.verifyWebhook = exports.signWebhook = exports.PAYMENT_INTENT_STATUSES = exports.isValidStatusTransition = exports.isBaseUnitString = exports.UNITS_PER_COIN = void 0;
|
|
3
|
+
exports.BILLING_SUBSCRIPTION_STATUSES = exports.SOCIAL_SOURCE_REF_MAX_LENGTH = exports.SOCIAL_SOURCE_APP_MAX_LENGTH = exports.MERCHANT_ENVIRONMENTS = exports.verifyWebhook = exports.signWebhook = exports.CARD_ONLY_STATUSES = exports.CHAIN_ONLY_STATUSES = exports.PAYMENT_INTENT_RAILS = exports.PAYMENT_INTENT_STATUSES = exports.POST_SETTLEMENT_STATUSES = exports.reachesSettlementFromPayer = exports.canStillBePaid = exports.isValidStatusTransition = exports.isCurrencyCode = exports.decimalsFor = exports.CURRENCY_DECIMALS = exports.CURRENCY_CODES = exports.isBaseUnitString = exports.UNITS_PER_COIN = void 0;
|
|
4
4
|
// @peable.to/shared-types — public API for the Peable Gateway contract.
|
|
5
5
|
var money_1 = require("./money");
|
|
6
6
|
Object.defineProperty(exports, "UNITS_PER_COIN", { enumerable: true, get: function () { return money_1.UNITS_PER_COIN; } });
|
|
7
7
|
Object.defineProperty(exports, "isBaseUnitString", { enumerable: true, get: function () { return money_1.isBaseUnitString; } });
|
|
8
|
+
Object.defineProperty(exports, "CURRENCY_CODES", { enumerable: true, get: function () { return money_1.CURRENCY_CODES; } });
|
|
9
|
+
Object.defineProperty(exports, "CURRENCY_DECIMALS", { enumerable: true, get: function () { return money_1.CURRENCY_DECIMALS; } });
|
|
10
|
+
Object.defineProperty(exports, "decimalsFor", { enumerable: true, get: function () { return money_1.decimalsFor; } });
|
|
11
|
+
Object.defineProperty(exports, "isCurrencyCode", { enumerable: true, get: function () { return money_1.isCurrencyCode; } });
|
|
8
12
|
var paymentIntent_1 = require("./paymentIntent");
|
|
9
13
|
Object.defineProperty(exports, "isValidStatusTransition", { enumerable: true, get: function () { return paymentIntent_1.isValidStatusTransition; } });
|
|
14
|
+
Object.defineProperty(exports, "canStillBePaid", { enumerable: true, get: function () { return paymentIntent_1.canStillBePaid; } });
|
|
15
|
+
Object.defineProperty(exports, "reachesSettlementFromPayer", { enumerable: true, get: function () { return paymentIntent_1.reachesSettlementFromPayer; } });
|
|
16
|
+
Object.defineProperty(exports, "POST_SETTLEMENT_STATUSES", { enumerable: true, get: function () { return paymentIntent_1.POST_SETTLEMENT_STATUSES; } });
|
|
10
17
|
Object.defineProperty(exports, "PAYMENT_INTENT_STATUSES", { enumerable: true, get: function () { return paymentIntent_1.PAYMENT_INTENT_STATUSES; } });
|
|
18
|
+
Object.defineProperty(exports, "PAYMENT_INTENT_RAILS", { enumerable: true, get: function () { return paymentIntent_1.PAYMENT_INTENT_RAILS; } });
|
|
19
|
+
Object.defineProperty(exports, "CHAIN_ONLY_STATUSES", { enumerable: true, get: function () { return paymentIntent_1.CHAIN_ONLY_STATUSES; } });
|
|
20
|
+
Object.defineProperty(exports, "CARD_ONLY_STATUSES", { enumerable: true, get: function () { return paymentIntent_1.CARD_ONLY_STATUSES; } });
|
|
11
21
|
var webhookSigner_1 = require("./webhookSigner");
|
|
12
22
|
Object.defineProperty(exports, "signWebhook", { enumerable: true, get: function () { return webhookSigner_1.signWebhook; } });
|
|
13
23
|
Object.defineProperty(exports, "verifyWebhook", { enumerable: true, get: function () { return webhookSigner_1.verifyWebhook; } });
|
|
14
24
|
var merchant_1 = require("./merchant");
|
|
15
25
|
Object.defineProperty(exports, "MERCHANT_ENVIRONMENTS", { enumerable: true, get: function () { return merchant_1.MERCHANT_ENVIRONMENTS; } });
|
|
26
|
+
var social_1 = require("./social");
|
|
27
|
+
Object.defineProperty(exports, "SOCIAL_SOURCE_APP_MAX_LENGTH", { enumerable: true, get: function () { return social_1.SOCIAL_SOURCE_APP_MAX_LENGTH; } });
|
|
28
|
+
Object.defineProperty(exports, "SOCIAL_SOURCE_REF_MAX_LENGTH", { enumerable: true, get: function () { return social_1.SOCIAL_SOURCE_REF_MAX_LENGTH; } });
|
|
29
|
+
var billing_1 = require("./billing");
|
|
30
|
+
Object.defineProperty(exports, "BILLING_SUBSCRIPTION_STATUSES", { enumerable: true, get: function () { return billing_1.BILLING_SUBSCRIPTION_STATUSES; } });
|
package/dist/cjs/money.js
CHANGED
|
@@ -1,20 +1,60 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Amount helpers
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// touches a monetary value.
|
|
2
|
+
// Amount helpers. Every monetary value moves through this gateway as a
|
|
3
|
+
// canonical integer string in the currency's SMALLEST unit — m⊜ for FairCoin,
|
|
4
|
+
// cents for a fiat currency — so no float ever touches one.
|
|
6
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.UNITS_PER_COIN = void 0;
|
|
6
|
+
exports.UNITS_PER_COIN = exports.CURRENCY_DECIMALS = exports.CURRENCY_CODES = void 0;
|
|
7
|
+
exports.isCurrencyCode = isCurrencyCode;
|
|
8
|
+
exports.decimalsFor = decimalsFor;
|
|
8
9
|
exports.isBaseUnitString = isBaseUnitString;
|
|
10
|
+
/**
|
|
11
|
+
* The currencies this gateway can denominate a payment in.
|
|
12
|
+
*
|
|
13
|
+
* A closed set, and the database mirrors it as a CHECK
|
|
14
|
+
* (`db/schema/valueSets.ts`). ADR 0001 D4: the scale of an amount is a property
|
|
15
|
+
* of its currency and is NOT implied by the field's type, which is why
|
|
16
|
+
* {@link CURRENCY_DECIMALS} exists and why {@link UNITS_PER_COIN} must never be
|
|
17
|
+
* used to interpret a fiat amount.
|
|
18
|
+
*/
|
|
19
|
+
exports.CURRENCY_CODES = ['FAIR', 'EUR', 'USD'];
|
|
20
|
+
/** Whether an arbitrary string is a currency this gateway knows. */
|
|
21
|
+
function isCurrencyCode(value) {
|
|
22
|
+
return exports.CURRENCY_CODES.includes(value);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* How many decimal places each currency's smallest unit represents — i.e. an
|
|
26
|
+
* amount of `1` means `10^-decimals` of one whole unit.
|
|
27
|
+
*
|
|
28
|
+
* FairCoin's 8 is a consensus constant and is pinned against
|
|
29
|
+
* {@link UNITS_PER_COIN} by this package's own tests, so the two spellings of
|
|
30
|
+
* one fact cannot drift.
|
|
31
|
+
*/
|
|
32
|
+
exports.CURRENCY_DECIMALS = Object.freeze({
|
|
33
|
+
FAIR: 8,
|
|
34
|
+
EUR: 2,
|
|
35
|
+
USD: 2,
|
|
36
|
+
});
|
|
37
|
+
/** The decimal places of `currency`'s smallest unit. */
|
|
38
|
+
function decimalsFor(currency) {
|
|
39
|
+
return exports.CURRENCY_DECIMALS[currency];
|
|
40
|
+
}
|
|
9
41
|
// Frozen FairCoin consensus constant (smallest units per FAIR). Mirrors
|
|
10
42
|
// @fairco.in/core's branding.ts; inlined here (rather than imported) to keep
|
|
11
43
|
// @peable.to/shared-types zero-runtime-dep for a clean CJS+ESM publish — must
|
|
12
44
|
// stay a bigint, never converted to number.
|
|
45
|
+
//
|
|
46
|
+
// FAIRCOIN ONLY. A fiat amount has its own scale (`CURRENCY_DECIMALS`), and
|
|
47
|
+
// dividing a EUR amount by this constant is off by six orders of magnitude with
|
|
48
|
+
// nothing failing.
|
|
13
49
|
exports.UNITS_PER_COIN = 100000000n;
|
|
14
50
|
/**
|
|
15
|
-
* True only for a canonical non-negative integer string in
|
|
16
|
-
* one or more decimal digits, no sign, no decimal point, no
|
|
17
|
-
* (except the literal "0"). Floats and negatives are rejected.
|
|
51
|
+
* True only for a canonical non-negative integer string in the currency's
|
|
52
|
+
* smallest unit: one or more decimal digits, no sign, no decimal point, no
|
|
53
|
+
* leading zeros (except the literal "0"). Floats and negatives are rejected.
|
|
54
|
+
*
|
|
55
|
+
* Currency-agnostic on purpose — the pattern is about the SPELLING of an
|
|
56
|
+
* amount, not its scale, and the same one is restated as a CHECK in the
|
|
57
|
+
* database (`BASE_UNIT_STRING_PATTERN`), byte-identical.
|
|
18
58
|
*/
|
|
19
59
|
function isBaseUnitString(s) {
|
|
20
60
|
return /^(0|[1-9][0-9]*)$/.test(s);
|
|
@@ -1,26 +1,117 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PAYMENT_INTENT_STATUSES = void 0;
|
|
3
|
+
exports.POST_SETTLEMENT_STATUSES = exports.PAYMENT_INTENT_STATUSES = exports.CARD_ONLY_STATUSES = exports.CHAIN_ONLY_STATUSES = exports.PAYMENT_INTENT_RAILS = void 0;
|
|
4
4
|
exports.isValidStatusTransition = isValidStatusTransition;
|
|
5
|
+
exports.canStillBePaid = canStillBePaid;
|
|
6
|
+
exports.reachesSettlementFromPayer = reachesSettlementFromPayer;
|
|
7
|
+
/** {@link PaymentIntentRail} as the tuple the database CHECK reads. */
|
|
8
|
+
exports.PAYMENT_INTENT_RAILS = ['faircoin', 'card'];
|
|
9
|
+
/**
|
|
10
|
+
* Which statuses belong to which rail (ADR 0001 D5).
|
|
11
|
+
*
|
|
12
|
+
* The four chain states describe a transaction on a blockchain and the four
|
|
13
|
+
* card states describe an authorization at a card acquirer; neither set is
|
|
14
|
+
* expressible on the other rail. Mapping a card authorization onto `broadcast`
|
|
15
|
+
* would be cheaper and would be a lie — nothing was broadcast, there is no
|
|
16
|
+
* transaction, and `payment_intents_broadcast_requires_txid_check` would then
|
|
17
|
+
* be demanding a txid from a payment that can never have one.
|
|
18
|
+
*
|
|
19
|
+
* `db/schema/payments.ts` renders these two sets into CHECK constraints, so the
|
|
20
|
+
* restriction holds against a write that skipped the application.
|
|
21
|
+
*/
|
|
22
|
+
exports.CHAIN_ONLY_STATUSES = [
|
|
23
|
+
'awaiting_approval',
|
|
24
|
+
'approved',
|
|
25
|
+
'broadcast',
|
|
26
|
+
'confirming',
|
|
27
|
+
];
|
|
28
|
+
exports.CARD_ONLY_STATUSES = [
|
|
29
|
+
'requires_action',
|
|
30
|
+
'processing',
|
|
31
|
+
'refunded',
|
|
32
|
+
'partially_refunded',
|
|
33
|
+
];
|
|
5
34
|
/**
|
|
6
35
|
* Legal forward transitions of the PaymentIntent lifecycle. Terminal states
|
|
7
|
-
* (
|
|
36
|
+
* (refunded/expired/failed/rejected) allow no further transition here — the one
|
|
8
37
|
* documented exception, a reorg dropping `settled` back to `confirming`, is
|
|
9
38
|
* modelled by the backend state machine, not this happy-path table.
|
|
39
|
+
*
|
|
40
|
+
* ONE table for both rails, deliberately. It answers "is this a legal lifecycle
|
|
41
|
+
* edge at all"; WHICH rail may reach a given status is the separate, structural
|
|
42
|
+
* question the two sets above answer, in the database. A rail-parameterised
|
|
43
|
+
* table would put the same restriction in two places and let them disagree.
|
|
44
|
+
*
|
|
45
|
+
* Self-transitions are absent because `applyEvent` short-circuits on
|
|
46
|
+
* `current === target` before consulting this table — that is the idempotent
|
|
47
|
+
* re-poll of the settlement watcher, and a second partial refund, which change
|
|
48
|
+
* an amount without changing a status.
|
|
10
49
|
*/
|
|
11
50
|
const ALLOWED = {
|
|
12
|
-
// `created` supports
|
|
13
|
-
// approved → broadcast)
|
|
14
|
-
// and reports the txid directly (→ broadcast),
|
|
15
|
-
//
|
|
16
|
-
|
|
51
|
+
// `created` supports three paths: the rich wallet flow (→ awaiting_approval →
|
|
52
|
+
// approved → broadcast), the minimal path where the payer pays out-of-band
|
|
53
|
+
// and reports the txid directly (→ broadcast), and the card path (→
|
|
54
|
+
// requires_action for an SCA challenge, → processing, or straight to settled
|
|
55
|
+
// for a charge that confirms in one call). `failed` is reachable from here
|
|
56
|
+
// because a card can be declined on first confirmation, having never been
|
|
57
|
+
// anything else; no FairCoin path uses that edge, since `underpaid` is only
|
|
58
|
+
// emitted for an intent that already carries a txid.
|
|
59
|
+
created: [
|
|
60
|
+
'awaiting_approval',
|
|
61
|
+
'broadcast',
|
|
62
|
+
'requires_action',
|
|
63
|
+
'processing',
|
|
64
|
+
'settled',
|
|
65
|
+
'failed',
|
|
66
|
+
'rejected',
|
|
67
|
+
'expired',
|
|
68
|
+
],
|
|
17
69
|
awaiting_approval: ['approved', 'rejected', 'expired'],
|
|
18
70
|
approved: ['broadcast', 'failed'],
|
|
19
71
|
broadcast: ['confirming', 'failed'],
|
|
20
72
|
confirming: ['settled', 'failed'],
|
|
21
|
-
|
|
73
|
+
requires_action: ['processing', 'settled', 'failed', 'expired', 'rejected'],
|
|
74
|
+
processing: ['settled', 'failed'],
|
|
75
|
+
/**
|
|
76
|
+
* A failed ATTEMPT is not a failed payment, on the card rail.
|
|
77
|
+
*
|
|
78
|
+
* `payment_intent.payment_failed` means one authorization attempt was
|
|
79
|
+
* declined; Stripe returns the PaymentIntent to `requires_payment_method` and
|
|
80
|
+
* the payer can confirm it again with another card. With `failed` terminal,
|
|
81
|
+
* the later `payment_intent.succeeded` was an ILLEGAL transition — so the
|
|
82
|
+
* drain recorded a processing failure and retried it forever, the payment
|
|
83
|
+
* stayed `failed`, and a merchant who had been paid was told their payment
|
|
84
|
+
* had been declined.
|
|
85
|
+
*
|
|
86
|
+
* On the chain rail `failed` means `underpaid`, which IS terminal — coins
|
|
87
|
+
* arrived and were not enough. Nothing here opens that door: `underpaid` is
|
|
88
|
+
* emitted only by the settlement watcher, and `LEGAL_SOURCES` in
|
|
89
|
+
* `services/intentState.ts` keeps every `card_*` event to card statuses and
|
|
90
|
+
* the chain's `confirmed` to `confirming`. This table answers "is the edge
|
|
91
|
+
* legal at all"; which rail may walk it is the separate question those two
|
|
92
|
+
* guards answer.
|
|
93
|
+
*/
|
|
94
|
+
failed: ['requires_action', 'processing', 'settled'],
|
|
95
|
+
// Money coming back requires money to have arrived, so both refund states are
|
|
96
|
+
// reachable only from `settled`.
|
|
97
|
+
settled: ['refunded', 'partially_refunded'],
|
|
98
|
+
/**
|
|
99
|
+
* ...and money coming back can be UNDONE, which is why these edges run both
|
|
100
|
+
* ways.
|
|
101
|
+
*
|
|
102
|
+
* A refund is not final when the provider accepts it: a bank can reject one
|
|
103
|
+
* days later, and the provider then says so (`refund.failed`). Without a path
|
|
104
|
+
* back, a payment whose only refund failed would claim `refunded` forever —
|
|
105
|
+
* the merchant's books saying money went back that is still with them, and no
|
|
106
|
+
* transition anywhere able to correct it.
|
|
107
|
+
*
|
|
108
|
+
* The target is always recomputed from the SUM of succeeded refunds
|
|
109
|
+
* (`applyRefundToIntent`), never stepped, so these edges describe where that
|
|
110
|
+
* sum can legally land rather than a sequence anyone walks.
|
|
111
|
+
*/
|
|
112
|
+
partially_refunded: ['refunded', 'settled'],
|
|
113
|
+
refunded: ['partially_refunded', 'settled'],
|
|
22
114
|
expired: [],
|
|
23
|
-
failed: [],
|
|
24
115
|
rejected: [],
|
|
25
116
|
};
|
|
26
117
|
function isValidStatusTransition(from, to) {
|
|
@@ -29,3 +120,88 @@ function isValidStatusTransition(from, to) {
|
|
|
29
120
|
/** Every legal `PaymentIntentStatus`, derived from `ALLOWED`'s keys — the
|
|
30
121
|
* single source of truth consumed by the Gateway's list-route status filter. */
|
|
31
122
|
exports.PAYMENT_INTENT_STATUSES = Object.keys(ALLOWED);
|
|
123
|
+
/**
|
|
124
|
+
* The statuses a payer can still complete a payment FROM.
|
|
125
|
+
*
|
|
126
|
+
* **Twice now this question has been answered by walking the transition table,
|
|
127
|
+
* and twice the table has moved underneath it.**
|
|
128
|
+
*
|
|
129
|
+
* The hosted checkout first asked leaf-ness — "has this status no outgoing
|
|
130
|
+
* edges" — which meant `settled` while `settled` had none. Adding the refund
|
|
131
|
+
* transitions (`settled → refunded | partially_refunded`) gave it edges, so a
|
|
132
|
+
* settled payment stopped looking finished, and the checkout began REUSING a
|
|
133
|
+
* remembered settled intent instead of minting a fresh one: a payer who had
|
|
134
|
+
* already paid saw their old receipt forever, with no way to pay the link
|
|
135
|
+
* again.
|
|
136
|
+
*
|
|
137
|
+
* The fix was reachability — "is `settled` still reachable" — and that held
|
|
138
|
+
* until refunds could be UNDONE. `refunded → settled` (a bank rejecting a
|
|
139
|
+
* refund days later) makes `settled` reachable from `settled` in two steps, so
|
|
140
|
+
* reachability answers `true` for a payment that is finished, and the original
|
|
141
|
+
* bug returns unchanged.
|
|
142
|
+
*
|
|
143
|
+
* So the set is stated, not derived. It is exactly the statuses in which the
|
|
144
|
+
* gateway is still waiting on the PAYER — which is what the question means, in
|
|
145
|
+
* the payer's own terms, and is the one formulation no edge added elsewhere can
|
|
146
|
+
* silently change. `paymentIntent.test.ts` pins it against the table: every
|
|
147
|
+
* member must still be able to reach `settled` without passing through a
|
|
148
|
+
* post-settlement status, and no non-member may.
|
|
149
|
+
*/
|
|
150
|
+
const PAYABLE_STATUSES = new Set([
|
|
151
|
+
'created',
|
|
152
|
+
'awaiting_approval',
|
|
153
|
+
'approved',
|
|
154
|
+
'broadcast',
|
|
155
|
+
'confirming',
|
|
156
|
+
'requires_action',
|
|
157
|
+
'processing',
|
|
158
|
+
]);
|
|
159
|
+
/**
|
|
160
|
+
* Whether a payer can still complete a payment against an intent in this
|
|
161
|
+
* status.
|
|
162
|
+
*
|
|
163
|
+
* `settled` answers `false`: it is where paying ENDS. `partially_refunded`
|
|
164
|
+
* answers `false` too — money can still move, but not from the payer.
|
|
165
|
+
*/
|
|
166
|
+
function canStillBePaid(status) {
|
|
167
|
+
return PAYABLE_STATUSES.has(status);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The statuses a payment reaches only AFTER the payer has finished.
|
|
171
|
+
*
|
|
172
|
+
* Exported for the gate below rather than for production use: the property that
|
|
173
|
+
* makes `PAYABLE_STATUSES` checkable is that a payable status reaches `settled`
|
|
174
|
+
* without going through one of these.
|
|
175
|
+
*/
|
|
176
|
+
exports.POST_SETTLEMENT_STATUSES = [
|
|
177
|
+
'settled',
|
|
178
|
+
'refunded',
|
|
179
|
+
'partially_refunded',
|
|
180
|
+
];
|
|
181
|
+
/**
|
|
182
|
+
* Whether `settled` is reachable from `status` without passing through a
|
|
183
|
+
* post-settlement status. The derivation `canStillBePaid` used to BE, kept as
|
|
184
|
+
* the thing that checks it rather than as the thing that answers it.
|
|
185
|
+
*/
|
|
186
|
+
function reachesSettlementFromPayer(status) {
|
|
187
|
+
const blocked = new Set(exports.POST_SETTLEMENT_STATUSES);
|
|
188
|
+
// A payment that has ALREADY settled is one the payer has finished with,
|
|
189
|
+
// whatever edges lead out of it. Checked before the walk rather than inside
|
|
190
|
+
// it, because `partially_refunded → settled` is a direct edge and the walk
|
|
191
|
+
// would find it in one step — and that edge is a refund being undone, not a
|
|
192
|
+
// payer paying.
|
|
193
|
+
if (blocked.has(status))
|
|
194
|
+
return false;
|
|
195
|
+
const seen = new Set();
|
|
196
|
+
const queue = [...ALLOWED[status]];
|
|
197
|
+
while (queue.length > 0) {
|
|
198
|
+
const next = queue.shift();
|
|
199
|
+
if (next === 'settled')
|
|
200
|
+
return true;
|
|
201
|
+
if (seen.has(next) || blocked.has(next))
|
|
202
|
+
continue;
|
|
203
|
+
seen.add(next);
|
|
204
|
+
queue.push(...ALLOWED[next]);
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
package/dist/cjs/social.js
CHANGED
|
@@ -3,3 +3,22 @@
|
|
|
3
3
|
// Gateway backend, the wallet frontend, and (indirectly) the enrichment
|
|
4
4
|
// service's callers. Mirrors the Stripe-parity style of paymentIntent.ts.
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SOCIAL_SOURCE_REF_MAX_LENGTH = exports.SOCIAL_SOURCE_APP_MAX_LENGTH = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Longest `SocialPaymentSource.app` the gateway accepts. An app slug, not a
|
|
9
|
+
* label — 32 characters is more than any Oxy app id needs and short enough that
|
|
10
|
+
* the column can never become a place to put prose.
|
|
11
|
+
*
|
|
12
|
+
* Published with the contract because it is what a request is REFUSED for: a
|
|
13
|
+
* caller that cannot read the bound from the types finds it out with a 422.
|
|
14
|
+
*/
|
|
15
|
+
exports.SOCIAL_SOURCE_APP_MAX_LENGTH = 32;
|
|
16
|
+
/**
|
|
17
|
+
* Longest `SocialPaymentSource.ref` the gateway accepts.
|
|
18
|
+
*
|
|
19
|
+
* The bound is the whole defence here, because the value is opaque: Peable
|
|
20
|
+
* never parses, resolves or links a `ref`, so nothing downstream would notice
|
|
21
|
+
* it growing. 128 characters holds any id an app mints (a uuid, a snowflake, a
|
|
22
|
+
* base64 hash) and refuses a payload.
|
|
23
|
+
*/
|
|
24
|
+
exports.SOCIAL_SOURCE_REF_MAX_LENGTH = 128;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Recurring billing transport. Store/plan references belong to the authenticated app;
|
|
2
|
+
* they do not identify an Oxy payer or convey entitlements. */
|
|
3
|
+
export const BILLING_SUBSCRIPTION_STATUSES = ['incomplete', 'incomplete_expired', 'trialing', 'active', 'past_due', 'canceled', 'unpaid', 'paused'];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dispute — the card network holding a settled payment's money while a
|
|
3
|
+
* cardholder contests it.
|
|
4
|
+
*
|
|
5
|
+
* Published because `payment_intent.disputed` and `payment_intent.dispute_closed`
|
|
6
|
+
* carry it as their `data.object`. It was route-local at first, with the webhook
|
|
7
|
+
* carrying the PaymentIntent and the detail reachable only through
|
|
8
|
+
* `GET /v1/payment_intents/:id/disputes`. That was inconsistent with the reason
|
|
9
|
+
* those two events exist: `disputed` is a DEADLINE, and a merchant who has to
|
|
10
|
+
* make a second call to learn when evidence is due is a merchant who can miss it
|
|
11
|
+
* while their integration is working exactly as documented.
|
|
12
|
+
*
|
|
13
|
+
* It is also what the provider does. Stripe's `charge.dispute.created` carries
|
|
14
|
+
* the dispute, not the charge — the parity was half-applied.
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// @peable.to/shared-types — public API for the Peable Gateway contract.
|
|
2
|
-
export { UNITS_PER_COIN, isBaseUnitString } from './money.js';
|
|
3
|
-
export { isValidStatusTransition, PAYMENT_INTENT_STATUSES, } from './paymentIntent.js';
|
|
2
|
+
export { UNITS_PER_COIN, isBaseUnitString, CURRENCY_CODES, CURRENCY_DECIMALS, decimalsFor, isCurrencyCode, } from './money.js';
|
|
3
|
+
export { isValidStatusTransition, canStillBePaid, reachesSettlementFromPayer, POST_SETTLEMENT_STATUSES, PAYMENT_INTENT_STATUSES, PAYMENT_INTENT_RAILS, CHAIN_ONLY_STATUSES, CARD_ONLY_STATUSES, } from './paymentIntent.js';
|
|
4
4
|
export { signWebhook, verifyWebhook } from './webhookSigner.js';
|
|
5
5
|
export { MERCHANT_ENVIRONMENTS, } from './merchant.js';
|
|
6
|
+
export { SOCIAL_SOURCE_APP_MAX_LENGTH, SOCIAL_SOURCE_REF_MAX_LENGTH, } from './social.js';
|
|
7
|
+
export { BILLING_SUBSCRIPTION_STATUSES, } from './billing.js';
|
package/dist/esm/money.js
CHANGED
|
@@ -1,16 +1,54 @@
|
|
|
1
|
-
// Amount helpers
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
|
|
1
|
+
// Amount helpers. Every monetary value moves through this gateway as a
|
|
2
|
+
// canonical integer string in the currency's SMALLEST unit — m⊜ for FairCoin,
|
|
3
|
+
// cents for a fiat currency — so no float ever touches one.
|
|
4
|
+
/**
|
|
5
|
+
* The currencies this gateway can denominate a payment in.
|
|
6
|
+
*
|
|
7
|
+
* A closed set, and the database mirrors it as a CHECK
|
|
8
|
+
* (`db/schema/valueSets.ts`). ADR 0001 D4: the scale of an amount is a property
|
|
9
|
+
* of its currency and is NOT implied by the field's type, which is why
|
|
10
|
+
* {@link CURRENCY_DECIMALS} exists and why {@link UNITS_PER_COIN} must never be
|
|
11
|
+
* used to interpret a fiat amount.
|
|
12
|
+
*/
|
|
13
|
+
export const CURRENCY_CODES = ['FAIR', 'EUR', 'USD'];
|
|
14
|
+
/** Whether an arbitrary string is a currency this gateway knows. */
|
|
15
|
+
export function isCurrencyCode(value) {
|
|
16
|
+
return CURRENCY_CODES.includes(value);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* How many decimal places each currency's smallest unit represents — i.e. an
|
|
20
|
+
* amount of `1` means `10^-decimals` of one whole unit.
|
|
21
|
+
*
|
|
22
|
+
* FairCoin's 8 is a consensus constant and is pinned against
|
|
23
|
+
* {@link UNITS_PER_COIN} by this package's own tests, so the two spellings of
|
|
24
|
+
* one fact cannot drift.
|
|
25
|
+
*/
|
|
26
|
+
export const CURRENCY_DECIMALS = Object.freeze({
|
|
27
|
+
FAIR: 8,
|
|
28
|
+
EUR: 2,
|
|
29
|
+
USD: 2,
|
|
30
|
+
});
|
|
31
|
+
/** The decimal places of `currency`'s smallest unit. */
|
|
32
|
+
export function decimalsFor(currency) {
|
|
33
|
+
return CURRENCY_DECIMALS[currency];
|
|
34
|
+
}
|
|
5
35
|
// Frozen FairCoin consensus constant (smallest units per FAIR). Mirrors
|
|
6
36
|
// @fairco.in/core's branding.ts; inlined here (rather than imported) to keep
|
|
7
37
|
// @peable.to/shared-types zero-runtime-dep for a clean CJS+ESM publish — must
|
|
8
38
|
// stay a bigint, never converted to number.
|
|
39
|
+
//
|
|
40
|
+
// FAIRCOIN ONLY. A fiat amount has its own scale (`CURRENCY_DECIMALS`), and
|
|
41
|
+
// dividing a EUR amount by this constant is off by six orders of magnitude with
|
|
42
|
+
// nothing failing.
|
|
9
43
|
export const UNITS_PER_COIN = 100000000n;
|
|
10
44
|
/**
|
|
11
|
-
* True only for a canonical non-negative integer string in
|
|
12
|
-
* one or more decimal digits, no sign, no decimal point, no
|
|
13
|
-
* (except the literal "0"). Floats and negatives are rejected.
|
|
45
|
+
* True only for a canonical non-negative integer string in the currency's
|
|
46
|
+
* smallest unit: one or more decimal digits, no sign, no decimal point, no
|
|
47
|
+
* leading zeros (except the literal "0"). Floats and negatives are rejected.
|
|
48
|
+
*
|
|
49
|
+
* Currency-agnostic on purpose — the pattern is about the SPELLING of an
|
|
50
|
+
* amount, not its scale, and the same one is restated as a CHECK in the
|
|
51
|
+
* database (`BASE_UNIT_STRING_PATTERN`), byte-identical.
|
|
14
52
|
*/
|
|
15
53
|
export function isBaseUnitString(s) {
|
|
16
54
|
return /^(0|[1-9][0-9]*)$/.test(s);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|