@solvapay/react 2.2.2 → 2.3.0-preview-e77af6c08d8a185342e1a74903c8545b94a9f51d

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/CHANGELOG.md CHANGED
@@ -1,10 +1,31 @@
1
1
  # @solvapay/react changelog
2
2
 
3
- ## 2.2.2
3
+ ## 2.3.0-preview-e77af6c08d8a185342e1a74903c8545b94a9f51d
4
+
5
+ ### Minor Changes
6
+
7
+ - 227c25e: Drop Stripe's duplicate mandate line from the MCP payment surfaces. Enabling auto-recharge makes the backend set `setup_future_usage`, which made Stripe's `PaymentElement` render its own terms sentence ("you allow <merchant> to charge your card for future payments…") between the card fields and the country selector — a second authorization in Stripe's wording, directly above the `MandateText` that already states the charge. `McpTopupView` and the checkout PAYG step now pass `terms: { card: 'never' }` and own the full mandate.
8
+
9
+ `MandateText` takes a new `savesPaymentMethod` prop; the `topup` mandate template appends a saved-card sentence when it is set, so the card-storage disclosure moves into SolvaPay's copy rather than disappearing. `MandateContext` gains a matching optional `savesPaymentMethod` field for custom copy bundles. The default is unchanged for every other call site: `DEFAULT_PAYMENT_ELEMENT_OPTIONS` still leaves Stripe's terms on, so integrators composing `TopupForm` without `MandateText` keep Stripe's line.
4
10
 
5
11
  ### Patch Changes
6
12
 
13
+ - b3b5b72: Checkout PAYG amount step now collects the same inline auto-recharge toggle as top-up. The account tool no longer advertises `view: auto-recharge`; leftover stamps still open the account surface.
14
+ - c3e5135: MCP checkout and top-up now always show plans and amounts. The Stripe CSP probe still starts at mount so Stripe.js can warm in the background, but a blocked or slow host only falls back to hosted checkout at the payment step — a cold Stripe.js load can no longer hide the plan picker. Passing `publishableKey={null}` keeps the same plan/amount steps and forces the hosted handoff only when the customer continues to pay.
15
+
16
+ A blocked host now costs one extra click (pick a plan or amount, then get the hosted handoff). The cancelled-purchase notice that lives inside the hosted fallback also appears at the payment step rather than immediately on the checkout surface.
17
+
7
18
  - 567db3e: The customer's active plan is no longer selectable in the plan picker. The active PAYG plan is no longer auto-selected — topping up happens from the account surface, not by re-activating the current plan from the list.
19
+ - 35da494: MCP account now shows real auto-recharge status and links out to the hosted portal form. Top-up `create_payment_intent` forwards `autoRecharge` so the inline toggle actually persists. `McpAutoRechargeView` and the `views.autoRecharge` override are removed — `view: 'auto-recharge'` renders the account surface.
20
+ - b3b5b72: Top-up success is now a terminal receipt (matching checkout): no back link, "Add more credits", or portal CTA. The conversation continues via `notifySuccess` instead of routing back to a stale account snapshot.
21
+ - Updated dependencies [c67e78e]
22
+ - Updated dependencies [dbd0108]
23
+ - Updated dependencies [b3b5b72]
24
+ - Updated dependencies [35da494]
25
+ - Updated dependencies [c67e78e]
26
+ - Updated dependencies [c67e78e]
27
+ - Updated dependencies [c67e78e]
28
+ - @solvapay/mcp-core@0.4.2-preview-e77af6c08d8a185342e1a74903c8545b94a9f51d
8
29
 
9
30
  ## 2.2.1
10
31
 
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { _ as UsageSnapshot } from './useUsage-DSPWQ37m.js';
2
+ import { _ as UsageSnapshot } from './useUsage-CwvxnICf.js';
3
3
 
