@funnelsgrove/payments 0.1.53 → 0.1.55

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.
@@ -80,7 +80,7 @@ const resolveSubscriptionStatusLabel = (subscription) => {
80
80
  };
81
81
  export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId, plans, }) {
82
82
  var _a, _b;
83
- const { attributes, completeStep, goToStep, setAnswer, setUser, user } = useFunnel();
83
+ const { attributes, goToStep, setAnswer, setUser, user } = useFunnel();
84
84
  const supportEmail = runtimePublicConfig.supportEmail;
85
85
  const planList = useMemo(() => getFallbackBillingPlans(plans), [plans]);
86
86
  const [stage, setStage] = useState('subscriptions');
@@ -184,20 +184,12 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
184
184
  setData(payload);
185
185
  const refreshedSubscription = toManageSubscriptions(payload.subscriptions).find((subscription) => subscription.id === selectedSubscription.id);
186
186
  setCancelledActiveUntilDate(formatDate((refreshedSubscription === null || refreshedSubscription === void 0 ? void 0 : refreshedSubscription.currentPeriodEnd) || selectedSubscription.currentPeriodEnd));
187
- const finalAnswers = {
188
- manageSubscriptionHasActive: true,
189
- manageSubscriptionReason: selectedReason.id,
190
- manageSubscriptionReasonLabel: selectedReason.label,
191
- manageSubscriptionCancelled: true,
192
- manageSubscriptionCancelledId: selectedSubscription.id,
193
- };
194
187
  await persistCancellationAnswers({
195
188
  cancelled: true,
196
189
  reasonId: selectedReason.id,
197
190
  reasonLabel: selectedReason.label,
198
191
  subscriptionId: selectedSubscription.id,
199
192
  });
200
- completeStep(stepId, finalAnswers);
201
193
  setStage('done');
202
194
  }
