@zkp2p/pay-shared 4.0.1 → 5.0.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/README.md +2 -2
- package/dist/addressValidators.d.ts +2 -0
- package/dist/addressValidators.js +12 -1
- package/dist/chains.js +9 -0
- package/dist/clientTelemetry.d.ts +25 -0
- package/dist/clientTelemetry.js +30 -0
- package/dist/conciergeIntake.d.ts +56 -0
- package/dist/conciergeIntake.js +15 -0
- package/dist/crypto.d.ts +4 -2
- package/dist/crypto.js +13 -0
- package/dist/fees.d.ts +2 -0
- package/dist/fees.js +10 -0
- package/dist/fiatAmount.d.ts +9 -4
- package/dist/fiatAmount.js +40 -4
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/llmIntegration.d.ts +0 -4
- package/dist/masterMerchantSubMerchants.d.ts +74 -0
- package/dist/masterMerchantSubMerchants.js +20 -0
- package/dist/onboarding.js +6 -5
- package/dist/payeeFeeCap.d.ts +10 -0
- package/dist/payeeFeeCap.js +20 -0
- package/dist/paymentRecovery.d.ts +26 -0
- package/dist/paymentRecovery.js +27 -0
- package/dist/rails.d.ts +20 -2
- package/dist/rails.js +42 -4
- package/dist/sandboxPricing.d.ts +30 -0
- package/dist/sandboxPricing.js +53 -0
- package/dist/tiers.d.ts +3 -1
- package/dist/tiers.js +4 -2
- package/dist/types.d.ts +314 -10
- package/dist/types.js +129 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Shared TypeScript types, enums, and chain utilities used by ZKP2P Pay API, SDK,
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install @zkp2p/pay-shared@
|
|
12
|
+
npm install @zkp2p/pay-shared@5.0.0
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Published artifacts
|
|
@@ -115,5 +115,5 @@ Chain constants and helpers:
|
|
|
115
115
|
## Notes
|
|
116
116
|
|
|
117
117
|
- Amount fields are strings to preserve precision across APIs and clients.
|
|
118
|
-
- Canonical order/payment amounts are available on `CheckoutOrder` and `CheckoutPayment` (for example `requestedUsdcAmount`, `remainingUsdcAmount`, `netSettledUsdcAmount`).
|
|
118
|
+
- Canonical order/payment amounts are available on `CheckoutOrder` and `CheckoutPayment` (for example `requestedUsdcAmount`, `remainingUsdcAmount`, `netSettledUsdcAmount`). `CheckoutPayment.penalties` lists attestation-applied reductions (`PaymentPenalty[]`, empty when none).
|
|
119
119
|
- This package is ESM-only.
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
/** NEAR Intents accepts mainnet transparent Zcash refund addresses only. */
|
|
2
|
+
export declare function isValidZcashAddress(value: string): boolean;
|
|
1
3
|
export declare function isValidSolanaAddress(value: string): boolean;
|
|
2
4
|
export declare function isValidTronAddress(value: string): boolean;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import { decodeBase58, encodeBase58, hasBase58CheckChecksum, } from './addressEncoding.js';
|
|
1
|
+
import { decodeBase58, doubleSha256, encodeBase58, hasBase58CheckChecksum, } from './addressEncoding.js';
|
|
2
|
+
/** NEAR Intents accepts mainnet transparent Zcash refund addresses only. */
|
|
3
|
+
export function isValidZcashAddress(value) {
|
|
4
|
+
if (!/^t[13][1-9A-HJ-NP-Za-km-z]{33}$/.test(value))
|
|
5
|
+
return false;
|
|
6
|
+
const decoded = decodeBase58(value);
|
|
7
|
+
if (decoded === null || decoded.length !== 26 || decoded[0] !== 0x1c
|
|
8
|
+
|| (decoded[1] !== 0xb8 && decoded[1] !== 0xbd))
|
|
9
|
+
return false;
|
|
10
|
+
const checksum = doubleSha256(decoded.subarray(0, 22));
|
|
11
|
+
return decoded.subarray(22).every((byte, index) => byte === checksum[index]);
|
|
12
|
+
}
|
|
2
13
|
export function isValidSolanaAddress(value) {
|
|
3
14
|
if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value)) {
|
|
4
15
|
return false;
|
package/dist/chains.js
CHANGED
|
@@ -88,6 +88,15 @@ export const MAINNET_CHAINS = {
|
|
|
88
88
|
isEvm: true,
|
|
89
89
|
explorerUrl: 'https://hyperscan.xyz',
|
|
90
90
|
},
|
|
91
|
+
5042: {
|
|
92
|
+
chainId: 5042,
|
|
93
|
+
name: 'Arc',
|
|
94
|
+
shortName: 'ARC',
|
|
95
|
+
usdcAddress: '0x3600000000000000000000000000000000000000',
|
|
96
|
+
isOriginChain: false,
|
|
97
|
+
isEvm: true,
|
|
98
|
+
explorerUrl: 'https://explorer.arc.io',
|
|
99
|
+
},
|
|
91
100
|
// Non-EVM Destination chains
|
|
92
101
|
[SOLANA_CHAIN_ID]: {
|
|
93
102
|
chainId: SOLANA_CHAIN_ID,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Shared, non-secret context for the Pay browser surfaces. */
|
|
2
|
+
export type PayClientSurface = 'checkout' | 'merchant-dashboard' | 'support-dashboard';
|
|
3
|
+
export declare function payClientContext(input: {
|
|
4
|
+
surface: PayClientSurface;
|
|
5
|
+
hostname: string;
|
|
6
|
+
productionBuild: boolean;
|
|
7
|
+
appVersion: string;
|
|
8
|
+
merchantId?: string;
|
|
9
|
+
environment?: 'LIVE' | 'SANDBOX';
|
|
10
|
+
qaRun: boolean;
|
|
11
|
+
}): {
|
|
12
|
+
merchant_id?: string | undefined;
|
|
13
|
+
product: string;
|
|
14
|
+
surface: PayClientSurface;
|
|
15
|
+
hostname: string;
|
|
16
|
+
deployment_environment: string;
|
|
17
|
+
app_version: string;
|
|
18
|
+
environment: string;
|
|
19
|
+
is_qa: boolean;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Route templates only: never emit customer IDs, search parameters or fragments. The support
|
|
23
|
+
* dashboard templates its routes with `supportTelemetryRoute` from the support contracts.
|
|
24
|
+
*/
|
|
25
|
+
export declare function payClientRoute(surface: Exclude<PayClientSurface, 'support-dashboard'>, pathname: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const PRODUCTION_HOST = {
|
|
2
|
+
checkout: 'pay.peer.xyz',
|
|
3
|
+
'merchant-dashboard': 'merchant.pay.peer.xyz',
|
|
4
|
+
'support-dashboard': 'support.pay.peer.xyz',
|
|
5
|
+
};
|
|
6
|
+
export function payClientContext(input) {
|
|
7
|
+
const productionHost = PRODUCTION_HOST[input.surface];
|
|
8
|
+
return {
|
|
9
|
+
product: 'pay',
|
|
10
|
+
surface: input.surface,
|
|
11
|
+
hostname: input.hostname,
|
|
12
|
+
deployment_environment: input.productionBuild && input.hostname === productionHost
|
|
13
|
+
? 'PRODUCTION' : input.productionBuild ? 'PREPROD' : 'DEVELOPMENT',
|
|
14
|
+
app_version: input.appVersion,
|
|
15
|
+
environment: input.environment ?? 'UNKNOWN',
|
|
16
|
+
is_qa: input.qaRun || input.merchantId === 'cmrunizzo02wf3ua8t6yx1mns',
|
|
17
|
+
...(input.merchantId ? { merchant_id: input.merchantId } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Route templates only: never emit customer IDs, search parameters or fragments. The support
|
|
22
|
+
* dashboard templates its routes with `supportTelemetryRoute` from the support contracts.
|
|
23
|
+
*/
|
|
24
|
+
export function payClientRoute(surface, pathname) {
|
|
25
|
+
const path = pathname.split(/[?#]/, 1)[0];
|
|
26
|
+
if (surface === 'checkout')
|
|
27
|
+
return path === '/' ? '/' : '/:checkout';
|
|
28
|
+
return /^\/(?:login|onboarding|orders|staking|referrals|scan-to-pay|webhooks|faq|resources|settings(?:\/(?:account|payments|checkout|developer|billing|interface|tap-to-pay))?)?$/.test(path)
|
|
29
|
+
? path : '/:unknown';
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export declare const CONCIERGE_QUESTIONS: readonly [{
|
|
2
|
+
readonly id: "business";
|
|
3
|
+
readonly label: "What do you sell, and what industry are you in?";
|
|
4
|
+
readonly hint: "Your products or services and industry.";
|
|
5
|
+
}, {
|
|
6
|
+
readonly id: "storefront";
|
|
7
|
+
readonly label: "Where do you sell?";
|
|
8
|
+
readonly hint: "Your website, plus what it runs on: Shopify, WooCommerce, custom site, Telegram, or in person.";
|
|
9
|
+
}, {
|
|
10
|
+
readonly id: "volume";
|
|
11
|
+
readonly label: "How much volume do you process today?";
|
|
12
|
+
readonly hint: "Daily, weekly, or monthly volume, plus your average order size. Include the currency.";
|
|
13
|
+
}, {
|
|
14
|
+
readonly id: "customers";
|
|
15
|
+
readonly label: "Where are your customers?";
|
|
16
|
+
readonly hint: "US, UK, EU, or elsewhere. This decides which payment apps we enable.";
|
|
17
|
+
}, {
|
|
18
|
+
readonly id: "apps";
|
|
19
|
+
readonly label: "Which payment apps do you want your customers paying with?";
|
|
20
|
+
readonly hint: "Venmo, Cash App, Zelle, PayPal, Revolut, Wise, or others.";
|
|
21
|
+
}, {
|
|
22
|
+
readonly id: "payments";
|
|
23
|
+
readonly label: "What do you use to accept payments today?";
|
|
24
|
+
readonly hint: "Your current payment providers or methods.";
|
|
25
|
+
}, {
|
|
26
|
+
readonly id: "crypto";
|
|
27
|
+
readonly label: "Do you also want to accept crypto directly?";
|
|
28
|
+
readonly hint: "Customers can pay with any coin on any network and you still settle in USDC.";
|
|
29
|
+
readonly options: readonly ["Yes", "No", "Not sure yet"];
|
|
30
|
+
}, {
|
|
31
|
+
readonly id: "feePayer";
|
|
32
|
+
readonly label: "Who pays the Peer Pay fee?";
|
|
33
|
+
readonly hint: "Your customer, you, or a split between both.";
|
|
34
|
+
readonly options: readonly ["Customer", "Merchant", "Split"];
|
|
35
|
+
}, {
|
|
36
|
+
readonly id: "integrator";
|
|
37
|
+
readonly label: "Who is doing the integration on your side, you or a developer?";
|
|
38
|
+
readonly hint: "We will send the right guide and set up an API key if needed.";
|
|
39
|
+
}];
|
|
40
|
+
export type ConciergeQuestionId = typeof CONCIERGE_QUESTIONS[number]['id'];
|
|
41
|
+
export type ConciergeQuestion = {
|
|
42
|
+
readonly id: ConciergeQuestionId;
|
|
43
|
+
readonly label: string;
|
|
44
|
+
readonly hint: string;
|
|
45
|
+
readonly options?: readonly string[];
|
|
46
|
+
};
|
|
47
|
+
export declare const CONCIERGE_QUESTION_IDS: readonly ConciergeQuestionId[];
|
|
48
|
+
export type ConciergeAnswers = Partial<Record<ConciergeQuestionId, string>>;
|
|
49
|
+
export type ConciergeInquiryInput = {
|
|
50
|
+
readonly contactEmail: string;
|
|
51
|
+
readonly businessName: string;
|
|
52
|
+
readonly telegramUsername: string;
|
|
53
|
+
readonly answers: ConciergeAnswers;
|
|
54
|
+
readonly message: string;
|
|
55
|
+
};
|
|
56
|
+
export declare function isCompleteConciergeAnswers(value: ConciergeAnswers): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const CONCIERGE_QUESTIONS = [
|
|
2
|
+
{ id: 'business', label: 'What do you sell, and what industry are you in?', hint: 'Your products or services and industry.' },
|
|
3
|
+
{ id: 'storefront', label: 'Where do you sell?', hint: 'Your website, plus what it runs on: Shopify, WooCommerce, custom site, Telegram, or in person.' },
|
|
4
|
+
{ id: 'volume', label: 'How much volume do you process today?', hint: 'Daily, weekly, or monthly volume, plus your average order size. Include the currency.' },
|
|
5
|
+
{ id: 'customers', label: 'Where are your customers?', hint: 'US, UK, EU, or elsewhere. This decides which payment apps we enable.' },
|
|
6
|
+
{ id: 'apps', label: 'Which payment apps do you want your customers paying with?', hint: 'Venmo, Cash App, Zelle, PayPal, Revolut, Wise, or others.' },
|
|
7
|
+
{ id: 'payments', label: 'What do you use to accept payments today?', hint: 'Your current payment providers or methods.' },
|
|
8
|
+
{ id: 'crypto', label: 'Do you also want to accept crypto directly?', hint: 'Customers can pay with any coin on any network and you still settle in USDC.', options: ['Yes', 'No', 'Not sure yet'] },
|
|
9
|
+
{ id: 'feePayer', label: 'Who pays the Peer Pay fee?', hint: 'Your customer, you, or a split between both.', options: ['Customer', 'Merchant', 'Split'] },
|
|
10
|
+
{ id: 'integrator', label: 'Who is doing the integration on your side, you or a developer?', hint: 'We will send the right guide and set up an API key if needed.' },
|
|
11
|
+
];
|
|
12
|
+
export const CONCIERGE_QUESTION_IDS = CONCIERGE_QUESTIONS.map(({ id }) => id);
|
|
13
|
+
export function isCompleteConciergeAnswers(value) {
|
|
14
|
+
return CONCIERGE_QUESTION_IDS.every((id) => Boolean(value[id]?.trim()));
|
|
15
|
+
}
|
package/dist/crypto.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export declare const BITCOIN_CHAIN_ID = 8253038;
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const ZCASH_CHAIN_ID = 133701;
|
|
3
|
+
export declare const ZCASH_ASSET = "nep141:zec.omft.near";
|
|
4
|
+
export declare const PAY_CRYPTO_TOKEN_SYMBOLS: readonly ["BTC", "USDC", "USDT", "ETH", "PYUSD", "SOL", "BNB", "HYPE", "WBTC", "USDH", "ZEC"];
|
|
3
5
|
export type PayCryptoTokenSymbol = (typeof PAY_CRYPTO_TOKEN_SYMBOLS)[number];
|
|
4
6
|
export declare const DEFAULT_ENABLED_PAY_CRYPTO_TOKENS: PayCryptoTokenSymbol[];
|
|
5
7
|
export declare const DEFAULT_ENABLED_DESTINATION_TOKENS: PayCryptoTokenSymbol[];
|
|
@@ -9,7 +11,7 @@ export declare const DEFAULT_ENABLED_DESTINATION_TOKENS: PayCryptoTokenSymbol[];
|
|
|
9
11
|
* iteration order of PAY_CRYPTO_CHAIN_CONFIGS as a stable tiebreaker.
|
|
10
12
|
*/
|
|
11
13
|
export declare const PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY: readonly number[];
|
|
12
|
-
export declare const STABLECOIN_SYMBOLS: Set<"USDC" | "USDT" | "ETH" | "BNB" | "SOL" | "BTC" | "PYUSD" | "HYPE" | "WBTC" | "USDH">;
|
|
14
|
+
export declare const STABLECOIN_SYMBOLS: Set<"USDC" | "USDT" | "ETH" | "BNB" | "SOL" | "BTC" | "PYUSD" | "HYPE" | "WBTC" | "USDH" | "ZEC">;
|
|
13
15
|
export interface SupportedCryptoTokenConfig {
|
|
14
16
|
chainId: number;
|
|
15
17
|
chainName: string;
|
package/dist/crypto.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { MAINNET_CHAINS, BASE_CHAIN_ID, ETHEREUM_CHAIN_ID, SOLANA_CHAIN_ID, TRON_CHAIN_ID, getChainName, } from './chains.js';
|
|
2
2
|
export const BITCOIN_CHAIN_ID = 8253038;
|
|
3
|
+
// Synthetic network ID shared with the mobile and web NEAR Intents clients.
|
|
4
|
+
export const ZCASH_CHAIN_ID = 133701;
|
|
5
|
+
export const ZCASH_ASSET = 'nep141:zec.omft.near';
|
|
3
6
|
export const PAY_CRYPTO_TOKEN_SYMBOLS = [
|
|
4
7
|
'BTC', // Kept in symbols for DB compatibility with existing sessions
|
|
5
8
|
'USDC',
|
|
@@ -11,6 +14,7 @@ export const PAY_CRYPTO_TOKEN_SYMBOLS = [
|
|
|
11
14
|
'HYPE',
|
|
12
15
|
'WBTC',
|
|
13
16
|
'USDH',
|
|
17
|
+
'ZEC',
|
|
14
18
|
];
|
|
15
19
|
export const DEFAULT_ENABLED_PAY_CRYPTO_TOKENS = [
|
|
16
20
|
'BTC',
|
|
@@ -23,6 +27,7 @@ export const DEFAULT_ENABLED_PAY_CRYPTO_TOKENS = [
|
|
|
23
27
|
'HYPE',
|
|
24
28
|
'WBTC',
|
|
25
29
|
'USDH',
|
|
30
|
+
'ZEC',
|
|
26
31
|
];
|
|
27
32
|
export const DEFAULT_ENABLED_DESTINATION_TOKENS = [
|
|
28
33
|
'USDC',
|
|
@@ -50,6 +55,7 @@ const NATIVE_SOL_ADDRESS = '11111111111111111111111111111111';
|
|
|
50
55
|
const NATIVE_BTC_ADDRESS = 'bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8';
|
|
51
56
|
const PAY_CRYPTO_CHAIN_CONFIGS = {
|
|
52
57
|
...MAINNET_CHAINS,
|
|
58
|
+
[ZCASH_CHAIN_ID]: { chainId: ZCASH_CHAIN_ID, name: 'Zcash', shortName: 'ZEC' },
|
|
53
59
|
[BITCOIN_CHAIN_ID]: {
|
|
54
60
|
chainId: BITCOIN_CHAIN_ID,
|
|
55
61
|
name: 'Bitcoin',
|
|
@@ -57,6 +63,9 @@ const PAY_CRYPTO_CHAIN_CONFIGS = {
|
|
|
57
63
|
},
|
|
58
64
|
};
|
|
59
65
|
const CHAIN_TOKEN_CONFIGS = {
|
|
66
|
+
[ZCASH_CHAIN_ID]: {
|
|
67
|
+
ZEC: { address: ZCASH_ASSET, decimals: 8, isNative: true },
|
|
68
|
+
},
|
|
60
69
|
[BITCOIN_CHAIN_ID]: {
|
|
61
70
|
BTC: { address: NATIVE_BTC_ADDRESS, decimals: 8, isNative: true },
|
|
62
71
|
},
|
|
@@ -92,6 +101,10 @@ const CHAIN_TOKEN_CONFIGS = {
|
|
|
92
101
|
USDC: { address: '0xb88339CB7199b77E23DB6E890353E22632Ba630f', decimals: 6, isNative: false },
|
|
93
102
|
USDH: { address: '0x111111a1a0667d36bd57c0a9f569b98057111111', decimals: 6, isNative: false },
|
|
94
103
|
},
|
|
104
|
+
5042: {
|
|
105
|
+
// Arc's ERC-20 USDC interface has 6 decimals; native gas uses 18.
|
|
106
|
+
USDC: { address: '0x3600000000000000000000000000000000000000', decimals: 6, isNative: false },
|
|
107
|
+
},
|
|
95
108
|
8453: {
|
|
96
109
|
ETH: { address: NATIVE_ETH_ADDRESS, decimals: 18, isNative: true },
|
|
97
110
|
USDC: { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6, isNative: false },
|
package/dist/fees.d.ts
CHANGED
|
@@ -24,4 +24,6 @@ export declare function resolveExactTokenGrossAmountUsdc(config: ReferralFeeConf
|
|
|
24
24
|
export declare function resolveMaxFeeConfig(config: MaxFeeConfig, amountUsd: string): {
|
|
25
25
|
maxFeePercentage: number;
|
|
26
26
|
};
|
|
27
|
+
/** Split endpoints use exactly the established merchant/buyer pricing paths. */
|
|
28
|
+
export declare function resolveEffectiveFeePayer(feePayer: 'MERCHANT' | 'PAYEE' | 'SPLIT', buyerFeeShareBps: number): 'MERCHANT' | 'PAYEE' | 'SPLIT';
|
|
27
29
|
export {};
|
package/dist/fees.js
CHANGED
|
@@ -193,3 +193,13 @@ export function resolveMaxFeeConfig(config, amountUsd) {
|
|
|
193
193
|
maxFeePercentage: config.tranches[matchedTrancheIndex].value,
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
|
+
/** Split endpoints use exactly the established merchant/buyer pricing paths. */
|
|
197
|
+
export function resolveEffectiveFeePayer(feePayer, buyerFeeShareBps) {
|
|
198
|
+
if (feePayer !== 'SPLIT')
|
|
199
|
+
return feePayer;
|
|
200
|
+
if (buyerFeeShareBps === 0)
|
|
201
|
+
return 'MERCHANT';
|
|
202
|
+
if (buyerFeeShareBps === 10000)
|
|
203
|
+
return 'PAYEE';
|
|
204
|
+
return 'SPLIT';
|
|
205
|
+
}
|
package/dist/fiatAmount.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export interface DisplayedFiatAmountInput {
|
|
2
|
-
readonly
|
|
2
|
+
readonly buyerFiatAmount?: string | null;
|
|
3
|
+
readonly feePayer: 'MERCHANT' | 'PAYEE' | 'SPLIT';
|
|
3
4
|
readonly signalIntentAmountBaseUnits: string | null;
|
|
4
5
|
readonly conversionRateDecimal: string | null;
|
|
5
6
|
readonly remainingUsdcAmount: string;
|
|
@@ -8,12 +9,16 @@ export interface DisplayedFiatAmountInput {
|
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
11
|
* The exact fiat amount the buyer is instructed to pay, as a decimal string.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Shared by checkout rendering and seller verification:
|
|
13
|
+
* PAYEE and SPLIT use the persisted quote, rounded upward to fiat cents.
|
|
14
|
+
* MERCHANT: remaining amount at the FX peg, or the actual settled payment amount.
|
|
14
15
|
* Always returns a string; the API caller passes it to fiatMinorUnitsFromDisplayedAmount,
|
|
15
16
|
* which returns null for an unusable "0"/malformed value so the API fails SAR closed.
|
|
16
17
|
*/
|
|
17
18
|
export declare function computeDisplayedFiatAmount(input: DisplayedFiatAmountInput): string;
|
|
18
19
|
/** Parse a 2-decimal (or fewer) positive decimal string to integer minor units. Null if non-positive/malformed/>2dp. */
|
|
19
20
|
export declare function fiatMinorUnitsFromDisplayedAmount(displayed: string): bigint | null;
|
|
21
|
+
/** Buyer/merchant share of the difference between paid value and merchant net. */
|
|
22
|
+
export declare function computeSplitFeeContribution(buyerValueUsd: string, netMerchantUsd: string, buyerFeeShareBps: number, quotedBridgeSpreadBps?: string): string;
|
|
23
|
+
/** Reads the server-owned buyer amount from a persisted quote. */
|
|
24
|
+
export declare function readQuoteBuyerFiatAmount(quote: unknown): string | undefined;
|
package/dist/fiatAmount.js
CHANGED
|
@@ -28,18 +28,29 @@ function centsToDecimalString(cents) {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* The exact fiat amount the buyer is instructed to pay, as a decimal string.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* Shared by checkout rendering and seller verification:
|
|
32
|
+
* PAYEE and SPLIT use the persisted quote, rounded upward to fiat cents.
|
|
33
|
+
* MERCHANT: remaining amount at the FX peg, or the actual settled payment amount.
|
|
34
34
|
* Always returns a string; the API caller passes it to fiatMinorUnitsFromDisplayedAmount,
|
|
35
35
|
* which returns null for an unusable "0"/malformed value so the API fails SAR closed.
|
|
36
36
|
*/
|
|
37
37
|
export function computeDisplayedFiatAmount(input) {
|
|
38
|
-
|
|
38
|
+
if (input.buyerFiatAmount != null && input.feePayer !== 'MERCHANT') {
|
|
39
|
+
const cents = fiatMinorUnitsFromDisplayedAmount(input.buyerFiatAmount);
|
|
40
|
+
if (cents === null)
|
|
41
|
+
throw new Error('Buffered quote buyer amount must be positive fiat cents');
|
|
42
|
+
return centsToDecimalString(cents);
|
|
43
|
+
}
|
|
44
|
+
const quotedCents = input.feePayer !== 'MERCHANT' && input.signalIntentAmountBaseUnits !== null && input.conversionRateDecimal !== null
|
|
39
45
|
? ceilCentsFromSignalIntent(input.signalIntentAmountBaseUnits, input.conversionRateDecimal)
|
|
40
46
|
: null;
|
|
41
47
|
if (quotedCents !== null)
|
|
42
48
|
return centsToDecimalString(quotedCents);
|
|
49
|
+
if (input.feePayer === 'SPLIT') {
|
|
50
|
+
if (Number(input.remainingUsdcAmount) <= 0)
|
|
51
|
+
return input.paymentAmountFallback;
|
|
52
|
+
throw new Error('Split pricing requires the persisted signal amount and conversion rate');
|
|
53
|
+
}
|
|
43
54
|
const remainingUsdc = Number(input.remainingUsdcAmount);
|
|
44
55
|
const rate = Number(input.currencyPerUsdRate);
|
|
45
56
|
if (Number.isFinite(remainingUsdc) && Number.isFinite(rate) && remainingUsdc > 0 && rate > 0) {
|
|
@@ -56,3 +67,28 @@ export function fiatMinorUnitsFromDisplayedAmount(displayed) {
|
|
|
56
67
|
const cents = BigInt(whole) * CENT_SCALE + BigInt(fraction.padEnd(2, '0'));
|
|
57
68
|
return cents > 0n ? cents : null;
|
|
58
69
|
}
|
|
70
|
+
/** Buyer/merchant share of the difference between paid value and merchant net. */
|
|
71
|
+
export function computeSplitFeeContribution(buyerValueUsd, netMerchantUsd, buyerFeeShareBps, quotedBridgeSpreadBps = '0') {
|
|
72
|
+
const paid = parseDecimalToScaledUnits(buyerValueUsd, 6);
|
|
73
|
+
const net = parseDecimalToScaledUnits(netMerchantUsd, 6);
|
|
74
|
+
if (paid === null || net === null)
|
|
75
|
+
throw new Error('Split fee contribution requires nonnegative decimal amounts');
|
|
76
|
+
const bridge = parseDecimalToScaledUnits(quotedBridgeSpreadBps, 18);
|
|
77
|
+
if (bridge === null)
|
|
78
|
+
throw new Error('Quoted bridge spread must be a nonnegative decimal');
|
|
79
|
+
const bridgeDenominator = 10000n * RATE_SCALE + bridge;
|
|
80
|
+
const units = (paid * BigInt(10000 - buyerFeeShareBps) * bridgeDenominator
|
|
81
|
+
+ net * BigInt(buyerFeeShareBps) * 10000n * RATE_SCALE) / (10000n * bridgeDenominator);
|
|
82
|
+
return `${units / USDC_SCALE}.${(units % USDC_SCALE).toString().padStart(6, '0')}`;
|
|
83
|
+
}
|
|
84
|
+
/** Reads the server-owned buyer amount from a persisted quote. */
|
|
85
|
+
export function readQuoteBuyerFiatAmount(quote) {
|
|
86
|
+
if (typeof quote !== 'object' || quote === null || !('amountAdjustment' in quote))
|
|
87
|
+
return undefined;
|
|
88
|
+
const adjustment = quote.amountAdjustment;
|
|
89
|
+
if (typeof adjustment !== 'object' || adjustment === null || !('buyerFiatAmount' in adjustment)
|
|
90
|
+
|| typeof adjustment.buyerFiatAmount !== 'string') {
|
|
91
|
+
throw new Error('Buffered quote is missing its buyer fiat amount');
|
|
92
|
+
}
|
|
93
|
+
return adjustment.buyerFiatAmount;
|
|
94
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
|
+
export * from './paymentRecovery.js';
|
|
2
3
|
export * from './chains.js';
|
|
3
4
|
export * from './fees.js';
|
|
4
5
|
export * from './crypto.js';
|
|
@@ -8,10 +9,14 @@ export * from './bitcoin.js';
|
|
|
8
9
|
export * from './addressValidators.js';
|
|
9
10
|
export { doubleSha256, encodeBase58 } from './addressEncoding.js';
|
|
10
11
|
export * from './fiatAmount.js';
|
|
12
|
+
export * from './sandboxPricing.js';
|
|
11
13
|
export * from './paypalSarPhase.js';
|
|
12
14
|
export * from './paypalSarVerify.js';
|
|
13
15
|
export * from './payeeFeeCap.js';
|
|
14
16
|
export * from './onboarding.js';
|
|
15
17
|
export * from './llmIntegration.js';
|
|
16
18
|
export * from './tiers.js';
|
|
19
|
+
export * from './masterMerchantSubMerchants.js';
|
|
17
20
|
export * from './attribution.js';
|
|
21
|
+
export * from './conciergeIntake.js';
|
|
22
|
+
export * from './clientTelemetry.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
|
+
export * from './paymentRecovery.js';
|
|
2
3
|
export * from './chains.js';
|
|
3
4
|
export * from './fees.js';
|
|
4
5
|
export * from './crypto.js';
|
|
@@ -8,10 +9,14 @@ export * from './bitcoin.js';
|
|
|
8
9
|
export * from './addressValidators.js';
|
|
9
10
|
export { doubleSha256, encodeBase58 } from './addressEncoding.js';
|
|
10
11
|
export * from './fiatAmount.js';
|
|
12
|
+
export * from './sandboxPricing.js';
|
|
11
13
|
export * from './paypalSarPhase.js';
|
|
12
14
|
export * from './paypalSarVerify.js';
|
|
13
15
|
export * from './payeeFeeCap.js';
|
|
14
16
|
export * from './onboarding.js';
|
|
15
17
|
export * from './llmIntegration.js';
|
|
16
18
|
export * from './tiers.js';
|
|
19
|
+
export * from './masterMerchantSubMerchants.js';
|
|
17
20
|
export * from './attribution.js';
|
|
21
|
+
export * from './conciergeIntake.js';
|
|
22
|
+
export * from './clientTelemetry.js';
|
package/dist/llmIntegration.d.ts
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** The highest master merchant fee in bps, the referral-split schema's per-entry and total ceiling. */
|
|
2
|
+
export declare const MASTER_MERCHANT_FEE_MAX_BPS = 5000;
|
|
3
|
+
/** A Master Merchant Account sub merchant's ownership as its Master Merchant Account sees it. PR 5 reports only OWNED_BY_MASTER_MERCHANT; PR 6 fills the others. */
|
|
4
|
+
export declare const SubMerchantTransferState: {
|
|
5
|
+
readonly OWNED_BY_MASTER_MERCHANT: "OWNED_BY_MASTER_MERCHANT";
|
|
6
|
+
readonly PENDING: "PENDING";
|
|
7
|
+
readonly TRANSFERRED: "TRANSFERRED";
|
|
8
|
+
};
|
|
9
|
+
export type SubMerchantTransferStateType = typeof SubMerchantTransferState[keyof typeof SubMerchantTransferState];
|
|
10
|
+
export type SubMerchantTransfer = {
|
|
11
|
+
state: typeof SubMerchantTransferState.OWNED_BY_MASTER_MERCHANT;
|
|
12
|
+
} | {
|
|
13
|
+
state: typeof SubMerchantTransferState.PENDING;
|
|
14
|
+
recipientEmail: string;
|
|
15
|
+
expiresAt: string;
|
|
16
|
+
} | {
|
|
17
|
+
state: typeof SubMerchantTransferState.TRANSFERRED;
|
|
18
|
+
recipientEmail: string;
|
|
19
|
+
acceptedAt: string;
|
|
20
|
+
};
|
|
21
|
+
/** One row of GET /merchants/me/sub-merchants, also the POST response. */
|
|
22
|
+
export type SubMerchantSummary = {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
masterMerchantFeeBps: number;
|
|
27
|
+
ownerEmail: string;
|
|
28
|
+
transfer: SubMerchantTransfer;
|
|
29
|
+
};
|
|
30
|
+
export type SubMerchantListResponse = {
|
|
31
|
+
subMerchants: SubMerchantSummary[];
|
|
32
|
+
};
|
|
33
|
+
export type CreateSubMerchantRequest = {
|
|
34
|
+
name: string;
|
|
35
|
+
masterMerchantFeeBps: number;
|
|
36
|
+
};
|
|
37
|
+
export type SetMasterMerchantFeeRequest = {
|
|
38
|
+
masterMerchantFeeBps: number;
|
|
39
|
+
};
|
|
40
|
+
export type MasterMerchantFeeResult = {
|
|
41
|
+
subMerchantId: string;
|
|
42
|
+
masterMerchantFeeBps: number;
|
|
43
|
+
};
|
|
44
|
+
export type BpsRange = {
|
|
45
|
+
min: number;
|
|
46
|
+
max: number;
|
|
47
|
+
};
|
|
48
|
+
/** Configured fees for one enabled rail: Peer's platform fee across its tranches, and that plus the master merchant fee. */
|
|
49
|
+
export type MasterMerchantFeePreviewRail = {
|
|
50
|
+
rail: string;
|
|
51
|
+
platformFeeBps: BpsRange;
|
|
52
|
+
totalFeeBps: BpsRange;
|
|
53
|
+
};
|
|
54
|
+
/** The first amount band (inclusive USD bounds; null max = open-ended) where configured fees exceed the max fee. */
|
|
55
|
+
export type MasterMerchantMaxFeeViolation = {
|
|
56
|
+
rail: string;
|
|
57
|
+
minAmountUsd: string;
|
|
58
|
+
maxAmountUsd: string | null;
|
|
59
|
+
platformFeeBps: number;
|
|
60
|
+
splitFeeBps: number;
|
|
61
|
+
maxFeePercentage: number;
|
|
62
|
+
};
|
|
63
|
+
export type MasterMerchantFeePreviewResponse = {
|
|
64
|
+
masterMerchantFeeBps: number;
|
|
65
|
+
/** Null means no Master Merchant Account-specific cap. */
|
|
66
|
+
masterMerchantMaxFeeBps: number | null;
|
|
67
|
+
rails: MasterMerchantFeePreviewRail[];
|
|
68
|
+
maxFeeViolation: MasterMerchantMaxFeeViolation | null;
|
|
69
|
+
};
|
|
70
|
+
export declare const MASTER_MERCHANT_NOT_ENABLED_MESSAGE = "This merchant is not enabled as a Master Merchant Account";
|
|
71
|
+
export declare const MASTER_MERCHANT_FEE_LOCKED_TRANSFERRED_MESSAGE = "This sub merchant now belongs to someone else; only an admin can change its master merchant fee";
|
|
72
|
+
export declare const MASTER_MERCHANT_FEE_LOCKED_PENDING_MESSAGE = "Cancel the pending ownership transfer before changing the master merchant fee";
|
|
73
|
+
export declare function formatMasterMerchantFeeExceedsCapMessage(masterMerchantFeeBps: number, masterMerchantMaxFeeBps: number): string;
|
|
74
|
+
export declare function formatMasterMerchantMaxFeeViolationMessage(violation: MasterMerchantMaxFeeViolation): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** The highest master merchant fee in bps, the referral-split schema's per-entry and total ceiling. */
|
|
2
|
+
export const MASTER_MERCHANT_FEE_MAX_BPS = 5000;
|
|
3
|
+
/** A Master Merchant Account sub merchant's ownership as its Master Merchant Account sees it. PR 5 reports only OWNED_BY_MASTER_MERCHANT; PR 6 fills the others. */
|
|
4
|
+
export const SubMerchantTransferState = {
|
|
5
|
+
OWNED_BY_MASTER_MERCHANT: 'OWNED_BY_MASTER_MERCHANT',
|
|
6
|
+
PENDING: 'PENDING',
|
|
7
|
+
TRANSFERRED: 'TRANSFERRED',
|
|
8
|
+
};
|
|
9
|
+
export const MASTER_MERCHANT_NOT_ENABLED_MESSAGE = 'This merchant is not enabled as a Master Merchant Account';
|
|
10
|
+
export const MASTER_MERCHANT_FEE_LOCKED_TRANSFERRED_MESSAGE = 'This sub merchant now belongs to someone else; only an admin can change its master merchant fee';
|
|
11
|
+
export const MASTER_MERCHANT_FEE_LOCKED_PENDING_MESSAGE = 'Cancel the pending ownership transfer before changing the master merchant fee';
|
|
12
|
+
export function formatMasterMerchantFeeExceedsCapMessage(masterMerchantFeeBps, masterMerchantMaxFeeBps) {
|
|
13
|
+
return `Master merchant fee ${masterMerchantFeeBps} bps exceeds the ${masterMerchantMaxFeeBps} bps cap`;
|
|
14
|
+
}
|
|
15
|
+
export function formatMasterMerchantMaxFeeViolationMessage(violation) {
|
|
16
|
+
const band = violation.maxAmountUsd === null
|
|
17
|
+
? `$${violation.minAmountUsd} and above`
|
|
18
|
+
: `$${violation.minAmountUsd}–$${violation.maxAmountUsd}`;
|
|
19
|
+
return `Configured fees on ${violation.rail} for orders of ${band} would be ${violation.platformFeeBps + violation.splitFeeBps} bps, above the ${violation.maxFeePercentage}% max fee`;
|
|
20
|
+
}
|
package/dist/onboarding.js
CHANGED
|
@@ -70,10 +70,11 @@ export const ONBOARDING_STEP_REGISTRY = [
|
|
|
70
70
|
{
|
|
71
71
|
id: OnboardingStepId.RAILS,
|
|
72
72
|
paths: 'ALL',
|
|
73
|
-
|
|
73
|
+
// Merchants start with default rails, so there is nothing to observe: the step is done once the
|
|
74
|
+
// merchant confirms their payment methods. The dashboard wizard acknowledges it after saving them.
|
|
75
|
+
verifiedBy: OnboardingStepVerifiedBy.MANUAL,
|
|
74
76
|
inChecklist: false,
|
|
75
|
-
//
|
|
76
|
-
// it to imply earlier steps would mark every merchant's onboarding complete.
|
|
77
|
+
// Confirming payment methods says nothing about the profile or industry.
|
|
77
78
|
impliesPrevious: false,
|
|
78
79
|
},
|
|
79
80
|
{ id: OnboardingStepId.PATH, paths: 'ALL', verifiedBy: OnboardingStepVerifiedBy.AUTO, inChecklist: true, impliesPrevious: true },
|
|
@@ -158,11 +159,11 @@ export const ONBOARDING_STEP_REGISTRY = [
|
|
|
158
159
|
},
|
|
159
160
|
];
|
|
160
161
|
const autoStepPredicates = {
|
|
161
|
-
|
|
162
|
+
// The logo is optional: the wizard acknowledges the profile once the merchant confirms a name.
|
|
163
|
+
[OnboardingStepId.PROFILE]: (facts) => facts.hasName && (facts.hasLogo || facts.manualSteps.includes(OnboardingStepId.PROFILE)),
|
|
162
164
|
[OnboardingStepId.INDUSTRY]: (facts) => facts.hasIndustry,
|
|
163
165
|
[OnboardingStepId.PLAN]: (facts) => facts.hasSelectedTier,
|
|
164
166
|
[OnboardingStepId.PATH]: (facts) => facts.hasIntegrationPath,
|
|
165
|
-
[OnboardingStepId.RAILS]: () => true,
|
|
166
167
|
[OnboardingStepId.INTEGRATION]: (facts) => facts.hasSandboxFulfilledOrder,
|
|
167
168
|
[OnboardingStepId.WEBHOOK]: (facts) => facts.hasWebhook,
|
|
168
169
|
[OnboardingStepId.TEST_ORDER]: (facts) => facts.hasSandboxFulfilledOrder,
|
package/dist/payeeFeeCap.d.ts
CHANGED
|
@@ -18,4 +18,14 @@ export declare function resolvePayeeReferralFeeCapacity(input: {
|
|
|
18
18
|
readonly platformRecipient: string;
|
|
19
19
|
readonly referralFees: readonly PayeeFeeEntry[];
|
|
20
20
|
}): PayeeReferralFeeCapacityResult;
|
|
21
|
+
export declare class QuoteAmountCapacityError extends RangeError {
|
|
22
|
+
}
|
|
23
|
+
/** Preserve the full-size quote's net settlement by reducing only Peer's fee. */
|
|
24
|
+
export declare function resolveQuoteAmountFeeCapacity(input: {
|
|
25
|
+
readonly requestedSignalAmountRaw: string;
|
|
26
|
+
readonly grossSignalAmountRaw: string;
|
|
27
|
+
readonly quotedTokenOutputAmountRaw: string;
|
|
28
|
+
readonly platformRecipient: string;
|
|
29
|
+
readonly referralFees: readonly PayeeFeeEntry[];
|
|
30
|
+
}): PayeeFeeEntry[];
|
|
21
31
|
export declare function computeNetAfterPreciseFees(grossAmount: string, feeRates18dec: readonly string[]): string;
|
package/dist/payeeFeeCap.js
CHANGED
|
@@ -50,6 +50,26 @@ export function resolvePayeeReferralFeeCapacity(input) {
|
|
|
50
50
|
requiredNetCovered,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
+
export class QuoteAmountCapacityError extends RangeError {
|
|
54
|
+
}
|
|
55
|
+
/** Preserve the full-size quote's net settlement by reducing only Peer's fee. */
|
|
56
|
+
export function resolveQuoteAmountFeeCapacity(input) {
|
|
57
|
+
const requested = BigInt(input.requestedSignalAmountRaw);
|
|
58
|
+
const gross = BigInt(input.grossSignalAmountRaw);
|
|
59
|
+
const output = BigInt(input.quotedTokenOutputAmountRaw);
|
|
60
|
+
const nominalFees = input.referralFees.reduce((total, fee) => total + requested * BigInt(fee.feeBps) / BPS_DENOMINATOR, 0n);
|
|
61
|
+
const capacity = resolvePayeeReferralFeeCapacity({
|
|
62
|
+
grossSignalAmount: gross.toString(),
|
|
63
|
+
quotedTokenOutput: output.toString(),
|
|
64
|
+
requiredNetAmount: (output + requested - gross - nominalFees).toString(),
|
|
65
|
+
platformRecipient: input.platformRecipient,
|
|
66
|
+
referralFees: input.referralFees,
|
|
67
|
+
});
|
|
68
|
+
if (capacity.insufficientCapacity || !capacity.requiredNetCovered) {
|
|
69
|
+
throw new QuoteAmountCapacityError('Insufficient Peer fee capacity for quote amount shortfall');
|
|
70
|
+
}
|
|
71
|
+
return capacity.referralFees;
|
|
72
|
+
}
|
|
53
73
|
export function computeNetAfterPreciseFees(grossAmount, feeRates18dec) {
|
|
54
74
|
const gross = parsePositiveUnits(grossAmount, 'gross amount');
|
|
55
75
|
const totalFees = feeRates18dec.reduce((total, feeRate) => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Customer-safe history. No quote/proof payloads or executable payment instructions. */
|
|
2
|
+
export type PaymentRecoveryAttempt = {
|
|
3
|
+
id: string;
|
|
4
|
+
rail: string;
|
|
5
|
+
currency: string;
|
|
6
|
+
amount: string | null;
|
|
7
|
+
recipient: string;
|
|
8
|
+
status: string;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
expiresAt: string;
|
|
11
|
+
};
|
|
12
|
+
export type PaymentRecoveryHistory = {
|
|
13
|
+
attempts: PaymentRecoveryAttempt[];
|
|
14
|
+
nextCursor: string | null;
|
|
15
|
+
};
|
|
16
|
+
/** Fiat payTo is a protocol hash; the saved quote owns the readable recipient. */
|
|
17
|
+
export declare function paymentRecoveryRecipient(payment: {
|
|
18
|
+
payTo: string;
|
|
19
|
+
quote: unknown;
|
|
20
|
+
}): string;
|
|
21
|
+
/** Old attempts without a snapshot have no reliable historical requested amount. */
|
|
22
|
+
export declare function paymentRecoveryAmount(payment: {
|
|
23
|
+
status: string;
|
|
24
|
+
paymentAmount: string;
|
|
25
|
+
quote: unknown;
|
|
26
|
+
}): string | null;
|