Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(billing): a user can add a payment method while upgrading their plan #2117

Closed
wants to merge 1 commit into from

Conversation

mcstepp
Copy link
Collaborator

@mcstepp mcstepp commented Sep 20, 2024

What does this PR do?

Previously, when a free user without a payment method was attempting to upgrade their plan, it would error out and notify the user that they must add add a payment method before upgrading.

This change will now catch a missing payment method (stripe association) when the user is upgrading their plan and redirect them Stripe to configure a payment method, and then continue the processing the plan change upon stripe success.

Edge case: When a user has already their stripe associated with Unkey, but has deleted their payment method from stripe, the user will get an error PRECONDITION FAILED when attempting to process their plan change. This is due to technical limitation due to where the stripe check lives (client-side), but it's still checked and guarded on the trpc route. The likelihood of a user configuring stripe and then deleting a payment is very slim.

New Package: PostHog-node to capture the plan change event from the server-side.

Fixes #2080

If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

Manual testing

  1. Sign into unkey as a free user without a Stripe association
  2. Go to Settings > Billing > Change Plans
  3. Select Pro and confirm
  4. Be redirected to Stripe
  5. Add payment method and confirm
  6. Redirect back to Billing
  7. Notice plan has changed to Pro

Upgrade flow when user has added a payment method first is unchanged.

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

  • New Features

    • Introduced a new button for changing subscription plans, with checks for payment methods.
    • Enhanced Stripe redirect functionality to handle new plan parameters during checkout.
    • Improved success handling for Stripe transactions, including plan change tracking and logging.
  • Bug Fixes

    • Corrected logic for updating subscription plans based on user actions.
  • Chores

    • Added PostHog analytics library for enhanced user interaction tracking.

Copy link

linear bot commented Sep 20, 2024

Copy link

changeset-bot bot commented Sep 20, 2024

⚠️ No Changeset found

Latest commit: f51a0ab

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Sep 20, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
dashboard ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 20, 2024 2:31pm
planetfall ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 20, 2024 2:31pm
play ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 20, 2024 2:31pm
workflows ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 20, 2024 2:31pm
www ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 20, 2024 2:31pm

Copy link
Contributor

coderabbitai bot commented Sep 20, 2024

Walkthrough

Walkthrough

The changes introduce a series of updates to the billing and payment handling functionality within the application. A new function, handleClick, is added to manage plan changes based on the user's payment method status. The handling of the Stripe redirect and success pages is enhanced to accommodate new plan parameters, ensuring that users are correctly redirected to payment screens when necessary. Additionally, a PostHog client wrapper is implemented for tracking analytics, and a new dependency is added to facilitate this functionality.

Changes

File Change Summary
apps/dashboard/app/(app)/settings/billing/plans/button.tsx Added handleClick function in ChangePlanButton to check payment method status before changing plans. Updated button's onClick to use this function.
apps/dashboard/app/(app)/settings/billing/stripe/page.tsx Updated StripeRedirect function to accept searchParams with an optional new_plan. Modified successUrl construction to include new_plan if applicable.
apps/dashboard/app/(app)/settings/billing/stripe/success/page.tsx Enhanced StripeSuccess function to handle new_plan parameter. Updated database logic to conditionally include plan updates and log plan changes.
apps/dashboard/lib/posthog.ts Introduced PostHogClientWrapper class for PostHog client management, implementing a singleton pattern.
apps/dashboard/package.json Added posthog-node dependency to the project.

Assessment against linked issues

Objective Addressed Explanation
User should be redirected to the payment screen when upgrading without a payment method ( #2080)
Display an error if the user attempts to upgrade without a payment method ( #2080) No error message is displayed for missing payment method.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    -- I pushed a fix in commit <commit_id>, please review it.
    -- Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    -- @coderabbitai generate unit testing code for this file.
    -- @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    -- @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    -- @coderabbitai read src/utils.ts and generate unit testing code.
    -- @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    -- @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

github-actions bot commented Sep 20, 2024

Thank you for following the naming conventions for pull request titles! 🙏

@@ -47,6 +48,18 @@ export const ChangePlanButton: React.FC<Props> = ({ workspace, newPlan, label })
},
});

