@zkp2p/pay-shared 1.0.0 → 2.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/buyerTee.d.ts +4 -0
- package/dist/buyerTee.js +3 -0
- package/dist/chains.d.ts +1 -0
- package/dist/chains.js +5 -0
- package/dist/crypto.d.ts +31 -0
- package/dist/crypto.js +68 -3
- package/dist/fiatAmount.d.ts +19 -0
- package/dist/fiatAmount.js +58 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/paypalSarPhase.d.ts +5 -0
- package/dist/paypalSarPhase.js +8 -0
- package/dist/paypalSarVerify.d.ts +41 -0
- package/dist/paypalSarVerify.js +31 -0
- package/dist/rails.d.ts +76 -0
- package/dist/rails.js +182 -6
- package/dist/types.d.ts +78 -3
- package/dist/types.js +36 -0
- package/package.json +5 -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@2.0.0
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Published artifacts
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
|
|
67
67
|
const chainId = parseSupportedChainSelection('polygon'); // 137
|
|
68
68
|
const usdc = resolveDestinationTokenAddress('USDC', 137); // Chain-specific USDC address
|
|
69
|
-
const supportedAliases = getSupportedDestinationTokenAliasesForChain(BASE_CHAIN_ID); // ["USDC"]
|
|
69
|
+
const supportedAliases = getSupportedDestinationTokenAliasesForChain(BASE_CHAIN_ID); // ["USDC", "USDT"]
|
|
70
70
|
|
|
71
71
|
console.log(getSupportedChainIds(), chainId, usdc, supportedAliases);
|
|
72
72
|
```
|
package/dist/buyerTee.js
ADDED
package/dist/chains.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Import this module instead of duplicating chain configs in each app.
|
|
6
6
|
*/
|
|
7
7
|
export declare const BASE_CHAIN_ID = 8453;
|
|
8
|
+
export declare const ETHEREUM_CHAIN_ID = 1;
|
|
8
9
|
export declare const HYPEREVM_CHAIN_ID = 999;
|
|
9
10
|
export declare const SOLANA_CHAIN_ID = 792703809;
|
|
10
11
|
export declare const SUPPORTED_DESTINATION_TOKEN_ALIASES: readonly ["USDC", "USDT"];
|
package/dist/chains.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
// Chain IDs
|
|
8
8
|
export const BASE_CHAIN_ID = 8453;
|
|
9
|
+
export const ETHEREUM_CHAIN_ID = 1;
|
|
9
10
|
export const HYPEREVM_CHAIN_ID = 999;
|
|
10
11
|
export const SOLANA_CHAIN_ID = 792703809;
|
|
11
12
|
export const SUPPORTED_DESTINATION_TOKEN_ALIASES = ['USDC', 'USDT'];
|
|
@@ -135,6 +136,10 @@ for (const chainId of SUPPORTED_CHAIN_IDS) {
|
|
|
135
136
|
}
|
|
136
137
|
}
|
|
137
138
|
const DESTINATION_TOKEN_ADDRESSES = {
|
|
139
|
+
// Base
|
|
140
|
+
8453: {
|
|
141
|
+
usdt: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2',
|
|
142
|
+
},
|
|
138
143
|
// Ethereum
|
|
139
144
|
1: {
|
|
140
145
|
usdt: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
package/dist/crypto.d.ts
CHANGED
|
@@ -4,6 +4,12 @@ export declare const PAY_CRYPTO_TOKEN_SYMBOLS: readonly ["BTC", "USDC", "USDT",
|
|
|
4
4
|
export type PayCryptoTokenSymbol = (typeof PAY_CRYPTO_TOKEN_SYMBOLS)[number];
|
|
5
5
|
export declare const DEFAULT_ENABLED_PAY_CRYPTO_TOKENS: PayCryptoTokenSymbol[];
|
|
6
6
|
export declare const DEFAULT_ENABLED_DESTINATION_TOKENS: PayCryptoTokenSymbol[];
|
|
7
|
+
/**
|
|
8
|
+
* Display-order priority for the "Pay with crypto" chain dropdown.
|
|
9
|
+
* Chains not in this list fall back to the existing numeric chain-ID
|
|
10
|
+
* iteration order of PAY_CRYPTO_CHAIN_CONFIGS as a stable tiebreaker.
|
|
11
|
+
*/
|
|
12
|
+
export declare const PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY: readonly number[];
|
|
7
13
|
export declare const STABLECOIN_SYMBOLS: Set<"USDC" | "USDT" | "ETH" | "BNB" | "SOL" | "BTC" | "PYUSD" | "HYPE" | "WBTC" | "USDH">;
|
|
8
14
|
export interface SupportedCryptoTokenConfig {
|
|
9
15
|
chainId: number;
|
|
@@ -18,6 +24,31 @@ export declare function normalizePayCryptoTokenSymbol(value: string | null | und
|
|
|
18
24
|
export declare function isStablecoinSymbol(symbol: string | null | undefined): boolean;
|
|
19
25
|
export declare function getSupportedPayCryptoTokenConfigsForChain(chainId: number, enabledSymbols?: readonly string[] | null): SupportedCryptoTokenConfig[];
|
|
20
26
|
export declare function getSupportedPayCryptoChainIds(enabledSymbols?: readonly string[] | null): number[];
|
|
27
|
+
/**
|
|
28
|
+
* Returns the union of tokens supported by any of `enabledChainIds`,
|
|
29
|
+
* sorted by the canonical pay-crypto display order
|
|
30
|
+
* (DEFAULT_ENABLED_PAY_CRYPTO_TOKENS order).
|
|
31
|
+
*
|
|
32
|
+
* When `enabledChainIds` is undefined, uses every supported chain.
|
|
33
|
+
* When `enabledChainIds` is an empty array, returns [].
|
|
34
|
+
*/
|
|
35
|
+
export declare function getAvailablePayCryptoTokens(enabledChainIds?: readonly number[]): PayCryptoTokenSymbol[];
|
|
36
|
+
/**
|
|
37
|
+
* Returns the chain IDs that support `symbol`, optionally filtered
|
|
38
|
+
* to `enabledChainIds`, sorted by PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY
|
|
39
|
+
* with ascending numeric chain-ID order as the stable tiebreaker.
|
|
40
|
+
*
|
|
41
|
+
* When `enabledChainIds` is undefined, uses every supported chain.
|
|
42
|
+
* When `enabledChainIds` is an empty array, returns [].
|
|
43
|
+
*/
|
|
44
|
+
export declare function getSupportedPayCryptoChainIdsForToken(symbol: PayCryptoTokenSymbol, enabledChainIds?: readonly number[]): number[];
|
|
45
|
+
/**
|
|
46
|
+
* Returns the display name for a chain used by the "Pay with crypto"
|
|
47
|
+
* flow. Resolves through PAY_CRYPTO_CHAIN_CONFIGS first so Bitcoin
|
|
48
|
+
* and Tron (which are not in MAINNET_CHAINS) get the correct label.
|
|
49
|
+
* Falls back to getChainName for chains not registered here.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getPayCryptoChainName(chainId: number): string;
|
|
21
52
|
export declare function resolvePayCryptoTokenConfig(chainId: number, symbol: string | null | undefined): SupportedCryptoTokenConfig | null;
|
|
22
53
|
/**
|
|
23
54
|
* Reverse lookup of a token config by contract address on a specific chain.
|
package/dist/crypto.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MAINNET_CHAINS, } from './chains.js';
|
|
1
|
+
import { MAINNET_CHAINS, BASE_CHAIN_ID, ETHEREUM_CHAIN_ID, SOLANA_CHAIN_ID, getChainName, } from './chains.js';
|
|
2
2
|
export const BITCOIN_CHAIN_ID = 8253038;
|
|
3
3
|
export const TRON_CHAIN_ID = 728126428;
|
|
4
4
|
export const PAY_CRYPTO_TOKEN_SYMBOLS = [
|
|
@@ -15,9 +15,9 @@ export const PAY_CRYPTO_TOKEN_SYMBOLS = [
|
|
|
15
15
|
];
|
|
16
16
|
export const DEFAULT_ENABLED_PAY_CRYPTO_TOKENS = [
|
|
17
17
|
'BTC',
|
|
18
|
-
'USDC',
|
|
19
|
-
'USDT',
|
|
20
18
|
'ETH',
|
|
19
|
+
'USDT',
|
|
20
|
+
'USDC',
|
|
21
21
|
'PYUSD',
|
|
22
22
|
'SOL',
|
|
23
23
|
'BNB',
|
|
@@ -29,6 +29,17 @@ export const DEFAULT_ENABLED_DESTINATION_TOKENS = [
|
|
|
29
29
|
'USDC',
|
|
30
30
|
'USDT',
|
|
31
31
|
];
|
|
32
|
+
/**
|
|
33
|
+
* Display-order priority for the "Pay with crypto" chain dropdown.
|
|
34
|
+
* Chains not in this list fall back to the existing numeric chain-ID
|
|
35
|
+
* iteration order of PAY_CRYPTO_CHAIN_CONFIGS as a stable tiebreaker.
|
|
36
|
+
*/
|
|
37
|
+
export const PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY = [
|
|
38
|
+
BITCOIN_CHAIN_ID,
|
|
39
|
+
ETHEREUM_CHAIN_ID,
|
|
40
|
+
SOLANA_CHAIN_ID,
|
|
41
|
+
BASE_CHAIN_ID,
|
|
42
|
+
];
|
|
32
43
|
export const STABLECOIN_SYMBOLS = new Set([
|
|
33
44
|
'USDC',
|
|
34
45
|
'USDT',
|
|
@@ -160,6 +171,60 @@ export function getSupportedPayCryptoChainIds(enabledSymbols) {
|
|
|
160
171
|
.map(Number)
|
|
161
172
|
.filter((chainId) => getSupportedPayCryptoTokenConfigsForChain(chainId, enabledSymbols).length > 0);
|
|
162
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Returns the union of tokens supported by any of `enabledChainIds`,
|
|
176
|
+
* sorted by the canonical pay-crypto display order
|
|
177
|
+
* (DEFAULT_ENABLED_PAY_CRYPTO_TOKENS order).
|
|
178
|
+
*
|
|
179
|
+
* When `enabledChainIds` is undefined, uses every supported chain.
|
|
180
|
+
* When `enabledChainIds` is an empty array, returns [].
|
|
181
|
+
*/
|
|
182
|
+
export function getAvailablePayCryptoTokens(enabledChainIds) {
|
|
183
|
+
const chainIds = enabledChainIds === undefined
|
|
184
|
+
? Object.keys(CHAIN_TOKEN_CONFIGS).map(Number)
|
|
185
|
+
: enabledChainIds;
|
|
186
|
+
const available = new Set();
|
|
187
|
+
for (const chainId of chainIds) {
|
|
188
|
+
const chainTokens = CHAIN_TOKEN_CONFIGS[chainId];
|
|
189
|
+
if (chainTokens === undefined)
|
|
190
|
+
continue;
|
|
191
|
+
for (const symbol of Object.keys(chainTokens)) {
|
|
192
|
+
available.add(symbol);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return DEFAULT_ENABLED_PAY_CRYPTO_TOKENS.filter((symbol) => available.has(symbol));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Returns the chain IDs that support `symbol`, optionally filtered
|
|
199
|
+
* to `enabledChainIds`, sorted by PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY
|
|
200
|
+
* with ascending numeric chain-ID order as the stable tiebreaker.
|
|
201
|
+
*
|
|
202
|
+
* When `enabledChainIds` is undefined, uses every supported chain.
|
|
203
|
+
* When `enabledChainIds` is an empty array, returns [].
|
|
204
|
+
*/
|
|
205
|
+
export function getSupportedPayCryptoChainIdsForToken(symbol, enabledChainIds) {
|
|
206
|
+
const candidateChainIds = enabledChainIds === undefined
|
|
207
|
+
? Object.keys(CHAIN_TOKEN_CONFIGS).map(Number)
|
|
208
|
+
: Array.from(new Set(enabledChainIds));
|
|
209
|
+
const supporting = candidateChainIds.filter((chainId) => (CHAIN_TOKEN_CONFIGS[chainId]?.[symbol] !== undefined));
|
|
210
|
+
const supportingSet = new Set(supporting);
|
|
211
|
+
const priorityPortion = PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY
|
|
212
|
+
.filter((chainId) => supportingSet.has(chainId));
|
|
213
|
+
const prioritySet = new Set(PAY_CRYPTO_CHAIN_DISPLAY_PRIORITY);
|
|
214
|
+
const restPortion = supporting
|
|
215
|
+
.filter((chainId) => !prioritySet.has(chainId))
|
|
216
|
+
.sort((a, b) => a - b);
|
|
217
|
+
return [...priorityPortion, ...restPortion];
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Returns the display name for a chain used by the "Pay with crypto"
|
|
221
|
+
* flow. Resolves through PAY_CRYPTO_CHAIN_CONFIGS first so Bitcoin
|
|
222
|
+
* and Tron (which are not in MAINNET_CHAINS) get the correct label.
|
|
223
|
+
* Falls back to getChainName for chains not registered here.
|
|
224
|
+
*/
|
|
225
|
+
export function getPayCryptoChainName(chainId) {
|
|
226
|
+
return PAY_CRYPTO_CHAIN_CONFIGS[chainId]?.name ?? getChainName(chainId);
|
|
227
|
+
}
|
|
163
228
|
export function resolvePayCryptoTokenConfig(chainId, symbol) {
|
|
164
229
|
const normalizedSymbol = normalizePayCryptoTokenSymbol(symbol);
|
|
165
230
|
if (!normalizedSymbol) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface DisplayedFiatAmountInput {
|
|
2
|
+
readonly feePayer: 'MERCHANT' | 'PAYEE';
|
|
3
|
+
readonly signalIntentAmountBaseUnits: string | null;
|
|
4
|
+
readonly conversionRateDecimal: string | null;
|
|
5
|
+
readonly remainingUsdcAmount: string;
|
|
6
|
+
readonly currencyPerUsdRate: string;
|
|
7
|
+
readonly paymentAmountFallback: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The exact fiat amount the buyer is instructed to pay, as a decimal string.
|
|
11
|
+
* BYTE-IDENTICAL to checkoutData.ts mapCheckoutPaymentToQuote (lines 336-351):
|
|
12
|
+
* quotedFiatAmount (PAYEE ceil-cents, else null)
|
|
13
|
+
* ?? (validRemaining&Rate ? (remaining*rate).toFixed(2) : paymentAmount)
|
|
14
|
+
* Always returns a string; the API caller passes it to fiatMinorUnitsFromDisplayedAmount,
|
|
15
|
+
* which returns null for an unusable "0"/malformed value so the API fails SAR closed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function computeDisplayedFiatAmount(input: DisplayedFiatAmountInput): string;
|
|
18
|
+
/** Parse a 2-decimal (or fewer) positive decimal string to integer minor units. Null if non-positive/malformed/>2dp. */
|
|
19
|
+
export declare function fiatMinorUnitsFromDisplayedAmount(displayed: string): bigint | null;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const USDC_SCALE = 1000000n;
|
|
2
|
+
const RATE_SCALE = 10n ** 18n;
|
|
3
|
+
const CENT_SCALE = 100n;
|
|
4
|
+
const QUOTE_DENOMINATOR = USDC_SCALE * RATE_SCALE; // 1e6 * 1e18
|
|
5
|
+
function parseDecimalToScaledUnits(value, decimals) {
|
|
6
|
+
const normalized = value.trim();
|
|
7
|
+
if (!/^\d+(\.\d+)?$/.test(normalized))
|
|
8
|
+
return null;
|
|
9
|
+
const [whole, fraction = ''] = normalized.split('.');
|
|
10
|
+
const base = 10n ** BigInt(decimals);
|
|
11
|
+
const scaledFraction = fraction.slice(0, decimals).padEnd(decimals, '0');
|
|
12
|
+
return BigInt(whole) * base + BigInt(scaledFraction);
|
|
13
|
+
}
|
|
14
|
+
function ceilCentsFromSignalIntent(amountBaseUnits, conversionRateDecimal) {
|
|
15
|
+
if (!/^\d+$/.test(amountBaseUnits))
|
|
16
|
+
return null;
|
|
17
|
+
const units = BigInt(amountBaseUnits);
|
|
18
|
+
const rate = parseDecimalToScaledUnits(conversionRateDecimal, 18);
|
|
19
|
+
if (rate === null || rate <= 0n || units <= 0n)
|
|
20
|
+
return null;
|
|
21
|
+
const numerator = units * rate * CENT_SCALE;
|
|
22
|
+
return (numerator + QUOTE_DENOMINATOR - 1n) / QUOTE_DENOMINATOR; // ceil
|
|
23
|
+
}
|
|
24
|
+
function centsToDecimalString(cents) {
|
|
25
|
+
const whole = cents / CENT_SCALE;
|
|
26
|
+
const fraction = (cents % CENT_SCALE).toString().padStart(2, '0');
|
|
27
|
+
return `${whole.toString()}.${fraction}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The exact fiat amount the buyer is instructed to pay, as a decimal string.
|
|
31
|
+
* BYTE-IDENTICAL to checkoutData.ts mapCheckoutPaymentToQuote (lines 336-351):
|
|
32
|
+
* quotedFiatAmount (PAYEE ceil-cents, else null)
|
|
33
|
+
* ?? (validRemaining&Rate ? (remaining*rate).toFixed(2) : paymentAmount)
|
|
34
|
+
* Always returns a string; the API caller passes it to fiatMinorUnitsFromDisplayedAmount,
|
|
35
|
+
* which returns null for an unusable "0"/malformed value so the API fails SAR closed.
|
|
36
|
+
*/
|
|
37
|
+
export function computeDisplayedFiatAmount(input) {
|
|
38
|
+
const quotedCents = input.feePayer === 'PAYEE' && input.signalIntentAmountBaseUnits !== null && input.conversionRateDecimal !== null
|
|
39
|
+
? ceilCentsFromSignalIntent(input.signalIntentAmountBaseUnits, input.conversionRateDecimal)
|
|
40
|
+
: null;
|
|
41
|
+
if (quotedCents !== null)
|
|
42
|
+
return centsToDecimalString(quotedCents);
|
|
43
|
+
const remainingUsdc = Number(input.remainingUsdcAmount);
|
|
44
|
+
const rate = Number(input.currencyPerUsdRate);
|
|
45
|
+
if (Number.isFinite(remainingUsdc) && Number.isFinite(rate) && remainingUsdc > 0 && rate > 0) {
|
|
46
|
+
return (remainingUsdc * rate).toFixed(2);
|
|
47
|
+
}
|
|
48
|
+
return input.paymentAmountFallback;
|
|
49
|
+
}
|
|
50
|
+
/** Parse a 2-decimal (or fewer) positive decimal string to integer minor units. Null if non-positive/malformed/>2dp. */
|
|
51
|
+
export function fiatMinorUnitsFromDisplayedAmount(displayed) {
|
|
52
|
+
const normalized = displayed.trim();
|
|
53
|
+
if (!/^\d+(\.\d{1,2})?$/.test(normalized))
|
|
54
|
+
return null;
|
|
55
|
+
const [whole, fraction = ''] = normalized.split('.');
|
|
56
|
+
const cents = BigInt(whole) * CENT_SCALE + BigInt(fraction.padEnd(2, '0'));
|
|
57
|
+
return cents > 0n ? cents : null;
|
|
58
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,4 +3,8 @@ export * from './chains.js';
|
|
|
3
3
|
export * from './fees.js';
|
|
4
4
|
export * from './crypto.js';
|
|
5
5
|
export * from './rails.js';
|
|
6
|
+
export * from './buyerTee.js';
|
|
6
7
|
export * from './bitcoin.js';
|
|
8
|
+
export * from './fiatAmount.js';
|
|
9
|
+
export * from './paypalSarPhase.js';
|
|
10
|
+
export * from './paypalSarVerify.js';
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,8 @@ export * from './chains.js';
|
|
|
3
3
|
export * from './fees.js';
|
|
4
4
|
export * from './crypto.js';
|
|
5
5
|
export * from './rails.js';
|
|
6
|
+
export * from './buyerTee.js';
|
|
6
7
|
export * from './bitcoin.js';
|
|
8
|
+
export * from './fiatAmount.js';
|
|
9
|
+
export * from './paypalSarPhase.js';
|
|
10
|
+
export * from './paypalSarVerify.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a name-mode SAR verify poll outcome is worth retrying. Only a "no receipt found yet" (404)
|
|
3
|
+
* may resolve on a later poll; every other terminal status is deterministic and must fail fast.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isRetryableSarPollStatus(statusCode: number): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const NO_RECEIPT_STATUS = 404; // SELLER_TXID_NOT_FOUND — the only status worth re-polling
|
|
2
|
+
/**
|
|
3
|
+
* Whether a name-mode SAR verify poll outcome is worth retrying. Only a "no receipt found yet" (404)
|
|
4
|
+
* may resolve on a later poll; every other terminal status is deterministic and must fail fast.
|
|
5
|
+
*/
|
|
6
|
+
export function isRetryableSarPollStatus(statusCode) {
|
|
7
|
+
return statusCode === NO_RECEIPT_STATUS;
|
|
8
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type SarAttemptOutcome = {
|
|
2
|
+
kind: 'success';
|
|
3
|
+
proofAttemptId: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'unavailable';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'error';
|
|
8
|
+
errorCode: string;
|
|
9
|
+
errorMessage: string;
|
|
10
|
+
statusCode: number;
|
|
11
|
+
};
|
|
12
|
+
export type SarVerifyResult = {
|
|
13
|
+
kind: 'success';
|
|
14
|
+
proofAttemptId: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'unavailable';
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'error';
|
|
19
|
+
errorCode: string;
|
|
20
|
+
errorMessage: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'aborted';
|
|
23
|
+
};
|
|
24
|
+
export interface RunPaypalSarNameVerifyDeps {
|
|
25
|
+
/** POST a name-mode attempt. attemptNumber is 1-based and increments per name poll. */
|
|
26
|
+
submitNameAttempt: (attemptNumber: number) => Promise<SarAttemptOutcome>;
|
|
27
|
+
sleep: (ms: number) => Promise<void>;
|
|
28
|
+
/** Re-checked after every await; returning false short-circuits to { kind: 'aborted' }. */
|
|
29
|
+
isMounted: () => boolean;
|
|
30
|
+
now: () => number;
|
|
31
|
+
retryIntervalMs: number;
|
|
32
|
+
retryDurationMs: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Name-only PayPal SAR verification.
|
|
36
|
+
* Polls name-mode verify (retrying only on retryable poll statuses until the deadline). Terminal
|
|
37
|
+
* name failures surface directly. Success / unavailable short-circuit. The caller maps the returned
|
|
38
|
+
* result to its own UI side effects; 'aborted' means the component unmounted and the caller should
|
|
39
|
+
* do nothing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function runPaypalSarNameVerify(deps: RunPaypalSarNameVerifyDeps): Promise<SarVerifyResult>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isRetryableSarPollStatus } from './paypalSarPhase.js';
|
|
2
|
+
/**
|
|
3
|
+
* Name-only PayPal SAR verification.
|
|
4
|
+
* Polls name-mode verify (retrying only on retryable poll statuses until the deadline). Terminal
|
|
5
|
+
* name failures surface directly. Success / unavailable short-circuit. The caller maps the returned
|
|
6
|
+
* result to its own UI side effects; 'aborted' means the component unmounted and the caller should
|
|
7
|
+
* do nothing.
|
|
8
|
+
*/
|
|
9
|
+
export async function runPaypalSarNameVerify(deps) {
|
|
10
|
+
const { submitNameAttempt, sleep, isMounted, now, retryIntervalMs, retryDurationMs } = deps;
|
|
11
|
+
const deadline = now() + retryDurationMs;
|
|
12
|
+
let attemptNumber = 1;
|
|
13
|
+
while (true) {
|
|
14
|
+
const outcome = await submitNameAttempt(attemptNumber);
|
|
15
|
+
if (!isMounted())
|
|
16
|
+
return { kind: 'aborted' };
|
|
17
|
+
if (outcome.kind === 'success')
|
|
18
|
+
return { kind: 'success', proofAttemptId: outcome.proofAttemptId };
|
|
19
|
+
if (outcome.kind === 'unavailable')
|
|
20
|
+
return { kind: 'unavailable' };
|
|
21
|
+
const deadlineReached = now() + retryIntervalMs > deadline;
|
|
22
|
+
if (isRetryableSarPollStatus(outcome.statusCode) && !deadlineReached) {
|
|
23
|
+
await sleep(retryIntervalMs);
|
|
24
|
+
if (!isMounted())
|
|
25
|
+
return { kind: 'aborted' };
|
|
26
|
+
attemptNumber += 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
return { kind: 'error', errorCode: outcome.errorCode, errorMessage: outcome.errorMessage };
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/rails.d.ts
CHANGED
|
@@ -42,6 +42,17 @@ export declare enum SupportedRelayRail {
|
|
|
42
42
|
export declare const DEFAULT_FIAT_RAILS: readonly SupportedRail[];
|
|
43
43
|
export declare const SUPPORTED_RAILS: readonly SupportedRail[];
|
|
44
44
|
export declare const SUPPORTED_RELAY_RAILS: readonly SupportedRelayRail[];
|
|
45
|
+
export declare const FIAT_SUPPORTED_RAILS: readonly ["venmo", "cashapp", "revolut", "wise", "zelle", "paypal", "monzo", "n26", "chime"];
|
|
46
|
+
export type FiatSupportedRail = typeof FIAT_SUPPORTED_RAILS[number];
|
|
47
|
+
export declare const PAYMENT_PLATFORM_LABELS: Record<string, string>;
|
|
48
|
+
export declare const FIAT_RAIL_DISPLAY_NAMES: Record<FiatSupportedRail, string>;
|
|
49
|
+
export declare const ORDER_SEARCH_MIN_QUERY_LENGTH = 3;
|
|
50
|
+
export declare function railKeysForQuery(query: string): string[];
|
|
51
|
+
export declare const RELAY_CHAIN_DISPLAY_NAMES: Record<number, string>;
|
|
52
|
+
/**
|
|
53
|
+
* Surfaces crypto-settled orders for token, chain, and "crypto" prefix queries.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isCryptoMethodQuery(query: string): boolean;
|
|
45
56
|
/**
|
|
46
57
|
* Normalizes a rail string into a crypto chain id.
|
|
47
58
|
*
|
|
@@ -51,11 +62,76 @@ export declare const SUPPORTED_RELAY_RAILS: readonly SupportedRelayRail[];
|
|
|
51
62
|
*/
|
|
52
63
|
export declare function parseCryptoRailChainId(rail: string | null | undefined): number | null;
|
|
53
64
|
export declare function isSupportedRail(rail: string): rail is SupportedRail;
|
|
65
|
+
export declare function isFiatSupportedRail(rail: string): rail is FiatSupportedRail;
|
|
66
|
+
/** Returns false for rails that remain recognized but are disabled at runtime. */
|
|
67
|
+
export declare function isRailRuntimeEnabled(rail: string | null | undefined): boolean;
|
|
68
|
+
/** Filters out globally disabled runtime rails while preserving caller ordering. */
|
|
69
|
+
export declare function filterRuntimeEnabledRails<T extends string>(rails: readonly T[]): T[];
|
|
70
|
+
/**
|
|
71
|
+
* Replaces the set of globally runtime-disabled fiat rails. Only EXACT base
|
|
72
|
+
* fiat rails are honored (e.g. `venmo`, `paypal`, `zelle`); rail variants
|
|
73
|
+
* (e.g. `zelle-chase`, or an env typo like `paypal-test`), crypto/relay rails,
|
|
74
|
+
* and unknown strings are ignored so a malformed env value cannot silently
|
|
75
|
+
* disable a live rail. Disable is base-granular: disabling `zelle` also
|
|
76
|
+
* disables its variants at check time (isRailRuntimeEnabled normalizes the
|
|
77
|
+
* checked rail to its base). Backend-only: the API applies the `DISABLED_RAILS`
|
|
78
|
+
* env at boot; the frontend never calls this, so its disabled set stays empty.
|
|
79
|
+
*/
|
|
80
|
+
export declare function setRuntimeDisabledFiatRails(rails: readonly string[]): void;
|
|
81
|
+
/** Returns the currently runtime-disabled fiat rails (canonical base rails). */
|
|
82
|
+
export declare function getRuntimeDisabledFiatRails(): string[];
|
|
54
83
|
/** Returns true when the rail targets a crypto chain (legacy numeric or relay-prefixed format). */
|
|
55
84
|
export declare function isCryptoRail(rail: string | null | undefined): boolean;
|
|
56
85
|
/** Formats a chain id into the canonical relay rail string: `relay_<chainId>`. */
|
|
57
86
|
export declare function formatRelayRail(chainId: number): string;
|
|
58
87
|
/** Normalizes any crypto rail into canonical relay format (or null for non-crypto rails). */
|
|
59
88
|
export declare function normalizeCryptoRail(rail: string | null | undefined): string | null;
|
|
89
|
+
/** Returns the canonical base fiat rail for base rails and supported variants such as `zelle-chase`. */
|
|
90
|
+
export declare function normalizeFiatRail(rail: string | null | undefined): string | null;
|
|
91
|
+
/**
|
|
92
|
+
* Fiat rails that support Seller Automated Release (SAR). Merchants in
|
|
93
|
+
* EXCLUSIVE_SAR mode may only offer these rails; every other fiat rail is
|
|
94
|
+
* force-disabled in the merchant and admin dashboards.
|
|
95
|
+
*/
|
|
96
|
+
export declare const SAR_SUPPORTED_FIAT_RAILS: readonly ["venmo", "cashapp", "wise", "paypal"];
|
|
97
|
+
export type SarSupportedFiatRail = typeof SAR_SUPPORTED_FIAT_RAILS[number];
|
|
98
|
+
/** Returns true when the fiat rail (incl. variants like `zelle-chase`) supports SAR. */
|
|
99
|
+
export declare function isSarSupportedFiatRail(rail: string | null | undefined): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Returns `rails` with non-SAR fiat rails removed, preserving crypto/relay rails,
|
|
102
|
+
* SAR-supported fiat rails, and original ordering. Used to keep an EXCLUSIVE_SAR
|
|
103
|
+
* merchant's `enabledRails` in sync with their quote preference: non-SAR fiat
|
|
104
|
+
* rails (e.g. `zelle`, `revolut`, incl. variants like `zelle-chase`) are dropped
|
|
105
|
+
* while crypto rails and SAR rails (venmo/cashapp/wise/paypal) are kept.
|
|
106
|
+
*/
|
|
107
|
+
export declare function filterRailsForExclusiveSar(rails: readonly string[]): string[];
|
|
108
|
+
/** Canonical Zelle paymentMethodId -> attestation-service actionType suffix. */
|
|
109
|
+
export declare const ZELLE_METHOD_ACTION_SUFFIX: Record<string, string>;
|
|
110
|
+
/** Strict create-boundary set for Zelle method ids and transitional variant rails. */
|
|
111
|
+
export declare const ZELLE_PAYMENT_METHOD_IDS: ReadonlySet<string>;
|
|
112
|
+
/** Bank-qualified buyer TEE action types accepted for generic Zelle proofs. */
|
|
113
|
+
export declare const ZELLE_BUYER_TEE_ACTION_TYPES: ReadonlySet<string>;
|
|
114
|
+
export type AttestationRoute = {
|
|
115
|
+
readonly platform: string;
|
|
116
|
+
readonly actionType: string;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Resolves the attestation-service verifier route (`platform` + `actionType`)
|
|
120
|
+
* for a persisted `payment.rail` value.
|
|
121
|
+
*
|
|
122
|
+
* Zelle variants map to the new generic `zelle` platform with a bank-qualified
|
|
123
|
+
* actionType. Legacy bank-specific verifier routes are drain-only and should
|
|
124
|
+
* stay outside shared core. Non-Zelle fiat rails use the base rail as platform
|
|
125
|
+
* and `transfer_{rail}` as actionType. Unknown / crypto rails fall back to the
|
|
126
|
+
* raw trimmed rail so callers still surface a verifier mismatch via the
|
|
127
|
+
* attestation response rather than silently mis-routing.
|
|
128
|
+
*/
|
|
129
|
+
export declare function resolveAttestationRoute(rail: string | null | undefined): AttestationRoute;
|
|
130
|
+
/** Canonical generic Zelle on-chain method hash: keccak256("zelle"), lowercase. */
|
|
131
|
+
export declare const GENERIC_ZELLE_PAYMENT_METHOD_HASH = "0xf752c7d19698ecb0bb8988abf9b9a53a4c3657f3bc8850a6fb59fdf3e3ce8cd3";
|
|
132
|
+
/** Case-insensitive check that a hash is the canonical generic Zelle on-chain method hash. */
|
|
133
|
+
export declare function isGenericZellePaymentMethodHash(hash: string | null | undefined): boolean;
|
|
134
|
+
/** True when the rail is Zelle (generic or a bank variant). */
|
|
135
|
+
export declare function isZelleRail(rail: string | null | undefined): boolean;
|
|
60
136
|
/** Formats a rail value for human-readable UI labels. */
|
|
61
137
|
export declare function getRailDisplayName(rail: string | null | undefined): string;
|
package/dist/rails.js
CHANGED
|
@@ -51,18 +51,49 @@ export const DEFAULT_FIAT_RAILS = [
|
|
|
51
51
|
export const SUPPORTED_RAILS = Object.values(SupportedRail);
|
|
52
52
|
export const SUPPORTED_RELAY_RAILS = Object.values(SupportedRelayRail);
|
|
53
53
|
const SUPPORTED_RAIL_SET = new Set(SUPPORTED_RAILS);
|
|
54
|
-
|
|
54
|
+
// Globally runtime-disabled fiat rails: removed from new offers/acceptance
|
|
55
|
+
// everywhere isRailRuntimeEnabled / filterRuntimeEnabledRails are consumed,
|
|
56
|
+
// while staying recognized for historical orders. Empty by default; the API
|
|
57
|
+
// seeds this from the DISABLED_RAILS env at boot via setRuntimeDisabledFiatRails.
|
|
58
|
+
// The frontend never sets it, so the browser bundle keeps an empty (no-op) set.
|
|
59
|
+
let runtimeDisabledFiatRails = new Set();
|
|
60
|
+
export const FIAT_SUPPORTED_RAILS = [
|
|
61
|
+
'venmo',
|
|
62
|
+
'cashapp',
|
|
63
|
+
'revolut',
|
|
64
|
+
'wise',
|
|
65
|
+
'zelle',
|
|
66
|
+
'paypal',
|
|
67
|
+
'monzo',
|
|
68
|
+
'n26',
|
|
69
|
+
'chime',
|
|
70
|
+
];
|
|
71
|
+
const PAYMENT_PLATFORM_LABEL_ENTRIES = {
|
|
55
72
|
[SupportedRail.VENMO]: 'Venmo',
|
|
56
73
|
[SupportedRail.CASHAPP]: 'Cash App',
|
|
57
74
|
[SupportedRail.REVOLUT]: 'Revolut',
|
|
75
|
+
[SupportedRail.WISE]: 'Wise',
|
|
58
76
|
[SupportedRail.ZELLE]: 'Zelle',
|
|
59
77
|
[SupportedRail.PAYPAL]: 'PayPal',
|
|
60
|
-
[SupportedRail.WISE]: 'Wise',
|
|
61
78
|
[SupportedRail.MONZO]: 'Monzo',
|
|
62
79
|
[SupportedRail.N26]: 'N26',
|
|
63
80
|
[SupportedRail.CHIME]: 'Chime',
|
|
64
81
|
};
|
|
65
|
-
const
|
|
82
|
+
export const PAYMENT_PLATFORM_LABELS = PAYMENT_PLATFORM_LABEL_ENTRIES;
|
|
83
|
+
export const FIAT_RAIL_DISPLAY_NAMES = PAYMENT_PLATFORM_LABEL_ENTRIES;
|
|
84
|
+
// Shared FE/BE minimum so queries too short for the pg_trgm trigram index are no-ops.
|
|
85
|
+
export const ORDER_SEARCH_MIN_QUERY_LENGTH = 3;
|
|
86
|
+
export function railKeysForQuery(query) {
|
|
87
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
88
|
+
if (normalizedQuery.length === 0) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
return Object.entries(PAYMENT_PLATFORM_LABELS)
|
|
92
|
+
.filter(([, label]) => label.toLowerCase().includes(normalizedQuery))
|
|
93
|
+
.map(([key]) => key);
|
|
94
|
+
}
|
|
95
|
+
const SUPPORTED_FIAT_RAILS = new Set(Object.keys(FIAT_RAIL_DISPLAY_NAMES));
|
|
96
|
+
export const RELAY_CHAIN_DISPLAY_NAMES = {
|
|
66
97
|
1: 'Ethereum',
|
|
67
98
|
10: 'Optimism',
|
|
68
99
|
56: 'BNB Smart Chain',
|
|
@@ -75,6 +106,22 @@ const RELAY_CHAIN_DISPLAY_NAMES = {
|
|
|
75
106
|
728126428: 'Tron',
|
|
76
107
|
792703809: 'Solana',
|
|
77
108
|
};
|
|
109
|
+
const CRYPTO_METHOD_ALIASES = [
|
|
110
|
+
'crypto',
|
|
111
|
+
'usdc',
|
|
112
|
+
'usdt',
|
|
113
|
+
...Object.values(RELAY_CHAIN_DISPLAY_NAMES).map((name) => name.toLowerCase()),
|
|
114
|
+
];
|
|
115
|
+
/**
|
|
116
|
+
* Surfaces crypto-settled orders for token, chain, and "crypto" prefix queries.
|
|
117
|
+
*/
|
|
118
|
+
export function isCryptoMethodQuery(query) {
|
|
119
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
120
|
+
if (normalizedQuery.length === 0) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return CRYPTO_METHOD_ALIASES.some((alias) => alias.startsWith(normalizedQuery));
|
|
124
|
+
}
|
|
78
125
|
/**
|
|
79
126
|
* Normalizes a rail string into a crypto chain id.
|
|
80
127
|
*
|
|
@@ -105,6 +152,45 @@ export function parseCryptoRailChainId(rail) {
|
|
|
105
152
|
export function isSupportedRail(rail) {
|
|
106
153
|
return SUPPORTED_RAIL_SET.has(rail);
|
|
107
154
|
}
|
|
155
|
+
export function isFiatSupportedRail(rail) {
|
|
156
|
+
return FIAT_SUPPORTED_RAILS.includes(rail);
|
|
157
|
+
}
|
|
158
|
+
/** Returns false for rails that remain recognized but are disabled at runtime. */
|
|
159
|
+
export function isRailRuntimeEnabled(rail) {
|
|
160
|
+
const normalizedFiatRail = normalizeFiatRail(rail);
|
|
161
|
+
return normalizedFiatRail === null || !runtimeDisabledFiatRails.has(normalizedFiatRail);
|
|
162
|
+
}
|
|
163
|
+
/** Filters out globally disabled runtime rails while preserving caller ordering. */
|
|
164
|
+
export function filterRuntimeEnabledRails(rails) {
|
|
165
|
+
return rails.filter((rail) => isRailRuntimeEnabled(rail));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Replaces the set of globally runtime-disabled fiat rails. Only EXACT base
|
|
169
|
+
* fiat rails are honored (e.g. `venmo`, `paypal`, `zelle`); rail variants
|
|
170
|
+
* (e.g. `zelle-chase`, or an env typo like `paypal-test`), crypto/relay rails,
|
|
171
|
+
* and unknown strings are ignored so a malformed env value cannot silently
|
|
172
|
+
* disable a live rail. Disable is base-granular: disabling `zelle` also
|
|
173
|
+
* disables its variants at check time (isRailRuntimeEnabled normalizes the
|
|
174
|
+
* checked rail to its base). Backend-only: the API applies the `DISABLED_RAILS`
|
|
175
|
+
* env at boot; the frontend never calls this, so its disabled set stays empty.
|
|
176
|
+
*/
|
|
177
|
+
export function setRuntimeDisabledFiatRails(rails) {
|
|
178
|
+
const next = new Set();
|
|
179
|
+
for (const rail of rails) {
|
|
180
|
+
if (typeof rail !== 'string') {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const normalized = rail.trim().toLowerCase();
|
|
184
|
+
if (isFiatSupportedRail(normalized)) {
|
|
185
|
+
next.add(normalized);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
runtimeDisabledFiatRails = next;
|
|
189
|
+
}
|
|
190
|
+
/** Returns the currently runtime-disabled fiat rails (canonical base rails). */
|
|
191
|
+
export function getRuntimeDisabledFiatRails() {
|
|
192
|
+
return [...runtimeDisabledFiatRails];
|
|
193
|
+
}
|
|
108
194
|
/** Returns true when the rail targets a crypto chain (legacy numeric or relay-prefixed format). */
|
|
109
195
|
export function isCryptoRail(rail) {
|
|
110
196
|
return parseCryptoRailChainId(rail) !== null;
|
|
@@ -121,6 +207,97 @@ export function normalizeCryptoRail(rail) {
|
|
|
121
207
|
const chainId = parseCryptoRailChainId(rail);
|
|
122
208
|
return chainId === null ? null : formatRelayRail(chainId);
|
|
123
209
|
}
|
|
210
|
+
/** Returns the canonical base fiat rail for base rails and supported variants such as `zelle-chase`. */
|
|
211
|
+
export function normalizeFiatRail(rail) {
|
|
212
|
+
if (typeof rail !== 'string') {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
const trimmed = rail.trim().toLowerCase();
|
|
216
|
+
if (trimmed === '') {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
if (SUPPORTED_FIAT_RAILS.has(trimmed)) {
|
|
220
|
+
return trimmed;
|
|
221
|
+
}
|
|
222
|
+
for (const baseRail of SUPPORTED_FIAT_RAILS) {
|
|
223
|
+
if (trimmed.startsWith(`${baseRail}-`)) {
|
|
224
|
+
return baseRail;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Fiat rails that support Seller Automated Release (SAR). Merchants in
|
|
231
|
+
* EXCLUSIVE_SAR mode may only offer these rails; every other fiat rail is
|
|
232
|
+
* force-disabled in the merchant and admin dashboards.
|
|
233
|
+
*/
|
|
234
|
+
export const SAR_SUPPORTED_FIAT_RAILS = ['venmo', 'cashapp', 'wise', 'paypal'];
|
|
235
|
+
const SAR_SUPPORTED_FIAT_RAIL_SET = new Set(SAR_SUPPORTED_FIAT_RAILS);
|
|
236
|
+
/** Returns true when the fiat rail (incl. variants like `zelle-chase`) supports SAR. */
|
|
237
|
+
export function isSarSupportedFiatRail(rail) {
|
|
238
|
+
const normalized = normalizeFiatRail(rail);
|
|
239
|
+
return normalized !== null && SAR_SUPPORTED_FIAT_RAIL_SET.has(normalized);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Returns `rails` with non-SAR fiat rails removed, preserving crypto/relay rails,
|
|
243
|
+
* SAR-supported fiat rails, and original ordering. Used to keep an EXCLUSIVE_SAR
|
|
244
|
+
* merchant's `enabledRails` in sync with their quote preference: non-SAR fiat
|
|
245
|
+
* rails (e.g. `zelle`, `revolut`, incl. variants like `zelle-chase`) are dropped
|
|
246
|
+
* while crypto rails and SAR rails (venmo/cashapp/wise/paypal) are kept.
|
|
247
|
+
*/
|
|
248
|
+
export function filterRailsForExclusiveSar(rails) {
|
|
249
|
+
return rails.filter((rail) => {
|
|
250
|
+
const normalizedFiat = normalizeFiatRail(rail);
|
|
251
|
+
// Non-fiat (crypto/relay) or unrecognized rails are preserved verbatim.
|
|
252
|
+
if (normalizedFiat === null) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
// Recognized fiat rail: keep only if it supports SAR.
|
|
256
|
+
return SAR_SUPPORTED_FIAT_RAIL_SET.has(normalizedFiat);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/** Canonical Zelle paymentMethodId -> attestation-service actionType suffix. */
|
|
260
|
+
export const ZELLE_METHOD_ACTION_SUFFIX = {
|
|
261
|
+
'zelle-bofa': 'bofa',
|
|
262
|
+
'zelle-chase': 'chase',
|
|
263
|
+
'zelle-citi': 'citi',
|
|
264
|
+
};
|
|
265
|
+
/** Strict create-boundary set for Zelle method ids and transitional variant rails. */
|
|
266
|
+
export const ZELLE_PAYMENT_METHOD_IDS = new Set(Object.keys(ZELLE_METHOD_ACTION_SUFFIX));
|
|
267
|
+
/** Bank-qualified buyer TEE action types accepted for generic Zelle proofs. */
|
|
268
|
+
export const ZELLE_BUYER_TEE_ACTION_TYPES = new Set(Object.values(ZELLE_METHOD_ACTION_SUFFIX).map((suffix) => `transfer_zelle_${suffix}`));
|
|
269
|
+
/**
|
|
270
|
+
* Resolves the attestation-service verifier route (`platform` + `actionType`)
|
|
271
|
+
* for a persisted `payment.rail` value.
|
|
272
|
+
*
|
|
273
|
+
* Zelle variants map to the new generic `zelle` platform with a bank-qualified
|
|
274
|
+
* actionType. Legacy bank-specific verifier routes are drain-only and should
|
|
275
|
+
* stay outside shared core. Non-Zelle fiat rails use the base rail as platform
|
|
276
|
+
* and `transfer_{rail}` as actionType. Unknown / crypto rails fall back to the
|
|
277
|
+
* raw trimmed rail so callers still surface a verifier mismatch via the
|
|
278
|
+
* attestation response rather than silently mis-routing.
|
|
279
|
+
*/
|
|
280
|
+
export function resolveAttestationRoute(rail) {
|
|
281
|
+
const trimmed = typeof rail === 'string' ? rail.trim().toLowerCase() : '';
|
|
282
|
+
// TRANSITIONAL: rail-variant fold, delete after drain window.
|
|
283
|
+
const zelleVariantSuffix = ZELLE_METHOD_ACTION_SUFFIX[trimmed];
|
|
284
|
+
if (zelleVariantSuffix !== undefined) {
|
|
285
|
+
return { platform: 'zelle', actionType: `transfer_zelle_${zelleVariantSuffix}` };
|
|
286
|
+
}
|
|
287
|
+
const baseRail = normalizeFiatRail(trimmed) ?? trimmed;
|
|
288
|
+
return { platform: baseRail, actionType: `transfer_${baseRail}` };
|
|
289
|
+
}
|
|
290
|
+
/** Canonical generic Zelle on-chain method hash: keccak256("zelle"), lowercase. */
|
|
291
|
+
export const GENERIC_ZELLE_PAYMENT_METHOD_HASH = '0xf752c7d19698ecb0bb8988abf9b9a53a4c3657f3bc8850a6fb59fdf3e3ce8cd3';
|
|
292
|
+
/** Case-insensitive check that a hash is the canonical generic Zelle on-chain method hash. */
|
|
293
|
+
export function isGenericZellePaymentMethodHash(hash) {
|
|
294
|
+
return typeof hash === 'string'
|
|
295
|
+
&& hash.trim().toLowerCase() === GENERIC_ZELLE_PAYMENT_METHOD_HASH;
|
|
296
|
+
}
|
|
297
|
+
/** True when the rail is Zelle (generic or a bank variant). */
|
|
298
|
+
export function isZelleRail(rail) {
|
|
299
|
+
return normalizeFiatRail(rail) === 'zelle';
|
|
300
|
+
}
|
|
124
301
|
/** Formats a rail value for human-readable UI labels. */
|
|
125
302
|
export function getRailDisplayName(rail) {
|
|
126
303
|
if (typeof rail !== 'string') {
|
|
@@ -131,9 +308,8 @@ export function getRailDisplayName(rail) {
|
|
|
131
308
|
return 'Unknown';
|
|
132
309
|
}
|
|
133
310
|
const normalized = trimmed.toLowerCase();
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return fiatDisplayName;
|
|
311
|
+
if (isFiatSupportedRail(normalized)) {
|
|
312
|
+
return FIAT_RAIL_DISPLAY_NAMES[normalized];
|
|
137
313
|
}
|
|
138
314
|
const chainId = parseCryptoRailChainId(trimmed);
|
|
139
315
|
if (chainId !== null) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PayCryptoTokenSymbol } from './crypto.js';
|
|
2
|
+
import type { ProofMode } from './buyerTee.js';
|
|
2
3
|
export declare const PaymentPlatform: {
|
|
3
4
|
readonly VENMO: "venmo";
|
|
4
5
|
readonly CASHAPP: "cashapp";
|
|
@@ -11,6 +12,11 @@ export declare const PaymentPlatform: {
|
|
|
11
12
|
readonly CHIME: "chime";
|
|
12
13
|
};
|
|
13
14
|
export type PaymentPlatformType = typeof PaymentPlatform[keyof typeof PaymentPlatform];
|
|
15
|
+
/**
|
|
16
|
+
* Type guard for PaymentPlatform values.
|
|
17
|
+
* Returns true if `value` is one of the known PaymentPlatform enum values.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isPaymentPlatform(value: unknown): value is PaymentPlatformType;
|
|
14
20
|
export declare const SupportedChains: Record<number, string>;
|
|
15
21
|
export type SupportedChainId = keyof typeof SupportedChains;
|
|
16
22
|
export declare const CheckoutMode: {
|
|
@@ -57,6 +63,13 @@ export declare const FeePayer: {
|
|
|
57
63
|
readonly PAYEE: "PAYEE";
|
|
58
64
|
};
|
|
59
65
|
export type FeePayerType = typeof FeePayer[keyof typeof FeePayer];
|
|
66
|
+
export declare const MerchantPaymentFlowMode: {
|
|
67
|
+
readonly EXCLUSIVE_SAR: "EXCLUSIVE_SAR";
|
|
68
|
+
readonly PREFER_SAR: "PREFER_SAR";
|
|
69
|
+
readonly PREFER_BUYER_TEE: "PREFER_BUYER_TEE";
|
|
70
|
+
readonly EXCLUSIVE_BUYER_TEE: "EXCLUSIVE_BUYER_TEE";
|
|
71
|
+
};
|
|
72
|
+
export type MerchantPaymentFlowModeType = typeof MerchantPaymentFlowMode[keyof typeof MerchantPaymentFlowMode];
|
|
60
73
|
export declare const CheckoutOrderStatus: {
|
|
61
74
|
readonly CREATED: "CREATED";
|
|
62
75
|
readonly PARTIALLY_FULFILLED: "PARTIALLY_FULFILLED";
|
|
@@ -64,6 +77,12 @@ export declare const CheckoutOrderStatus: {
|
|
|
64
77
|
readonly CANCELLED: "CANCELLED";
|
|
65
78
|
};
|
|
66
79
|
export type CheckoutOrderStatusType = typeof CheckoutOrderStatus[keyof typeof CheckoutOrderStatus];
|
|
80
|
+
export declare const CheckoutOrderRefundStatus: {
|
|
81
|
+
readonly NONE: "NONE";
|
|
82
|
+
readonly PENDING: "PENDING";
|
|
83
|
+
readonly COMPLETED: "COMPLETED";
|
|
84
|
+
};
|
|
85
|
+
export type CheckoutOrderRefundStatusType = typeof CheckoutOrderRefundStatus[keyof typeof CheckoutOrderRefundStatus];
|
|
67
86
|
export declare const CheckoutPaymentStatus: {
|
|
68
87
|
readonly CREATED: "CREATED";
|
|
69
88
|
readonly SETTLED: "SETTLED";
|
|
@@ -72,6 +91,12 @@ export declare const CheckoutPaymentStatus: {
|
|
|
72
91
|
readonly FAILED: "FAILED";
|
|
73
92
|
};
|
|
74
93
|
export type CheckoutPaymentStatusType = typeof CheckoutPaymentStatus[keyof typeof CheckoutPaymentStatus];
|
|
94
|
+
export declare const CheckoutPaymentMemoPolicy: {
|
|
95
|
+
readonly NONE: "NONE";
|
|
96
|
+
readonly REQUIRED: "REQUIRED";
|
|
97
|
+
readonly EMPTY: "EMPTY";
|
|
98
|
+
};
|
|
99
|
+
export type CheckoutPaymentMemoPolicyType = typeof CheckoutPaymentMemoPolicy[keyof typeof CheckoutPaymentMemoPolicy];
|
|
75
100
|
export type ReferralFeeEntry = {
|
|
76
101
|
recipient: string;
|
|
77
102
|
feeBps: number;
|
|
@@ -125,11 +150,19 @@ export type CheckoutMerchant = {
|
|
|
125
150
|
disableBranding: boolean;
|
|
126
151
|
checkoutTheme: MerchantCheckoutTheme | null;
|
|
127
152
|
defaultPaymentCurrency?: string | null;
|
|
153
|
+
/** Whether the merchant has been verified by ZKP2P. */
|
|
154
|
+
verified: boolean;
|
|
128
155
|
};
|
|
129
156
|
export type CheckoutOrder = {
|
|
130
157
|
id: string;
|
|
131
158
|
merchantId: string;
|
|
132
159
|
status: CheckoutOrderStatusType;
|
|
160
|
+
refundStatus: CheckoutOrderRefundStatusType;
|
|
161
|
+
refundAmountUsdc: string | null;
|
|
162
|
+
refundDepositId: string | null;
|
|
163
|
+
refundTransactionHash: string | null;
|
|
164
|
+
refundMetadata: unknown | null;
|
|
165
|
+
inPersonCheckout: boolean;
|
|
133
166
|
requestedUsdcAmount: string;
|
|
134
167
|
remainingUsdcAmount: string;
|
|
135
168
|
destinationAddress: string;
|
|
@@ -163,6 +196,7 @@ export type CheckoutPaymentQuote = {
|
|
|
163
196
|
depositData: Record<string, string>;
|
|
164
197
|
};
|
|
165
198
|
intentHash?: string;
|
|
199
|
+
sellerAutomatedReleaseAvailable?: boolean;
|
|
166
200
|
};
|
|
167
201
|
export type CheckoutQuote = {
|
|
168
202
|
displayAmount: string;
|
|
@@ -172,6 +206,7 @@ export type CheckoutQuoteEntry = {
|
|
|
172
206
|
platform: string;
|
|
173
207
|
displayAmount: string;
|
|
174
208
|
spreadPercentage: number;
|
|
209
|
+
sellerAutomatedReleaseAvailable?: boolean;
|
|
175
210
|
};
|
|
176
211
|
export type CheckoutQuotes = {
|
|
177
212
|
orderId: string;
|
|
@@ -183,10 +218,19 @@ export type CheckoutPayment = {
|
|
|
183
218
|
orderId: string;
|
|
184
219
|
status: CheckoutPaymentStatusType;
|
|
185
220
|
rail: string;
|
|
221
|
+
paymentMethodId: string | null;
|
|
222
|
+
proofMode: ProofMode;
|
|
223
|
+
attestation: {
|
|
224
|
+
serviceUrl: string;
|
|
225
|
+
};
|
|
186
226
|
currency: string;
|
|
187
227
|
payTo: string;
|
|
228
|
+
/** Recipient rating in the 1.0–5.0 range, rounded to one decimal. */
|
|
229
|
+
recipientRating: number;
|
|
188
230
|
quote: CheckoutPaymentQuote;
|
|
189
231
|
quoteExpiresAt: string;
|
|
232
|
+
memoPolicy: CheckoutPaymentMemoPolicyType;
|
|
233
|
+
suggestedMemo: string | null;
|
|
190
234
|
paymentAmount: string;
|
|
191
235
|
currencyPerUsdRate: string;
|
|
192
236
|
netSettledUsdcAmount: string | null;
|
|
@@ -194,14 +238,19 @@ export type CheckoutPayment = {
|
|
|
194
238
|
referralFees: SettlementReferralFeeEntry[] | null;
|
|
195
239
|
railIdentifier: string | null;
|
|
196
240
|
fulfillTransaction: string | null;
|
|
241
|
+
errorCode?: string | null;
|
|
242
|
+
errorMessage?: string | null;
|
|
197
243
|
completedAt: string | null;
|
|
198
244
|
createdAt: string;
|
|
199
245
|
updatedAt: string | null;
|
|
200
246
|
};
|
|
247
|
+
export type CheckoutAggregatePayment = Omit<CheckoutPayment, 'paymentMethodId'> & {
|
|
248
|
+
paymentMethodId?: string | null;
|
|
249
|
+
};
|
|
201
250
|
export type CheckoutAggregate = {
|
|
202
251
|
order: CheckoutOrder;
|
|
203
252
|
merchant: CheckoutMerchant;
|
|
204
|
-
currentPayment:
|
|
253
|
+
currentPayment: CheckoutAggregatePayment | null;
|
|
205
254
|
};
|
|
206
255
|
export type CreateOrderResponse = {
|
|
207
256
|
order: CheckoutOrder;
|
|
@@ -211,6 +260,7 @@ export type CreateCheckoutResponse = CreateOrderResponse;
|
|
|
211
260
|
export type CheckoutCreateResponse = CreateOrderResponse;
|
|
212
261
|
export type CreatePaymentRequest = {
|
|
213
262
|
rail: string;
|
|
263
|
+
paymentMethodId?: string;
|
|
214
264
|
fiatCurrency?: string;
|
|
215
265
|
currency?: string;
|
|
216
266
|
replacementPaymentId?: string;
|
|
@@ -227,6 +277,7 @@ export type PaymentDeeplinkResponse = {
|
|
|
227
277
|
url: string;
|
|
228
278
|
proofSubmissionUrl: string;
|
|
229
279
|
checkoutReturnUrl: string;
|
|
280
|
+
linkKind?: 'app_clip' | 'deeplink';
|
|
230
281
|
};
|
|
231
282
|
export type CheckoutDeeplinkResponse = PaymentDeeplinkResponse;
|
|
232
283
|
export type MerchantConfig = {
|
|
@@ -246,11 +297,24 @@ export type MerchantConfig = {
|
|
|
246
297
|
createdAt: string;
|
|
247
298
|
updatedAt: string | null;
|
|
248
299
|
};
|
|
300
|
+
export declare const DashboardPrivyApp: {
|
|
301
|
+
readonly LEGACY: "LEGACY";
|
|
302
|
+
readonly V1: "V1";
|
|
303
|
+
};
|
|
304
|
+
export type DashboardPrivyAppType = typeof DashboardPrivyApp[keyof typeof DashboardPrivyApp];
|
|
305
|
+
export declare const MerchantMigrationStatus: {
|
|
306
|
+
readonly PENDING: "PENDING";
|
|
307
|
+
readonly MIGRATING: "MIGRATING";
|
|
308
|
+
readonly COMPLETE: "COMPLETE";
|
|
309
|
+
readonly FAILED: "FAILED";
|
|
310
|
+
};
|
|
311
|
+
export type MerchantMigrationStatusType = typeof MerchantMigrationStatus[keyof typeof MerchantMigrationStatus];
|
|
249
312
|
export type MerchantUser = {
|
|
250
313
|
id: string;
|
|
251
314
|
email: string | null;
|
|
252
|
-
privyUserId: string;
|
|
253
|
-
|
|
315
|
+
privyUserId: string | null;
|
|
316
|
+
v1PrivyUserId: string | null;
|
|
317
|
+
role: 'OWNER' | 'MANAGER' | 'CASHIER';
|
|
254
318
|
merchantId: string | null;
|
|
255
319
|
createdAt: string;
|
|
256
320
|
updatedAt: string | null;
|
|
@@ -259,11 +323,17 @@ export type MerchantProfile = {
|
|
|
259
323
|
id: string;
|
|
260
324
|
name: string;
|
|
261
325
|
logoUrl: string | null;
|
|
326
|
+
industryType: string | null;
|
|
327
|
+
industryOtherText: string | null;
|
|
262
328
|
apiKey: string;
|
|
263
329
|
environment: MerchantEnvironmentType;
|
|
264
330
|
sandboxMerchantId: string | null;
|
|
331
|
+
inPersonCheckoutEnabled: boolean;
|
|
332
|
+
migrationStatus: MerchantMigrationStatusType;
|
|
265
333
|
evmWalletAddress: string | null;
|
|
266
334
|
solanaWalletAddress: string | null;
|
|
335
|
+
v1EvmWalletAddress: string | null;
|
|
336
|
+
v1SolanaWalletAddress: string | null;
|
|
267
337
|
tier: 'FREE' | 'PRO';
|
|
268
338
|
createdAt: string;
|
|
269
339
|
updatedAt: string | null;
|
|
@@ -328,6 +398,8 @@ export type Merchant = {
|
|
|
328
398
|
id: string;
|
|
329
399
|
name: string;
|
|
330
400
|
logoUrl?: string | null;
|
|
401
|
+
industryType?: string | null;
|
|
402
|
+
industryOtherText?: string | null;
|
|
331
403
|
disableBranding?: boolean;
|
|
332
404
|
apiKey: string;
|
|
333
405
|
environment?: MerchantEnvironmentType;
|
|
@@ -605,6 +677,8 @@ export declare const WebhookEventType: {
|
|
|
605
677
|
readonly PAYMENT_CANCELLED: "PAYMENT_CANCELLED";
|
|
606
678
|
readonly PAYMENT_EXPIRED: "PAYMENT_EXPIRED";
|
|
607
679
|
readonly PAYMENT_FAILED: "PAYMENT_FAILED";
|
|
680
|
+
readonly REFUND_PENDING: "REFUND_PENDING";
|
|
681
|
+
readonly REFUND_COMPLETED: "REFUND_COMPLETED";
|
|
608
682
|
readonly PAYMENT_BRIDGE_PENDING: "PAYMENT_BRIDGE_PENDING";
|
|
609
683
|
readonly PAYMENT_BRIDGE_SUBMITTED: "PAYMENT_BRIDGE_SUBMITTED";
|
|
610
684
|
readonly PAYMENT_BRIDGE_COMPLETED: "PAYMENT_BRIDGE_COMPLETED";
|
|
@@ -653,6 +727,7 @@ export type WebhookPayload = {
|
|
|
653
727
|
data: {
|
|
654
728
|
order: CheckoutOrder | null;
|
|
655
729
|
payment: CheckoutPayment | null;
|
|
730
|
+
refund: Record<string, unknown> | null;
|
|
656
731
|
paymentBridge: Record<string, unknown> | null;
|
|
657
732
|
};
|
|
658
733
|
};
|
package/dist/types.js
CHANGED
|
@@ -10,6 +10,14 @@ export const PaymentPlatform = {
|
|
|
10
10
|
N26: 'n26',
|
|
11
11
|
CHIME: 'chime',
|
|
12
12
|
};
|
|
13
|
+
const PAYMENT_PLATFORM_VALUES = new Set(Object.values(PaymentPlatform));
|
|
14
|
+
/**
|
|
15
|
+
* Type guard for PaymentPlatform values.
|
|
16
|
+
* Returns true if `value` is one of the known PaymentPlatform enum values.
|
|
17
|
+
*/
|
|
18
|
+
export function isPaymentPlatform(value) {
|
|
19
|
+
return typeof value === 'string' && PAYMENT_PLATFORM_VALUES.has(value);
|
|
20
|
+
}
|
|
13
21
|
// Supported chains for USDC destination
|
|
14
22
|
// Re-exported from chains.ts for backward compatibility
|
|
15
23
|
export const SupportedChains = SupportedChainsMap;
|
|
@@ -53,12 +61,23 @@ export const FeePayer = {
|
|
|
53
61
|
MERCHANT: 'MERCHANT',
|
|
54
62
|
PAYEE: 'PAYEE',
|
|
55
63
|
};
|
|
64
|
+
export const MerchantPaymentFlowMode = {
|
|
65
|
+
EXCLUSIVE_SAR: 'EXCLUSIVE_SAR',
|
|
66
|
+
PREFER_SAR: 'PREFER_SAR',
|
|
67
|
+
PREFER_BUYER_TEE: 'PREFER_BUYER_TEE',
|
|
68
|
+
EXCLUSIVE_BUYER_TEE: 'EXCLUSIVE_BUYER_TEE',
|
|
69
|
+
};
|
|
56
70
|
export const CheckoutOrderStatus = {
|
|
57
71
|
CREATED: 'CREATED',
|
|
58
72
|
PARTIALLY_FULFILLED: 'PARTIALLY_FULFILLED',
|
|
59
73
|
FULFILLED: 'FULFILLED',
|
|
60
74
|
CANCELLED: 'CANCELLED',
|
|
61
75
|
};
|
|
76
|
+
export const CheckoutOrderRefundStatus = {
|
|
77
|
+
NONE: 'NONE',
|
|
78
|
+
PENDING: 'PENDING',
|
|
79
|
+
COMPLETED: 'COMPLETED',
|
|
80
|
+
};
|
|
62
81
|
export const CheckoutPaymentStatus = {
|
|
63
82
|
CREATED: 'CREATED',
|
|
64
83
|
SETTLED: 'SETTLED',
|
|
@@ -66,6 +85,21 @@ export const CheckoutPaymentStatus = {
|
|
|
66
85
|
EXPIRED: 'EXPIRED',
|
|
67
86
|
FAILED: 'FAILED',
|
|
68
87
|
};
|
|
88
|
+
export const CheckoutPaymentMemoPolicy = {
|
|
89
|
+
NONE: 'NONE',
|
|
90
|
+
REQUIRED: 'REQUIRED',
|
|
91
|
+
EMPTY: 'EMPTY',
|
|
92
|
+
};
|
|
93
|
+
export const DashboardPrivyApp = {
|
|
94
|
+
LEGACY: 'LEGACY',
|
|
95
|
+
V1: 'V1',
|
|
96
|
+
};
|
|
97
|
+
export const MerchantMigrationStatus = {
|
|
98
|
+
PENDING: 'PENDING',
|
|
99
|
+
MIGRATING: 'MIGRATING',
|
|
100
|
+
COMPLETE: 'COMPLETE',
|
|
101
|
+
FAILED: 'FAILED',
|
|
102
|
+
};
|
|
69
103
|
// Structured error codes for Order failures
|
|
70
104
|
export const OrderErrorCode = {
|
|
71
105
|
// Proof verification errors
|
|
@@ -107,6 +141,8 @@ export const WebhookEventType = {
|
|
|
107
141
|
PAYMENT_CANCELLED: 'PAYMENT_CANCELLED',
|
|
108
142
|
PAYMENT_EXPIRED: 'PAYMENT_EXPIRED',
|
|
109
143
|
PAYMENT_FAILED: 'PAYMENT_FAILED',
|
|
144
|
+
REFUND_PENDING: 'REFUND_PENDING',
|
|
145
|
+
REFUND_COMPLETED: 'REFUND_COMPLETED',
|
|
110
146
|
PAYMENT_BRIDGE_PENDING: 'PAYMENT_BRIDGE_PENDING',
|
|
111
147
|
PAYMENT_BRIDGE_SUBMITTED: 'PAYMENT_BRIDGE_SUBMITTED',
|
|
112
148
|
PAYMENT_BRIDGE_COMPLETED: 'PAYMENT_BRIDGE_COMPLETED',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zkp2p/pay-shared",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Shared TypeScript types, enums, and chain utilities used by ZKP2P Pay API, SDK, and frontend apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,7 +37,11 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
39
39
|
"lint": "eslint . --ext .ts,.tsx",
|
|
40
|
+
"test": "vitest run",
|
|
40
41
|
"prepack": "npm run build",
|
|
41
42
|
"prepublishOnly": "npm pack --dry-run"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"vitest": "^1.6.1"
|
|
42
46
|
}
|
|
43
47
|
}
|