@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.
@@ -1,7 +1,35 @@
1
+ /**
2
+ * The currencies this gateway can denominate a payment in.
3
+ *
4
+ * A closed set, and the database mirrors it as a CHECK
5
+ * (`db/schema/valueSets.ts`). ADR 0001 D4: the scale of an amount is a property
6
+ * of its currency and is NOT implied by the field's type, which is why
7
+ * {@link CURRENCY_DECIMALS} exists and why {@link UNITS_PER_COIN} must never be
8
+ * used to interpret a fiat amount.
9
+ */
10
+ export declare const CURRENCY_CODES: readonly ["FAIR", "EUR", "USD"];
11
+ export type CurrencyCode = (typeof CURRENCY_CODES)[number];
12
+ /** Whether an arbitrary string is a currency this gateway knows. */
13
+ export declare function isCurrencyCode(value: string): value is CurrencyCode;
14
+ /**
15
+ * How many decimal places each currency's smallest unit represents — i.e. an
16
+ * amount of `1` means `10^-decimals` of one whole unit.
17
+ *
18
+ * FairCoin's 8 is a consensus constant and is pinned against
19
+ * {@link UNITS_PER_COIN} by this package's own tests, so the two spellings of
20
+ * one fact cannot drift.
21
+ */
22
+ export declare const CURRENCY_DECIMALS: Readonly<Record<CurrencyCode, number>>;
23
+ /** The decimal places of `currency`'s smallest unit. */
24
+ export declare function decimalsFor(currency: CurrencyCode): number;
1
25
  export declare const UNITS_PER_COIN = 100000000n;
2
26
  /**
3
- * True only for a canonical non-negative integer string in base units:
4
- * one or more decimal digits, no sign, no decimal point, no leading zeros
5
- * (except the literal "0"). Floats and negatives are rejected.
27
+ * True only for a canonical non-negative integer string in the currency's
28
+ * smallest unit: one or more decimal digits, no sign, no decimal point, no
29
+ * leading zeros (except the literal "0"). Floats and negatives are rejected.
30
+ *
31
+ * Currency-agnostic on purpose — the pattern is about the SPELLING of an
32
+ * amount, not its scale, and the same one is restated as a CHECK in the
33
+ * database (`BASE_UNIT_STRING_PATTERN`), byte-identical.
6
34
  */
7
35
  export declare function isBaseUnitString(s: string): boolean;
@@ -1,21 +1,80 @@
1
+ import type { CurrencyCode } from './money';
1
2
  import type { NetworkType } from './network';
2
- export type PaymentIntentStatus = 'created' | 'awaiting_approval' | 'approved' | 'broadcast' | 'confirming' | 'settled' | 'expired' | 'failed' | 'rejected';
3
+ /**
4
+ * Which rail moves the money (ADR 0001 D1).
5
+ *
6
+ * On the INTENT, not on the merchant: one merchant takes both, and a merchant's
7
+ * capabilities are a different question from what this particular payment is.
8
+ * The rail decides which of the fields below mean anything.
9
+ */
10
+ export type PaymentIntentRail = 'faircoin' | 'card';
11
+ /** {@link PaymentIntentRail} as the tuple the database CHECK reads. */
12
+ export declare const PAYMENT_INTENT_RAILS: readonly PaymentIntentRail[];
13
+ export type PaymentIntentStatus = 'created' | 'awaiting_approval' | 'approved' | 'broadcast' | 'confirming' | 'requires_action' | 'processing' | 'settled' | 'refunded' | 'partially_refunded' | 'expired' | 'failed' | 'rejected';
14
+ /**
15
+ * Which statuses belong to which rail (ADR 0001 D5).
16
+ *
17
+ * The four chain states describe a transaction on a blockchain and the four
18
+ * card states describe an authorization at a card acquirer; neither set is
19
+ * expressible on the other rail. Mapping a card authorization onto `broadcast`
20
+ * would be cheaper and would be a lie — nothing was broadcast, there is no
21
+ * transaction, and `payment_intents_broadcast_requires_txid_check` would then
22
+ * be demanding a txid from a payment that can never have one.
23
+ *
24
+ * `db/schema/payments.ts` renders these two sets into CHECK constraints, so the
25
+ * restriction holds against a write that skipped the application.
26
+ */
27
+ export declare const CHAIN_ONLY_STATUSES: readonly PaymentIntentStatus[];
28
+ export declare const CARD_ONLY_STATUSES: readonly PaymentIntentStatus[];
3
29
  export declare function isValidStatusTransition(from: PaymentIntentStatus, to: PaymentIntentStatus): boolean;
