@funnelsgrove/payments 0.1.45 → 0.1.47

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
@@ -2,70 +2,88 @@
2
2
 
3
3
  Shared billing and checkout helpers for funnels.
4
4
 
5
- Detailed implementation docs:
5
+ Read this file before editing checkout code. Deeper implementation docs:
6
6
 
7
7
  - [Shared Payments](../../docs/funnel-sdk/shared-payments.md)
8
8
  - [Funnel API payment endpoints](../../docs/funnel-sdk/funnel-api.md#checkout-endpoints)
9
9
 
10
- ## Build And Publish
10
+ ## Owns provider-backed checkout primitives
11
11
 
12
- - Build distributable output with `npm run build --workspace @funnelsgrove/payments`.
13
- - Publish from the repo root with `npm publish --workspace @funnelsgrove/payments --access public`.
14
- - The package pins `@funnelsgrove/runtime` to the same exact version and the build resolves against the runtime package's emitted `dist` contract.
15
- - The build normalizes generated relative ESM imports to explicit `.js` files for published package consumers.
12
+ `@funnelsgrove/payments` owns reusable payment contracts, provider adapters, checkout-session helpers, wallet slots, and checkout UI primitives. Funnel code owns paywall layout, copy, when an offer opens, post-payment navigation, and funnel-specific analytics timing.
16
13
 
17
- ## Use This Package For
14
+ ## Current Checkout Path
18
15
 
19
- - Stripe plan loading and checkout integration
20
- - global funnel billing catalog contracts
21
- - reusable plan-selector UI
22
- - reusable shared embedded checkout dialogs
23
- - reusable Stripe Express Checkout wrappers for funnels that need native Apple Pay or Google Pay controls outside the shared dialogs
24
- - provider-ready checkout session primitives that keep paywalls from owning provider SDK setup, client-secret preparation, or wallet-button replacement logic
25
- - compatibility exports for saved builder paywall drafts, including the Apple Pay subscribe fallback button
26
- - billing catalog types and runtime helpers
27
- - reusable paywall offer helpers for timed discounts, discounted display plans, promo labels, and renewal disclaimer text
16
+ Use these for new subscription paywalls:
17
+
18
+ - `useStripeSubscriptionCheckoutSession`
19
+ - `SharedStripeCheckoutV2Dialog`
20
+ - `ApplePaySubscriptionCheckoutSlot`
21
+ - `GooglePaySubscriptionCheckoutSlot`
22
+ - `WalletSubscriptionCheckoutSlot`
23
+ - `trackPaidStripeSubscriptionCheckoutCompleted`
24
+
25
+ Use `chargeStripeOneClickPayment` for post-checkout one-click upsells when the shared client helper fits the funnel. The API endpoint is `/sdk/public/payments/one-click`.
26
+
27
+ ## Compatibility-Only Surfaces
28
28
 
29
- ## Responsibilities
29
+ Keep these exports available for saved drafts, old published artifacts, and controlled migrations. Do not use them for new subscription checkout work:
30
30
 
31
- - keep paywall behavior consistent across funnels
32
- - define the shared billing types used by funnel-local catalogs
33
- - expose one ordered funnel-local billing catalog contract
34
- - keep the public checkout identity contract aligned on `user_id` for payment intents and any remaining hosted-checkout fallbacks
35
- - keep reusable discount math and display-plan shaping out of funnel step files
36
- - keep provider-specific implementation under `src/providers/<provider>` so funnels can consume stable payment slots while future providers add their own adapters
31
+ - `createStripePaymentIntent`
32
+ - `SharedStripeCheckoutDialog`
33
+ - `ApplePaySubscribeButton`
34
+ - `GooglePaySubscribeButton`
35
+ - `APPLE_PAY_QR_CODE_URL`
37
36
 
38
- ## What Belongs Here
37
+ When a live funnel still uses one of these, migrate the funnel deliberately with its own tests before removing the export.
38
+
39
+ ## Use This Package For
39
40
 
40
41
  - billing catalog types and normalization helpers
41
- - Stripe SDK wiring
42
- - shared checkout/plan-selector components
43
- - generic paywall offer helpers that accept funnel-provided plans and discount catalogs
44
- - the default embedded Stripe checkout dialog UX that funnels can feed with their own copy, pricing summary rows, guarantee note, and an optional editable customer email that is committed before card or wallet confirmation
45
- - the `StripeExpressCheckoutElement` wrapper, which keeps `Elements` provider ownership inside this package so catalog funnels with their own dependency installs do not pass Stripe promises across duplicate local Stripe type copies
46
- - `src/providers/paymentProvider.types.ts` for provider-neutral checkout preparation state and provider ids
47
- - `src/providers/stripe/useStripeSubscriptionCheckoutSession.ts` for Stripe SDK initialization, publishable-key resolution, subscription client-secret preparation, card/wallet loading state, and friendly checkout errors
48
- - `src/providers/stripe/ApplePaySubscriptionCheckoutSlot.tsx` and `src/providers/stripe/GooglePaySubscriptionCheckoutSlot.tsx` for wallet shells that prepare Stripe Express Checkout on load, keep the loading placeholder clickable, and let Stripe's real wallet controls sit above the placeholder as soon as they mount
49
- - Stripe subscription checkout sessions reuse the active client secret across card checkout and paywall wallet checkout for the same intent key, so opening the embedded checkout after a paywall wallet button is ready does not create a second subscription session.
50
- - `trackPaidStripeSubscriptionCheckoutCompleted` verifies a Stripe custom Checkout Session is paid before delegating to `@funnelsgrove/analytics` for the canonical `checkout_completed` purchase event.
51
- - Stripe Express Checkout elements must stay mounted and measurable until Stripe reports availability; only hide the container after `onReady` or `onLoadError` reports that the requested wallet method is unavailable.
52
- - Shared checkout dialogs must switch to card checkout when Stripe reports no requested wallet availability, so an unavailable Apple Pay or Google Pay shell never leaves a fake wallet tab blocking the card form.
53
- - Wallet slots should not leave disabled grey placeholder shells blocking the paywall. While availability is unknown, the placeholder can sit behind Stripe's real wallet element; after Stripe reports unavailable or preparation cannot create a client secret, the placeholder must disappear instead of opening a card checkout under wallet branding.
54
- - Checkout v2 owns only real Stripe Elements for card and wallet collection: Stripe controls the card-field placeholders, Stripe's `ExpressCheckoutElement` controls Apple Pay / Google Pay availability, desktop web can expose both wallet icons, and mobile narrows to Apple Pay on iOS or Google Pay on Android.
55
- - Checkout v2 can receive a paywall-confirmed wallet availability hint and seed its wallet tab from that state, letting a checkout reopen keep Apple Pay or Google Pay visible while its own Express Checkout element remounts against the same client secret.
56
- - Checkout v2 renders the wallet tab icons from the active platform wallet list and keeps Apple Pay / Google Pay marks large enough to read inside the tab.
57
- - Checkout v2 keeps the fixed mobile dialog viewport-bound and scrolls the inner checkout shell so card fields, wallet controls, trust copy, and bottom actions remain reachable on short screens.
58
- - Checkout v2 renders an editable email field when a funnel opens card checkout without a stored customer email, so direct preview paywall QA can complete Stripe confirmation instead of failing validation invisibly.
59
- - The shared special-offer dialog can render a funnel-provided gift image while keeping the discount label overlaid and retaining the inline SVG fallback for funnels without a custom asset.
60
- - the shared large-format Apple Pay and Google Pay subscribe CTAs, QR-code handoff link support, and the compact Visa / Mastercard / G Pay / Apple Pay image row used by the embedded checkout shell trust footer
61
- - the Google Pay fallback CTA uses the downloaded Google Pay `G` mark paths, keeps at least 8px clear space, and matches Apple Pay's roughly 27px rendered subscribe text size until Stripe replaces it with the real wallet control
62
-
63
- ## What Does Not Belong Here
42
+ - runtime-mode-aware plan catalog selection
43
+ - Stripe SDK and public payment API client helpers
44
+ - subscription checkout-session preparation, reuse, update, and recovery
45
+ - reusable plan-selector UI
46
+ - shared embedded checkout dialogs
47
+ - Stripe Express Checkout wallet slots and lower-level wallet primitives
48
+ - generic timed-offer display helpers
49
+ - checkout completion analytics handoff to `@funnelsgrove/analytics`
50
+
51
+ ## Do Not Use This Package For
64
52
 
65
53
  - funnel routing
66
- - funnel-owned analytics event definitions outside the shared checkout completion handoff
67
- - funnel-owned step layout outside the shared checkout shell
68
- - funnel-owned billing catalog entries
69
- - funnel-specific decisions about when to activate or upgrade an offer
70
54
  - manifest validation
55
+ - funnel-owned billing catalog entries
56
+ - funnel-specific paywall layout or copy
57
+ - funnel-specific decisions about when to activate, upgrade, or recover an offer
58
+ - analytics transport internals
71
59
  - direct provider SDK calls inside funnel paywall steps; add provider adapters here first, then wire the funnel to the shared session or slot
60
+
61
+ ## Public Surfaces
62
+
63
+ - `config/billing.config`: billing catalog, mapping, plan, and discount contracts.
64
+ - `services/planCatalog.service`: plan resolution and selection helpers.
65
+ - `services/runtimeBillingPlanCatalog.service`: test/live runtime catalog selection.
66
+ - `services/paywallOffer.service`: timed discount and display-plan helpers.
67
+ - `services/stripe.service`: Stripe loader, checkout-session, hosted checkout, one-click, and compatibility PaymentIntent clients.
68
+ - `providers/*`: provider-neutral checkout state and Stripe subscription checkout slots.
69
+ - `components/shared/*`: plan selector, checkout dialogs, Express Checkout wrappers, wallet placeholders, and trust assets.
70
+ - `testing/walletCheckoutSmoke`: wallet checkout smoke assertions.
71
+
72
+ ## Engineering Rules
73
+
74
+ - Keep provider-specific implementation under `src/providers/<provider>`.
75
+ - Stripe owns real card fields and wallet controls. Local wallet buttons are placeholders until Stripe confirms availability.
76
+ - Wallet unavailable state must fall back to card/manual checkout or disappear.
77
+ - Do not create checkout sessions until the visitor taps a visible checkout CTA.
78
+ - Keep discount math reusable here, but keep offer activation timing in funnel code.
79
+ - Checkout metadata must avoid sensitive raw query params and use `@funnelsgrove/analytics` metadata helpers.
80
+
81
+ ## Build, Test, Publish
82
+
83
+ ```bash
84
+ npm run test:run --workspace @funnelsgrove/payments
85
+ npm run build --workspace @funnelsgrove/payments
86
+ npm publish --workspace @funnelsgrove/payments --access public
87
+ ```
88
+
89
+ The build normalizes generated relative ESM imports to explicit `.js` files for published package consumers.
@@ -50,6 +50,10 @@ const formatCountdown = (remainingSeconds) => {
50
50
  return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
51
51
  };
52
52
  const getFixedSupportedBillingCountry = (supportedCountries) => supportedCountries.length === 1 ? supportedCountries[0] : null;
53
+ const checkoutCountryLabels = {
54
+ US: 'United States',
55
+ };
56
+ const getCheckoutCountryLabel = (countryCode) => { var _a; return (_a = checkoutCountryLabels[countryCode]) !== null && _a !== void 0 ? _a : countryCode; };
53
57
  function getFriendlyCardErrorMessage(error) {
54
58
  return error instanceof Error && error.message
55
59
  ? error.message
@@ -70,6 +74,9 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
70
74
  const [emailDraft, setEmailDraft] = useState(initialCustomerEmail);
71
75
  const normalizedSupportedCountries = useMemo(() => normalizeSupportedCheckoutCountries(supportedCountries), [supportedCountries]);
72
76
  const fixedBillingCountry = getFixedSupportedBillingCountry(normalizedSupportedCountries);
77
+ const fixedBillingCountryLabel = fixedBillingCountry
78
+ ? getCheckoutCountryLabel(fixedBillingCountry)
79
+ : null;
73
80
  const cardBrandLabels = useMemo(() => paymentMethodLabels.length > 0 ? paymentMethodLabels : defaultPaymentMethodLabels, [paymentMethodLabels]);
74
81
  const normalizedPromoCode = promoCode.trim();
75
82
  const showPromo = promoActive !== null && promoActive !== void 0 ? promoActive : (discountPercent > 0 && normalizedPromoCode.length > 0);
@@ -297,7 +304,7 @@ function SharedStripeCheckoutV2Form({ amountCents, buttonColor, cardSubmitLabel,
297
304
  }
298
305
  setEmailDraft(event.target.value);
299
306
  onCustomerEmailChange === null || onCustomerEmailChange === void 0 ? void 0 : onCustomerEmailChange(event.target.value);
300
- } }) })] }), _jsx(PaymentElement, { options: paymentElementOptions, onChange: handlePaymentElementChange })] }), visibleError ? _jsx("p", { className: 'shared-checkout-v2-error', children: visibleError }) : null, _jsxs("button", { type: 'submit', className: 'shared-checkout-v2-card-submit', disabled: submitting, children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-card-submit-icon' }), submitting ? processingLabel : cardSubmitLabel] }), _jsx("div", { className: 'shared-checkout-v2-card-brands', "aria-hidden": 'true', children: cardBrandLabels.map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'shared-checkout-v2-card-brand' }, label))) }), _jsxs("p", { className: 'shared-checkout-v2-secure-pill shared-checkout-v2-secure-pill--card', children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-secure-pill-icon' }), secureLabel] })] })] })] }));
307
+ } }) })] }), _jsx(PaymentElement, { options: paymentElementOptions, onChange: handlePaymentElementChange }), fixedBillingCountry && fixedBillingCountryLabel ? (_jsxs("label", { className: 'shared-checkout-v2-country-field', children: [_jsx("span", { className: 'shared-checkout-v2-country-label', children: "Country" }), _jsx("span", { className: 'shared-checkout-v2-country-control', children: _jsx("select", { "aria-label": 'Country', className: 'shared-checkout-v2-country-select', value: fixedBillingCountry, onChange: () => setBillingCountry(fixedBillingCountry), children: _jsx("option", { value: fixedBillingCountry, children: fixedBillingCountryLabel }) }) })] })) : null] }), visibleError ? _jsx("p", { className: 'shared-checkout-v2-error', children: visibleError }) : null, _jsxs("button", { type: 'submit', className: 'shared-checkout-v2-card-submit', disabled: submitting, children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-card-submit-icon' }), submitting ? processingLabel : cardSubmitLabel] }), _jsx("div", { className: 'shared-checkout-v2-card-brands', "aria-hidden": 'true', children: cardBrandLabels.map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'shared-checkout-v2-card-brand' }, label))) }), _jsxs("p", { className: 'shared-checkout-v2-secure-pill shared-checkout-v2-secure-pill--card', children: [_jsx(SecurityLockIcon, { className: 'shared-checkout-v2-secure-pill-icon' }), secureLabel] })] })] })] }));
301
308
  }
302
309
  export function SharedStripeCheckoutV2Dialog(_a) {
303
310
  var { checkoutAnalytics, checkoutSessionId, clientSecret, onClose, stripePromise, supportedCountries } = _a, props = __rest(_a, ["checkoutAnalytics", "checkoutSessionId", "clientSecret", "onClose", "stripePromise", "supportedCountries"]);
@@ -813,6 +820,58 @@ const sharedStripeCheckoutV2Styles = `
813
820
  box-shadow: none;
814
821
  }
815
822
 
823
+ .shared-checkout-v2-country-field {
824
+ box-sizing: border-box;
825
+ display: flex;
826
+ flex-direction: column;
827
+ gap: 7px;
828
+ margin: 14px 0 0;
829
+ color: #303136;
830
+ font-size: 14px;
831
+ font-weight: 400;
832
+ line-height: 1.2;
833
+ }
834
+
835
+ .shared-checkout-v2-country-label {
836
+ padding: 0 14px;
837
+ }
838
+
839
+ .shared-checkout-v2-country-control {
840
+ position: relative;
841
+ display: block;
842
+ }
843
+
844
+ .shared-checkout-v2-country-control::after {
845
+ position: absolute;
846
+ right: 17px;
847
+ top: 50%;
848
+ width: 8px;
849
+ height: 8px;
850
+ border-right: 2px solid #6f7380;
851
+ border-bottom: 2px solid #6f7380;
852
+ content: '';
853
+ pointer-events: none;
854
+ transform: translateY(-65%) rotate(45deg);
855
+ }
856
+
857
+ .shared-checkout-v2-country-select {
858
+ width: 100%;
859
+ min-height: 50px;
860
+ box-sizing: border-box;
861
+ appearance: none;
862
+ border: 1px solid #dedede;
863
+ border-radius: 0;
864
+ background: #fff;
865
+ box-shadow: 0 1px 2px rgb(16 24 40 / 8%);
866
+ color: #303136;
867
+ font: inherit;
868
+ font-size: 18px;
869
+ font-weight: 400;
870
+ line-height: 1.2;
871
+ outline: none;
872
+ padding: 0 44px 0 14px;
873
+ }
874
+
816
875
  .shared-checkout-v2-error {
817
876
  margin: 0;
818
877
  color: #c43737;
package/dist/index.d.ts CHANGED
@@ -13,13 +13,13 @@ export * from './providers/stripe/WalletSubscriptionCheckoutSlot.js';
13
13
  export * from './providers/stripe/ApplePaySubscriptionCheckoutSlot.js';
14
14
  export * from './providers/stripe/GooglePaySubscriptionCheckoutSlot.js';
15
15
  export { ManageSubscriptionScreen, type ManageSubscriptionContent, type ManageSubscriptionScreenProps, } from './components/ManageSubscriptionScreen.js';
16
- export * from './components/shared/SharedStripeCheckoutDialog.js';
17
16
  export * from './components/shared/SharedStripeCheckoutV2Dialog.js';
18
- export * from './components/shared/ApplePaySubscribeButton.js';
19
- export * from './components/shared/GooglePaySubscribeButton.js';
20
- export * from './components/shared/StripeCheckoutExpressCheckoutButton.js';
21
- export * from './components/shared/StripeExpressCheckoutButton.js';
22
17
  export * from './components/shared/StripeExpressCheckoutElement.js';
23
18
  export * from './components/shared/StripePlanSelector.js';
24
19
  export * from './components/shared/walletPlatform.js';
20
+ export * from './components/shared/StripeCheckoutExpressCheckoutButton.js';
21
+ export * from './components/shared/StripeExpressCheckoutButton.js';
22
+ export * from './components/shared/SharedStripeCheckoutDialog.js';
23
+ export * from './components/shared/ApplePaySubscribeButton.js';
24
+ export * from './components/shared/GooglePaySubscribeButton.js';
25
25
  export * from './testing/walletCheckoutSmoke.js';
package/dist/index.js CHANGED
@@ -1,25 +1,31 @@
1
+ // Billing contracts and plan resolution.
1
2
  export * from './config/billing.config.js';
2
3
  export * from './services/preview-frame.service.js';
3
4
  export * from './services/runtime-mode.service.js';
4
5
  export * from './services/planCatalog.service.js';
5
6
  export * from './services/runtimeBillingPlanCatalog.service.js';
6
7
  export * from './services/paywallOffer.service.js';
8
+ // Stripe API clients and checkout completion handoff.
7
9
  export * from './services/stripe.service.js';
8
10
  export * from './services/checkoutCompletionAnalytics.service.js';
9
11
  export * from './hooks/useResolvedPaywallPlans.js';
12
+ // Provider-neutral and Stripe subscription checkout primitives.
10
13
  export * from './providers/paymentProvider.types.js';
11
14
  export * from './providers/stripe/useStripeSubscriptionCheckoutSession.js';
12
15
  export * from './providers/stripe/WalletSubscriptionCheckoutSlot.js';
13
16
  export * from './providers/stripe/ApplePaySubscriptionCheckoutSlot.js';
14
17
  export * from './providers/stripe/GooglePaySubscriptionCheckoutSlot.js';
18
+ // Shared checkout and subscription-management UI.
15
19
  export { ManageSubscriptionScreen, } from './components/ManageSubscriptionScreen.js';
16
- export * from './components/shared/SharedStripeCheckoutDialog.js';
17
20
  export * from './components/shared/SharedStripeCheckoutV2Dialog.js';
18
- export * from './components/shared/ApplePaySubscribeButton.js';
19
- export * from './components/shared/GooglePaySubscribeButton.js';
20
- export * from './components/shared/StripeCheckoutExpressCheckoutButton.js';
21
- export * from './components/shared/StripeExpressCheckoutButton.js';
22
21
  export * from './components/shared/StripeExpressCheckoutElement.js';
23
22
  export * from './components/shared/StripePlanSelector.js';
24
23
  export * from './components/shared/walletPlatform.js';
24
+ export * from './components/shared/StripeCheckoutExpressCheckoutButton.js';
25
+ export * from './components/shared/StripeExpressCheckoutButton.js';
26
+ // Compatibility-only surfaces kept for saved drafts and published artifacts.
27
+ export * from './components/shared/SharedStripeCheckoutDialog.js';
28
+ export * from './components/shared/ApplePaySubscribeButton.js';
29
+ export * from './components/shared/GooglePaySubscribeButton.js';
30
+ // QA helpers.
25
31
  export * from './testing/walletCheckoutSmoke.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.1.45",
3
+ "version": "0.1.47",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./dist/index.js",
@@ -27,8 +27,8 @@
27
27
  "test:run": "vitest run --passWithNoTests"
28
28
  },
29
29
  "dependencies": {
30
- "@funnelsgrove/analytics": "^0.1.15",
31
- "@funnelsgrove/runtime": "^0.1.18",
30
+ "@funnelsgrove/analytics": "^0.1.28",
31
+ "@funnelsgrove/runtime": "^0.1.45",
32
32
  "@stripe/react-stripe-js": "^5.6.0",
33
33
  "@stripe/stripe-js": "^8.7.0",
34
34
  "react": "19.2.3",