@zkp2p/pay-shared 4.0.1 → 6.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.
@@ -0,0 +1,53 @@
1
+ import { computeDisplayedFiatAmount } from './fiatAmount.js';
2
+ const MAX_RAIL_SPREAD = 0.03; // ±3% around the live FX rate
3
+ const SPREAD_BUCKETS = 601; // integer granularity for the deterministic hash
4
+ /**
5
+ * Applies a deterministic per-rail spread (±3%) on top of the live fiat-per-USD
6
+ * rate so sandbox quotes vary by payment method. The spread is hashed from the
7
+ * rail identifier + currency so refreshes are stable and different merchants
8
+ * see consistent rates, without any rail getting an unrealistic 0% spread.
9
+ */
10
+ export function applySandboxRailSpread(baseRate, rail, currency) {
11
+ const seed = `${rail.trim().toLowerCase()}|${currency.trim().toUpperCase()}`;
12
+ let hash = 0;
13
+ for (let i = 0; i < seed.length; i += 1) {
14
+ hash = ((hash * 31) + seed.charCodeAt(i)) | 0;
15
+ }
16
+ const bucket = Math.abs(hash) % SPREAD_BUCKETS; // [0, 600]
17
+ const normalized = (bucket - (SPREAD_BUCKETS - 1) / 2) / ((SPREAD_BUCKETS - 1) / 2); // [-1, 1]
18
+ const spread = normalized * MAX_RAIL_SPREAD;
19
+ return baseRate * (1 + spread);
20
+ }
21
+ function decimalUnits(value, decimals) {
22
+ const [whole, fraction = ''] = value.split('.');
23
+ return BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction.slice(0, decimals).padEnd(decimals, '0'));
24
+ }
25
+ function usdFromUnits(units) {
26
+ return `${units / 1000000n}.${(units % 1000000n).toString().padStart(6, '0')}`;
27
+ }
28
+ /** Deterministic sandbox maker spread and fee-share sizing, shared with the CLI. */
29
+ export function buildSandboxSplitFiatAmounts(input) {
30
+ const rawRate = String(applySandboxRailSpread(Number(input.currencyPerUsdRate), input.rail, input.fiatCurrency));
31
+ const [whole, fraction = ''] = rawRate.split('.');
32
+ const conversionRate = fraction === '' ? whole : `${whole}.${fraction.slice(0, 6)}`;
33
+ const rate = decimalUnits(conversionRate, 18);
34
+ const peg = decimalUnits(input.currencyPerUsdRate, 18);
35
+ const share = BigInt(input.buyerFeeShareBps);
36
+ const denominator = (10000n - share) * rate * 10000n + share * (10000n - BigInt(input.totalReferralFeeBps)) * peg;
37
+ if (denominator <= 0n)
38
+ throw new Error('Split sandbox quote has no positive principal contribution');
39
+ const numerator = decimalUnits(input.principalUsd, 6) * 100000000n * peg;
40
+ const signalIntentAmountBaseUnits = ((numerator + denominator - 1n) / denominator).toString();
41
+ return { conversionRate, signalIntentAmountBaseUnits,
42
+ ...settleSandboxSplitFiatAmounts({ signalIntentAmountBaseUnits, conversionRateDecimal: conversionRate }) };
43
+ }
44
+ /** Preserve the saved signal for a full payment; scale a partial fiat payment at its saved maker rate. */
45
+ export function settleSandboxSplitFiatAmounts(input) {
46
+ const paymentAmount = input.paidFiatAmount ?? computeDisplayedFiatAmount({
47
+ feePayer: 'SPLIT', signalIntentAmountBaseUnits: input.signalIntentAmountBaseUnits,
48
+ conversionRateDecimal: input.conversionRateDecimal, remainingUsdcAmount: '1', currencyPerUsdRate: '1', paymentAmountFallback: '0',
49
+ });
50
+ const grossUnits = input.paidFiatAmount === undefined ? BigInt(input.signalIntentAmountBaseUnits)
51
+ : decimalUnits(paymentAmount, 18) * 1000000n / decimalUnits(input.conversionRateDecimal, 18);
52
+ return { paymentAmount, grossUsdcAmount: usdFromUnits(grossUnits) };
53
+ }
package/dist/tiers.d.ts CHANGED
@@ -4,6 +4,8 @@ export declare const TierChangeSource: {
4
4
  readonly SELF_SERVE: "SELF_SERVE";
5
5
  readonly ADMIN: "ADMIN";
6
6
  readonly MIGRATION: "MIGRATION";
7
+ /** A Master Merchant Account sub-merchant copied its Master Merchant Account's tier at creation. */
8
+ readonly SUB_MERCHANT: "SUB_MERCHANT";
7
9
  };
8
10
  export type TierChangeSourceType = typeof TierChangeSource[keyof typeof TierChangeSource];
9
11
  /**
@@ -60,7 +62,7 @@ export declare function canUseScanToPay(input: {
60
62
  readonly environment: MerchantEnvironmentType;
61
63
  }): boolean;
62
64
  /**
63
- * Formats a tier's monthly caps for display. `volumeUsdc` is a whole-USDC decimal string;
65
+ * Formats a tier's monthly caps for display; caps count fiat-paid orders only. `volumeUsdc` is a whole-USDC decimal string;
64
66
  * exact thousands render as `$10K`, anything else as `$2,500`.
65
67
  */
66
68
  export declare function formatTierMonthlyLimitsCopy(limits: TierMonthlyLimits | null): string;
package/dist/tiers.js CHANGED
@@ -3,6 +3,8 @@ export const TierChangeSource = {
3
3
  SELF_SERVE: 'SELF_SERVE',
4
4
  ADMIN: 'ADMIN',
5
5
  MIGRATION: 'MIGRATION',
6
+ /** A Master Merchant Account sub-merchant copied its Master Merchant Account's tier at creation. */
7
+ SUB_MERCHANT: 'SUB_MERCHANT',
6
8
  };
7
9
  export const TIER_DEFINITIONS = {
8
10
  BASE: {
@@ -50,7 +52,7 @@ export function canUseScanToPay(input) {
50
52
  return input.environment === 'SANDBOX' || input.tier === 'PRO' || input.tier === 'CONCIERGE';
51
53
  }
52
54
  /**
53
- * Formats a tier's monthly caps for display. `volumeUsdc` is a whole-USDC decimal string;
55
+ * Formats a tier's monthly caps for display; caps count fiat-paid orders only. `volumeUsdc` is a whole-USDC decimal string;
54
56
  * exact thousands render as `$10K`, anything else as `$2,500`.
55
57
  */
56
58
  export function formatTierMonthlyLimitsCopy(limits) {
@@ -61,5 +63,5 @@ export function formatTierMonthlyLimitsCopy(limits) {
61
63
  const volume = whole.length > 3 && /^\d+000$/.test(whole)
62
64
  ? `$${whole.slice(0, -3)}K`
63
65
  : `$${whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`;
64
- return `Up to ${volume} volume and ${limits.orderCount} orders per month`;
66
+ return `Up to ${volume} fiat volume and ${limits.orderCount} fiat orders per month`;
65
67
  }