4
30
  /** Every legal `PaymentIntentStatus`, derived from `ALLOWED`'s keys — the
5
31
  * single source of truth consumed by the Gateway's list-route status filter. */
6
32
  export declare const PAYMENT_INTENT_STATUSES: readonly PaymentIntentStatus[];
33
+ /**
34
+ * Whether a payer can still complete a payment against an intent in this
35
+ * status.
36
+ *
37
+ * `settled` answers `false`: it is where paying ENDS. `partially_refunded`
38
+ * answers `false` too — money can still move, but not from the payer.
39
+ */
40
+ export declare function canStillBePaid(status: PaymentIntentStatus): boolean;
41
+ /**
42
+ * The statuses a payment reaches only AFTER the payer has finished.
43
+ *
44
+ * Exported for the gate below rather than for production use: the property that
45
+ * makes `PAYABLE_STATUSES` checkable is that a payable status reaches `settled`
46
+ * without going through one of these.
47
+ */
48
+ export declare const POST_SETTLEMENT_STATUSES: readonly PaymentIntentStatus[];
49
+ /**
50
+ * Whether `settled` is reachable from `status` without passing through a
51
+ * post-settlement status. The derivation `canStillBePaid` used to BE, kept as
52
+ * the thing that checks it rather than as the thing that answers it.
53
+ */
54
+ export declare function reachesSettlementFromPayer(status: PaymentIntentStatus): boolean;
7
55
  export interface PaymentIntent {
8
56
  id: string;
9
57
  object: 'payment_intent';
10
58
  status: PaymentIntentStatus;
11
- /** Amount in base units (m⊜) as a canonical integer string. Never a float. */
59
+ /** Which rail moves this payment. Decides which fields below are populated. */
60
+ rail: PaymentIntentRail;
61
+ /**
62
+ * Amount in the currency's SMALLEST unit as a canonical integer string.
63
+ * Never a float, and never interpretable without `currency`: m⊜ (10^-8 FAIR)
64
+ * on the faircoin rail, cents on a fiat one.
65
+ */
12
66
  amount: string;
13
- currency: 'FAIR';
14
- network: NetworkType;
15
- /** Watch-only receive address derived per-intent from the merchant xpub. */
16
- address: string;
67
+ currency: CurrencyCode;
68
+ /** FairCoin rail only. `null` on a card payment, which has no network. */
69
+ network: NetworkType | null;
70
+ /**
71
+ * Watch-only receive address derived per-intent from the merchant xpub.
72
+ * FairCoin rail only; `null` on a card payment, which reserves no address.
73
+ */
74
+ address: string | null;
17
75
  merchantId: string;
18
76
  txid: string | null;
77
+ /** On-chain confirmations. Structurally `0` for the whole life of a card payment. */
19
78
  confirmations: number;
20
79
  clientSecret: string;
21
80
  metadata: Record<string, string>;
@@ -24,9 +83,17 @@ export interface PaymentIntent {
24
83
  updatedAt: string;
25
84
  }
26
85
  export interface CreatePaymentIntentParams {
27
- /** Amount in base units (m⊜) as a canonical integer string. */
86
+ /** Amount in the currency's smallest unit, as a canonical integer string. */
28
87
  amount: string;
29
- network: NetworkType;
88
+ /**
89
+ * Defaults to `'faircoin'` — the rail this gateway shipped with, so every
90
+ * integration written before ADR 0001 keeps working unchanged.
91
+ */
92
+ rail?: PaymentIntentRail;
93
+ /** Required on the faircoin rail; refused on the card rail, which has none. */
94
+ network?: NetworkType;
95
+ /** Defaults to `'FAIR'` on the faircoin rail; required on the card rail. */
96
+ currency?: CurrencyCode;
30
97
  metadata?: Record<string, string>;
31
98
  expiresInSeconds?: number;
32
99
  }
@@ -1,12 +1,20 @@
1
+ import type { CurrencyCode } from './money';
1
2
  import type { NetworkType } from './network';
2
3
  import type { MerchantDisplay } from './merchantDisplay';
4
+ import type { PaymentIntentRail } from './paymentIntent';
3
5
  /** The merchant-facing DTO — returned by the merchant-authed CRUD routes. */
4
6
  export interface PaymentLink {
5
7
  id: string;
6
8
  object: 'payment_link';
7
- /** Amount in base units (m⊜) as a canonical integer string. Never a float. */
9
+ /**
10
+ * Amount in the currency's smallest unit, as a canonical integer string.
11
+ * Never a float, and never interpretable without `currency`.
12
+ */
8
13
  amount: string;
9
- network: NetworkType;
14
+ currency: CurrencyCode;
15
+ rail: PaymentIntentRail;
16
+ /** FairCoin rail only; `null` on a card link. */
17
+ network: NetworkType | null;
10
18
  active: boolean;
11
19
  metadata: Record<string, string>;
12
20
  /** Optional post-payment redirect target for the checkout page. */
@@ -21,13 +29,20 @@ export interface PublicPaymentLink {
21
29
  id: string;
22
30
  object: 'payment_link';
23
31
  amount: string;
24
- network: NetworkType;
32
+ currency: CurrencyCode;
33
+ rail: PaymentIntentRail;
34
+ network: NetworkType | null;
25
35
  active: boolean;
26
36
  merchant: MerchantDisplay;
27
37
  }
28
38
  export interface CreatePaymentLinkParams {
29
39
  amount: string;
30
- network: NetworkType;
40
+ /** Defaults to `'faircoin'`, so pre-ADR-0001 integrations keep working. */
41
+ rail?: PaymentIntentRail;
42
+ /** Required on the faircoin rail; refused on the card rail. */
43
+ network?: NetworkType;
44
+ /** Defaults to `'FAIR'` on the faircoin rail; required on the card rail. */
45
+ currency?: CurrencyCode;
31
46
  metadata?: Record<string, string>;
32
47
  successUrl?: string;
33
48
  }
@@ -0,0 +1,171 @@
1
+ import type { CurrencyCode } from './money';
2
+ /** How a provider reports one capability on a seller's account. */
3
+ export type CapabilityStatus = 'active' | 'pending' | 'inactive';
4
+ /** Where one settlement stands. */
5
+ export type TransferStatus = 'pending' | 'paid' | 'partially_reversed' | 'reversed' | 'failed';
6
+ /** Where one reversal of a settlement stands. */
7
+ export type TransferReversalStatus = 'pending' | 'succeeded' | 'failed';
8
+ export interface ConnectedAccount {
9
+ id: string;
10
+ object: 'connected_account';
11
+ /** The merchant's OWN id for this seller — how they address it. */
12
+ externalRef: string;
13
+ /** ISO 3166-1 alpha-2, upper-case. */
14
+ country: string;
15
+ defaultCurrency: string | null;
16
+ /**
17
+ * Whether this seller can receive a settlement right now.
18
+ *
19
+ * A CONVENIENCE, not the authority: every field it is derived from is here
20
+ * too, and a marketplace with its own readiness policy reads those instead.
21
+ * Offering only this boolean would make the gateway the authority on a
22
+ * question that belongs to the merchant.
23
+ */
24
+ payable: boolean;
25
+ payoutsEnabled: boolean;
26
+ chargesEnabled: boolean;
27
+ /**
28
+ * `null` means the capability was never REQUESTED, which is a different fact
29
+ * from the provider declining it (`inactive`). Collapsing the two makes "why
30
+ * is this seller stuck" unanswerable.
31
+ */
32
+ transfersCapability: CapabilityStatus | null;
33
+ cardPaymentsCapability: CapabilityStatus | null;
34
+ /**
35
+ * COUNTS of outstanding requirements, never the requirements themselves and
36
+ * never their values. The gateway records that a seller owes a document; it
37
+ * does not learn what the document says.
38
+ */
39
+ requirements: {
40
+ currentlyDue: number;
41
+ eventuallyDue: number;
42
+ pastDue: number;
43
+ pendingVerification: number;
44
+ };
45
+ disabledReasonCodes: readonly string[];
46
+ lastSyncedAt: string | null;
47
+ createdAt: string;
48
+ updatedAt: string;
49
+ }
50
+ export interface Transfer {
51
+ id: string;
52
+ object: 'transfer';
53
+ /** The merchant's OWN id for what this settles. Their idempotency. */
54
+ externalRef: string;
55
+ /** The `ca_…` of the seller — never their account id at the provider. */
56
+ connectedAccountId: string;
57
+ /** The `pi_…` this settlement was funded by — never an internal primary key. */
58
+ paymentIntentId: string;
59
+ amount: string;
60
+ currency: string;
61
+ /**
62
+ * CUMULATIVE, in the same units as `amount` — never one reversal's figure.
63
+ *
64
+ * A caller deciding whether a settlement is fully reversed must not have to
65
+ * add up reversals it may not have all seen, which is why this is the
66
+ * provider's own total rather than a sum of the reversals below.
67
+ */
68
+ amountReversed: string;
69
+ status: TransferStatus;
70
+ failureMessage: string | null;
71
+ createdAt: string;
72
+ updatedAt: string;
73
+ }
74
+ /**
75
+ * One reversal operation, returned beside the settlement it acted on.
76
+ *
77
+ * It exists as an object because an AMOUNT IS NOT AN IDENTITY: two reversals of
78
+ * one settlement for the same amount are two operations, and a caller needs to
79
+ * be able to tell which one a response is about.
80
+ */
81
+ export interface TransferReversal {
82
+ id: string;
83
+ object: 'transfer_reversal';
84
+ /** The merchant's own id for this reversal — their `Idempotency-Key`. */
85
+ externalRef: string;
86
+ amount: string;
87
+ currency: string;
88
+ status: TransferReversalStatus;
89
+ failureMessage: string | null;
90
+ createdAt: string;
91
+ }
92
+ /** What `POST /v1/transfers/:id/reversals` answers: the transfer, and the leg. */
93
+ export interface TransferWithReversal extends Transfer {
94
+ reversal: TransferReversal;
95
+ }
96
+ /**
97
+ * What a payment actually came to, as the PROVIDER reports it.
98
+ *
99
+ * ## Every figure is nullable, and that is the contract
100
+ *
101
+ * The difference between "the fee was zero" and "the fee is not known yet" is
102
+ * the whole reason this shape exists. A report that renders an unknown as `0`
103
+ * is one a merchant reconciles against and cannot explain — and zero is a
104
+ * number somebody will subtract. `status` says which of the three situations
105
+ * produced the nulls.
106
+ *
107
+ * ## What it does NOT say
108
+ *
109
+ * It reports what the provider took. It does **not** attribute that cost to an
110
+ * entity: a fee paid by the operator of a Peable deployment is not
111
+ * automatically an expense of the marketplace running on it, and that is a
112
+ * commercial decision no contract can make. Nothing here implies one.
113
+ *
114
+ * `gross` is not the payment's `amount`: a partial capture, a refund or a
115
+ * currency conversion all make them differ, which is why it is reported rather
116
+ * than assumed.
117
+ */
118
+ export interface Settlement {
119
+ object: 'settlement';
120
+ /** The `pi_…` this describes. */
121
+ paymentIntentId: string;
122
+ /**
123
+ * - `available` — settled; these figures are final.
124
+ * - `pending` — a settlement record exists and is not final yet.
125
+ * - `unknown` — there is none to read. **Not zero.**
126
+ */
127
+ status: 'available' | 'pending' | 'unknown';
128
+ /** Minor units, as canonical integer strings, in `currency`. */
129
+ gross: string | null;
130
+ fee: string | null;
131
+ net: string | null;
132
+ /** The SETTLEMENT currency, which may differ from the payment's. */
133
+ currency: string | null;
134
+ /** When the funds become available, ISO-8601. */
135
+ availableOn: string | null;
136
+ /** Present only when a conversion happened. `null` is not a rate of 1. */
137
+ exchangeRate: number | null;
138
+ }
139
+ /** Where one refund stands — the MONEY's own lifecycle, not the payment's. */
140
+ export type RefundStatus = 'pending' | 'succeeded' | 'failed';
141
+ /**
142
+ * Who created a refund.
143
+ *
144
+ * `provider` means it appeared at the acquirer — a dashboard refund, or the
145
+ * network resolving a dispute — and was imported. Such a refund carries no
146
+ * `externalRef`, because the merchant did not make it and has no id for it.
147
+ */
148
+ export type RefundOrigin = 'merchant' | 'provider';
149
+ export interface Refund {
150
+ id: string;
151
+ object: 'refund';
152
+ /** `null` on an IMPORTED refund; see {@link RefundOrigin}. */
153
+ externalRef: string | null;
154
+ origin: RefundOrigin;
155
+ paymentIntentId: string;
156
+ amount: string;
157
+ currency: CurrencyCode | string;
158
+ status: RefundStatus;
159
+ /**
160
+ * Where the PAYMENT stands after this refund.
161
+ *
162
+ * On the refund deliberately: a caller that has just refunded needs to know
163
+ * whether the payment is now `partially_refunded` or `refunded`, and making
164
+ * them re-read the intent is a round trip whose answer can move on before it
165
+ * arrives.
166
+ */
167
+ paymentStatus: string;
168
+ failureCode: string | null;
169
+ createdAt: string;
170
+ updatedAt: string;
171
+ }
@@ -1,3 +1,59 @@
1
+ import type { NetworkType } from './network';
2
+ /**
3
+ * Longest `SocialPaymentSource.app` the gateway accepts. An app slug, not a
4
+ * label — 32 characters is more than any Oxy app id needs and short enough that
5
+ * the column can never become a place to put prose.
6
+ *
7
+ * Published with the contract because it is what a request is REFUSED for: a
8
+ * caller that cannot read the bound from the types finds it out with a 422.
9
+ */
10
+ export declare const SOCIAL_SOURCE_APP_MAX_LENGTH = 32;
11
+ /**
12
+ * Longest `SocialPaymentSource.ref` the gateway accepts.
13
+ *
14
+ * The bound is the whole defence here, because the value is opaque: Peable
15
+ * never parses, resolves or links a `ref`, so nothing downstream would notice
16
+ * it growing. 128 characters holds any id an app mints (a uuid, a snowflake, a
17
+ * base64 hash) and refuses a payload.
18
+ */
19
+ export declare const SOCIAL_SOURCE_REF_MAX_LENGTH = 128;
20
+ /**
21
+ * Display-only context for one social payment: which app the payer was in, and
22
+ * that app's own id for what the payment was for (a Mention post, say).
23
+ *
24
+ * **Opaque to Peable, by design.** `ref` is never parsed, resolved, joined or
25
+ * shown to anyone but the two parties — the gateway stores a string and hands
26
+ * the same string back. That is what keeps "tip a post" from widening what the
27
+ * payment gateway knows about a user: it learns that an id exists, not what it
28
+ * names. The app that minted the `ref` is the only thing that can resolve it,
29
+ * and it already knows.
30
+ *
31
+ * It affects NOTHING about the money. The address a payer is shown comes from
32
+ * the recipient's identity key and the reservation cursor, neither of which
33
+ * this is an input to; a request carrying a source and one carrying none
34
+ * reserve the same index.
35
+ */
36
+ export interface SocialPaymentSource {
37
+ /** The calling app, e.g. `'mention'`. At most {@link SOCIAL_SOURCE_APP_MAX_LENGTH} characters. */
38
+ app: string;
39
+ /**
40
+ * That app's own opaque id for what the payment was for, e.g. a post id. At
41
+ * most {@link SOCIAL_SOURCE_REF_MAX_LENGTH} characters. Absent when the app
42
+ * has no single thing to name.
43
+ */
44
+ ref?: string;
45
+ }
46
+ /**
47
+ * Body of `POST /v1/social/:username/next_address` (spec §4.4 step 3).
48
+ *
49
+ * `source` is OPTIONAL and stays that way: a plain person-to-person payment is
50
+ * for nothing in particular, and a required field would make every caller
51
+ * invent an answer.
52
+ */
53
+ export interface SocialNextAddressRequest {
54
+ network: NetworkType;
55
+ source?: SocialPaymentSource;
56
+ }
1
57
  /** Response of `POST /v1/social/:username/next_address` (spec §4.4 step 3). */
2
58
  export interface SocialNextAddressResponse {
3
59
  address: string;
@@ -23,6 +79,16 @@ export interface SocialReceiveCursorResponse {
23
79
  * caller's stable default address and is never reserved through this flow.
24
80
  */
25
81
  reservedThrough: number;
82
+ /**
83
+ * The identity public key (hex) those addresses were derived from, as the
84
+ * backend saw it. A device compares it with the key it derives from itself:
85
+ * if they differ, its watch window is in the wrong tree and widening it only
86
+ * watches more addresses nobody is paying into.
87
+ *
88
+ * `null` means exactly one thing: this caller has no cursor, so no address
89
+ * has ever been reserved for them. Every cursor that exists names its key.
90
+ */
91
+ identityPublicKey: string | null;
26
92
  }
27
93
  /** Where a transaction's counterparty identity came from (spec §4.8). */
28
94
  export type EnrichmentKind = 'merchant' | 'user' | 'unknown';
@@ -48,3 +114,47 @@ export interface EnrichRequest {
48
114
  export interface EnrichResponse {
49
115
  data: Record<string, EnrichmentResult>;
50
116
  }
117
+ /** Which side of a social payment the caller was on. */
118
+ export type SocialPaymentDirection = 'sent' | 'received';
119
+ /**
120
+ * One social payment as the CALLER saw it, for `GET /v1/social/me/payments`.
121
+ *
122
+ * The address-free view, and the only payment history a surface without a key
123
+ * can ask for: deriving addresses needs a seed, so a browser cannot use the
124
+ * address-list endpoints (`POST /v1/enrich`, the cursor) at all. `direction` is
125
+ * the field that cannot be recovered from the address alone — the backend knows
126
+ * it because the attribution names both parties, and resolving it here is what
127
+ * keeps the caller from having to learn its own user id to compare against.
128
+ *
129
+ * Carries NO amount. An attribution records which address was minted for a
130
+ * payment relationship, not what was paid; the amount lives on-chain, and the
131
+ * client reads it from the Explorer against `address`.
132
+ */
133
+ export interface SocialPayment {
134
+ /** The single-use social-receive address minted for this payment. */
135
+ address: string;
136
+ direction: SocialPaymentDirection;
137
+ /**
138
+ * The other party. Reuses the enrichment contract, including its degradation:
139
+ * a failed identity lookup answers `{ kind: 'unknown' }` rather than dropping
140
+ * the payment, so a history never silently loses rows to an Oxy outage.
141
+ */
142
+ counterparty: EnrichmentResult;
143
+ /**
144
+ * What the payment was for, as the paying app named it at reservation time.
145
+ * Absent for a plain person-to-person payment, which is most of them —
146
+ * present only when the payer's app sent one, and never invented here.
147
+ *
148
+ * Display-only, exactly like `counterparty`: rendering it is all a client may
149
+ * do with it, and a client that cannot resolve `source.ref` (because it is
150
+ * not the app that minted it) shows the payment without the context rather
151
+ * than dropping the payment.
152
+ */
153
+ source?: SocialPaymentSource;
154
+ /** ISO-8601. When the address was minted, which is when the payment was set up. */
155
+ createdAt: string;
156
+ }
157
+ /** Response of `GET /v1/social/me/payments`, newest first. */
158
+ export interface SocialPaymentsResponse {
159
+ payments: SocialPayment[];
160
+ }
@@ -1,15 +1,51 @@
1
1
  import type { WebhookEventType } from './event';
2
+ /**
3
+ * Where one webhook delivery stands (ADR 0001 D7).
4
+ *
5
+ * Was `'delivered' | 'failed'` — the two outcomes an inline, best-effort
6
+ * delivery could report once it had already given up. Delivery is now a durable
7
+ * outbox, so a delivery exists BEFORE any attempt and this set has to say so.
8
+ *
9
+ * - `pending` — enqueued and will be attempted. `nextAttemptAt` says when.
10
+ * - `delivered` — terminal success.
11
+ * - `failed` — the endpoint REFUSED it (a 4xx, a blocked address). No retry
12
+ * can fix that, so none is made.
13
+ * - `dead` — every attempt failed transiently and the budget ran out. The
14
+ * envelope is kept and can be redelivered by hand.
15
+ *
16
+ * ⚠️ Widening a published union is a breaking change for a consumer that
17
+ * switches exhaustively on it. It is the honest shape: the previous one could
18
+ * not describe a delivery that had not been attempted yet, which is now the
19
+ * state every delivery starts in.
20
+ */
21
+ export type WebhookDeliveryStatus = 'pending' | 'delivered' | 'failed' | 'dead';
2
22
  export interface WebhookDelivery {
3
23
  id: string;
4
24
  object: 'webhook_delivery';
5
25
  merchantId: string;
6
- intentId: string;
26
+ /**
27
+ * The `pi_…` this delivery is about — `null` when the event is not about a
28
+ * payment.
29
+ *
30
+ * Nullable since `connected_account.updated`, which is about a SELLER. The
31
+ * alternative was to name an unrelated payment to keep the field a string,
32
+ * which is worse than a null: a consumer correlating on it would attach a
33
+ * seller's readiness change to a payment that has nothing to do with it.
34
+ */
35
+ intentId: string | null;
7
36
  eventId: string;
8
37
  eventType: WebhookEventType;
9
38
  url: string;
10
39
  attempts: number;
11
40
  delivered: boolean;
12
- lastStatus: 'delivered' | 'failed';
41
+ lastStatus: WebhookDeliveryStatus;
42
+ /**
43
+ * Why the last attempt did not succeed — operator-facing, never a secret.
44
+ * Absent while pending with no attempts, and after a success.
45
+ */
46
+ lastError?: string;
47
+ /** When the next attempt is due, ISO-8601. Absent once the delivery is terminal. */
48
+ nextAttemptAt?: string;
13
49
  createdAt: string;
14
50
  updatedAt: string;
15
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peable.to/shared-types",
3
- "version": "0.2.0",
3
+ "version": "0.3.1-oxy-one.0",
4
4
  "description": "Shared TypeScript types for the Peable Gateway — the wire contract shared by the backend, SDK, and frontend.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -8,7 +8,9 @@
8
8
  "source": "src/index.ts",
9
9
  "sideEffects": false,
10
10
  "publishConfig": {
11
- "access": "public"
11
+ "access": "public",
12
+ "registry": "https://registry.npmjs.org/",
13
+ "tag": "oxy-one"
12
14
  },
13
15
  "exports": {
14
16
  ".": {