const handleClick = () => {
const hasPaymentMethod = !!workspace.stripeCustomerId;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only checking if there's a customer associated because I can't configure the stripe env within the client side component, and I want to avoid passing stripe info as a prop from the server page.

this is where the edge case can occur: if they have a stripe customer id, but they've deleted their payment method from stripe, it won't error out until the trpc procedure attempts to change the plan, so it's just a UX inconvenience. the likelihood that the user will delete their payment method is very slim.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Outside diff range and nitpick comments (1)
apps/dashboard/lib/posthog.ts (1)

1-28: Consider adding type annotations to improve code readability and maintainability.

While the code is functional, adding type annotations to function parameters, return values, and variables can enhance code readability and catch potential type-related issues during development. For example:

private static instance: PostHog | null = null;

public static getInstance(): PostHog {
    // ...
}
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between cc0eedd and f51a0ab.

Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
Files selected for processing (5)
  • apps/dashboard/app/(app)/settings/billing/plans/button.tsx (3 hunks)
  • apps/dashboard/app/(app)/settings/billing/stripe/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/settings/billing/stripe/success/page.tsx (3 hunks)
  • apps/dashboard/lib/posthog.ts (1 hunks)
  • apps/dashboard/package.json (1 hunks)
Additional context used
Biome
apps/dashboard/app/(app)/settings/billing/stripe/page.tsx

[error] 65-65: Use !== instead of !=.
!= is only allowed when comparing against null

!= is only allowed when comparing against null

Using != may be unsafe if you are relying on type coercion
Unsafe fix: Use !==

(lint/suspicious/noDoubleEquals)

apps/dashboard/app/(app)/settings/billing/stripe/success/page.tsx

[error] 75-75: Use !== instead of !=.
!= is only allowed when comparing against null

!= is only allowed when comparing against null

Using != may be unsafe if you are relying on type coercion
Unsafe fix: Use !==

(lint/suspicious/noDoubleEquals)

Additional comments not posted (8)
apps/dashboard/lib/posthog.ts (1)

1-28: Great use of the singleton pattern for the PostHog client!

The implementation of the PostHogClientWrapper class ensures that only one instance of the PostHog client is created and shared throughout the application. This approach promotes efficient resource utilization and prevents multiple instances from being created unnecessarily.

apps/dashboard/app/(app)/settings/billing/stripe/page.tsx (3)

10-14: LGTM!

The new Props type definition is correct and matches the updated function signature. The new_plan property accurately represents the expected plan types.


15-16: LGTM!

The function signature update and the destructuring of new_plan from props.searchParams are implemented correctly.


62-63: LGTM!

The successUrl construction is implemented correctly, and the comment provides valuable context about avoiding new URL(...).searchParams to prevent escaping curly braces.

apps/dashboard/app/(app)/settings/billing/plans/button.tsx (3)

51-61: Great job addressing the upgrade flow issue! 👍

The new handleClick function correctly handles the case when a user attempts to switch to the "pro" plan without a payment method by redirecting them to the Stripe payment setup page. This addresses the issue described in the linked issue #2080, where users were encountering errors without any guidance on how to resolve them.

The function also handles the case when the user has a payment method or is switching to the "free" plan by calling the changePlan.mutateAsync function, ensuring a smooth plan change process.

Overall, the changes in this code segment are well-structured and follow the expected logic based on the PR objectives and summary.


111-111: Nice refactoring! 🎉

Updating the button's onClick event handler to call the new handleClick function instead of directly invoking changePlan.mutateAsync is a great refactoring decision.

This change improves the control flow by centralizing the logic for handling the plan change within the handleClick function. It also enhances the maintainability and readability of the code by separating the plan change logic from the button's event handler.

Good job on this refactoring!


Line range hint 1-120: Excellent work on this PR! 🚀

The overall changes in this file align perfectly with the PR objectives of fixing the upgrade account flow issue and addressing the problem described in the linked issue #2080.

The introduction of the handleClick function and the refactoring of the button's onClick event handler significantly improve the control flow and maintainability of the code. The changes handle the case when a user attempts to upgrade without a payment method by redirecting them to the Stripe payment setup page, addressing the confusion and errors encountered by users as described in the linked issue.

Moreover, the changes ensure a smooth plan change process for users with a payment method or those switching to the "free" plan, enhancing the overall user experience.

The refactoring also improves the maintainability and readability of the code by centralizing the plan change logic within the handleClick function, making it easier for future developers to understand and modify the code if needed.

Great job on this PR! The changes effectively address the identified issues and improve the codebase. 👏

apps/dashboard/package.json (1)

92-92: Dependency addition looks good!

The posthog-node dependency has been added correctly with an appropriate version constraint. This aligns with the objective of capturing plan change events from the server side.

Please ensure that the captured events respect user privacy and consent. Consider providing an opt-out mechanism if necessary.

Comment on lines +10 to +11
if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) {
// Return a mock client when the key is not present
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for missing or invalid environment variables.

To improve the robustness of the code, consider adding error handling for cases where the required environment variables (NEXT_PUBLIC_POSTHOG_KEY, NEXT_PUBLIC_POSTHOG_HOST) are missing or have invalid values. You can throw an error or log a warning to alert developers about the misconfiguration.

Here's an example of how you can handle missing or invalid environment variables:

if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) {
    console.warn('PostHog key is missing. Analytics data will not be sent.');
    // Return a mock client when the key is not present
    // ...
} else {
    const host = process.env.NEXT_PUBLIC_POSTHOG_HOST;
    if (!host) {
        console.warn('PostHog host is missing. Using the default host.');
    }
    PostHogClientWrapper.instance = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
        host: host || 'https://app.posthog.com',
        // ...
    });
}

