@funnelsgrove/payments 0.28.0 → 0.30.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.
@@ -1 +1 @@
1
- export declare const PAYMENTS_PACKAGE_VERSION = "0.28.0";
1
+ export declare const PAYMENTS_PACKAGE_VERSION = "0.30.0";
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/generate-package-version.mjs.
2
- export const PAYMENTS_PACKAGE_VERSION = "0.28.0";
2
+ export const PAYMENTS_PACKAGE_VERSION = "0.30.0";
@@ -3,8 +3,9 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import { completePaywallCheckout } from '@funnelsgrove/runtime';
4
4
  import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
5
5
  import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
6
- import { pollSolidgateCheckoutStatus, prepareSolidgateCheckout, } from '../services/solidgate.service.js';
6
+ import { pollSolidgateCheckoutStatus, prepareSolidgateCheckout, SolidgateExistingSubscriptionError, } from '../services/solidgate.service.js';
7
7
  import { trackCheckoutCanceled, trackCheckoutFailureShown, } from '../../../services/checkoutObservability.service.js';
8
+ import { solidgateDeclineMessage } from '../services/solidgateDeclineMessage.js';
8
9
  import { SolidgateCheckoutSummary } from './SolidgateCheckoutSummary.js';
9
10
  import { SolidgatePaymentForm } from './SolidgatePaymentForm.js';
10
11
  import { solidgateCheckoutDialogStyles } from './SolidgateCheckoutDialog.styles.js';