203
195
  catch (nextError) {
@@ -213,7 +205,7 @@ export function ManageSubscriptionScreen({ stepId, content, homeStepId, nodeId,
213
205
  ? content.confirmStage.cancellingLabel
214
206
  : content.confirmStage.cancelLabel })] })) : null, stage === 'done' ? (_jsxs("div", { className: 'manage-subscription-inner is-short', children: [_jsx("h1", { className: 'manage-subscription-title', children: content.doneStage.title }), _jsx("p", { className: 'manage-subscription-done-status', children: "Cancelled" }), _jsx("p", { className: 'manage-subscription-done-copy', children: content.doneStage.cancelledMessage }), cancelledActiveUntilDate ? (_jsxs("p", { className: 'manage-subscription-done-copy is-active-until', children: ["Active until: ", cancelledActiveUntilDate] })) : null, _jsx("button", { type: 'button', className: 'manage-subscription-primary', onClick: () => {
215
207
  if (homeStepId) {
216
- goToStep(homeStepId);
208
+ goToStep(homeStepId, { type: 'exit', reason: 'cancel' });
217
209
  return;
218
210
  }
219
211
  setStage('subscriptions');
@@ -16,6 +16,11 @@ export type BillingPlanConfig = {
16
16
  amountCents: number;
17
17
  analyticsPurchaseValue?: number;
18
18
  checkoutSummaryLabel?: string;
19
+ followUpProviderPlanId?: string;
20
+ followUpPlanTitle?: string;
21
+ followUpBillingInterval?: BillingPlanInterval;
22
+ followUpBillingIntervalCount?: number;
23
+ introIterations?: number;
19
24
  };
20
25
  export type BillingFallbackPlanConfig = BillingPlanConfig & {
21
26
  id?: string;
@@ -1,13 +1,14 @@
1
- import type { RuntimeMode } from '@funnelsgrove/runtime';
1
+ import type { FunnelStepType, RuntimeMode } from '@funnelsgrove/runtime';
2
2
  import { type StripeRuntimeConfigOverrides } from './stripe.service.js';
3
3
  export type StripeSubscriptionCheckoutAnalyticsContext = {
4
4
  environment?: RuntimeMode;
5
5
  featureFlags?: Record<string, string | null | undefined>;
6
6
  metadata?: Record<string, unknown> | null;
7
7
  runtimeConfig?: StripeRuntimeConfigOverrides;
8
- stepId?: string;
9
- stepName?: string;
10
- stepType?: string;
8
+ stepId: string;
9
+ stepName: string;
10
+ stepType: Extract<FunnelStepType, 'checkout' | 'paywall_offer' | 'upsell_offer'>;
11
+ stepContractVersion: number;
11
12
  };
12
13
  export type TrackStripeSubscriptionCheckoutStartedInput = StripeSubscriptionCheckoutAnalyticsContext & {
13
14
  checkoutSessionId?: string | null;
@@ -15,7 +16,7 @@ export type TrackStripeSubscriptionCheckoutStartedInput = StripeSubscriptionChec
15
16
  export type TrackPaidStripeSubscriptionCheckoutCompletedInput = StripeSubscriptionCheckoutAnalyticsContext & {
16
17
  checkoutSessionId?: string | null;
17
18
  };
18
- export type TrackStripeSubscriptionCheckoutStartedResult = 'already_tracked' | 'missing_checkout_session' | 'tracked';
19
- export type TrackPaidStripeSubscriptionCheckoutCompletedResult = 'already_tracked' | 'missing_checkout_session' | 'tracked' | 'unpaid';
19
+ export type TrackStripeSubscriptionCheckoutStartedResult = 'already_tracked' | 'analytics_rejected' | 'missing_checkout_session' | 'tracked';
20
+ export type TrackPaidStripeSubscriptionCheckoutCompletedResult = 'already_tracked' | 'analytics_rejected' | 'missing_checkout_session' | 'tracked' | 'unpaid';
20
21
  export declare function trackStripeSubscriptionCheckoutStarted(input: TrackStripeSubscriptionCheckoutStartedInput): Promise<TrackStripeSubscriptionCheckoutStartedResult>;
21
22
  export declare function trackPaidStripeSubscriptionCheckoutCompleted(input: TrackPaidStripeSubscriptionCheckoutCompletedInput): Promise<TrackPaidStripeSubscriptionCheckoutCompletedResult>;
@@ -56,9 +56,13 @@ export async function trackStripeSubscriptionCheckoutStarted(input) {
56
56
  stepId: input.stepId,
57
57
  stepName: input.stepName,
58
58
  stepType: input.stepType,
59
+ stepContractVersion: input.stepContractVersion,
59
60
  metadata: Object.assign(Object.assign({}, metadata), { checkoutSessionId, checkout_session_id: checkoutSessionId, environment: input.environment || metadata.environment }),
60
61
  };
61
- publicAnalyticsSdk.trackCheckoutStarted(conversionInput);
62
+ const eventId = publicAnalyticsSdk.trackCheckoutStarted(conversionInput);
63
+ if (!eventId) {
64
+ return 'analytics_rejected';
65
+ }
62
66
  markCheckoutSessionTracked('checkout_started', checkoutSessionId, input.runtimeConfig);
63
67
  await publicAnalyticsSdk.flush().catch(() => 0);
64
68
  return 'tracked';
@@ -95,9 +99,13 @@ export async function trackPaidStripeSubscriptionCheckoutCompleted(input) {
95
99
  stepId: input.stepId,
96
100
  stepName: input.stepName,
97
101
  stepType: input.stepType,
102
+ stepContractVersion: input.stepContractVersion,
98
103
  metadata: Object.assign(Object.assign(Object.assign({}, asRecord(input.metadata)), analyticsMetadata), { checkoutSessionId, checkout_session_id: checkoutSessionId, checkout_session_status: checkoutStatus.status || undefined, payment_status: checkoutStatus.paymentStatus || undefined, environment: checkoutStatus.environment || input.environment || analyticsMetadata.environment }),
99
104
  };
100
- publicAnalyticsSdk.trackCheckoutCompleted(conversionInput);
105
+ const eventId = publicAnalyticsSdk.trackCheckoutCompleted(conversionInput);
106
+ if (!eventId) {
107
+ return 'analytics_rejected';
108
+ }
101
109
  markCheckoutSessionTracked('checkout_completed', checkoutSessionId, input.runtimeConfig);
102
110
  await publicAnalyticsSdk.flush().catch(() => 0);
103
111
  return 'tracked';
@@ -35,6 +35,10 @@ const BILLING_INTERVAL_DAYS = {
35
35
  month: 30,
36
36
  year: 365,
37
37
  };
38
+ const PAYWALL_DISCOUNT_COOLDOWN_MS = 30 * 60 * 1000;
39
+ const hasDiscountCooldownElapsed = (nowMs, expiresAtMs) => {
40
+ return nowMs >= expiresAtMs + PAYWALL_DISCOUNT_COOLDOWN_MS;
41
+ };
38
42
  const clampRemainingSeconds = (value) => {
39
43
  if (!Number.isFinite(value) || value <= 0) {
40
44
  return 0;
@@ -142,6 +146,9 @@ const createDiscountStateFromStarts = (input) => {
142
146
  const expiresAtMs = input.secondStartedAtMs + getDiscountDurationMs(input.discounts, 'second');
143
147
  const remainingSeconds = clampRemainingSeconds((expiresAtMs - input.nowMs) / 1000);
144
148
  if (remainingSeconds <= 0) {
149
+ if (hasDiscountCooldownElapsed(input.nowMs, expiresAtMs)) {
150
+ return createDiscountStateFromStarts(Object.assign(Object.assign({}, input), { firstStartedAtMs: input.nowMs, secondStartedAtMs: null }));
151
+ }
145
152
  return createExpiredDiscountState({
146
153
  stage: 'completed',
147
154
  firstStartedAtMs: input.firstStartedAtMs,
@@ -167,6 +174,9 @@ const createDiscountStateFromStarts = (input) => {
167
174
  const expiresAtMs = input.firstStartedAtMs + getDiscountDurationMs(input.discounts, 'first');
168
175
  const remainingSeconds = clampRemainingSeconds((expiresAtMs - input.nowMs) / 1000);
169
176
  if (remainingSeconds <= 0) {
177
+ if (hasDiscountCooldownElapsed(input.nowMs, expiresAtMs)) {
178
+ return createDiscountStateFromStarts(Object.assign(Object.assign({}, input), { firstStartedAtMs: input.nowMs, secondStartedAtMs: null }));
179
+ }
170
180
  return createExpiredDiscountState({
171
181
  stage: 'first',
172
182
  firstStartedAtMs: input.firstStartedAtMs,
@@ -227,7 +237,8 @@ export const advancePaywallDiscountState = (input) => {
227
237
  };
228
238
  export const activateSecondPaywallDiscount = (input) => {
229
239
  var _a;
230
- if (typeof input.state.secondStartedAtMs === 'number' ||
240
+ if (input.state.status !== 'active' ||
241
+ typeof input.state.secondStartedAtMs === 'number' ||
231
242
  input.state.stage === 'completed') {
232
243
  return input.state;
233
244
  }
@@ -20,6 +20,11 @@ export type PaywallPlan = {
20
20
  checkoutSummaryLabel?: string;
21
21
  billingInterval?: BillingPlanInterval;
22
22
  billingIntervalCount?: number;
23
+ followUpProviderPlanId?: string;
24
+ followUpPlanTitle?: string;
25
+ followUpBillingInterval?: BillingPlanInterval;
26
+ followUpBillingIntervalCount?: number;
27
+ introIterations?: number;
23
28
  source: 'stripe' | 'config';
24
29
  };
25
30
  export type StripeRuntimeConfigOverrides = FunnelSdkRuntimeConfigOverrides;
@@ -40,7 +45,7 @@ export declare function resolvePaywallPlansFromProjectPlans(projectPlans: readon
40
45
  export declare function getPaywallPlans(mode: RuntimeMode, planCatalog?: BillingPlanInputCatalog | readonly BillingFallbackPlanConfig[], options?: PaywallPlanResolutionOptions): Promise<readonly PaywallPlan[]>;
41
46
  export declare function isStripeConfigured(mode: RuntimeMode): boolean;
42
47
  export declare function getStripePromise(mode: RuntimeMode, runtimePublishableKey?: string | null): Promise<Stripe | null> | null;
43
- type CheckoutSessionPlanInput = Pick<PaywallPlan, 'amountCents' | 'analyticsPurchaseValue' | 'billingInterval' | 'billingIntervalCount' | 'description' | 'id' | 'providerPlanId' | 'title'>;
48
+ type CheckoutSessionPlanInput = Pick<PaywallPlan, 'amountCents' | 'analyticsPurchaseValue' | 'billingInterval' | 'billingIntervalCount' | 'description' | 'id' | 'providerPlanId' | 'title' | 'followUpProviderPlanId' | 'followUpPlanTitle' | 'followUpBillingInterval' | 'followUpBillingIntervalCount' | 'introIterations'>;
44
49
  type CheckoutPlanRecurringConfig = {
45
50
  interval: BillingPlanInterval;
46
51
  intervalCount: number;
@@ -268,6 +268,11 @@ const toPlanFromRemoteProjectPlan = (rawPlan, options, planIdOverride) => {
268
268
  checkoutSummaryLabel: asTrimmedStringOrNull(metadata.checkoutSummaryLabel) || undefined,
269
269
  billingInterval: asBillingPlanIntervalOrNull(interval) || undefined,
270
270
  billingIntervalCount: intervalCount || undefined,
271
+ followUpProviderPlanId: asTrimmedStringOrNull(metadata.followUpProviderPlanId) || undefined,
272
+ followUpPlanTitle: asTrimmedStringOrNull(metadata.followUpPlanTitle) || undefined,
273
+ followUpBillingInterval: asBillingPlanIntervalOrNull(metadata.followUpBillingInterval) || undefined,
274
+ followUpBillingIntervalCount: asPositiveIntOrNull(metadata.followUpBillingIntervalCount) || undefined,
275
+ introIterations: asPositiveIntOrNull(metadata.introIterations) || undefined,
271
276
  source: 'stripe',
272
277
  };
273
278
  };
@@ -579,7 +584,7 @@ export async function chargeStripeOneClickPayment(input) {
579
584
  });
580
585
  }
581
586
  export async function createStripeSubscriptionCheckout(input) {
582
- var _a, _b, _c, _d, _e;
587
+ var _a, _b, _c, _d, _e, _f, _g;
583
588
  const recurring = resolveCheckoutPlanRecurringConfig(input.plan);
584
589
  if (!recurring) {
585
590
  throw new Error('Unable to resolve the recurring interval for the selected plan');
@@ -592,17 +597,22 @@ export async function createStripeSubscriptionCheckout(input) {
592
597
  amountCents: input.plan.amountCents,
593
598
  billingInterval: recurring.interval,
594
599
  billingIntervalCount: recurring.intervalCount,
595
- couponId: ((_c = input.couponId) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
600
+ followUpProviderPlanId: ((_c = input.plan.followUpProviderPlanId) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
601
+ followUpPlanTitle: ((_d = input.plan.followUpPlanTitle) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
602
+ followUpBillingInterval: input.plan.followUpBillingInterval,
603
+ followUpBillingIntervalCount: input.plan.followUpBillingIntervalCount,
604
+ introIterations: input.plan.introIterations,
605
+ couponId: ((_e = input.couponId) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
596
606
  analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan),
597
- customerEmail: ((_d = input.customerEmail) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
607
+ customerEmail: ((_f = input.customerEmail) === null || _f === void 0 ? void 0 : _f.trim()) || undefined,
598
608
  returnUrl: input.returnUrl,
599
- userId: ((_e = input.user_id) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
609
+ userId: ((_g = input.user_id) === null || _g === void 0 ? void 0 : _g.trim()) || undefined,
600
610
  environment: input.environment,
601
611
  runtimeConfig: input.runtimeConfig,
602
612
  });
603
613
  }
604
614
  export async function updateStripeSubscriptionCheckoutPlan(input) {
605
- var _a, _b, _c, _d, _e;
615
+ var _a, _b, _c, _d, _e, _f, _g;
606
616
  const recurring = resolveCheckoutPlanRecurringConfig(input.plan);
607
617
  if (!recurring) {
608
618
  throw new Error('Unable to resolve the recurring interval for the selected plan');
@@ -616,10 +626,15 @@ export async function updateStripeSubscriptionCheckoutPlan(input) {
616
626
  amountCents: input.plan.amountCents,
617
627
  billingInterval: recurring.interval,
618
628
  billingIntervalCount: recurring.intervalCount,
619
- couponId: ((_c = input.couponId) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
629
+ followUpProviderPlanId: ((_c = input.plan.followUpProviderPlanId) === null || _c === void 0 ? void 0 : _c.trim()) || undefined,
630
+ followUpPlanTitle: ((_d = input.plan.followUpPlanTitle) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
631
+ followUpBillingInterval: input.plan.followUpBillingInterval,
632
+ followUpBillingIntervalCount: input.plan.followUpBillingIntervalCount,
633
+ introIterations: input.plan.introIterations,
634
+ couponId: ((_e = input.couponId) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
620
635
  analyticsMetadata: normalizeCheckoutAnalyticsMetadata(input.analyticsMetadata, input.plan),
621
- customerEmail: ((_d = input.customerEmail) === null || _d === void 0 ? void 0 : _d.trim()) || undefined,
622
- userId: ((_e = input.user_id) === null || _e === void 0 ? void 0 : _e.trim()) || undefined,
636
+ customerEmail: ((_f = input.customerEmail) === null || _f === void 0 ? void 0 : _f.trim()) || undefined,
637
+ userId: ((_g = input.user_id) === null || _g === void 0 ? void 0 : _g.trim()) || undefined,
623
638
  environment: input.environment,
624
639
  runtimeConfig: input.runtimeConfig,
625
640
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/payments",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./dist/index.js",