Also applies to: 17-21

Comment on lines +64 to +67
// if they're coming from the change plan flow, pass along the new plan param
if (new_plan && new_plan != ws.plan) {
successUrl += `&new_plan=${new_plan}`;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use !== instead of != for the comparison.

To avoid potential type coercion issues, it's safer to use !== instead of != when comparing new_plan and ws.plan.

Apply this diff to fix the comparison:

-if (new_plan && new_plan != ws.plan) {
+if (new_plan && new_plan !== ws.plan) {
   successUrl += `&new_plan=${new_plan}`;
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// if they're coming from the change plan flow, pass along the new plan param
if (new_plan && new_plan != ws.plan) {
successUrl += `&new_plan=${new_plan}`;
}
// if they're coming from the change plan flow, pass along the new plan param
if (new_plan && new_plan !== ws.plan) {
successUrl += `&new_plan=${new_plan}`;
}
Tools
Biome

[error] 65-65: Use !== instead of !=.
!= is only allowed when comparing against null

!= is only allowed when comparing against null

Using != may be unsafe if you are relying on type coercion
Unsafe fix: Use !==

(lint/suspicious/noDoubleEquals)

Comment on lines +91 to +93
properties: {
properties: { plan: new_plan, workspace: ws.id }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flatten the properties object in PostHogClient.capture.

The properties key is nested within another properties object, which may lead to incorrect event data being sent. Flatten the object to ensure the properties are correctly captured.

Apply this diff to fix the issue:

 properties: {
-   properties: { plan: new_plan, workspace: ws.id }
+   plan: new_plan,
+   workspace: ws.id
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
properties: {
properties: { plan: new_plan, workspace: ws.id }
}
properties: {
plan: new_plan,
workspace: ws.id
}

@@ -69,14 +72,27 @@ export default async function StripeSuccess(props: Props) {
);
}

const isChangingPlan = new_plan && new_plan != ws.plan;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use strict inequality operator !==.

When comparing strings, it's safer to use the strict inequality operator !== instead of != to avoid unintended type coercion issues.

Apply this diff to fix the issue:

- const isChangingPlan = new_plan && new_plan != ws.plan;
+ const isChangingPlan = new_plan && new_plan !== ws.plan;
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isChangingPlan = new_plan && new_plan != ws.plan;
const isChangingPlan = new_plan && new_plan !== ws.plan;
Tools
Biome

[error] 75-75: Use !== instead of !=.
!= is only allowed when comparing against null

!= is only allowed when comparing against null

Using != may be unsafe if you are relying on type coercion
Unsafe fix: Use !==

(lint/suspicious/noDoubleEquals)

Copy link

pullflow-com bot commented Sep 20, 2024

From Meg Stepp ‣ I'm cleaning up the suggestions

Copy link
Collaborator

feel free to ignore them if they don’t make sense
I’m over all happy with the bot, but use your judgement

Copy link

pullflow-com bot commented Sep 20, 2024

From Meg Stepp ‣ it caught something wrong with the posthog node analytics, and im just putting out a new branch to fix this merge conflict hell im in with the pnpm-lock.yaml

@mcstepp mcstepp closed this Sep 20, 2024
@mcstepp
Copy link
Collaborator Author

mcstepp commented Sep 20, 2024

I've moved this to a new branch for merge conflict reasons

@mcstepp mcstepp deleted the ENG-1181-upgrade-flow branch September 20, 2024 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Upgrade account flow is broken due to requiring a payment
2 participants