@@ -75,6 +76,7 @@ export function SolidgateCheckoutDialog({ open, layout = 'dialog', request, cust
75
76
  const [emailError, setEmailError] = useState(null);
76
77
  const [emailSubmitting, setEmailSubmitting] = useState(false);
77
78
  const [mountedPaymentAttemptId, setMountedPaymentAttemptId] = useState(null);
79
+ const declinedAttemptRef = useRef(null);
78
80
  const prepareAbortRef = useRef(null);
79
81
  const preparedRequestRef = useRef(null);
80
82
  const statusAbortRef = useRef(null);
@@ -105,6 +107,7 @@ export function SolidgateCheckoutDialog({ open, layout = 'dialog', request, cust
105
107
  const { discountKeyOrProviderId, failUrl, funnelPlanKey, paymentProfileId, providerPlanId, checkoutType, environment, funnelEndUserId, funnelId, idempotencyKey, offerSetId, returnUrl, runtimeConfig, successUrl, } = request;
106
108
  const checkoutKey = (retryRequest === null || retryRequest === void 0 ? void 0 : retryRequest.originalKey) === idempotencyKey
107
109
  ? retryRequest.key : idempotencyKey;
110
+ const retryAttemptId = (retryRequest === null || retryRequest === void 0 ? void 0 : retryRequest.originalKey) === idempotencyKey ? retryRequest.attemptId : request.retryAttemptId;
108
111
  const { apiBaseUrl, funnelId: runtimeFunnelId, funnelSdkPublishableKey, funnelVersionId, paymentsApiVersion, } = runtimeConfig || {};
109
112
  const hasRuntimeConfig = runtimeConfig !== undefined;
110
113
  const normalizedRequest = useMemo(() => ({
@@ -117,6 +120,7 @@ export function SolidgateCheckoutDialog({ open, layout = 'dialog', request, cust
117
120
  checkoutType: checkoutType || null,
118
121
  environment: environment || null,
119
122
  idempotencyKey: checkoutKey.trim(),
123
+ retryAttemptId,
120
124
  discountKeyOrProviderId: (discountKeyOrProviderId === null || discountKeyOrProviderId === void 0 ? void 0 : discountKeyOrProviderId.trim()) || null,
121
125
  returnUrl: returnUrl.trim(),
122
126
  successUrl: successUrl.trim(),
@@ -143,6 +147,7 @@ export function SolidgateCheckoutDialog({ open, layout = 'dialog', request, cust
143
147
  funnelVersionId,
144
148
  paymentsApiVersion,
145
149
  checkoutKey,
150
+ retryAttemptId,
146
151
  offerSetId,
147
152
  returnUrl,
148
153
  hasRuntimeConfig,
@@ -210,6 +215,7 @@ export function SolidgateCheckoutDialog({ open, layout = 'dialog', request, cust
210
215
  void prepareSolidgateCheckout(Object.assign(Object.assign({}, preparedRequest), { signal: controller.signal }))
211
216
  .then((checkout) => {
212
217
  if (!controller.signal.aborted) {
218
+ declinedAttemptRef.current = null;
213
219
  setState({ state: 'ready', checkout, canResumeVerification: false });
214
220
  if (layout !== 'wallet') {
215
221
  void trackSolidgateCheckoutEvent('checkout_started', checkout.attemptId, checkoutAnalyticsRef.current);
@@ -219,9 +225,11 @@ export function SolidgateCheckoutDialog({ open, layout = 'dialog', request, cust
219
225
  .catch((error) => {
220
226
  var _a;
221
227
  if (!controller.signal.aborted && !isAbortError(error)) {
222
- setState({ state: 'recoverable_error', checkout: null, canResumeVerification: false });
223
- trackFailure(copyRef.current.error, 'checkout_session_creation');
224
- (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, copyRef.current.error);
228
+ const failureMessage = error instanceof SolidgateExistingSubscriptionError
229
+ ? error.message : copyRef.current.error;
230
+ setState({ state: 'recoverable_error', checkout: null, canResumeVerification: false, failureMessage });
231
+ trackFailure(failureMessage, 'checkout_session_creation');
232
+ (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, failureMessage, error instanceof SolidgateExistingSubscriptionError ? error.code : undefined);
225
233
  }
226
234
  });
227
235
  return () => controller.abort();
@@ -304,9 +312,11 @@ export function SolidgateCheckoutDialog({ open, layout = 'dialog', request, cust
304
312
  trackFailure(copyRef.current.expired, 'payment_expired', checkout.attemptId);
305
313
  }
306
314
  else if (result.status === 'failed') {
307
- setState({ state: 'recoverable_error', checkout, canResumeVerification: false });
308
- trackFailure(copyRef.current.error, 'payment_failed', checkout.attemptId);
309
- (_c = onErrorRef.current) === null || _c === void 0 ? void 0 : _c.call(onErrorRef, copyRef.current.error);
315
+ declinedAttemptRef.current = checkout.attemptId;
316
+ const failureMessage = solidgateDeclineMessage(result.failureCode);
317
+ setState({ state: 'recoverable_error', checkout, canResumeVerification: false, failureMessage });
318
+ trackFailure(failureMessage, 'payment_failed', checkout.attemptId);
319
+ (_c = onErrorRef.current) === null || _c === void 0 ? void 0 : _c.call(onErrorRef, failureMessage);
310
320
  }
311
321
  else if (result.status === 'requires_payment') {
312
322
  setState({ state: 'ready', checkout, canResumeVerification: false });
@@ -380,20 +390,29 @@ export function SolidgateCheckoutDialog({ open, layout = 'dialog', request, cust
380
390
  : view.state === 'expired'
381
391
  ? copy.expired
382
392
  : view.state === 'recoverable_error'
383
- ? copy.error
393
+ ? view.failureMessage || copy.error
384
394
  : null;
385
395
  const presentationStyle = presentation
386
396
  ? {
387
397
  '--solidgate-checkout-theme-color': presentation.themeColor,
388
398
  }
389
399
  : undefined;
390
- const recoveryActions = (_jsxs(_Fragment, { children: [view.canResumeVerification ? (_jsx("button", { type: 'button', onClick: () => void verifyCanonicalStatus(), className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.resumeVerification })) : null, (view.state === 'recoverable_error' || view.state === 'expired') && !view.canResumeVerification ? (_jsx("button", { type: 'button', onClick: onRetry || (() => {
400
+ const recoveryActions = (_jsxs(_Fragment, { children: [view.canResumeVerification ? (_jsx("button", { type: 'button', onClick: () => void verifyCanonicalStatus(), className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.resumeVerification })) : null, (view.state === 'recoverable_error' || view.state === 'expired') && !view.canResumeVerification ? (_jsx("button", { type: 'button', onClick: () => {
401
+ var _a;
402
+ const source = checkoutType === 'subscription'
403
+ ? declinedAttemptRef.current || (retryAttemptId ? ((_a = view.checkout) === null || _a === void 0 ? void 0 : _a.attemptId) || retryAttemptId : null)
404
+ : null;
405
+ if (!source && onRetry) {
406
+ onRetry();
407
+ return;
408
+ }
391
409
  preparedRequestRef.current = null;
392
410
  setRetryRequest({
393
411
  originalKey: idempotencyKey,
394
412
  key: crypto.randomUUID(),
413
+ attemptId: source,
395
414
  });
396
- }), className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.retry })) : null] }));
415
+ }, className: 'solidgate-checkout-action solidgate-checkout-action--spaced', children: copy.retry })) : null] }));
397
416
  const paymentForm = view.checkout ? (_jsx(SolidgatePaymentForm, { walletOnly: layout === 'wallet', secureLabel: presentation === null || presentation === void 0 ? void 0 : presentation.secureLabel, cardLabel: presentation === null || presentation === void 0 ? void 0 : presentation.cardLabel, paymentMethodLabel: presentation === null || presentation === void 0 ? void 0 : presentation.paymentMethodLabel, buttonColor: (presentation === null || presentation === void 0 ? void 0 : presentation.buttonColor) || '#3f51b5', cardSubmitLabel: (presentation === null || presentation === void 0 ? void 0 : presentation.cardSubmitLabel) || 'Pay securely', checkout: view.checkout, hidden: !showForm, loadingLabel: copy.loading, onMounted: () => {
398
417
  var _a;
399
418
  setMountedPaymentAttemptId(((_a = view.checkout) === null || _a === void 0 ? void 0 : _a.attemptId) || null);
@@ -56,7 +56,7 @@ export type SolidgateCheckoutDialogProps = {
56
56
  onCustomerEmailCommit?: (email: string) => Promise<string>;
57
57
  onSuccess?: (checkout: SolidgateCheckoutPreparation) => void | Promise<void>;
58
58
  onRetry?: () => void;
59
- onError?: (message: string) => void;
59
+ onError?: (message: string, code?: 'already_subscribed_to_product') => void;
60
60
  onPreparationStart?: () => void;
61
61
  /** True while a submitted payment still needs verification (including resumable errors). */
62
62
  onPaymentPendingChange?: (pending: boolean) => void;
@@ -33,7 +33,7 @@ export function SolidgatePaymentForm({ buttonColor, walletOnly = false, cardSubm
33
33
  ].filter((method) => Boolean(method));
34
34
  setWallets((previous) => previous.join() === available.join() ? previous : available);
35
35
  if (!mountedReportedRef.current
36
- && boundary.querySelector('iframe#solid-payment-form-iframe, iframe[name="solid-payment-form-iframe"]')) {
36
+ && boundary.querySelector('iframe#payment-form-iframe, iframe[name="payment-form-iframe"], iframe#solid-payment-form-iframe, iframe[name="solid-payment-form-iframe"]')) {
37
37
  mountedReportedRef.current = true;
38
38
  onMountedRef.current();
39
39
  }
@@ -44,7 +44,7 @@ export function SolidgatePaymentForm({ buttonColor, walletOnly = false, cardSubm
44
44
  return () => observer.disconnect();
45
45
  }, [checkout.attemptId]);
46
46
  const walletButtonType = checkout.display.billingInterval ? 'subscribe' : 'buy';
47
- return (_jsxs("div", { ref: paymentBoundaryRef, className: `solidgate-checkout-payment${secureLabel && !walletOnly ? ' is-branded' : ''}`, "data-solidgate-attempt-id": checkout.attemptId, hidden: hidden, "aria-hidden": hidden || undefined, children: [showTabs ? (_jsxs("div", { className: 'solidgate-checkout-methods', role: 'tablist', "aria-label": paymentMethodLabel, children: [_jsx("button", { type: 'button', role: 'tab', id: `${panelId}-wallet-tab`, "aria-label": wallets.join(' / '), "aria-selected": selectedMethod === 'wallet', "aria-controls": `${panelId}-wallet`, className: 'solidgate-checkout-method', onClick: () => setSelectedMethod('wallet'), children: wallets.map((label) => _jsx(PaymentBrandMark, { label: label, className: 'solidgate-checkout-tab-wallet' }, label)) }), _jsxs("button", { type: 'button', role: 'tab', id: `${panelId}-card-tab`, "aria-selected": selectedMethod === 'card', "aria-controls": `${panelId}-card`, className: 'solidgate-checkout-method', onClick: () => setSelectedMethod('card'), children: [_jsx("strong", { children: cardLabel }), _jsx("span", { className: 'solidgate-checkout-tab-brands', "aria-hidden": 'true', children: ['Visa', 'Mastercard', 'Maestro', 'Discover'].map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'solidgate-checkout-tab-brand' }, label))) })] })] })) : null, !walletOnly && secureLabel && wallets.length > 0 && showWallet ? (_jsxs("p", { className: 'solidgate-checkout-secure', children: [_jsx(SecurityLockIcon, {}), secureLabel] })) : null, _jsxs("div", { className: 'solidgate-checkout-wallets', id: `${panelId}-wallet`, role: showTabs ? 'tabpanel' : undefined, "aria-labelledby": showTabs ? `${panelId}-wallet-tab` : undefined, hidden: !showWallet, children: [_jsx("div", { ref: applePayContainerRef, "data-solidgate-wallet": 'apple-pay' }), _jsx("div", { ref: googlePayContainerRef, "data-solidgate-wallet": 'google-pay' })] }), _jsx("div", { className: 'solidgate-checkout-card', id: `${panelId}-card`, role: showTabs ? 'tabpanel' : undefined, "aria-labelledby": showTabs ? `${panelId}-card-tab` : undefined, hidden: !showCard, children: _jsx(SolidgateSdkPayment, { onInitializationError: onError, merchantData: checkout.initialization.merchantData, width: '100%', formParams: Object.assign({ autoFocus: false, enabled: !walletOnly, isSolidLogoVisible: true, submitButtonText: cardSubmitLabel }, (secureLabel ? { cardNumberLabel: 'Card number', cardExpiryDateLabel: 'Expiration date', cardCvvLabel: 'Security code' } : {})), styles: {
47
+ return (_jsxs("div", { ref: paymentBoundaryRef, className: `solidgate-checkout-payment${secureLabel && !walletOnly ? ' is-branded' : ''}`, "data-solidgate-attempt-id": checkout.attemptId, hidden: hidden, "aria-hidden": hidden || undefined, children: [showTabs ? (_jsxs("div", { className: 'solidgate-checkout-methods', role: 'tablist', "aria-label": paymentMethodLabel, children: [_jsx("button", { type: 'button', role: 'tab', id: `${panelId}-wallet-tab`, "aria-label": wallets.join(' / '), "aria-selected": selectedMethod === 'wallet', "aria-controls": `${panelId}-wallet`, className: 'solidgate-checkout-method', onClick: () => setSelectedMethod('wallet'), children: wallets.map((label) => _jsx(PaymentBrandMark, { label: label, className: 'solidgate-checkout-tab-wallet' }, label)) }), _jsxs("button", { type: 'button', role: 'tab', id: `${panelId}-card-tab`, "aria-selected": selectedMethod === 'card', "aria-controls": `${panelId}-card`, className: 'solidgate-checkout-method', onClick: () => setSelectedMethod('card'), children: [_jsx("strong", { children: cardLabel }), _jsx("span", { className: 'solidgate-checkout-tab-brands', "aria-hidden": 'true', children: ['Visa', 'Mastercard', 'Maestro', 'Discover'].map((label) => (_jsx(PaymentBrandMark, { label: label, className: 'solidgate-checkout-tab-brand' }, label))) })] })] })) : null, !walletOnly && secureLabel && wallets.length > 0 && showWallet ? (_jsxs("p", { className: 'solidgate-checkout-secure', children: [_jsx(SecurityLockIcon, {}), secureLabel] })) : null, _jsxs("div", { className: 'solidgate-checkout-wallets', id: `${panelId}-wallet`, role: showTabs ? 'tabpanel' : undefined, "aria-labelledby": showTabs ? `${panelId}-wallet-tab` : undefined, hidden: !showWallet, children: [_jsx("div", { ref: applePayContainerRef, "data-solidgate-wallet": 'apple-pay' }), _jsx("div", { ref: googlePayContainerRef, "data-solidgate-wallet": 'google-pay' })] }), _jsx("div", { className: 'solidgate-checkout-card', id: `${panelId}-card`, role: showTabs ? 'tabpanel' : undefined, "aria-labelledby": showTabs ? `${panelId}-card-tab` : undefined, hidden: !showCard, children: _jsx(SolidgateSdkPayment, { onInitializationError: onError, merchantData: checkout.initialization.merchantData, width: '100%', formParams: Object.assign({ autoFocus: false, enabled: !walletOnly, isSolidLogoVisible: false, submitButtonText: cardSubmitLabel }, (secureLabel ? { cardNumberLabel: 'Card number', cardExpiryDateLabel: 'Expiration date', cardCvvLabel: 'Security code' } : {})), styles: {
48
48
  form_body: {
49
49
  'font-family': 'Inter, Arial, sans-serif',
50
50
  },
@@ -23,7 +23,7 @@ export function SolidgateSdkPayment(props) {
23
23
  google.id = `${id}-google`;
24
24
  void import('@solidgate/react-sdk').then(async (sdk) => {
25
25
  var _a, _b;
26
- const loaded = await sdk.SdkLoader.load();
26
+ const loaded = await sdk.SdkLoader.load('https://cdn.charge-auth.com/js/form.js');
27
27
  if (!active || !loaded)
28
28
  return;
29
29
  client = loaded;
@@ -1,5 +1,9 @@
1
1
  import { type FunnelSdkRuntimeConfigOverrides } from '@funnelsgrove/runtime';
2
2
  export type SolidgateCheckoutStatus = 'preparing' | 'requires_payment' | 'verifying' | 'succeeded' | 'failed' | 'expired';
3
+ export declare class SolidgateExistingSubscriptionError extends Error {
4
+ readonly code: "already_subscribed_to_product";
5
+ constructor();
6
+ }
3
7
  export type SolidgateCheckoutPreparation = {
4
8
  attemptId: string;
5
9
  display: {
@@ -31,6 +35,7 @@ export type PrepareSolidgateCheckoutInput = {
31
35
  checkoutType?: 'subscription' | 'one_time' | null;
32
36
  environment?: 'test' | 'live' | null;
33
37
  idempotencyKey: string;
38
+ retryAttemptId?: string | null;
34
39
  discountKeyOrProviderId?: string | null;
35
40
  returnUrl: string;
36
41
  successUrl: string;
@@ -49,6 +54,7 @@ export type SolidgateCheckoutStatusResponse = {
49
54
  attemptId: string;
50
55
  status: SolidgateCheckoutStatus;
51
56
  retryAfterMs: number | null;
57
+ failureCode?: string;
52
58
  };
53
59
  export type PollSolidgateCheckoutStatusResult = SolidgateCheckoutStatusResponse & {
54
60
  timedOut: boolean;
@@ -1,4 +1,11 @@
1
1
  import { paywallCheckoutReturnUrl, retainPaywallCheckout, FUNNEL_ID, FUNNEL_SDK_PUBLISHABLE_KEY, buildMainApiUrl, resolvePreviewPaymentPublishableKey, } from '@funnelsgrove/runtime';
2
+ export class SolidgateExistingSubscriptionError extends Error {
3
+ constructor() {
4
+ super('You already have an active subscription to this product.');
5
+ this.code = 'already_subscribed_to_product';
6
+ this.name = 'SolidgateExistingSubscriptionError';
7
+ }
8
+ }
2
9
  const trimTrailingSlash = (value) => value.replace(/\/+$/, '');
3
10
  const requiredString = (value, name) => {
4
11
  const normalized = typeof value === 'string' ? value.trim() : '';
@@ -28,6 +35,13 @@ const postJson = async (path, body, runtimeConfig, signal) => {
28
35
  signal,
29
36
  });
30
37
  if (!response.ok) {
38
+ if (response.status === 409) {
39
+ const payload = await response.json().catch(() => null);
40
+ if (payload && typeof payload === 'object' && 'code' in payload
41
+ && payload.code === 'already_subscribed_to_product') {
42
+ throw new SolidgateExistingSubscriptionError();
43
+ }
44
+ }
31
45
  throw new Error('Solidgate checkout is temporarily unavailable');
32
46
  }
33
47
  return response.json();
@@ -103,11 +117,8 @@ const parseStatus = (value) => {
103
117
  && (typeof retryAfterMs !== 'number' || !Number.isFinite(retryAfterMs) || retryAfterMs < 0)) {
104
118
  throw new Error('Solidgate checkout status returned an invalid retry delay');
105
119
  }
106
- return {
107
- attemptId: requiredString(value.attemptId, 'attemptId'),
108
- status: value.status,
109
- retryAfterMs,
110
- };
120
+ return Object.assign({ attemptId: requiredString(value.attemptId, 'attemptId'), status: value.status, retryAfterMs }, (value.status === 'failed' && typeof value.failureCode === 'string'
121
+ && /^\d\.\d{2}$/.test(value.failureCode) ? { failureCode: value.failureCode } : {}));
111
122
  };
112
123
  export const prepareSolidgateCheckout = async (input) => {
113
124
  var _a, _b, _c;
@@ -118,23 +129,8 @@ export const prepareSolidgateCheckout = async (input) => {
118
129
  if (!paymentProfileId || !providerPlanId || !checkoutType || !environment) {
119
130
  throw new Error('Solidgate V2 checkout selection is incomplete');
120
131
  }
121
- const result = parsePreparation(await postJson('/sdk/public/v2/payments/checkout-sessions', {
122
- checkoutType,
123
- uiMode: 'custom',
124
- environment,
125
- paymentProfileId,
126
- provider: 'solidgate',
127
- providerPlanId,
128
- planKey: requiredString(input.funnelPlanKey, 'funnelPlanKey'),
129
- funnelId: resolveFunnelId(input.funnelId, input.runtimeConfig),
130
- funnelEndUserId: requiredString(input.funnelEndUserId, 'funnelEndUserId'),
131
- offerSetId: requiredString(input.offerSetId, 'offerSetId'),
132
- idempotencyKey: requiredString(input.idempotencyKey, 'idempotencyKey'),
133
- couponId: ((_c = input.discountKeyOrProviderId) === null || _c === void 0 ? void 0 : _c.trim()) || null,
134
- returnUrl: paywallCheckoutReturnUrl(requiredString(input.returnUrl, 'returnUrl'), 'solidgate'),
135
- successUrl: paywallCheckoutReturnUrl(requiredString(input.successUrl, 'successUrl'), 'solidgate'),
136
- failUrl: paywallCheckoutReturnUrl(requiredString(input.failUrl, 'failUrl'), 'solidgate', true),
137
- }, input.runtimeConfig, input.signal));
132
+ const result = parsePreparation(await postJson('/sdk/public/v2/payments/checkout-sessions', Object.assign(Object.assign({ checkoutType, uiMode: 'custom', environment,
133
+ paymentProfileId, provider: 'solidgate', providerPlanId, planKey: requiredString(input.funnelPlanKey, 'funnelPlanKey'), funnelId: resolveFunnelId(input.funnelId, input.runtimeConfig), funnelEndUserId: requiredString(input.funnelEndUserId, 'funnelEndUserId'), offerSetId: requiredString(input.offerSetId, 'offerSetId'), idempotencyKey: requiredString(input.idempotencyKey, 'idempotencyKey') }, (input.retryAttemptId ? { retryAttemptId: input.retryAttemptId } : {})), { couponId: ((_c = input.discountKeyOrProviderId) === null || _c === void 0 ? void 0 : _c.trim()) || null, returnUrl: paywallCheckoutReturnUrl(requiredString(input.returnUrl, 'returnUrl'), 'solidgate'), successUrl: paywallCheckoutReturnUrl(requiredString(input.successUrl, 'successUrl'), 'solidgate'), failUrl: paywallCheckoutReturnUrl(requiredString(input.failUrl, 'failUrl'), 'solidgate', true) }), input.runtimeConfig, input.signal));
138
134
  retainPaywallCheckout('solidgate', result.attemptId);
139
135
  return result;
140
136
  };
@@ -0,0 +1 @@
1
+ export declare const solidgateDeclineMessage: (code?: string) => string;
@@ -0,0 +1,24 @@
1
+ // Customer-safe descriptions; never render raw processor messages or risk decisions.
2
+ // https://docs.solidgate.com/payments/payments-insights/error-codes/
3
+ const messages = {
4
+ '0.02': 'The payment session expired. Start a new checkout to continue.',
5
+ '2.04': 'This card type is not supported. Use another payment method.',
6
+ '2.06': 'The security code is incorrect. Check the code on your card and try again.',
7
+ '2.08': 'The card number is invalid. Check the number and try again.',
8
+ '2.09': 'The card expiration date is invalid. Check the date or use another card.',
9
+ '2.14': 'We could not start this subscription. Check whether you already have access, or contact support.',
10
+ '2.15': 'Your bank requires additional verification. Try again and complete the bank verification step.',
11
+ '3.01': 'Your bank has blocked this card. Contact your bank or use another payment method.',
12
+ '3.02': 'Your card has insufficient funds. Add funds or use another payment method.',
13
+ '3.03': 'This payment exceeds your card limit. Check your limit with your bank or use another card.',
14
+ '3.04': 'Your bank declined this payment. Contact your bank or use another payment method.',
15
+ '3.05': 'Contact your bank to authorize this payment, or use another payment method.',
16
+ '3.06': 'Debit cards cannot be used for this payment. Use a credit card or another payment method.',
17
+ '3.07': 'This card type is not supported. Use another payment method.',
18
+ '3.09': 'Your bank requires additional verification. Try again and complete the bank verification step.',
19
+ '3.11': 'Recurring payments are not authorized for this card. Use another payment method.',
20
+ '3.13': 'The payment was cancelled. Try again and keep the payment window open until it finishes.',
21
+ '4.08': 'The billing address does not match your card. Check the address with your bank and try again.',
22
+ };
23
+ export const solidgateDeclineMessage = (code) => ((code && Object.hasOwn(messages, code) ? messages[code] : undefined)
24
+ || 'Your payment was declined. Use another payment method or contact your bank.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",
@@ -46,11 +46,11 @@
46
46
  "jsdom": "20.0.3",
47
47
  "typescript": "^5",
48
48
  "vitest": "^3.2.4",
49
- "@funnelsgrove/runtime": "0.25.0",
50
- "@funnelsgrove/analytics": "0.7.0"
49
+ "@funnelsgrove/runtime": "0.26.0",
50
+ "@funnelsgrove/analytics": "0.8.0"
51
51
  },
52
52
  "peerDependencies": {
53
- "@funnelsgrove/runtime": "^0.25.0",
54
- "@funnelsgrove/analytics": "^0.7.0"
53
+ "@funnelsgrove/runtime": "^0.26.0",
54
+ "@funnelsgrove/analytics": "^0.8.0"
55
55
  }
56
56
  }