@solvapay/react 2.2.2-preview-cf2672297243fbf98ba33872a7348aca170e7b01 → 2.2.2

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,25 +1,10 @@
1
1
  # @solvapay/react changelog
2
2
 
3
- ## 2.2.2-preview-cf2672297243fbf98ba33872a7348aca170e7b01
3
+ ## 2.2.2
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - 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.
8
- - 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.
9
-
10
- 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.
11
-
12
7
  - 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.
13
- - 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.
14
- - 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.
15
- - Updated dependencies [c67e78e]
16
- - Updated dependencies [dbd0108]
17
- - Updated dependencies [b3b5b72]
18
- - Updated dependencies [35da494]
19
- - Updated dependencies [c67e78e]
20
- - Updated dependencies [c67e78e]
21
- - Updated dependencies [c67e78e]
22
- - @solvapay/mcp-core@0.4.2-preview-cf2672297243fbf98ba33872a7348aca170e7b01
23
8
 
24
9
  ## 2.2.1
25
10
 
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { _ as UsageSnapshot } from './useUsage-SizdkzW3.js';
2
+ import { _ as UsageSnapshot } from './useUsage-DSPWQ37m.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-BpIrBHDT.cjs';
2
+ import { _ as UsageSnapshot } from './useUsage-CRd_E6J3.cjs';
3
3
 