4
4
  /**
5
5
  * `<UsageMeter>` compound primitive — renders the authenticated customer's
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { _ as UsageSnapshot } from './useUsage-CRd_E6J3.cjs';
2
+ import { _ as UsageSnapshot } from './useUsage-DNXxsmyG.cjs';
3
3
 
4
4
  /**
5
5
  * `<UsageMeter>` compound primitive — renders the authenticated customer's
@@ -377,7 +377,8 @@ var enCopy = {
377
377
  },
378
378
  topup: (ctx) => {
379
379
  const product = ctx.product?.name ? ` to add credits to your ${ctx.product.name} balance` : " to add credits to your balance";
380
- return `By confirming, you authorize ${ctx.merchant.legalName} to charge ${ctx.amountFormatted}${product}. Credits are non-refundable once used. Payments are processed by SolvaPay.${termsSentence(ctx)}`;
380
+ const savedCard = ctx.savesPaymentMethod ? ` You also authorize ${ctx.merchant.legalName} to save this card and charge it for future auto-recharges, which you can turn off any time.` : "";
381
+ return `By confirming, you authorize ${ctx.merchant.legalName} to charge ${ctx.amountFormatted}${product}. Credits are non-refundable once used.${savedCard} Payments are processed by SolvaPay.${termsSentence(ctx)}`;
381
382
  },
382
383
  usageMetered: (ctx) => {
383
384
  const measures = ctx.plan?.measures ?? "request";
@@ -616,8 +617,10 @@ var enCopy = {
616
617
  addFunds: "Add funds",
617
618
  autoRechargeOn: "Auto-recharge on",
618
619
  autoRechargeOff: "Auto-recharge off",
619
- turnOn: "Turn on \u2192",
620
- manage: "Manage \u2192",
620
+ autoRechargePending: "Auto-recharge starts once this payment clears",
621
+ turnOn: "Turn on",
622
+ manage: "Manage",
623
+ fixCard: "Fix card",
621
624
  autoRechargeOffCaption: "Calls fail the moment the balance runs out.",
622
625
  autoRechargeOffFixCaption: "Turning it on stops this happening again.",
623
626
  callsFailingCaption: "The plan is active, but calls fail until you add credits.",
@@ -3126,6 +3129,7 @@ var MandateText = forwardRef4(
3126
3129
  mode,
3127
3130
  amountMinor,
3128
3131
  currency,
3132
+ savesPaymentMethod,
3129
3133
  asChild,
3130
3134
  children,
3131
3135
  ...rest
@@ -3167,9 +3171,10 @@ var MandateText = forwardRef4(
3167
3171
  } : void 0,
3168
3172
  product: product ? { name: product.name } : void 0,
3169
3173
  amountFormatted,
3170
- trialDays: plan?.trialDays
3174
+ trialDays: plan?.trialDays,
3175
+ savesPaymentMethod
3171
3176
  }),
3172
- [merchant, plan, product, amountFormatted]
3177
+ [merchant, plan, product, amountFormatted, savesPaymentMethod]
3173
3178
  );
3174
3179
  const template = copy.mandate[resolvedVariant];
3175
3180
  const text = typeof template === "function" ? template(ctx) : template;
@@ -7037,183 +7042,6 @@ function useUsageMeter() {
7037
7042
  return useUsageMeterCtx("useUsageMeter");
7038
7043
  }
7039
7044
 
7040
- // src/hooks/useAutoRecharge.ts
7041
- import { useCallback as useCallback21, useEffect as useEffect14, useRef as useRef9, useState as useState18 } from "react";
7042
- function mergeAutoRechargeConfig(config, display) {
7043
- return display ? { ...config, display } : config;
7044
- }
7045
- async function fetchAutoRecharge(config) {
7046
- const transport = config?.transport ?? createHttpTransport(config);
7047
- if (!transport.getAutoRecharge) return null;
7048
- const response = await transport.getAutoRecharge();
7049
- if (!response.config) return null;
7050
- return mergeAutoRechargeConfig(response.config, response.display);
7051
- }
7052
- function useAutoRecharge() {
7053
- const { _config } = useSolvaPay();
7054
- const key = autoRechargeCacheKeyFor(_config);
7055
- const [config, setConfig] = useState18(
7056
- () => autoRechargeCache.get(key)?.config ?? null
7057
- );
7058
- const [loading, setLoading] = useState18(() => {
7059
- const cached = autoRechargeCache.get(key);
7060
- return !cached || !cached.config && !cached.promise;
7061
- });
7062
- const [saving, setSaving] = useState18(false);
7063
- const [disabling, setDisabling] = useState18(false);
7064
- const [error, setError] = useState18(null);
7065
- const requestSeq = useRef9(0);
7066
- const load = useCallback21(
7067
- async (force = false) => {
7068
- const seq = requestSeq.current;
7069
- const cached = autoRechargeCache.get(key);
7070
- const now = Date.now();
7071
- if (!force && cached?.config && now - cached.timestamp < CACHE_DURATION2) {
7072
- if (seq !== requestSeq.current) return;
7073
- setConfig(cached.config);
7074
- setLoading(false);
7075
- setError(null);
7076
- return;
7077
- }
7078
- if (!force && cached?.promise) {
7079
- setLoading(true);
7080
- try {
7081
- const value = await cached.promise;
7082
- if (seq !== requestSeq.current) return;
7083
- setConfig(value);
7084
- setError(null);
7085
- } catch (caught) {
7086
- if (seq !== requestSeq.current) return;
7087
- setError(caught instanceof Error ? caught : new Error(String(caught)));
7088
- } finally {
7089
- if (seq === requestSeq.current) {
7090
- setLoading(false);
7091
- }
7092
- }
7093
- return;
7094
- }
7095
- setLoading(true);
7096
- setError(null);
7097
- const promise = fetchAutoRecharge(_config);
7098
- writeAutoRechargeCache(key, {
7099
- config: cached?.config ?? null,
7100
- promise,
7101
- timestamp: now
7102
- });
7103
- try {
7104
- const value = await promise;
7105
- if (seq !== requestSeq.current) return;
7106
- writeAutoRechargeCache(key, { config: value, promise: null, timestamp: Date.now() });
7107
- setConfig(value);
7108
- } catch (caught) {
7109
- if (seq !== requestSeq.current) return;
7110
- const err = caught instanceof Error ? caught : new Error(String(caught));
7111
- writeAutoRechargeCache(key, {
7112
- config: cached?.config ?? null,
7113
- promise: null,
7114
- timestamp: Date.now()
7115
- });
7116
- setError(err);
7117
- } finally {
7118
- if (seq === requestSeq.current) {
7119
- setLoading(false);
7120
- }
7121
- }
7122
- },
7123
- [_config, key]
7124
- );
7125
- useEffect14(() => {
7126
- void load();
7127
- }, [load]);
7128
- useEffect14(() => {
7129
- return subscribeAutoRecharge(key, () => {
7130
- const cached = autoRechargeCache.get(key);
7131
- if (cached) {
7132
- setConfig(cached.config);
7133
- return;
7134
- }
7135
- void load(true);
7136
- });
7137
- }, [key, load]);
7138
- const refresh = useCallback21(
7139
- async (force = true) => {
7140
- await load(force);
7141
- },
7142
- [load]
7143
- );
7144
- const save = useCallback21(
7145
- async (input) => {
7146
- const transport = _config?.transport ?? createHttpTransport(_config);
7147
- if (!transport.saveAutoRecharge) {
7148
- throw new Error("saveAutoRecharge is not available on this transport");
7149
- }
7150
- setSaving(true);
7151
- setError(null);
7152
- const seq = ++requestSeq.current;
7153
- try {
7154
- const result = await transport.saveAutoRecharge(input);
7155
- if (seq !== requestSeq.current) return result;
7156
- const nextConfig = mergeAutoRechargeConfig(result.config, result.display);
7157
- writeAutoRechargeCache(key, {
7158
- config: nextConfig,
7159
- promise: null,
7160
- timestamp: Date.now()
7161
- });
7162
- setConfig(nextConfig);
7163
- return result;
7164
- } catch (caught) {
7165
- const err = caught instanceof Error ? caught : new Error(String(caught));
7166
- setError(err);
7167
- throw err;
7168
- } finally {
7169
- setSaving(false);
7170
- }
7171
- },
7172
- [_config, key]
7173
- );
7174
- const disable = useCallback21(async () => {
7175
- const transport = _config?.transport ?? createHttpTransport(_config);
7176
- if (!transport.disableAutoRecharge) {
7177
- throw new Error("disableAutoRecharge is not available on this transport");
7178
- }
7179
- setDisabling(true);
7180
- setError(null);
7181
- const seq = ++requestSeq.current;
7182
- try {
7183
- const result = await transport.disableAutoRecharge();
7184
- if (seq === requestSeq.current) {
7185
- setConfig((current) => {
7186
- const disabledConfig = current ? { ...current, enabled: false } : null;
7187
- writeAutoRechargeCache(key, {
7188
- config: disabledConfig,
7189
- promise: null,
7190
- timestamp: Date.now()
7191
- });
7192
- return disabledConfig;
7193
- });
7194
- await refresh(true);
7195
- setConfig((current) => {
7196
- const disabledConfig = current ? { ...current, enabled: false } : null;
7197
- writeAutoRechargeCache(key, {
7198
- config: disabledConfig,
7199
- promise: null,
7200
- timestamp: Date.now()
7201
- });
7202
- return disabledConfig;
7203
- });
7204
- }
7205
- return result;
7206
- } catch (caught) {
7207
- const err = caught instanceof Error ? caught : new Error(String(caught));
7208
- setError(err);
7209
- throw err;
7210
- } finally {
7211
- setDisabling(false);
7212
- }
7213
- }, [_config, key, refresh]);
7214
- return { config, loading, saving, disabling, error, refresh, save, disable };
7215
- }
7216
-
7217
7045
  // src/utils/credit-estimation.ts
7218
7046
  import { creditsToDisplayMinorUnits as creditsToDisplayMinorUnits2 } from "@solvapay/core";
7219
7047
  function estimateCredits(resolvedAmountMajor, currency, creditsPerMinorUnit, displayExchangeRate) {
@@ -7518,7 +7346,7 @@ function flipUnitValue(anchor, targetUnit, currency, creditsPerMinorUnit, displa
7518
7346
  }
7519
7347
 
7520
7348
  // src/hooks/useCheckoutFlow.ts
7521
- import { useCallback as useCallback22, useMemo as useMemo17, useRef as useRef10, useState as useState19 } from "react";
7349
+ import { useCallback as useCallback21, useMemo as useMemo17, useRef as useRef9, useState as useState18 } from "react";
7522
7350
 
7523
7351
  // src/primitives/checkout/shared.ts
7524
7352
  import {
@@ -7644,10 +7472,10 @@ function resolveInitialCheckoutStep(initialStep) {
7644
7472
  }
7645
7473
  function useCheckoutFlow(opts) {
7646
7474
  const { productRef, initialStep = "plan", initialAmountMinor = null } = opts;
7647
- const onPlanSelectRef = useRef10(opts.onPlanSelect);
7648
- const onAmountSelectRef = useRef10(opts.onAmountSelect);
7649
- const onPurchaseSuccessRef = useRef10(opts.onPurchaseSuccess);
7650
- const onErrorRef = useRef10(opts.onError);
7475
+ const onPlanSelectRef = useRef9(opts.onPlanSelect);
7476
+ const onAmountSelectRef = useRef9(opts.onAmountSelect);
7477
+ const onPurchaseSuccessRef = useRef9(opts.onPurchaseSuccess);
7478
+ const onErrorRef = useRef9(opts.onError);
7651
7479
  onPlanSelectRef.current = opts.onPlanSelect;
7652
7480
  onAmountSelectRef.current = opts.onAmountSelect;
7653
7481
  onPurchaseSuccessRef.current = opts.onPurchaseSuccess;
@@ -7660,7 +7488,7 @@ function useCheckoutFlow(opts) {
7660
7488
  const { creditsPerMinorUnit, displayExchangeRate, adjustBalance } = balance;
7661
7489
  const { refetchPurchase } = useSolvaPay();
7662
7490
  const { merchant } = useMerchant();
7663
- const [topupCurrencyOverride, setTopupCurrencyOverride] = useState19(null);
7491
+ const [topupCurrencyOverride, setTopupCurrencyOverride] = useState18(null);
7664
7492
  const topupCurrencies = useMemo17(() => {
7665
7493
  const fromMerchant = (merchant?.supportedTopupCurrencies ?? []).map((code) => code.toUpperCase()).filter(Boolean);
7666
7494
  const fallback = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
@@ -7671,22 +7499,22 @@ function useCheckoutFlow(opts) {
7671
7499
  const planPickerCurrency = planCtx.preferredCurrency?.toUpperCase() ?? null;
7672
7500
  const topupCurrency = (topupCurrencyOverride && topupCurrencies.includes(topupCurrencyOverride) ? topupCurrencyOverride : null) ?? (planPickerCurrency && topupCurrencies.includes(planPickerCurrency) ? planPickerCurrency : null) ?? resolvedDefaultTopupCurrency;
7673
7501
  const topupCurrencyReady = topupCurrency != null;
7674
- const setTopupCurrency = useCallback22((code) => {
7502
+ const setTopupCurrency = useCallback21((code) => {
7675
7503
  const normalized = code.toUpperCase();
7676
7504
  setTopupCurrencyOverride(normalized);
7677
7505
  }, []);
7678
- const [step, setStep] = useState19(() => resolveInitialCheckoutStep(initialStep));
7679
- const [status, setStatus] = useState19("idle");
7680
- const [selectedAmountMinor, setSelectedAmountMinor] = useState19(initialAmountMinor);
7681
- const [successMeta, setSuccessMeta] = useState19(null);
7682
- const [error, setError] = useState19(null);
7683
- const selectedAmountMinorRef = useRef10(initialAmountMinor);
7506
+ const [step, setStep] = useState18(() => resolveInitialCheckoutStep(initialStep));
7507
+ const [status, setStatus] = useState18("idle");
7508
+ const [selectedAmountMinor, setSelectedAmountMinor] = useState18(initialAmountMinor);
7509
+ const [successMeta, setSuccessMeta] = useState18(null);
7510
+ const [error, setError] = useState18(null);
7511
+ const selectedAmountMinorRef = useRef9(initialAmountMinor);
7684
7512
  const selectedPlan = planCtx.selectedPlan;
7685
7513
  const selectedPlanRef = planCtx.selectedPlanRef;
7686
7514
  const selectedPlanShape = toBootstrapPlanLike(selectedPlan);
7687
7515
  const branch = selectedPlanShape ? isPayg(selectedPlanShape) ? "payg" : "recurring" : null;
7688
7516
  const canGoBack = step === "amount" || step === "payment";
7689
- const selectPlan = useCallback22(
7517
+ const selectPlan = useCallback21(
7690
7518
  (ref) => {
7691
7519
  planCtx.select(ref);
7692
7520
  const plan = planCtx.plans.find((p) => p.reference === ref) ?? null;
@@ -7696,7 +7524,7 @@ function useCheckoutFlow(opts) {
7696
7524
  },
7697
7525
  [planCtx]
7698
7526
  );
7699
- const selectAmount = useCallback22(
7527
+ const selectAmount = useCallback21(
7700
7528
  (amountMinor) => {
7701
7529
  selectedAmountMinorRef.current = amountMinor;
7702
7530
  setSelectedAmountMinor(amountMinor);
@@ -7705,7 +7533,7 @@ function useCheckoutFlow(opts) {
7705
7533
  },
7706
7534
  [topupCurrency]
7707
7535
  );
7708
- const runActivate = useCallback22(async () => {
7536
+ const runActivate = useCallback21(async () => {
7709
7537
  if (!selectedPlanShape || !selectedPlanRef) return false;
7710
7538
  setError(null);
7711
7539
  setStatus("activating");
@@ -7737,7 +7565,7 @@ function useCheckoutFlow(opts) {
7737
7565
  return false;
7738
7566
  }
7739
7567
  }, [balance, locale, productRef, selectedPlanRef, selectedPlanShape, transport]);
7740
- const advanceFromPlan = useCallback22(async () => {
7568
+ const advanceFromPlan = useCallback21(async () => {
7741
7569
  if (!selectedPlanShape || !selectedPlanRef) return;
7742
7570
  if (branch === "payg") {
7743
7571
  if (planCtx.currentPlanRef === selectedPlanRef) {
@@ -7775,7 +7603,7 @@ function useCheckoutFlow(opts) {
7775
7603
  setError(null);
7776
7604
  setStep("payment");
7777
7605
  }, [balance, branch, locale, planCtx.currentPlanRef, runActivate, selectedPlanRef, selectedPlanShape]);
7778
- const recordPaygSuccess = useCallback22(
7606
+ const recordPaygSuccess = useCallback21(
7779
7607
  (creditsAddedFromBackend) => {
7780
7608
  if (!selectedPlanShape || selectedAmountMinor == null) return;
7781
7609
  const currency = topupCurrency ?? "USD";
@@ -7819,7 +7647,7 @@ function useCheckoutFlow(opts) {
7819
7647
  transport
7820
7648
  ]
7821
7649
  );
7822
- const recordRecurringSuccess = useCallback22(() => {
7650
+ const recordRecurringSuccess = useCallback21(() => {
7823
7651
  if (!selectedPlanShape) return;
7824
7652
  const pricingOption = selectedPlan ? resolvePlanPricingOption(selectedPlan, planSelection?.selectedCurrency) : {
7825
7653
  currency: selectedPlanShape.currency ?? "USD",
@@ -7839,7 +7667,7 @@ function useCheckoutFlow(opts) {
7839
7667
  setStep("success");
7840
7668
  onPurchaseSuccessRef.current?.(meta);
7841
7669
  }, [selectedPlanShape, selectedPlan, planSelection?.selectedCurrency]);
7842
- const advance = useCallback22(async () => {
7670
+ const advance = useCallback21(async () => {
7843
7671
  if (step === "plan") {
7844
7672
  await advanceFromPlan();
7845
7673
  return;
@@ -7866,7 +7694,7 @@ function useCheckoutFlow(opts) {
7866
7694
  selectedAmountMinor,
7867
7695
  step
7868
7696
  ]);
7869
- const back = useCallback22(() => {
7697
+ const back = useCallback21(() => {
7870
7698
  setError(null);
7871
7699
  if (step === "amount") {
7872
7700
  setStep("plan");
@@ -7877,7 +7705,7 @@ function useCheckoutFlow(opts) {
7877
7705
  return;
7878
7706
  }
7879
7707
  }, [branch, step]);
7880
- const reset = useCallback22(() => {
7708
+ const reset = useCallback21(() => {
7881
7709
  setStep("plan");
7882
7710
  selectedAmountMinorRef.current = null;
7883
7711
  setSelectedAmountMinor(null);
@@ -7886,13 +7714,13 @@ function useCheckoutFlow(opts) {
7886
7714
  setStatus("idle");
7887
7715
  setTopupCurrencyOverride(null);
7888
7716
  }, []);
7889
- const retry = useCallback22(async () => {
7717
+ const retry = useCallback21(async () => {
7890
7718
  if (status !== "error") return;
7891
7719
  if (step === "plan" && branch === "payg") {
7892
7720
  await runActivate();
7893
7721
  }
7894
7722
  }, [branch, runActivate, status, step]);
7895
- const notifyPaymentSuccess = useCallback22(
7723
+ const notifyPaymentSuccess = useCallback21(
7896
7724
  (_intent, extras) => {
7897
7725
  if (branch === "payg") {
7898
7726
  recordPaygSuccess(extras?.creditsAdded);
@@ -7952,6 +7780,11 @@ function useCheckoutFlow(opts) {
7952
7780
 
7953
7781
  export {
7954
7782
  createTransportCacheKey,
7783
+ autoRechargeCache,
7784
+ CACHE_DURATION2 as CACHE_DURATION,
7785
+ autoRechargeCacheKeyFor,
7786
+ writeAutoRechargeCache,
7787
+ subscribeAutoRecharge,
7955
7788
  filterPurchases,
7956
7789
  getActivePurchases,
7957
7790
  getCancelledPurchasesWithEndDate,
@@ -8112,7 +7945,6 @@ export {
8112
7945
  formatPaygRate,
8113
7946
  inferIncludedUnits,
8114
7947
  planMeterName,
8115
- useAutoRecharge,
8116
7948
  estimateCredits,
8117
7949
  estimateCurrencyMajorFromCredits,
8118
7950
  createDefaultAutoRechargeForm,
@@ -8121,7 +7953,6 @@ export {
8121
7953
  validateAutoRechargeForm,
8122
7954
  configToAutoRechargeInput,
8123
7955
  configToForm,
8124
- formatAmountWithUnit,
8125
7956
  buildSummaryLine,
8126
7957
  flipUnitValue,
8127
7958
  useCheckoutFlow