@zkp2p/pay-shared 3.1.0 → 4.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 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@3.1.0
12
+ npm install @zkp2p/pay-shared@4.0.0
13
13
  ```
14
14
 
15
15
  ## Published artifacts
@@ -58,6 +58,7 @@ console.log(CheckoutOrderStatus.CREATED); // "CREATED"
58
58
  ```ts
59
59
  import {
60
60
  BASE_CHAIN_ID,
61
+ TRON_CHAIN_ID,
61
62
  getSupportedChainIds,
62
63
  parseSupportedChainSelection,
63
64
  resolveDestinationTokenAddress,
@@ -67,8 +68,9 @@ import {
67
68
  const chainId = parseSupportedChainSelection('polygon'); // 137
68
69
  const usdc = resolveDestinationTokenAddress('USDC', 137); // Chain-specific USDC address
69
70
  const supportedAliases = getSupportedDestinationTokenAliasesForChain(BASE_CHAIN_ID); // ["USDC", "USDT"]
71
+ const tronAliases = getSupportedDestinationTokenAliasesForChain(TRON_CHAIN_ID); // ["USDT"]
70
72
 
71
- console.log(getSupportedChainIds(), chainId, usdc, supportedAliases);
73
+ console.log(getSupportedChainIds(), chainId, usdc, supportedAliases, tronAliases);
72
74
  ```
73
75
 
74
76
  ## Key Exports
@@ -100,6 +102,7 @@ Chain constants and helpers:
100
102
  - `BASE_CHAIN_ID`
101
103
  - `HYPEREVM_CHAIN_ID`
102
104
  - `SOLANA_CHAIN_ID`
105
+ - `TRON_CHAIN_ID`
103
106
  - `SUPPORTED_DESTINATION_TOKEN_ALIASES`
104
107
  - `parseSupportedChainSelection(...)`
105
108
  - `resolveDestinationTokenAddress(...)`
@@ -0,0 +1,4 @@
1
+ export declare enum IntentAttributionReferrer {
2
+ PAY = "zkp2p-pay",
3
+ SUPPORT = "zkp2p-pay-cs"
4
+ }
@@ -0,0 +1,5 @@
1
+ export var IntentAttributionReferrer;
2
+ (function (IntentAttributionReferrer) {
3
+ IntentAttributionReferrer["PAY"] = "zkp2p-pay";
4
+ IntentAttributionReferrer["SUPPORT"] = "zkp2p-pay-cs";
5
+ })(IntentAttributionReferrer || (IntentAttributionReferrer = {}));
package/dist/chains.d.ts CHANGED
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Centralized chain configuration for ZKP2P Pay.
3
3
  *
4
- * All supported chains and their USDC addresses are defined here.
4
+ * All supported payout chains and their destination token addresses are defined here.
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
8
  export declare const ETHEREUM_CHAIN_ID = 1;
9
9
  export declare const HYPEREVM_CHAIN_ID = 999;
10
10
  export declare const SOLANA_CHAIN_ID = 792703809;
11
+ export declare const TRON_CHAIN_ID = 728126428;
11
12
  export declare const SUPPORTED_DESTINATION_TOKEN_ALIASES: readonly ["USDC", "USDT"];
12
13
  export type DestinationTokenAlias = (typeof SUPPORTED_DESTINATION_TOKEN_ALIASES)[number];
13
14
  export declare const DEFAULT_DESTINATION_TOKEN_ALIAS: DestinationTokenAlias;
@@ -20,7 +21,7 @@ export interface ChainConfig {
20
21
  chainId: number;
21
22
  name: string;
22
23
  shortName: string;
23
- usdcAddress: string;
24
+ usdcAddress: string | null;
24
25
  isOriginChain: boolean;
25
26
  isEvm: boolean;
26
27
  explorerUrl?: string;
package/dist/chains.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Centralized chain configuration for ZKP2P Pay.
3
3
  *
4
- * All supported chains and their USDC addresses are defined here.
4
+ * All supported payout chains and their destination token addresses are defined here.
5
5
  * Import this module instead of duplicating chain configs in each app.
6
6
  */
7
7
  // Chain IDs
@@ -9,6 +9,7 @@ export const BASE_CHAIN_ID = 8453;
9
9
  export const ETHEREUM_CHAIN_ID = 1;
10
10
  export const HYPEREVM_CHAIN_ID = 999;
11
11
  export const SOLANA_CHAIN_ID = 792703809;
12
+ export const TRON_CHAIN_ID = 728126428;
12
13
  export const SUPPORTED_DESTINATION_TOKEN_ALIASES = ['USDC', 'USDT'];
13
14
  export const DEFAULT_DESTINATION_TOKEN_ALIAS = 'USDC';
14
15
  // Mainnet chain configurations
@@ -97,6 +98,15 @@ export const MAINNET_CHAINS = {
97
98
  isEvm: false,
98
99
  explorerUrl: 'https://solscan.io',
99
100
  },
101
+ [TRON_CHAIN_ID]: {
102
+ chainId: TRON_CHAIN_ID,
103
+ name: 'Tron',
104
+ shortName: 'TRON',
105
+ usdcAddress: null,
106
+ isOriginChain: false,
107
+ isEvm: false,
108
+ explorerUrl: 'https://tronscan.org/#',
109
+ },
100
110
  };
101
111
  function normalizeChainSelectionKey(value) {
102
112
  return value.trim().toLowerCase().replace(/[^a-z0-9]/g, '');
@@ -123,6 +133,8 @@ const CHAIN_SELECTION_ALIASES = {
123
133
  hl: 999,
124
134
  solana: 792703809,
125
135
  sol: 792703809,
136
+ tron: TRON_CHAIN_ID,
137
+ trx: TRON_CHAIN_ID,
126
138
  };
127
139
  for (const chainId of SUPPORTED_CHAIN_IDS) {
128
140
  const chainConfig = MAINNET_CHAINS[chainId];
@@ -164,6 +176,10 @@ const DESTINATION_TOKEN_ADDRESSES = {
164
176
  999: {
165
177
  usdt: '0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb',
166
178
  },
179
+ // Tron
180
+ [TRON_CHAIN_ID]: {
181
+ usdt: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
182
+ },
167
183
  };
168
184
  function isKnownDestinationTokenAlias(value) {
169
185
  return SUPPORTED_DESTINATION_TOKEN_ALIASES.includes(value);
@@ -245,7 +261,7 @@ export function getOriginChainId() {
245
261
  * Get USDC address for a chain.
246
262
  */
247
263
  export function getUsdcAddress(chainId) {
248
- return getChainConfig(chainId)?.usdcAddress;
264
+ return getChainConfig(chainId)?.usdcAddress ?? undefined;
249
265
  }
250
266
  /**
251
267
  * Resolve destination token alias to chain-specific token address.
package/dist/crypto.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export declare const BITCOIN_CHAIN_ID = 8253038;
2
- export declare const TRON_CHAIN_ID = 728126428;
3
2
  export declare const PAY_CRYPTO_TOKEN_SYMBOLS: readonly ["BTC", "USDC", "USDT", "ETH", "PYUSD", "SOL", "BNB", "HYPE", "WBTC", "USDH"];
4
3
  export type PayCryptoTokenSymbol = (typeof PAY_CRYPTO_TOKEN_SYMBOLS)[number];
5
4
  export declare const DEFAULT_ENABLED_PAY_CRYPTO_TOKENS: PayCryptoTokenSymbol[];
@@ -45,7 +44,7 @@ export declare function getSupportedPayCryptoChainIdsForToken(symbol: PayCryptoT
45
44
  /**
46
45
  * Returns the display name for a chain used by the "Pay with crypto"
47
46
  * flow. Resolves through PAY_CRYPTO_CHAIN_CONFIGS first so Bitcoin
48
- * and Tron (which are not in MAINNET_CHAINS) get the correct label.
47
+ * gets the correct label.
49
48
  * Falls back to getChainName for chains not registered here.
50
49
  */
51
50
  export declare function getPayCryptoChainName(chainId: number): string;
package/dist/crypto.js CHANGED
@@ -1,6 +1,5 @@
1
- import { MAINNET_CHAINS, BASE_CHAIN_ID, ETHEREUM_CHAIN_ID, SOLANA_CHAIN_ID, getChainName, } from './chains.js';
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
- export const TRON_CHAIN_ID = 728126428;
4
3
  export const PAY_CRYPTO_TOKEN_SYMBOLS = [
5
4
  'BTC', // Kept in symbols for DB compatibility with existing sessions
6
5
  'USDC',
@@ -56,11 +55,6 @@ const PAY_CRYPTO_CHAIN_CONFIGS = {
56
55
  name: 'Bitcoin',
57
56
  shortName: 'BTC',
58
57
  },
59
- [TRON_CHAIN_ID]: {
60
- chainId: TRON_CHAIN_ID,
61
- name: 'Tron',
62
- shortName: 'TRON',
63
- },
64
58
  };
65
59
  const CHAIN_TOKEN_CONFIGS = {
66
60
  [BITCOIN_CHAIN_ID]: {
@@ -219,7 +213,7 @@ export function getSupportedPayCryptoChainIdsForToken(symbol, enabledChainIds) {
219
213
  /**
220
214
  * Returns the display name for a chain used by the "Pay with crypto"
221
215
  * flow. Resolves through PAY_CRYPTO_CHAIN_CONFIGS first so Bitcoin
222
- * and Tron (which are not in MAINNET_CHAINS) get the correct label.
216
+ * gets the correct label.
223
217
  * Falls back to getChainName for chains not registered here.
224
218
  */
225
219
  export function getPayCryptoChainName(chainId) {
package/dist/index.d.ts CHANGED
@@ -6,8 +6,12 @@ export * from './rails.js';
6
6
  export * from './buyerTee.js';
7
7
  export * from './bitcoin.js';
8
8
  export * from './addressValidators.js';
9
+ export { doubleSha256, encodeBase58 } from './addressEncoding.js';
9
10
  export * from './fiatAmount.js';
10
11
  export * from './paypalSarPhase.js';
11
12
  export * from './paypalSarVerify.js';
12
13
  export * from './payeeFeeCap.js';
13
14
  export * from './onboarding.js';
15
+ export * from './llmIntegration.js';
16
+ export * from './tiers.js';
17
+ export * from './attribution.js';
package/dist/index.js CHANGED
@@ -6,8 +6,12 @@ export * from './rails.js';
6
6
  export * from './buyerTee.js';
7
7
  export * from './bitcoin.js';
8
8
  export * from './addressValidators.js';
9
+ export { doubleSha256, encodeBase58 } from './addressEncoding.js';
9
10
  export * from './fiatAmount.js';
10
11
  export * from './paypalSarPhase.js';
11
12
  export * from './paypalSarVerify.js';
12
13
  export * from './payeeFeeCap.js';
13
14
  export * from './onboarding.js';
15
+ export * from './llmIntegration.js';
16
+ export * from './tiers.js';
17
+ export * from './attribution.js';
@@ -0,0 +1,111 @@
1
+ import type { OnboardingStepIdValue } from './onboarding.js';
2
+ import type { MerchantEnvironmentType, MerchantIntegrationPathValue, OnboardingStepStatusValue } from './types.js';
3
+ export declare const IntegrationStepActor: {
4
+ readonly AGENT: "AGENT";
5
+ readonly MERCHANT: "MERCHANT";
6
+ };
7
+ export type IntegrationStepActorValue = typeof IntegrationStepActor[keyof typeof IntegrationStepActor];
8
+ /**
9
+ * Which party can satisfy each onboarding step when a coding agent drives the
10
+ * integration. AGENT steps are reachable with an API key alone; MERCHANT steps
11
+ * need a Privy-authenticated dashboard session (tier selection), a human decision
12
+ * (industry, path), or physical action. An agent that retries a MERCHANT step
13
+ * gets 403 with no way to learn the wall is structural, so it is told up front.
14
+ */
15
+ export declare const INTEGRATION_STEP_ACTORS: Readonly<Record<OnboardingStepIdValue, IntegrationStepActorValue>>;
16
+ export type IntegrationDeliveryEvidence = {
17
+ /** WebhookDeliveryStatus. PENDING means queued but not yet attempted. */
18
+ readonly deliveryStatus: string;
19
+ readonly attempts: number;
20
+ readonly responseCode: number | null;
21
+ /**
22
+ * Locally generated transport failure reason — SSRF block, DNS timeout, fetch
23
+ * error. `null` whenever ANY HTTP response was received, including 401 and 500:
24
+ * webhook.service.ts records `responseBody: null` on every received response and
25
+ * never stores receiver body content. Diagnose received responses from
26
+ * `responseCode`; use this field only to explain why nothing was received.
27
+ */
28
+ readonly error: string | null;
29
+ readonly attemptedAt: string | null;
30
+ };
31
+ /**
32
+ * Webhooks are scoped per merchant row, and sandbox is its own row. An agent needs
33
+ * both: the sandbox registration is what a test order actually delivers to, and the
34
+ * live registration is what real payments deliver to (and what the onboarding step
35
+ * reads). Reporting only one hides half the integration.
36
+ */
37
+ export type IntegrationWebhookEvidence = {
38
+ readonly webhookId: string | null;
39
+ readonly webhookUrl: string | null;
40
+ readonly subscribedEvents: readonly string[];
41
+ readonly lastDelivery: IntegrationDeliveryEvidence | null;
42
+ };
43
+ export type IntegrationStepEvidence = {
44
+ readonly sandboxOrderId?: string | null;
45
+ readonly sandboxOrderStatus?: string | null;
46
+ readonly liveOrderId?: string | null;
47
+ readonly canCreateLiveOrders?: boolean;
48
+ readonly sandboxWebhook?: IntegrationWebhookEvidence | null;
49
+ readonly liveWebhook?: IntegrationWebhookEvidence | null;
50
+ readonly tier?: string | null;
51
+ readonly missingFields?: readonly string[];
52
+ };
53
+ export type IntegrationStatusStep = {
54
+ readonly id: OnboardingStepIdValue;
55
+ readonly status: OnboardingStepStatusValue;
56
+ readonly actor: IntegrationStepActorValue;
57
+ readonly evidence: IntegrationStepEvidence;
58
+ };
59
+ export type IntegrationNextAction = {
60
+ readonly stepId: OnboardingStepIdValue;
61
+ readonly actor: IntegrationStepActorValue;
62
+ readonly instruction: string;
63
+ readonly docs: string;
64
+ };
65
+ export type IntegrationStatusResponse = {
66
+ readonly merchant: {
67
+ readonly id: string;
68
+ readonly name: string;
69
+ readonly integrationPath: MerchantIntegrationPathValue | null;
70
+ };
71
+ readonly environmentOfKey: MerchantEnvironmentType;
72
+ /** Onboarding checklist completion. See `verified` before trusting this. */
73
+ readonly complete: boolean;
74
+ /**
75
+ * True only when a sandbox ORDER_FULFILLED delivery has actually been accepted
76
+ * by the merchant's handler.
77
+ *
78
+ * Deliberately independent of `complete`. `GO_LIVE` carries
79
+ * `impliesPrevious: true`, so a single live order forces every earlier step —
80
+ * including `webhook` — to DONE, and `complete` would report a verified
81
+ * integration for a handler that has never successfully received anything.
82
+ * `verified` is the field the agent must gate on.
83
+ */
84
+ readonly verified: boolean;
85
+ /**
86
+ * True when the live merchant row has an active webhook subscribed to
87
+ * ORDER_FULFILLED. This is independent of the broader onboarding webhook step.
88
+ */
89
+ readonly liveWebhookReady: boolean;
90
+ readonly steps: readonly IntegrationStatusStep[];
91
+ readonly nextAction: IntegrationNextAction | null;
92
+ /**
93
+ * Missing agent work in dependency order: sandbox webhook, accepted sandbox
94
+ * delivery, then live webhook plus live order. Independent of registry order.
95
+ */
96
+ readonly nextAgentAction: IntegrationNextAction | null;
97
+ };
98
+ export type SandboxTestOrderRequest = {
99
+ readonly orderId?: string;
100
+ readonly requestedUsdcAmount?: string;
101
+ readonly rail?: string;
102
+ };
103
+ export type SandboxTestOrderResponse = {
104
+ readonly orderId: string;
105
+ readonly status: string;
106
+ readonly paymentId: string;
107
+ };
108
+ export type LlmIntegrationPromptResponse = {
109
+ readonly prompt: string;
110
+ readonly filename: string;
111
+ };
@@ -0,0 +1,28 @@
1
+ import { OnboardingStepId } from './onboarding.js';
2
+ export const IntegrationStepActor = {
3
+ AGENT: 'AGENT',
4
+ MERCHANT: 'MERCHANT',
5
+ };
6
+ /**
7
+ * Which party can satisfy each onboarding step when a coding agent drives the
8
+ * integration. AGENT steps are reachable with an API key alone; MERCHANT steps
9
+ * need a Privy-authenticated dashboard session (tier selection), a human decision
10
+ * (industry, path), or physical action. An agent that retries a MERCHANT step
11
+ * gets 403 with no way to learn the wall is structural, so it is told up front.
12
+ */
13
+ export const INTEGRATION_STEP_ACTORS = {
14
+ [OnboardingStepId.PROFILE]: IntegrationStepActor.MERCHANT,
15
+ [OnboardingStepId.INDUSTRY]: IntegrationStepActor.MERCHANT,
16
+ [OnboardingStepId.PLAN]: IntegrationStepActor.MERCHANT,
17
+ [OnboardingStepId.PATH]: IntegrationStepActor.MERCHANT,
18
+ [OnboardingStepId.RAILS]: IntegrationStepActor.MERCHANT,
19
+ [OnboardingStepId.INTEGRATION]: IntegrationStepActor.AGENT,
20
+ [OnboardingStepId.WEBHOOK]: IntegrationStepActor.AGENT,
21
+ [OnboardingStepId.GO_LIVE]: IntegrationStepActor.AGENT,
22
+ [OnboardingStepId.DOWNLOAD_PLUGIN]: IntegrationStepActor.MERCHANT,
23
+ [OnboardingStepId.PLUGIN_API_KEY]: IntegrationStepActor.MERCHANT,
24
+ [OnboardingStepId.TEST_ORDER]: IntegrationStepActor.MERCHANT,
25
+ [OnboardingStepId.FIRST_QR]: IntegrationStepActor.MERCHANT,
26
+ [OnboardingStepId.LINK_BOT]: IntegrationStepActor.MERCHANT,
27
+ [OnboardingStepId.REQUEST_SETUP]: IntegrationStepActor.MERCHANT,
28
+ };
@@ -1,8 +1,10 @@
1
1
  import type { MerchantIntegrationPathValue, OnboardingStepState, OnboardingStepVerifiedByValue } from './types.js';
2
+ import type { MerchantTierName } from './tiers.js';
2
3
  export type OnboardingFacts = {
3
4
  hasName: boolean;
4
5
  hasLogo: boolean;
5
6
  hasIndustry: boolean;
7
+ hasSelectedTier: boolean;
6
8
  hasIntegrationPath: boolean;
7
9
  hasWebhook: boolean;
8
10
  hasSandboxOrder: boolean;
@@ -14,6 +16,7 @@ export type OnboardingFacts = {
14
16
  export declare const OnboardingStepId: {
15
17
  readonly PROFILE: "profile";
16
18
  readonly INDUSTRY: "industry";
19
+ readonly PLAN: "plan";
17
20
  readonly PATH: "path";
18
21
  readonly RAILS: "rails";
19
22
  readonly INTEGRATION: "integration";
@@ -34,6 +37,21 @@ export type OnboardingStepRegistryEntry = {
34
37
  readonly inChecklist: boolean;
35
38
  readonly impliesPrevious: boolean;
36
39
  };
40
+ export declare const TierCardAction: {
41
+ readonly SELECT: "select";
42
+ readonly REQUEST: "request";
43
+ };
44
+ export type TierCardActionValue = typeof TierCardAction[keyof typeof TierCardAction];
45
+ export type SelfServeMerchantTier = Exclude<MerchantTierName, 'CONCIERGE'>;
46
+ export type TierCopy = {
47
+ readonly displayLabel: string;
48
+ readonly kicker: string;
49
+ readonly price: string;
50
+ readonly features: readonly string[];
51
+ readonly cardAction: TierCardActionValue;
52
+ };
53
+ /** Shared copy and card behavior for every displayable merchant tier. */
54
+ export declare const TIER_COPY: Readonly<Record<MerchantTierName, TierCopy>>;
37
55
  export declare const ONBOARDING_STEP_REGISTRY: readonly OnboardingStepRegistryEntry[];
38
56
  export declare function resolveOnboardingSteps(facts: OnboardingFacts, path: MerchantIntegrationPathValue | null): OnboardingStepState[];
39
57
  export declare function isOnboardingComplete(steps: readonly OnboardingStepState[]): boolean;
@@ -1,7 +1,9 @@
1
1
  import { MerchantIntegrationPath, OnboardingStepStatus, OnboardingStepVerifiedBy, } from './types.js';
2
+ import { TIER_DEFINITIONS, formatTierMonthlyLimitsCopy } from './tiers.js';
2
3
  export const OnboardingStepId = {
3
4
  PROFILE: 'profile',
4
5
  INDUSTRY: 'industry',
6
+ PLAN: 'plan',
5
7
  PATH: 'path',
6
8
  RAILS: 'rails',
7
9
  INTEGRATION: 'integration',
@@ -14,6 +16,54 @@ export const OnboardingStepId = {
14
16
  LINK_BOT: 'linkBot',
15
17
  REQUEST_SETUP: 'requestSetup',
16
18
  };
19
+ export const TierCardAction = {
20
+ SELECT: 'select',
21
+ REQUEST: 'request',
22
+ };
23
+ /** Shared copy and card behavior for every displayable merchant tier. */
24
+ export const TIER_COPY = {
25
+ // features[0] is the plan's monthly cap, formatted from TIER_DEFINITIONS so
26
+ // copy always matches the enforced limits. Every surface leads with it (the
27
+ // landing's rate rows, the onboarding tier cards, billing).
28
+ BASE: {
29
+ displayLabel: 'Base',
30
+ kicker: 'Self-serve',
31
+ price: '2.95% fiat / 1% crypto per successful payment',
32
+ features: [
33
+ formatTierMonthlyLimitsCopy(TIER_DEFINITIONS.BASE.monthlyLimits),
34
+ 'Every platform included; Venmo, Cash App, and PayPal require staking',
35
+ 'Verify payments with the Peer app or extension',
36
+ 'Instant settlement, merchant-funded refunds',
37
+ ],
38
+ cardAction: TierCardAction.SELECT,
39
+ },
40
+ PRO: {
41
+ displayLabel: 'Pro',
42
+ kicker: 'Self-serve',
43
+ price: '4.95% fiat / 1% crypto per successful payment',
44
+ features: [
45
+ formatTierMonthlyLimitsCopy(TIER_DEFINITIONS.PRO.monthlyLimits),
46
+ 'Everything in Base',
47
+ 'Seller Autopilot: customers pay with no Peer app or extension',
48
+ 'Checkout branding and priority support',
49
+ ],
50
+ cardAction: TierCardAction.SELECT,
51
+ },
52
+ CONCIERGE: {
53
+ displayLabel: 'Concierge',
54
+ kicker: 'With our team',
55
+ price: 'Custom pricing',
56
+ features: [
57
+ formatTierMonthlyLimitsCopy(TIER_DEFINITIONS.CONCIERGE.monthlyLimits),
58
+ 'No staking and no lockups',
59
+ 'Negotiated rate or flat-fee invoicing',
60
+ 'Full integration support',
61
+ 'Named support in a dedicated group',
62
+ 'Custom embedded checkout and integrations',
63
+ ],
64
+ cardAction: TierCardAction.REQUEST,
65
+ },
66
+ };
17
67
  export const ONBOARDING_STEP_REGISTRY = [
18
68
  { id: OnboardingStepId.PROFILE, paths: 'ALL', verifiedBy: OnboardingStepVerifiedBy.AUTO, inChecklist: true, impliesPrevious: true },
19
69
  { id: OnboardingStepId.INDUSTRY, paths: 'ALL', verifiedBy: OnboardingStepVerifiedBy.AUTO, inChecklist: true, impliesPrevious: true },
@@ -48,13 +98,6 @@ export const ONBOARDING_STEP_REGISTRY = [
48
98
  inChecklist: true,
49
99
  impliesPrevious: true,
50
100
  },
51
- {
52
- id: OnboardingStepId.GO_LIVE,
53
- paths: [MerchantIntegrationPath.CUSTOM_API, MerchantIntegrationPath.TELEGRAM],
54
- verifiedBy: OnboardingStepVerifiedBy.AUTO,
55
- inChecklist: true,
56
- impliesPrevious: true,
57
- },
58
101
  {
59
102
  id: OnboardingStepId.DOWNLOAD_PLUGIN,
60
103
  paths: [MerchantIntegrationPath.WOOCOMMERCE],
@@ -76,13 +119,6 @@ export const ONBOARDING_STEP_REGISTRY = [
76
119
  inChecklist: true,
77
120
  impliesPrevious: true,
78
121
  },
79
- {
80
- id: OnboardingStepId.GO_LIVE,
81
- paths: [MerchantIntegrationPath.WOOCOMMERCE],
82
- verifiedBy: OnboardingStepVerifiedBy.AUTO,
83
- inChecklist: true,
84
- impliesPrevious: true,
85
- },
86
122
  {
87
123
  id: OnboardingStepId.FIRST_QR,
88
124
  paths: [MerchantIntegrationPath.IN_PERSON],
@@ -99,17 +135,39 @@ export const ONBOARDING_STEP_REGISTRY = [
99
135
  inChecklist: true,
100
136
  impliesPrevious: true,
101
137
  },
138
+ {
139
+ id: OnboardingStepId.PLAN,
140
+ paths: 'ALL',
141
+ verifiedBy: OnboardingStepVerifiedBy.AUTO,
142
+ inChecklist: true,
143
+ // Deliberately the last ask before go-live: a tier is only required for LIVE
144
+ // orders, and it can also be selected out-of-band (billing page, admin), so a
145
+ // done plan step must not imply the integration steps that now precede it.
146
+ impliesPrevious: false,
147
+ },
148
+ {
149
+ id: OnboardingStepId.GO_LIVE,
150
+ paths: [
151
+ MerchantIntegrationPath.CUSTOM_API,
152
+ MerchantIntegrationPath.TELEGRAM,
153
+ MerchantIntegrationPath.WOOCOMMERCE,
154
+ ],
155
+ verifiedBy: OnboardingStepVerifiedBy.AUTO,
156
+ inChecklist: true,
157
+ impliesPrevious: true,
158
+ },
102
159
  ];
103
160
  const autoStepPredicates = {
104
161
  [OnboardingStepId.PROFILE]: (facts) => facts.hasName && facts.hasLogo,
105
162
  [OnboardingStepId.INDUSTRY]: (facts) => facts.hasIndustry,
163
+ [OnboardingStepId.PLAN]: (facts) => facts.hasSelectedTier,
106
164
  [OnboardingStepId.PATH]: (facts) => facts.hasIntegrationPath,
107
165
  [OnboardingStepId.RAILS]: () => true,
108
166
  [OnboardingStepId.INTEGRATION]: (facts) => facts.hasSandboxFulfilledOrder,
109
167
  [OnboardingStepId.WEBHOOK]: (facts) => facts.hasWebhook,
110
168
  [OnboardingStepId.TEST_ORDER]: (facts) => facts.hasSandboxFulfilledOrder,
111
169
  [OnboardingStepId.LINK_BOT]: (facts) => facts.hasSandboxFulfilledOrder,
112
- [OnboardingStepId.GO_LIVE]: (facts) => facts.hasLiveFulfilledOrder,
170
+ [OnboardingStepId.GO_LIVE]: (facts) => facts.hasLiveOrder,
113
171
  [OnboardingStepId.FIRST_QR]: (facts) => facts.hasSandboxOrder,
114
172
  };
115
173
  export function resolveOnboardingSteps(facts, path) {
@@ -0,0 +1,57 @@
1
+ import { type FeeTranche, type MerchantPaymentFlowModeType, type MerchantTierName } from './types.js';
2
+ export type { MerchantTierName } from './types.js';
3
+ export declare const TierChangeSource: {
4
+ readonly SELF_SERVE: "SELF_SERVE";
5
+ readonly ADMIN: "ADMIN";
6
+ readonly MIGRATION: "MIGRATION";
7
+ };
8
+ export type TierChangeSourceType = typeof TierChangeSource[keyof typeof TierChangeSource];
9
+ /**
10
+ * A fee config resolved to concrete numbers. `mode: 'default'` is deliberately
11
+ * unrepresentable — it would resolve against a mutable global at read time.
12
+ *
13
+ * `tranches` IS included: it is frozen data, just amount-parameterised. The bracket
14
+ * is chosen from the order amount at resolution time by `feeService`, so it cannot be
15
+ * flattened to a single `valueBps` at snapshot time.
16
+ */
17
+ export type MaterializedFeeConfig = {
18
+ readonly mode: 'exempt';
19
+ } | {
20
+ readonly mode: 'flat';
21
+ readonly currency: 'USDC';
22
+ readonly valueBps: number;
23
+ } | {
24
+ readonly mode: 'tranches';
25
+ readonly currency: 'USDC';
26
+ readonly tranches: readonly FeeTranche[];
27
+ };
28
+ /** Monthly caps for a self-serve tier. `volumeUsdc` is a decimal string in whole USDC. */
29
+ export type TierMonthlyLimits = {
30
+ readonly volumeUsdc: string;
31
+ readonly orderCount: number;
32
+ };
33
+ type TierFeeDefinition = {
34
+ readonly fiatFeeConfig: MaterializedFeeConfig;
35
+ readonly cryptoFeeConfig: MaterializedFeeConfig;
36
+ } | {
37
+ /** Paired nulls mean commercial terms are admin-configured and must never be overwritten. */
38
+ readonly fiatFeeConfig: null;
39
+ readonly cryptoFeeConfig: null;
40
+ };
41
+ export type TierDefinition = TierFeeDefinition & {
42
+ readonly quotePreference: MerchantPaymentFlowModeType | null;
43
+ /** Policy-role name in the support dashboard. A string literal — SupportPolicyRole is a type alias, not a runtime enum. */
44
+ readonly supportPolicyRole: string;
45
+ /** Monthly order-volume / order-count caps enforced at order creation. null = unlimited. */
46
+ readonly monthlyLimits: TierMonthlyLimits | null;
47
+ /** Marketing/display copy only — render verbatim, never parse or branch on entries. */
48
+ readonly features: readonly string[];
49
+ };
50
+ export declare const TIER_DEFINITIONS: Readonly<Record<MerchantTierName, TierDefinition>>;
51
+ /** Server-side allowlist for self-serve tier selection — the only authority; `features` copy gates nothing. */
52
+ export declare const SELF_SERVE_TIERS: readonly MerchantTierName[];
53
+ /**
54
+ * Formats a tier's monthly caps for display. `volumeUsdc` is a whole-USDC decimal string;
55
+ * exact thousands render as `$10K`, anything else as `$2,500`.
56
+ */
57
+ export declare function formatTierMonthlyLimitsCopy(limits: TierMonthlyLimits | null): string;
package/dist/tiers.js ADDED
@@ -0,0 +1,57 @@
1
+ import { MerchantPaymentFlowMode, } from './types.js';
2
+ export const TierChangeSource = {
3
+ SELF_SERVE: 'SELF_SERVE',
4
+ ADMIN: 'ADMIN',
5
+ MIGRATION: 'MIGRATION',
6
+ };
7
+ export const TIER_DEFINITIONS = {
8
+ BASE: {
9
+ fiatFeeConfig: { mode: 'flat', currency: 'USDC', valueBps: 295 },
10
+ cryptoFeeConfig: { mode: 'flat', currency: 'USDC', valueBps: 100 },
11
+ quotePreference: MerchantPaymentFlowMode.PREFER_BUYER_TEE,
12
+ supportPolicyRole: 'BASIC_MERCHANT',
13
+ monthlyLimits: { volumeUsdc: '10000', orderCount: 50 },
14
+ features: [
15
+ '2.95% fiat / 1% crypto platform fees',
16
+ 'Staking required before transacting on chargebackable rails',
17
+ ],
18
+ },
19
+ PRO: {
20
+ fiatFeeConfig: { mode: 'flat', currency: 'USDC', valueBps: 495 },
21
+ cryptoFeeConfig: { mode: 'flat', currency: 'USDC', valueBps: 100 },
22
+ quotePreference: MerchantPaymentFlowMode.PREFER_SAR,
23
+ supportPolicyRole: 'PRO_MERCHANT',
24
+ monthlyLimits: { volumeUsdc: '30000', orderCount: 100 },
25
+ features: [
26
+ '4.95% fiat / 1% crypto platform fees',
27
+ 'Staking required before transacting on chargebackable rails',
28
+ ],
29
+ },
30
+ CONCIERGE: {
31
+ fiatFeeConfig: null,
32
+ cryptoFeeConfig: null,
33
+ quotePreference: null,
34
+ supportPolicyRole: 'CONCIERGE_MERCHANT',
35
+ monthlyLimits: null,
36
+ features: [
37
+ 'Custom commercial terms',
38
+ 'Flat-fee pricing available (admin-configured per merchant)',
39
+ ],
40
+ },
41
+ };
42
+ /** Server-side allowlist for self-serve tier selection — the only authority; `features` copy gates nothing. */
43
+ export const SELF_SERVE_TIERS = ['BASE', 'PRO'];
44
+ /**
45
+ * Formats a tier's monthly caps for display. `volumeUsdc` is a whole-USDC decimal string;
46
+ * exact thousands render as `$10K`, anything else as `$2,500`.
47
+ */
48
+ export function formatTierMonthlyLimitsCopy(limits) {
49
+ if (limits === null) {
50
+ return 'Unlimited volume and orders';
51
+ }
52
+ const whole = limits.volumeUsdc.split('.')[0] ?? '0';
53
+ const volume = whole.length > 3 && /^\d+000$/.test(whole)
54
+ ? `$${whole.slice(0, -3)}K`
55
+ : `$${whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`;
56
+ return `Up to ${volume} volume and ${limits.orderCount} orders per month`;
57
+ }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { PayCryptoTokenSymbol } from './crypto.js';
2
2
  import type { ProofMode } from './buyerTee.js';
3
+ import type { OnboardingStepIdValue } from './onboarding.js';
3
4
  export declare const PaymentPlatform: {
4
5
  readonly VENMO: "venmo";
5
6
  readonly CASHAPP: "cashapp";
@@ -91,6 +92,46 @@ export declare const CheckoutPaymentStatus: {
91
92
  readonly FAILED: "FAILED";
92
93
  };
93
94
  export type CheckoutPaymentStatusType = typeof CheckoutPaymentStatus[keyof typeof CheckoutPaymentStatus];
95
+ export declare const PaymentChargebackStatus: {
96
+ readonly NONE: "NONE";
97
+ readonly CHARGEBACKED: "CHARGEBACKED";
98
+ };
99
+ export type PaymentChargebackStatusType = typeof PaymentChargebackStatus[keyof typeof PaymentChargebackStatus];
100
+ export declare const OrderChargebackStatus: {
101
+ readonly NONE: "NONE";
102
+ readonly PARTIALLY_CHARGEBACKED: "PARTIALLY_CHARGEBACKED";
103
+ readonly CHARGEBACKED: "CHARGEBACKED";
104
+ };
105
+ export type OrderChargebackStatusType = typeof OrderChargebackStatus[keyof typeof OrderChargebackStatus];
106
+ export declare const ChargebackRefundOverlap: {
107
+ readonly NONE: "NONE";
108
+ readonly PENDING_HALTED: "PENDING_HALTED";
109
+ readonly SUBMITTING_OR_UNKNOWN: "SUBMITTING_OR_UNKNOWN";
110
+ readonly ALREADY_SUBMITTED: "ALREADY_SUBMITTED";
111
+ readonly ALREADY_REFUNDED: "ALREADY_REFUNDED";
112
+ };
113
+ export type ChargebackRefundOverlapType = typeof ChargebackRefundOverlap[keyof typeof ChargebackRefundOverlap];
114
+ export declare const PaymentBridgeStatus: {
115
+ readonly PENDING: "PENDING";
116
+ readonly SUBMITTED: "SUBMITTED";
117
+ readonly COMPLETED: "COMPLETED";
118
+ readonly FAILED: "FAILED";
119
+ };
120
+ export type PaymentBridgeStatusType = typeof PaymentBridgeStatus[keyof typeof PaymentBridgeStatus];
121
+ export type PaymentChargebackFact = {
122
+ paymentId: string;
123
+ intentHash: string;
124
+ disputeId: string;
125
+ status: typeof PaymentChargebackStatus.CHARGEBACKED;
126
+ compensatedUsdcAmount: string;
127
+ disputedAt: string;
128
+ disputeTxHash: string;
129
+ refundOverlap: ChargebackRefundOverlapType;
130
+ bridgeOrSweepOverlap: null | {
131
+ paymentBridgeId: string;
132
+ status: PaymentBridgeStatusType;
133
+ };
134
+ };
94
135
  export declare const CheckoutPaymentMemoPolicy: {
95
136
  readonly NONE: "NONE";
96
137
  readonly REQUIRED: "REQUIRED";
@@ -159,6 +200,8 @@ export type CheckoutOrder = {
159
200
  id: string;
160
201
  merchantId: string;
161
202
  status: CheckoutOrderStatusType;
203
+ chargebackStatus: OrderChargebackStatusType;
204
+ chargebacks: PaymentChargebackFact[];
162
205
  refundStatus: CheckoutOrderRefundStatusType;
163
206
  refundAmountUsdc: string | null;
164
207
  refundDepositId: string | null;
@@ -268,6 +311,8 @@ export type CheckoutPayment = {
268
311
  id: string;
269
312
  orderId: string;
270
313
  status: CheckoutPaymentStatusType;
314
+ chargebackStatus: PaymentChargebackStatusType;
315
+ chargeback: PaymentChargebackFact | null;
271
316
  rail: string;
272
317
  paymentMethodId: string | null;
273
318
  proofMode: ProofMode;
@@ -319,6 +364,7 @@ export type CreatePaymentRequest = {
319
364
  };
320
365
  export type CheckoutPaymentCreationResponse = CheckoutPayment & {
321
366
  relayerTransactionId?: string;
367
+ recipientRecovery?: 'matched' | 'missed' | 'not_applicable';
322
368
  };
323
369
  export type PaymentDeeplinkResponse = {
324
370
  paymentId: string;
@@ -359,6 +405,7 @@ export type MerchantUser = {
359
405
  createdAt: string;
360
406
  updatedAt: string | null;
361
407
  };
408
+ export type MerchantTierName = 'BASE' | 'PRO' | 'CONCIERGE';
362
409
  export type MerchantProfile = {
363
410
  id: string;
364
411
  name: string;
@@ -376,7 +423,7 @@ export type MerchantProfile = {
376
423
  solanaWalletAddress: string | null;
377
424
  v1EvmWalletAddress: string | null;
378
425
  v1SolanaWalletAddress: string | null;
379
- tier: 'FREE' | 'PRO';
426
+ tier: MerchantTierName | null;
380
427
  createdAt: string;
381
428
  updatedAt: string | null;
382
429
  merchantConfig: MerchantConfig | null;
@@ -399,6 +446,12 @@ export declare const OrderErrorCode: {
399
446
  readonly UNKNOWN_ERROR: "UNKNOWN_ERROR";
400
447
  };
401
448
  export type OrderErrorCodeType = typeof OrderErrorCode[keyof typeof OrderErrorCode];
449
+ export type PaymentSigningErrorCode = 'MERCHANT_WALLET_NOT_DELEGATED' | 'MERCHANT_SIGNER_WALLET_MISSING' | 'MERCHANT_SIGNING_UNAVAILABLE';
450
+ export declare const SubmissionChannel: {
451
+ readonly OZ_RELAYER: "OZ_RELAYER";
452
+ readonly MERCHANT_PRIVY: "MERCHANT_PRIVY";
453
+ };
454
+ export type SubmissionChannelType = (typeof SubmissionChannel)[keyof typeof SubmissionChannel];
402
455
  export declare const MerchantEnvironment: {
403
456
  readonly LIVE: "LIVE";
404
457
  readonly SANDBOX: "SANDBOX";
@@ -701,7 +754,7 @@ export interface BridgeStatusResponse {
701
754
  failureMessage?: string;
702
755
  }
703
756
  export interface OrderBridgeInfo {
704
- status: 'PENDING' | 'SUBMITTED' | 'COMPLETED' | 'FAILED';
757
+ status: PaymentBridgeStatusType;
705
758
  destinationChainId: string;
706
759
  outputAmount?: string;
707
760
  estimatedOutputAmount?: string;
@@ -730,7 +783,7 @@ export declare const OnboardingStepVerifiedBy: {
730
783
  };
731
784
  export type OnboardingStepVerifiedByValue = typeof OnboardingStepVerifiedBy[keyof typeof OnboardingStepVerifiedBy];
732
785
  export type OnboardingStepState = {
733
- id: string;
786
+ id: OnboardingStepIdValue;
734
787
  status: OnboardingStepStatusValue;
735
788
  verifiedBy: OnboardingStepVerifiedByValue;
736
789
  };
@@ -757,6 +810,9 @@ export declare const WebhookEventType: {
757
810
  readonly PAYMENT_BRIDGE_SUBMITTED: "PAYMENT_BRIDGE_SUBMITTED";
758
811
  readonly PAYMENT_BRIDGE_COMPLETED: "PAYMENT_BRIDGE_COMPLETED";
759
812
  readonly PAYMENT_BRIDGE_FAILED: "PAYMENT_BRIDGE_FAILED";
813
+ readonly PAYMENT_CHARGEBACKED: "PAYMENT_CHARGEBACKED";
814
+ readonly ORDER_PARTIALLY_CHARGEBACKED: "ORDER_PARTIALLY_CHARGEBACKED";
815
+ readonly ORDER_CHARGEBACKED: "ORDER_CHARGEBACKED";
760
816
  };
761
817
  export type WebhookEventTypeValue = typeof WebhookEventType[keyof typeof WebhookEventType];
762
818
  export declare const WebhookDeliveryStatus: {
@@ -800,6 +856,13 @@ export type WebhookPayload = {
800
856
  payment: CheckoutPayment | null;
801
857
  refund: Record<string, unknown> | null;
802
858
  paymentBridge: Record<string, unknown> | null;
859
+ trigger?: {
860
+ type: 'CHARGEBACK_APPLIED';
861
+ chargeback: PaymentChargebackFact;
862
+ } | {
863
+ type: 'PAYMENT_SETTLED';
864
+ paymentId: string;
865
+ };
803
866
  resize?: {
804
867
  previousAmountUsdc: string;
805
868
  newAmountUsdc: string;
package/dist/types.js CHANGED
@@ -85,6 +85,28 @@ export const CheckoutPaymentStatus = {
85
85
  EXPIRED: 'EXPIRED',
86
86
  FAILED: 'FAILED',
87
87
  };
88
+ export const PaymentChargebackStatus = {
89
+ NONE: 'NONE',
90
+ CHARGEBACKED: 'CHARGEBACKED',
91
+ };
92
+ export const OrderChargebackStatus = {
93
+ NONE: 'NONE',
94
+ PARTIALLY_CHARGEBACKED: 'PARTIALLY_CHARGEBACKED',
95
+ CHARGEBACKED: 'CHARGEBACKED',
96
+ };
97
+ export const ChargebackRefundOverlap = {
98
+ NONE: 'NONE',
99
+ PENDING_HALTED: 'PENDING_HALTED',
100
+ SUBMITTING_OR_UNKNOWN: 'SUBMITTING_OR_UNKNOWN',
101
+ ALREADY_SUBMITTED: 'ALREADY_SUBMITTED',
102
+ ALREADY_REFUNDED: 'ALREADY_REFUNDED',
103
+ };
104
+ export const PaymentBridgeStatus = {
105
+ PENDING: 'PENDING',
106
+ SUBMITTED: 'SUBMITTED',
107
+ COMPLETED: 'COMPLETED',
108
+ FAILED: 'FAILED',
109
+ };
88
110
  export const CheckoutPaymentMemoPolicy = {
89
111
  NONE: 'NONE',
90
112
  REQUIRED: 'REQUIRED',
@@ -113,6 +135,10 @@ export const OrderErrorCode = {
113
135
  SDK_ERROR: 'SDK_ERROR', // Unknown SDK error
114
136
  UNKNOWN_ERROR: 'UNKNOWN_ERROR', // Uncategorized error
115
137
  };
138
+ export const SubmissionChannel = {
139
+ OZ_RELAYER: 'OZ_RELAYER',
140
+ MERCHANT_PRIVY: 'MERCHANT_PRIVY',
141
+ };
116
142
  export const MerchantEnvironment = {
117
143
  LIVE: 'LIVE',
118
144
  SANDBOX: 'SANDBOX',
@@ -154,6 +180,9 @@ export const WebhookEventType = {
154
180
  PAYMENT_BRIDGE_SUBMITTED: 'PAYMENT_BRIDGE_SUBMITTED',
155
181
  PAYMENT_BRIDGE_COMPLETED: 'PAYMENT_BRIDGE_COMPLETED',
156
182
  PAYMENT_BRIDGE_FAILED: 'PAYMENT_BRIDGE_FAILED',
183
+ PAYMENT_CHARGEBACKED: 'PAYMENT_CHARGEBACKED',
184
+ ORDER_PARTIALLY_CHARGEBACKED: 'ORDER_PARTIALLY_CHARGEBACKED',
185
+ ORDER_CHARGEBACKED: 'ORDER_CHARGEBACKED',
157
186
  };
158
187
  export const WebhookDeliveryStatus = {
159
188
  PENDING: 'PENDING',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zkp2p/pay-shared",
3
- "version": "3.1.0",
3
+ "version": "4.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",