4
4
  /**
5
5
  * `<UsageMeter>` compound primitive — renders the authenticated customer's
@@ -8,7 +8,7 @@ import {
8
8
  useOpenExternal,
9
9
  useSolvaPay,
10
10
  useTransport
11
- } from "./chunk-JVBZHE7B.js";
11
+ } from "./chunk-GPP4MY3I.js";
12
12
 
13
13
  // src/hooks/usePaymentMethod.ts
14
14
  import { useCallback, useEffect, useState } from "react";
@@ -616,10 +616,8 @@ var enCopy = {
616
616
  addFunds: "Add funds",
617
617
  autoRechargeOn: "Auto-recharge on",
618
618
  autoRechargeOff: "Auto-recharge off",
619
- autoRechargePending: "Auto-recharge starts once this payment clears",
620
- turnOn: "Turn on",
621
- manage: "Manage",
622
- fixCard: "Fix card",
619
+ turnOn: "Turn on \u2192",
620
+ manage: "Manage \u2192",
623
621
  autoRechargeOffCaption: "Calls fail the moment the balance runs out.",
624
622
  autoRechargeOffFixCaption: "Turning it on stops this happening again.",
625
623
  callsFailingCaption: "The plan is active, but calls fail until you add credits.",
@@ -7039,6 +7037,183 @@ function useUsageMeter() {
7039
7037
  return useUsageMeterCtx("useUsageMeter");
7040
7038
  }
7041
7039
 
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
+
7042
7217
  // src/utils/credit-estimation.ts
7043
7218
  import { creditsToDisplayMinorUnits as creditsToDisplayMinorUnits2 } from "@solvapay/core";
7044
7219
  function estimateCredits(resolvedAmountMajor, currency, creditsPerMinorUnit, displayExchangeRate) {
@@ -7343,7 +7518,7 @@ function flipUnitValue(anchor, targetUnit, currency, creditsPerMinorUnit, displa
7343
7518
  }
7344
7519
 
7345
7520
  // src/hooks/useCheckoutFlow.ts
7346
- import { useCallback as useCallback21, useMemo as useMemo17, useRef as useRef9, useState as useState18 } from "react";
7521
+ import { useCallback as useCallback22, useMemo as useMemo17, useRef as useRef10, useState as useState19 } from "react";
7347
7522
 
7348
7523
  // src/primitives/checkout/shared.ts
7349
7524
  import {
@@ -7469,10 +7644,10 @@ function resolveInitialCheckoutStep(initialStep) {
7469
7644
  }
7470
7645
  function useCheckoutFlow(opts) {
7471
7646
  const { productRef, initialStep = "plan", initialAmountMinor = null } = opts;
7472
- const onPlanSelectRef = useRef9(opts.onPlanSelect);
7473
- const onAmountSelectRef = useRef9(opts.onAmountSelect);
7474
- const onPurchaseSuccessRef = useRef9(opts.onPurchaseSuccess);
7475
- const onErrorRef = useRef9(opts.onError);
7647
+ const onPlanSelectRef = useRef10(opts.onPlanSelect);
7648
+ const onAmountSelectRef = useRef10(opts.onAmountSelect);
7649
+ const onPurchaseSuccessRef = useRef10(opts.onPurchaseSuccess);
7650
+ const onErrorRef = useRef10(opts.onError);
7476
7651
  onPlanSelectRef.current = opts.onPlanSelect;
7477
7652
  onAmountSelectRef.current = opts.onAmountSelect;
7478
7653
  onPurchaseSuccessRef.current = opts.onPurchaseSuccess;
@@ -7485,7 +7660,7 @@ function useCheckoutFlow(opts) {
7485
7660
  const { creditsPerMinorUnit, displayExchangeRate, adjustBalance } = balance;
7486
7661
  const { refetchPurchase } = useSolvaPay();
7487
7662
  const { merchant } = useMerchant();
7488
- const [topupCurrencyOverride, setTopupCurrencyOverride] = useState18(null);
7663
+ const [topupCurrencyOverride, setTopupCurrencyOverride] = useState19(null);
7489
7664
  const topupCurrencies = useMemo17(() => {
7490
7665
  const fromMerchant = (merchant?.supportedTopupCurrencies ?? []).map((code) => code.toUpperCase()).filter(Boolean);
7491
7666
  const fallback = opts.topupCurrency?.toUpperCase() ?? merchant?.defaultCurrency?.toUpperCase() ?? null;
@@ -7496,22 +7671,22 @@ function useCheckoutFlow(opts) {
7496
7671
  const planPickerCurrency = planCtx.preferredCurrency?.toUpperCase() ?? null;
7497
7672
  const topupCurrency = (topupCurrencyOverride && topupCurrencies.includes(topupCurrencyOverride) ? topupCurrencyOverride : null) ?? (planPickerCurrency && topupCurrencies.includes(planPickerCurrency) ? planPickerCurrency : null) ?? resolvedDefaultTopupCurrency;
7498
7673
  const topupCurrencyReady = topupCurrency != null;
7499
- const setTopupCurrency = useCallback21((code) => {
7674
+ const setTopupCurrency = useCallback22((code) => {
7500
7675
  const normalized = code.toUpperCase();
7501
7676
  setTopupCurrencyOverride(normalized);
7502
7677
  }, []);
7503
- const [step, setStep] = useState18(() => resolveInitialCheckoutStep(initialStep));
7504
- const [status, setStatus] = useState18("idle");
7505
- const [selectedAmountMinor, setSelectedAmountMinor] = useState18(initialAmountMinor);
7506
- const [successMeta, setSuccessMeta] = useState18(null);
7507
- const [error, setError] = useState18(null);
7508
- const selectedAmountMinorRef = useRef9(initialAmountMinor);
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);
7509
7684
  const selectedPlan = planCtx.selectedPlan;
7510
7685
  const selectedPlanRef = planCtx.selectedPlanRef;
7511
7686
  const selectedPlanShape = toBootstrapPlanLike(selectedPlan);
7512
7687
  const branch = selectedPlanShape ? isPayg(selectedPlanShape) ? "payg" : "recurring" : null;
7513
7688
  const canGoBack = step === "amount" || step === "payment";
7514
- const selectPlan = useCallback21(
7689
+ const selectPlan = useCallback22(
7515
7690
  (ref) => {
7516
7691
  planCtx.select(ref);
7517
7692
  const plan = planCtx.plans.find((p) => p.reference === ref) ?? null;
@@ -7521,7 +7696,7 @@ function useCheckoutFlow(opts) {
7521
7696
  },
7522
7697
  [planCtx]
7523
7698
  );
7524
- const selectAmount = useCallback21(
7699
+ const selectAmount = useCallback22(
7525
7700
  (amountMinor) => {
7526
7701
  selectedAmountMinorRef.current = amountMinor;
7527
7702
  setSelectedAmountMinor(amountMinor);
@@ -7530,7 +7705,7 @@ function useCheckoutFlow(opts) {
7530
7705
  },
7531
7706
  [topupCurrency]
7532
7707
  );
7533
- const runActivate = useCallback21(async () => {
7708
+ const runActivate = useCallback22(async () => {
7534
7709
  if (!selectedPlanShape || !selectedPlanRef) return false;
7535
7710
  setError(null);
7536
7711
  setStatus("activating");
@@ -7562,7 +7737,7 @@ function useCheckoutFlow(opts) {
7562
7737
  return false;
7563
7738
  }
7564
7739
  }, [balance, locale, productRef, selectedPlanRef, selectedPlanShape, transport]);
7565
- const advanceFromPlan = useCallback21(async () => {
7740
+ const advanceFromPlan = useCallback22(async () => {
7566
7741
  if (!selectedPlanShape || !selectedPlanRef) return;
7567
7742
  if (branch === "payg") {
7568
7743
  if (planCtx.currentPlanRef === selectedPlanRef) {
@@ -7600,7 +7775,7 @@ function useCheckoutFlow(opts) {
7600
7775
  setError(null);
7601
7776
  setStep("payment");
7602
7777
  }, [balance, branch, locale, planCtx.currentPlanRef, runActivate, selectedPlanRef, selectedPlanShape]);
7603
- const recordPaygSuccess = useCallback21(
7778
+ const recordPaygSuccess = useCallback22(
7604
7779
  (creditsAddedFromBackend) => {
7605
7780
  if (!selectedPlanShape || selectedAmountMinor == null) return;
7606
7781
  const currency = topupCurrency ?? "USD";
@@ -7644,7 +7819,7 @@ function useCheckoutFlow(opts) {
7644
7819
  transport
7645
7820
  ]
7646
7821
  );
7647
- const recordRecurringSuccess = useCallback21(() => {
7822
+ const recordRecurringSuccess = useCallback22(() => {
7648
7823
  if (!selectedPlanShape) return;
7649
7824
  const pricingOption = selectedPlan ? resolvePlanPricingOption(selectedPlan, planSelection?.selectedCurrency) : {
7650
7825
  currency: selectedPlanShape.currency ?? "USD",
@@ -7664,7 +7839,7 @@ function useCheckoutFlow(opts) {
7664
7839
  setStep("success");
7665
7840
  onPurchaseSuccessRef.current?.(meta);
7666
7841
  }, [selectedPlanShape, selectedPlan, planSelection?.selectedCurrency]);
7667
- const advance = useCallback21(async () => {
7842
+ const advance = useCallback22(async () => {
7668
7843
  if (step === "plan") {
7669
7844
  await advanceFromPlan();
7670
7845
  return;
@@ -7691,7 +7866,7 @@ function useCheckoutFlow(opts) {
7691
7866
  selectedAmountMinor,
7692
7867
  step
7693
7868
  ]);
7694
- const back = useCallback21(() => {
7869
+ const back = useCallback22(() => {
7695
7870
  setError(null);
7696
7871
  if (step === "amount") {
7697
7872
  setStep("plan");
@@ -7702,7 +7877,7 @@ function useCheckoutFlow(opts) {
7702
7877
  return;
7703
7878
  }
7704
7879
  }, [branch, step]);
7705
- const reset = useCallback21(() => {
7880
+ const reset = useCallback22(() => {
7706
7881
  setStep("plan");
7707
7882
  selectedAmountMinorRef.current = null;
7708
7883
  setSelectedAmountMinor(null);
@@ -7711,13 +7886,13 @@ function useCheckoutFlow(opts) {
7711
7886
  setStatus("idle");
7712
7887
  setTopupCurrencyOverride(null);
7713
7888
  }, []);
7714
- const retry = useCallback21(async () => {
7889
+ const retry = useCallback22(async () => {
7715
7890
  if (status !== "error") return;
7716
7891
  if (step === "plan" && branch === "payg") {
7717
7892
  await runActivate();
7718
7893
  }
7719
7894
  }, [branch, runActivate, status, step]);
7720
- const notifyPaymentSuccess = useCallback21(
7895
+ const notifyPaymentSuccess = useCallback22(
7721
7896
  (_intent, extras) => {
7722
7897
  if (branch === "payg") {
7723
7898
  recordPaygSuccess(extras?.creditsAdded);
@@ -7777,11 +7952,6 @@ function useCheckoutFlow(opts) {
7777
7952
 
7778
7953
  export {
7779
7954
  createTransportCacheKey,
7780
- autoRechargeCache,
7781
- CACHE_DURATION2 as CACHE_DURATION,
7782
- autoRechargeCacheKeyFor,
7783
- writeAutoRechargeCache,
7784
- subscribeAutoRecharge,
7785
7955
  filterPurchases,
7786
7956
  getActivePurchases,
7787
7957
  getCancelledPurchasesWithEndDate,
@@ -7942,6 +8112,7 @@ export {
7942
8112
  formatPaygRate,
7943
8113
  inferIncludedUnits,
7944
8114
  planMeterName,
8115
+ useAutoRecharge,
7945
8116
  estimateCredits,
7946
8117
  estimateCurrencyMajorFromCredits,
7947
8118
  createDefaultAutoRechargeForm,
@@ -7950,6 +8121,7 @@ export {
7950
8121
  validateAutoRechargeForm,
7951
8122
  configToAutoRechargeInput,
7952
8123
  configToForm,
8124
+ formatAmountWithUnit,
7953
8125
  buildSummaryLine,
7954
8126
  flipUnitValue,
7955
8127
  useCheckoutFlow