@peable.to/shared-types 0.2.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/checkoutSession.js +2 -0
- package/dist/cjs/event.js +2 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/merchant.js +4 -0
- package/dist/cjs/merchantDisplay.js +2 -0
- package/dist/cjs/money.js +21 -0
- package/dist/cjs/network.js +2 -0
- package/dist/cjs/paymentIntent.js +31 -0
- package/dist/cjs/paymentLink.js +2 -0
- package/dist/cjs/social.js +5 -0
- package/dist/cjs/webhookDelivery.js +2 -0
- package/dist/cjs/webhookSigner.js +68 -0
- package/dist/esm/checkoutSession.js +1 -0
- package/dist/esm/event.js +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/merchant.js +1 -0
- package/dist/esm/merchantDisplay.js +1 -0
- package/dist/esm/money.js +17 -0
- package/dist/esm/network.js +1 -0
- package/dist/esm/paymentIntent.js +27 -0
- package/dist/esm/paymentLink.js +1 -0
- package/dist/esm/social.js +4 -0
- package/dist/esm/webhookDelivery.js +1 -0
- package/dist/esm/webhookSigner.js +64 -0
- package/dist/types/checkoutSession.d.ts +41 -0
- package/dist/types/event.d.ts +11 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/merchant.d.ts +15 -0
- package/dist/types/merchantDisplay.d.ts +7 -0
- package/dist/types/money.d.ts +7 -0
- package/dist/types/network.d.ts +1 -0
- package/dist/types/paymentIntent.d.ts +32 -0
- package/dist/types/paymentLink.d.ts +33 -0
- package/dist/types/social.d.ts +50 -0
- package/dist/types/webhookDelivery.d.ts +15 -0
- package/dist/types/webhookSigner.d.ts +12 -0
- package/package.json +65 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
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;
|
|
4
|
+
// @peable.to/shared-types — public API for the Peable Gateway contract.
|
|
5
|
+
var money_1 = require("./money");
|
|
6
|
+
Object.defineProperty(exports, "UNITS_PER_COIN", { enumerable: true, get: function () { return money_1.UNITS_PER_COIN; } });
|
|
7
|
+
Object.defineProperty(exports, "isBaseUnitString", { enumerable: true, get: function () { return money_1.isBaseUnitString; } });
|
|
8
|
+
var paymentIntent_1 = require("./paymentIntent");
|
|
9
|
+
Object.defineProperty(exports, "isValidStatusTransition", { enumerable: true, get: function () { return paymentIntent_1.isValidStatusTransition; } });
|
|
10
|
+
Object.defineProperty(exports, "PAYMENT_INTENT_STATUSES", { enumerable: true, get: function () { return paymentIntent_1.PAYMENT_INTENT_STATUSES; } });
|
|
11
|
+
var webhookSigner_1 = require("./webhookSigner");
|
|
12
|
+
Object.defineProperty(exports, "signWebhook", { enumerable: true, get: function () { return webhookSigner_1.signWebhook; } });
|
|
13
|
+
Object.defineProperty(exports, "verifyWebhook", { enumerable: true, get: function () { return webhookSigner_1.verifyWebhook; } });
|
|
14
|
+
var merchant_1 = require("./merchant");
|
|
15
|
+
Object.defineProperty(exports, "MERCHANT_ENVIRONMENTS", { enumerable: true, get: function () { return merchant_1.MERCHANT_ENVIRONMENTS; } });
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Amount helpers over FairCoin base units (m⊜). One coin is UNITS_PER_COIN
|
|
3
|
+
// base units. Amounts move through the system as bigint conceptually; the
|
|
4
|
+
// PaymentIntent DTO stores them as a base-unit integer string so no float ever
|
|
5
|
+
// touches a monetary value.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.UNITS_PER_COIN = void 0;
|
|
8
|
+
exports.isBaseUnitString = isBaseUnitString;
|
|
9
|
+
// Frozen FairCoin consensus constant (smallest units per FAIR). Mirrors
|
|
10
|
+
// @fairco.in/core's branding.ts; inlined here (rather than imported) to keep
|
|
11
|
+
// @peable.to/shared-types zero-runtime-dep for a clean CJS+ESM publish — must
|
|
12
|
+
// stay a bigint, never converted to number.
|
|
13
|
+
exports.UNITS_PER_COIN = 100000000n;
|
|
14
|
+
/**
|
|
15
|
+
* True only for a canonical non-negative integer string in base units:
|
|
16
|
+
* one or more decimal digits, no sign, no decimal point, no leading zeros
|
|
17
|
+
* (except the literal "0"). Floats and negatives are rejected.
|
|
18
|
+
*/
|
|
19
|
+
function isBaseUnitString(s) {
|
|
20
|
+
return /^(0|[1-9][0-9]*)$/.test(s);
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PAYMENT_INTENT_STATUSES = void 0;
|
|
4
|
+
exports.isValidStatusTransition = isValidStatusTransition;
|
|
5
|
+
/**
|
|
6
|
+
* Legal forward transitions of the PaymentIntent lifecycle. Terminal states
|
|
7
|
+
* (settled/expired/failed/rejected) allow no further transition here — the one
|
|
8
|
+
* documented exception, a reorg dropping `settled` back to `confirming`, is
|
|
9
|
+
* modelled by the backend state machine, not this happy-path table.
|
|
10
|
+
*/
|
|
11
|
+
const ALLOWED = {
|
|
12
|
+
// `created` supports two paths: the rich wallet flow (→ awaiting_approval →
|
|
13
|
+
// approved → broadcast) AND the minimal path where the payer pays out-of-band
|
|
14
|
+
// and reports the txid directly (→ broadcast), or the merchant cancels an
|
|
15
|
+
// unpaid intent (→ rejected). Both are legal forward transitions.
|
|
16
|
+
created: ['awaiting_approval', 'broadcast', 'rejected', 'expired'],
|
|
17
|
+
awaiting_approval: ['approved', 'rejected', 'expired'],
|
|
18
|
+
approved: ['broadcast', 'failed'],
|
|
19
|
+
broadcast: ['confirming', 'failed'],
|
|
20
|
+
confirming: ['settled', 'failed'],
|
|
21
|
+
settled: [],
|
|
22
|
+
expired: [],
|
|
23
|
+
failed: [],
|
|
24
|
+
rejected: [],
|
|
25
|
+
};
|
|
26
|
+
function isValidStatusTransition(from, to) {
|
|
27
|
+
return ALLOWED[from].includes(to);
|
|
28
|
+
}
|
|
29
|
+
/** Every legal `PaymentIntentStatus`, derived from `ALLOWED`'s keys — the
|
|
30
|
+
* single source of truth consumed by the Gateway's list-route status filter. */
|
|
31
|
+
exports.PAYMENT_INTENT_STATUSES = Object.keys(ALLOWED);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Social-receive + transaction-enrichment contracts — shared by the Peable
|
|
3
|
+
// Gateway backend, the wallet frontend, and (indirectly) the enrichment
|
|
4
|
+
// service's callers. Mirrors the Stripe-parity style of paymentIntent.ts.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.signWebhook = signWebhook;
|
|
4
|
+
exports.verifyWebhook = verifyWebhook;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
/**
|
|
7
|
+
* Compute the hex HMAC-SHA256 of the signed payload `${timestamp}.${rawBody}`.
|
|
8
|
+
* The signed payload binds the timestamp to the body so a captured signature
|
|
9
|
+
* cannot be replayed against a different body or a shifted timestamp.
|
|
10
|
+
*/
|
|
11
|
+
function computeSignature(secret, rawBody, timestamp) {
|
|
12
|
+
return (0, node_crypto_1.createHmac)('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Produce a Stripe-style signature header for an outbound webhook:
|
|
16
|
+
* `t=${timestamp},v1=${hexHmacSha256}`.
|
|
17
|
+
*/
|
|
18
|
+
function signWebhook(secret, rawBody, timestamp) {
|
|
19
|
+
return `t=${timestamp},v1=${computeSignature(secret, rawBody, timestamp)}`;
|
|
20
|
+
}
|
|
21
|
+
function parseHeader(header) {
|
|
22
|
+
let timestamp = null;
|
|
23
|
+
let signature = null;
|
|
24
|
+
for (const part of header.split(',')) {
|
|
25
|
+
const separator = part.indexOf('=');
|
|
26
|
+
if (separator === -1)
|
|
27
|
+
continue;
|
|
28
|
+
const key = part.slice(0, separator);
|
|
29
|
+
const value = part.slice(separator + 1);
|
|
30
|
+
if (key === 't') {
|
|
31
|
+
const parsed = Number(value);
|
|
32
|
+
if (Number.isInteger(parsed))
|
|
33
|
+
timestamp = parsed;
|
|
34
|
+
}
|
|
35
|
+
else if (key === 'v1') {
|
|
36
|
+
signature = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (timestamp === null || signature === null)
|
|
40
|
+
return null;
|
|
41
|
+
return { timestamp, signature };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Constant-time hex-string comparison. Returns false for unequal-length inputs
|
|
45
|
+
* (timingSafeEqual throws on mismatched buffer lengths) instead of throwing.
|
|
46
|
+
*/
|
|
47
|
+
function constantTimeEqualHex(a, b) {
|
|
48
|
+
const bufferA = Buffer.from(a, 'hex');
|
|
49
|
+
const bufferB = Buffer.from(b, 'hex');
|
|
50
|
+
if (bufferA.length === 0 || bufferA.length !== bufferB.length)
|
|
51
|
+
return false;
|
|
52
|
+
return (0, node_crypto_1.timingSafeEqual)(bufferA, bufferB);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Verify a webhook signature header against the raw body.
|
|
56
|
+
* - Parses `t` and `v1` from the header.
|
|
57
|
+
* - Rejects if the timestamp drifts more than `toleranceSec` from `nowSec`.
|
|
58
|
+
* - Recomputes the expected signature and compares it in constant time.
|
|
59
|
+
*/
|
|
60
|
+
function verifyWebhook(secret, rawBody, header, toleranceSec, nowSec) {
|
|
61
|
+
const parsed = parseHeader(header);
|
|
62
|
+
if (parsed === null)
|
|
63
|
+
return false;
|
|
64
|
+
if (Math.abs(nowSec - parsed.timestamp) > toleranceSec)
|
|
65
|
+
return false;
|
|
66
|
+
const expected = computeSignature(secret, rawBody, parsed.timestamp);
|
|
67
|
+
return constantTimeEqualHex(expected, parsed.signature);
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
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';
|
|
4
|
+
export { signWebhook, verifyWebhook } from './webhookSigner.js';
|
|
5
|
+
export { MERCHANT_ENVIRONMENTS, } from './merchant.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MERCHANT_ENVIRONMENTS = ['development', 'staging', 'production'];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Amount helpers over FairCoin base units (m⊜). One coin is UNITS_PER_COIN
|
|
2
|
+
// base units. Amounts move through the system as bigint conceptually; the
|
|
3
|
+
// PaymentIntent DTO stores them as a base-unit integer string so no float ever
|
|
4
|
+
// touches a monetary value.
|
|
5
|
+
// Frozen FairCoin consensus constant (smallest units per FAIR). Mirrors
|
|
6
|
+
// @fairco.in/core's branding.ts; inlined here (rather than imported) to keep
|
|
7
|
+
// @peable.to/shared-types zero-runtime-dep for a clean CJS+ESM publish — must
|
|
8
|
+
// stay a bigint, never converted to number.
|
|
9
|
+
export const UNITS_PER_COIN = 100000000n;
|
|
10
|
+
/**
|
|
11
|
+
* True only for a canonical non-negative integer string in base units:
|
|
12
|
+
* one or more decimal digits, no sign, no decimal point, no leading zeros
|
|
13
|
+
* (except the literal "0"). Floats and negatives are rejected.
|
|
14
|
+
*/
|
|
15
|
+
export function isBaseUnitString(s) {
|
|
16
|
+
return /^(0|[1-9][0-9]*)$/.test(s);
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legal forward transitions of the PaymentIntent lifecycle. Terminal states
|
|
3
|
+
* (settled/expired/failed/rejected) allow no further transition here — the one
|
|
4
|
+
* documented exception, a reorg dropping `settled` back to `confirming`, is
|
|
5
|
+
* modelled by the backend state machine, not this happy-path table.
|
|
6
|
+
*/
|
|
7
|
+
const ALLOWED = {
|
|
8
|
+
// `created` supports two paths: the rich wallet flow (→ awaiting_approval →
|
|
9
|
+
// approved → broadcast) AND the minimal path where the payer pays out-of-band
|
|
10
|
+
// and reports the txid directly (→ broadcast), or the merchant cancels an
|
|
11
|
+
// unpaid intent (→ rejected). Both are legal forward transitions.
|
|
12
|
+
created: ['awaiting_approval', 'broadcast', 'rejected', 'expired'],
|
|
13
|
+
awaiting_approval: ['approved', 'rejected', 'expired'],
|
|
14
|
+
approved: ['broadcast', 'failed'],
|
|
15
|
+
broadcast: ['confirming', 'failed'],
|
|
16
|
+
confirming: ['settled', 'failed'],
|
|
17
|
+
settled: [],
|
|
18
|
+
expired: [],
|
|
19
|
+
failed: [],
|
|
20
|
+
rejected: [],
|
|
21
|
+
};
|
|
22
|
+
export function isValidStatusTransition(from, to) {
|
|
23
|
+
return ALLOWED[from].includes(to);
|
|
24
|
+
}
|
|
25
|
+
/** Every legal `PaymentIntentStatus`, derived from `ALLOWED`'s keys — the
|
|
26
|
+
* single source of truth consumed by the Gateway's list-route status filter. */
|
|
27
|
+
export const PAYMENT_INTENT_STATUSES = Object.keys(ALLOWED);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Compute the hex HMAC-SHA256 of the signed payload `${timestamp}.${rawBody}`.
|
|
4
|
+
* The signed payload binds the timestamp to the body so a captured signature
|
|
5
|
+
* cannot be replayed against a different body or a shifted timestamp.
|
|
6
|
+
*/
|
|
7
|
+
function computeSignature(secret, rawBody, timestamp) {
|
|
8
|
+
return createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Produce a Stripe-style signature header for an outbound webhook:
|
|
12
|
+
* `t=${timestamp},v1=${hexHmacSha256}`.
|
|
13
|
+
*/
|
|
14
|
+
export function signWebhook(secret, rawBody, timestamp) {
|
|
15
|
+
return `t=${timestamp},v1=${computeSignature(secret, rawBody, timestamp)}`;
|
|
16
|
+
}
|
|
17
|
+
function parseHeader(header) {
|
|
18
|
+
let timestamp = null;
|
|
19
|
+
let signature = null;
|
|
20
|
+
for (const part of header.split(',')) {
|
|
21
|
+
const separator = part.indexOf('=');
|
|
22
|
+
if (separator === -1)
|
|
23
|
+
continue;
|
|
24
|
+
const key = part.slice(0, separator);
|
|
25
|
+
const value = part.slice(separator + 1);
|
|
26
|
+
if (key === 't') {
|
|
27
|
+
const parsed = Number(value);
|
|
28
|
+
if (Number.isInteger(parsed))
|
|
29
|
+
timestamp = parsed;
|
|
30
|
+
}
|
|
31
|
+
else if (key === 'v1') {
|
|
32
|
+
signature = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (timestamp === null || signature === null)
|
|
36
|
+
return null;
|
|
37
|
+
return { timestamp, signature };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Constant-time hex-string comparison. Returns false for unequal-length inputs
|
|
41
|
+
* (timingSafeEqual throws on mismatched buffer lengths) instead of throwing.
|
|
42
|
+
*/
|
|
43
|
+
function constantTimeEqualHex(a, b) {
|
|
44
|
+
const bufferA = Buffer.from(a, 'hex');
|
|
45
|
+
const bufferB = Buffer.from(b, 'hex');
|
|
46
|
+
if (bufferA.length === 0 || bufferA.length !== bufferB.length)
|
|
47
|
+
return false;
|
|
48
|
+
return timingSafeEqual(bufferA, bufferB);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Verify a webhook signature header against the raw body.
|
|
52
|
+
* - Parses `t` and `v1` from the header.
|
|
53
|
+
* - Rejects if the timestamp drifts more than `toleranceSec` from `nowSec`.
|
|
54
|
+
* - Recomputes the expected signature and compares it in constant time.
|
|
55
|
+
*/
|
|
56
|
+
export function verifyWebhook(secret, rawBody, header, toleranceSec, nowSec) {
|
|
57
|
+
const parsed = parseHeader(header);
|
|
58
|
+
if (parsed === null)
|
|
59
|
+
return false;
|
|
60
|
+
if (Math.abs(nowSec - parsed.timestamp) > toleranceSec)
|
|
61
|
+
return false;
|
|
62
|
+
const expected = computeSignature(secret, rawBody, parsed.timestamp);
|
|
63
|
+
return constantTimeEqualHex(expected, parsed.signature);
|
|
64
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { NetworkType } from './network';
|
|
2
|
+
import type { MerchantDisplay } from './merchantDisplay';
|
|
3
|
+
import type { PaymentIntent } from './paymentIntent';
|
|
4
|
+
/** The merchant/SDK-facing DTO — returned on create and by merchant retrieve. */
|
|
5
|
+
export interface CheckoutSession {
|
|
6
|
+
id: string;
|
|
7
|
+
object: 'checkout_session';
|
|
8
|
+
paymentIntentId: string;
|
|
9
|
+
/** The wrapped intent's `client_secret`, returned to the merchant on create. */
|
|
10
|
+
clientSecret: string;
|
|
11
|
+
amount: string;
|
|
12
|
+
network: NetworkType;
|
|
13
|
+
metadata: Record<string, string>;
|
|
14
|
+
successUrl?: string;
|
|
15
|
+
cancelUrl?: string;
|
|
16
|
+
/** Canonical `checkout.peable.to/c/<id>` URL. */
|
|
17
|
+
url: string;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* What the checkout page loads at `/c/:id`, authorized by possession of the
|
|
23
|
+
* wrapped intent's `client_secret` (never a service token). `paymentIntent`
|
|
24
|
+
* reuses `toPaymentIntentDTO`'s output unchanged — it already carries
|
|
25
|
+
* `clientSecret`, which the page needs to open the socket and to `submit_tx`.
|
|
26
|
+
*/
|
|
27
|
+
export interface CheckoutSessionPublic {
|
|
28
|
+
id: string;
|
|
29
|
+
object: 'checkout_session';
|
|
30
|
+
successUrl?: string;
|
|
31
|
+
cancelUrl?: string;
|
|
32
|
+
merchant: MerchantDisplay;
|
|
33
|
+
paymentIntent: PaymentIntent;
|
|
34
|
+
}
|
|
35
|
+
export interface CreateCheckoutSessionParams {
|
|
36
|
+
amount: string;
|
|
37
|
+
network: NetworkType;
|
|
38
|
+
metadata?: Record<string, string>;
|
|
39
|
+
successUrl?: string;
|
|
40
|
+
cancelUrl?: string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PaymentIntent } from './paymentIntent';
|
|
2
|
+
export type WebhookEventType = 'payment_intent.confirming' | 'payment_intent.settled' | 'payment_intent.failed' | 'payment_intent.rejected' | 'payment_intent.expired';
|
|
3
|
+
export interface WebhookEvent<T = PaymentIntent> {
|
|
4
|
+
id: string;
|
|
5
|
+
object: 'event';
|
|
6
|
+
type: WebhookEventType;
|
|
7
|
+
created: string;
|
|
8
|
+
data: {
|
|
9
|
+
object: T;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { UNITS_PER_COIN, isBaseUnitString } from './money';
|
|
2
|
+
export { type NetworkType } from './network';
|
|
3
|
+
export { type PaymentIntentStatus, type PaymentIntent, type CreatePaymentIntentParams, isValidStatusTransition, PAYMENT_INTENT_STATUSES, } from './paymentIntent';
|
|
4
|
+
export { type WebhookEventType, type WebhookEvent } from './event';
|
|
5
|
+
export { type WebhookDelivery } from './webhookDelivery';
|
|
6
|
+
export { signWebhook, verifyWebhook } from './webhookSigner';
|
|
7
|
+
export { type MerchantEnvironment, type Merchant, MERCHANT_ENVIRONMENTS, } from './merchant';
|
|
8
|
+
export { type MerchantDisplay } from './merchantDisplay';
|
|
9
|
+
export { type PaymentLink, type PublicPaymentLink, type CreatePaymentLinkParams, } from './paymentLink';
|
|
10
|
+
export { type CheckoutSession, type CheckoutSessionPublic, type CreateCheckoutSessionParams, } from './checkoutSession';
|
|
11
|
+
export { type SocialNextAddressResponse, type SocialReceiveCursorResponse, type EnrichmentKind, type EnrichmentResult, type EnrichRequest, type EnrichResponse, } from './social';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NetworkType } from './network';
|
|
2
|
+
export declare const MERCHANT_ENVIRONMENTS: readonly ["development", "staging", "production"];
|
|
3
|
+
export type MerchantEnvironment = (typeof MERCHANT_ENVIRONMENTS)[number];
|
|
4
|
+
export interface Merchant {
|
|
5
|
+
id: string;
|
|
6
|
+
object: 'merchant';
|
|
7
|
+
oxyAppId: string;
|
|
8
|
+
environment: MerchantEnvironment;
|
|
9
|
+
network: NetworkType;
|
|
10
|
+
xpub: string;
|
|
11
|
+
webhookUrl?: string;
|
|
12
|
+
requiredConfirmations: number;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface MerchantDisplay {
|
|
2
|
+
/** `Merchant.displayName`, or a neutral fallback when unset. */
|
|
3
|
+
name: string;
|
|
4
|
+
/** Resolved server-side via the canonical media chokepoint; `null` when the merchant has no avatar. */
|
|
5
|
+
avatarUrl: string | null;
|
|
6
|
+
description: string | null;
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const UNITS_PER_COIN = 100000000n;
|
|
2
|
+
/**
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isBaseUnitString(s: string): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type NetworkType = 'mainnet' | 'testnet';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { NetworkType } from './network';
|
|
2
|
+
export type PaymentIntentStatus = 'created' | 'awaiting_approval' | 'approved' | 'broadcast' | 'confirming' | 'settled' | 'expired' | 'failed' | 'rejected';
|
|
3
|
+
export declare function isValidStatusTransition(from: PaymentIntentStatus, to: PaymentIntentStatus): boolean;
|
|
4
|
+
/** Every legal `PaymentIntentStatus`, derived from `ALLOWED`'s keys — the
|
|
5
|
+
* single source of truth consumed by the Gateway's list-route status filter. */
|
|
6
|
+
export declare const PAYMENT_INTENT_STATUSES: readonly PaymentIntentStatus[];
|
|
7
|
+
export interface PaymentIntent {
|
|
8
|
+
id: string;
|
|
9
|
+
object: 'payment_intent';
|
|
10
|
+
status: PaymentIntentStatus;
|
|
11
|
+
/** Amount in base units (m⊜) as a canonical integer string. Never a float. */
|
|
12
|
+
amount: string;
|
|
13
|
+
currency: 'FAIR';
|
|
14
|
+
network: NetworkType;
|
|
15
|
+
/** Watch-only receive address derived per-intent from the merchant xpub. */
|
|
16
|
+
address: string;
|
|
17
|
+
merchantId: string;
|
|
18
|
+
txid: string | null;
|
|
19
|
+
confirmations: number;
|
|
20
|
+
clientSecret: string;
|
|
21
|
+
metadata: Record<string, string>;
|
|
22
|
+
expiresAt: string;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
updatedAt: string;
|
|
25
|
+
}
|
|
26
|
+
export interface CreatePaymentIntentParams {
|
|
27
|
+
/** Amount in base units (m⊜) as a canonical integer string. */
|
|
28
|
+
amount: string;
|
|
29
|
+
network: NetworkType;
|
|
30
|
+
metadata?: Record<string, string>;
|
|
31
|
+
expiresInSeconds?: number;
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { NetworkType } from './network';
|
|
2
|
+
import type { MerchantDisplay } from './merchantDisplay';
|
|
3
|
+
/** The merchant-facing DTO — returned by the merchant-authed CRUD routes. */
|
|
4
|
+
export interface PaymentLink {
|
|
5
|
+
id: string;
|
|
6
|
+
object: 'payment_link';
|
|
7
|
+
/** Amount in base units (m⊜) as a canonical integer string. Never a float. */
|
|
8
|
+
amount: string;
|
|
9
|
+
network: NetworkType;
|
|
10
|
+
active: boolean;
|
|
11
|
+
metadata: Record<string, string>;
|
|
12
|
+
/** Optional post-payment redirect target for the checkout page. */
|
|
13
|
+
successUrl?: string;
|
|
14
|
+
/** Canonical `checkout.peable.to/l/<id>` URL. */
|
|
15
|
+
url: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
/** What an unauthenticated checkout-page visitor may see — no secrets, no metadata. */
|
|
20
|
+
export interface PublicPaymentLink {
|
|
21
|
+
id: string;
|
|
22
|
+
object: 'payment_link';
|
|
23
|
+
amount: string;
|
|
24
|
+
network: NetworkType;
|
|
25
|
+
active: boolean;
|
|
26
|
+
merchant: MerchantDisplay;
|
|
27
|
+
}
|
|
28
|
+
export interface CreatePaymentLinkParams {
|
|
29
|
+
amount: string;
|
|
30
|
+
network: NetworkType;
|
|
31
|
+
metadata?: Record<string, string>;
|
|
32
|
+
successUrl?: string;
|
|
33
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Response of `POST /v1/social/:username/next_address` (spec §4.4 step 3). */
|
|
2
|
+
export interface SocialNextAddressResponse {
|
|
3
|
+
address: string;
|
|
4
|
+
index: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Response of `GET /v1/social/me/cursor` — lets the authenticated user's
|
|
8
|
+
* device resync its social-receive watch window against the backend's
|
|
9
|
+
* reservation cursor. `reserveNextSocialAddress` advances a MONOTONIC index
|
|
10
|
+
* on every `next_address` call regardless of whether the reserved address is
|
|
11
|
+
* ever paid, but a receiving device only widens its watch window when a
|
|
12
|
+
* payment lands on an index it already watches — so a burst of reservations
|
|
13
|
+
* with no payment in between (griefing, or a payer browsing/re-picking a
|
|
14
|
+
* recipient) can silently outrun the device. Calling this endpoint tells the
|
|
15
|
+
* device how far to widen: watch `0..reservedThrough+gap`.
|
|
16
|
+
*/
|
|
17
|
+
export interface SocialReceiveCursorResponse {
|
|
18
|
+
/**
|
|
19
|
+
* Highest social-receive index the backend has EVER reserved for the
|
|
20
|
+
* caller, for the queried network. `0` when the caller has never had an
|
|
21
|
+
* address reserved (no cursor exists yet) — mirrors
|
|
22
|
+
* `SOCIAL_RECEIVE_FIRST_FRESH_INDEX - 1`, since index 0 itself is the
|
|
23
|
+
* caller's stable default address and is never reserved through this flow.
|
|
24
|
+
*/
|
|
25
|
+
reservedThrough: number;
|
|
26
|
+
}
|
|
27
|
+
/** Where a transaction's counterparty identity came from (spec §4.8). */
|
|
28
|
+
export type EnrichmentKind = 'merchant' | 'user' | 'unknown';
|
|
29
|
+
/**
|
|
30
|
+
* Display-only counterparty identity for one address/txid, resolved by
|
|
31
|
+
* `POST /v1/enrich`. Never affects custody; a failed/partial resolution
|
|
32
|
+
* degrades to `{ kind: 'unknown' }`.
|
|
33
|
+
*/
|
|
34
|
+
export interface EnrichmentResult {
|
|
35
|
+
kind: EnrichmentKind;
|
|
36
|
+
/** Merchant name or user's `name.displayName ?? handle`. */
|
|
37
|
+
displayName?: string;
|
|
38
|
+
/** Bare Oxy file id — render via the canonical media chokepoint, never a URL. */
|
|
39
|
+
avatarFileId?: string;
|
|
40
|
+
/** Present for `kind: 'user'` only. */
|
|
41
|
+
username?: string;
|
|
42
|
+
/** Present for `kind: 'merchant'` only. */
|
|
43
|
+
description?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface EnrichRequest {
|
|
46
|
+
addresses: string[];
|
|
47
|
+
}
|
|
48
|
+
export interface EnrichResponse {
|
|
49
|
+
data: Record<string, EnrichmentResult>;
|
|
50
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { WebhookEventType } from './event';
|
|
2
|
+
export interface WebhookDelivery {
|
|
3
|
+
id: string;
|
|
4
|
+
object: 'webhook_delivery';
|
|
5
|
+
merchantId: string;
|
|
6
|
+
intentId: string;
|
|
7
|
+
eventId: string;
|
|
8
|
+
eventType: WebhookEventType;
|
|
9
|
+
url: string;
|
|
10
|
+
attempts: number;
|
|
11
|
+
delivered: boolean;
|
|
12
|
+
lastStatus: 'delivered' | 'failed';
|
|
13
|
+
createdAt: string;
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Produce a Stripe-style signature header for an outbound webhook:
|
|
3
|
+
* `t=${timestamp},v1=${hexHmacSha256}`.
|
|
4
|
+
*/
|
|
5
|
+
export declare function signWebhook(secret: string, rawBody: string, timestamp: number): string;
|
|
6
|
+
/**
|
|
7
|
+
* Verify a webhook signature header against the raw body.
|
|
8
|
+
* - Parses `t` and `v1` from the header.
|
|
9
|
+
* - Rejects if the timestamp drifts more than `toleranceSec` from `nowSec`.
|
|
10
|
+
* - Recomputes the expected signature and compares it in constant time.
|
|
11
|
+
*/
|
|
12
|
+
export declare function verifyWebhook(secret: string, rawBody: string, header: string, toleranceSec: number, nowSec: number): boolean;
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@peable.to/shared-types",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shared TypeScript types for the Peable Gateway — the wire contract shared by the backend, SDK, and frontend.",
|
|
5
|
+
"main": "dist/cjs/index.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
|
+
"source": "src/index.ts",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"react-native": "./dist/esm/index.js",
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/types/index.d.ts",
|
|
18
|
+
"default": "./dist/esm/index.js"
|
|
19
|
+
},
|
|
20
|
+
"require": {
|
|
21
|
+
"types": "./dist/types/index.d.ts",
|
|
22
|
+
"default": "./dist/cjs/index.js"
|
|
23
|
+
},
|
|
24
|
+
"default": "./dist/esm/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"keywords": [
|
|
32
|
+
"peable",
|
|
33
|
+
"gateway",
|
|
34
|
+
"payments",
|
|
35
|
+
"types"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/OxyHQ/Peable.git",
|
|
40
|
+
"directory": "packages/shared-types"
|
|
41
|
+
},
|
|
42
|
+
"author": "Peable",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"homepage": "https://peable.to",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "bun run build:cjs && bun run build:esm && bun run build:types",
|
|
50
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
51
|
+
"build:esm": "tsc -p tsconfig.esm.json && node scripts/fix-esm-imports.mjs",
|
|
52
|
+
"build:types": "tsc -p tsconfig.types.json",
|
|
53
|
+
"clean": "rm -rf dist",
|
|
54
|
+
"dev": "tsc --watch",
|
|
55
|
+
"lint": "tsc --noEmit",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"test": "bun test",
|
|
58
|
+
"prepublishOnly": "bun run clean && bun run build"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"typescript": "~5.9.2",
|
|
62
|
+
"@types/node": "^20.0.0",
|
|
63
|
+
"@types/bun": "latest"
|
|
64
|
+
}
|
|
65
|
+
}
|