- {t('Upgrade')}
+ {t('Upgrade to Ultra')}
{t('No Strings Attached - Cancel Anytime!')}
diff --git a/public/locales/zh-Hans/model.json b/public/locales/zh-Hans/model.json
index a67d587bb7..3604280902 100644
--- a/public/locales/zh-Hans/model.json
+++ b/public/locales/zh-Hans/model.json
@@ -46,6 +46,7 @@
"Please sign-up before upgrading to pro plan": "请先注册和登入帐号",
"Upgrade": "升级为专业版",
"Upgrade for one month only": "只购买一个月的专业版",
+ "Upgrade to Ultra": "升級到Ultra专业版",
"Unlock all the amazing features by upgrading to our Pro plan, cancel anytime!": "升级为专业版,解锁以下所有功能!",
"AI Image": "AI图像生成",
"Creative": "创意",
diff --git a/public/locales/zh-Hant/model.json b/public/locales/zh-Hant/model.json
index 22544c6f81..40017bd361 100644
--- a/public/locales/zh-Hant/model.json
+++ b/public/locales/zh-Hant/model.json
@@ -44,6 +44,7 @@
"Please sign-up before upgrading to pro plan": "請先註冊和登入帳號",
"Upgrade": "升級到Pro專業版",
"Upgrade for one month only": "只購買一個月的Pro專業版",
+ "Upgrade to Ultra": "升級到Ultra專業版",
"Unlock all the amazing features by upgrading to our Pro plan, cancel anytime!": "升級為專業版,解鎖以下的所有功能!",
"Creative": "創意",
"Balanced": "平衡",
From 10b18df4a4684f283dc1ce17a1db300959853bbe Mon Sep 17 00:00:00 2001
From: 1orzero
Date: Wed, 29 May 2024 17:46:40 +0800
Subject: [PATCH 046/134] refactor: update the stripe product type format
---
components/User/Settings/PlanComparison.tsx | 46 ++--
types/paid_plan.ts | 1 +
utils/app/const.ts | 12 +-
utils/app/paid_plan_helper.ts | 25 +-
utils/app/stripe_config.ts | 261 ++++++++++++++++----
5 files changed, 262 insertions(+), 83 deletions(-)
diff --git a/components/User/Settings/PlanComparison.tsx b/components/User/Settings/PlanComparison.tsx
index b1fc6851d1..51fa8284e3 100644
--- a/components/User/Settings/PlanComparison.tsx
+++ b/components/User/Settings/PlanComparison.tsx
@@ -2,15 +2,9 @@ import { useMemo, useState } from 'react';
import toast from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
-import {
- OrderedSubscriptionPlans,
- PRO_MONTHLY_PLAN_PAYMENT_LINK_TWD,
- PRO_MONTHLY_PLAN_PAYMENT_LINK_USD,
- ULTRA_MONTHLY_PLAN_PAYMENT_LINK_TWD,
- ULTRA_MONTHLY_PLAN_PAYMENT_LINK_USD,
- ULTRA_YEARLY_PLAN_PAYMENT_LINK_TWD,
- ULTRA_YEARLY_PLAN_PAYMENT_LINK_USD,
-} from '@/utils/app/const';
+import { useChangeSubscriptionPlan } from '@/hooks/useChangeSubscriptionPlan';
+
+import { OrderedSubscriptionPlans, STRIPE_PRODUCTS } from '@/utils/app/const';
import { trackEvent } from '@/utils/app/eventTracking';
import { FeatureItem, PlanDetail } from '@/utils/app/ui';
@@ -84,11 +78,18 @@ const ProPlanContent = ({ user }: { user: User | null }) => {
const proPlanIndex = OrderedSubscriptionPlans.indexOf('pro');
return userPlanIndex < proPlanIndex;
}, [user]);
+ const isPaidUser = user?.plan === 'pro' || user?.plan === 'ultra';
+ const { mutate: changeSubscriptionPlan } = useChangeSubscriptionPlan();
+
const upgradeLinkOnClick = () => {
+ if (isPaidUser) {
+ changeSubscriptionPlan();
+ return;
+ }
const paymentLink =
i18n.language === 'zh-Hant' || i18n.language === 'zh'
- ? PRO_MONTHLY_PLAN_PAYMENT_LINK_TWD
- : PRO_MONTHLY_PLAN_PAYMENT_LINK_USD;
+ ? STRIPE_PRODUCTS.MEMBERSHIP_PLAN.pro.monthly.currencies.TWD.link
+ : STRIPE_PRODUCTS.MEMBERSHIP_PLAN.pro.monthly.currencies.USD.link;
const userEmail = user?.email;
const userId = user?.id;
@@ -163,19 +164,32 @@ const UltraPlanContent = ({ user }: { user: User | null }) => {
return userPlanIndex < ultraPlanIndex;
}, [user]);
+ const isPaidUser = user?.plan === 'pro' || user?.plan === 'ultra';
+ const { mutate: changeSubscriptionPlan } = useChangeSubscriptionPlan();
+
const upgradeLinkOnClick = () => {
- let paymentLink = ULTRA_MONTHLY_PLAN_PAYMENT_LINK_USD;
+ if (isPaidUser) {
+ changeSubscriptionPlan();
+ return;
+ }
+
+ let paymentLink =
+ STRIPE_PRODUCTS.MEMBERSHIP_PLAN.ultra.monthly.currencies.USD.link;
if (priceType === 'monthly') {
if (i18n.language === 'zh-Hant' || i18n.language === 'zh') {
- paymentLink = ULTRA_MONTHLY_PLAN_PAYMENT_LINK_TWD;
+ paymentLink =
+ STRIPE_PRODUCTS.MEMBERSHIP_PLAN.ultra.monthly.currencies.TWD.link;
} else {
- paymentLink = ULTRA_MONTHLY_PLAN_PAYMENT_LINK_USD;
+ paymentLink =
+ STRIPE_PRODUCTS.MEMBERSHIP_PLAN.ultra.monthly.currencies.USD.link;
}
} else {
if (i18n.language === 'zh-Hant' || i18n.language === 'zh') {
- paymentLink = ULTRA_YEARLY_PLAN_PAYMENT_LINK_TWD;
+ paymentLink =
+ STRIPE_PRODUCTS.MEMBERSHIP_PLAN.ultra.yearly.currencies.TWD.link;
} else {
- paymentLink = ULTRA_YEARLY_PLAN_PAYMENT_LINK_USD;
+ paymentLink =
+ STRIPE_PRODUCTS.MEMBERSHIP_PLAN.ultra.yearly.currencies.USD.link;
}
}
diff --git a/types/paid_plan.ts b/types/paid_plan.ts
index bce0056af9..7c3e04e39e 100644
--- a/types/paid_plan.ts
+++ b/types/paid_plan.ts
@@ -1,6 +1,7 @@
export enum PaidPlan {
ProMonthly = 'pro-monthly',
ProOneTime = 'pro-one-time',
+ ProYearly = 'pro-yearly',
UltraMonthly = 'ultra-monthly',
UltraOneTime = 'ultra-one-time',
UltraYearly = 'ultra-yearly',
diff --git a/utils/app/const.ts b/utils/app/const.ts
index e8a9b83b45..152691a0d1 100644
--- a/utils/app/const.ts
+++ b/utils/app/const.ts
@@ -5,19 +5,9 @@ import dayjs from 'dayjs';
import { v4 as uuidv4 } from 'uuid';
export {
+ STRIPE_PRODUCTS,
STRIPE_PLAN_CODE_GPT4_CREDIT,
STRIPE_PLAN_CODE_IMAGE_CREDIT,
- STRIPE_PLAN_CODE_MONTHLY_PRO_PLAN_SUBSCRIPTION,
- STRIPE_PLAN_CODE_MONTHLY_ULTRA_PLAN_SUBSCRIPTION,
- STRIPE_PLAN_CODE_YEARLY_ULTRA_PLAN_SUBSCRIPTION,
- STRIPE_PLAN_CODE_ONE_TIME_PRO_PLAN_FOR_1_MONTH,
- STRIPE_PLAN_CODE_ONE_TIME_ULTRA_PLAN_FOR_1_MONTH,
- PRO_MONTHLY_PLAN_PAYMENT_LINK_USD,
- ULTRA_MONTHLY_PLAN_PAYMENT_LINK_USD,
- PRO_MONTHLY_PLAN_PAYMENT_LINK_TWD,
- ULTRA_MONTHLY_PLAN_PAYMENT_LINK_TWD,
- ULTRA_YEARLY_PLAN_PAYMENT_LINK_USD,
- ULTRA_YEARLY_PLAN_PAYMENT_LINK_TWD,
GPT4_CREDIT_PURCHASE_LINKS,
AI_IMAGE_CREDIT_PURCHASE_LINKS,
V2_CHAT_UPGRADE_LINK,
diff --git a/utils/app/paid_plan_helper.ts b/utils/app/paid_plan_helper.ts
index 1b56abb93f..3ab8abbb3b 100644
--- a/utils/app/paid_plan_helper.ts
+++ b/utils/app/paid_plan_helper.ts
@@ -3,27 +3,30 @@ import { PaidPlan, SubscriptionPlan, TopUpRequest } from '@/types/paid_plan';
import {
STRIPE_PLAN_CODE_GPT4_CREDIT,
STRIPE_PLAN_CODE_IMAGE_CREDIT,
- STRIPE_PLAN_CODE_MONTHLY_PRO_PLAN_SUBSCRIPTION,
- STRIPE_PLAN_CODE_MONTHLY_ULTRA_PLAN_SUBSCRIPTION,
- STRIPE_PLAN_CODE_ONE_TIME_PRO_PLAN_FOR_1_MONTH,
- STRIPE_PLAN_CODE_ONE_TIME_ULTRA_PLAN_FOR_1_MONTH,
- STRIPE_PLAN_CODE_YEARLY_ULTRA_PLAN_SUBSCRIPTION,
+ STRIPE_PRODUCTS,
} from './const';
export const getPaidPlan = (
planCode: string,
): PaidPlan | TopUpRequest | undefined => {
switch (planCode.toUpperCase()) {
- case STRIPE_PLAN_CODE_MONTHLY_PRO_PLAN_SUBSCRIPTION.toUpperCase():
+ case STRIPE_PRODUCTS.MEMBERSHIP_PLAN.pro.monthly.plan_code.toUpperCase():
return PaidPlan.ProMonthly;
- case STRIPE_PLAN_CODE_ONE_TIME_PRO_PLAN_FOR_1_MONTH.toUpperCase():
+ case STRIPE_PRODUCTS.MEMBERSHIP_PLAN.pro[
+ 'one-time'
+ ].plan_code.toUpperCase():
return PaidPlan.ProOneTime;
- case STRIPE_PLAN_CODE_MONTHLY_ULTRA_PLAN_SUBSCRIPTION.toUpperCase():
+ case STRIPE_PRODUCTS.MEMBERSHIP_PLAN.pro['yearly'].plan_code.toUpperCase():
+ return PaidPlan.ProYearly;
+ case STRIPE_PRODUCTS.MEMBERSHIP_PLAN.ultra.monthly.plan_code.toUpperCase():
return PaidPlan.UltraMonthly;
- case STRIPE_PLAN_CODE_YEARLY_ULTRA_PLAN_SUBSCRIPTION.toUpperCase():
+ case STRIPE_PRODUCTS.MEMBERSHIP_PLAN.ultra.yearly.plan_code.toUpperCase():
return PaidPlan.UltraYearly;
- case STRIPE_PLAN_CODE_ONE_TIME_ULTRA_PLAN_FOR_1_MONTH.toUpperCase():
+ case STRIPE_PRODUCTS.MEMBERSHIP_PLAN.ultra[
+ 'one-time'
+ ].plan_code.toUpperCase():
return PaidPlan.UltraOneTime;
+
case STRIPE_PLAN_CODE_IMAGE_CREDIT.toUpperCase():
return TopUpRequest.ImageCredit;
case STRIPE_PLAN_CODE_GPT4_CREDIT.toUpperCase():
@@ -41,6 +44,8 @@ export const getSubscriptionPlanByPaidPlan = (
return 'pro';
case PaidPlan.ProOneTime:
return 'pro';
+ case PaidPlan.ProYearly:
+ return 'pro';
case PaidPlan.UltraYearly:
return 'ultra';
case PaidPlan.UltraMonthly:
diff --git a/utils/app/stripe_config.ts b/utils/app/stripe_config.ts
index db85232389..487e025fac 100644
--- a/utils/app/stripe_config.ts
+++ b/utils/app/stripe_config.ts
@@ -1,61 +1,230 @@
-// P.S. All of the code below is used in the product payment link
-
// STRIPE CREDIT CODE
export const STRIPE_PLAN_CODE_GPT4_CREDIT = 'GPT4_CREDIT';
export const STRIPE_PLAN_CODE_IMAGE_CREDIT = 'IMAGE_CREDIT';
-// STRIPE MONTHLY PLAN CODE
-export const STRIPE_PLAN_CODE_MONTHLY_PRO_PLAN_SUBSCRIPTION =
- 'monthly_pro_plan_subscription';
-export const STRIPE_PLAN_CODE_MONTHLY_ULTRA_PLAN_SUBSCRIPTION =
- 'monthly_ultra_plan_subscription';
-
-// STRIPE YEARLY PLAN CODE
-export const STRIPE_PLAN_CODE_YEARLY_ULTRA_PLAN_SUBSCRIPTION =
- 'yearly_ultra_plan_subscription';
-
-// STRIPE ONE TIME PLAN CODE
-export const STRIPE_PLAN_CODE_ONE_TIME_PRO_PLAN_FOR_1_MONTH =
- 'one_time_pro_plan_for_1_month';
-
-// (Not in used)
-export const STRIPE_PLAN_CODE_ONE_TIME_ULTRA_PLAN_FOR_1_MONTH =
- 'one_time_ultra_plan_for_1_month';
-
// =========== PRO PLAN LINKS ===========
// PRO MONTHLY PLAN
-export const PRO_MONTHLY_PLAN_PAYMENT_LINK_USD =
- process.env.NEXT_PUBLIC_ENV === 'production'
- ? 'https://buy.stripe.com/8wM8Av2DM0u99fWfZ1'
- : 'https://buy.stripe.com/test_4gw4hLcvq52Odt6fYY';
+type MemberShipPlanPeriodType = 'monthly' | 'yearly' | 'one-time';
+type MemberShipPlanCurrencyType = 'USD' | 'TWD';
-export const PRO_MONTHLY_PLAN_PAYMENT_LINK_TWD =
- process.env.NEXT_PUBLIC_ENV === 'production'
- ? '' // TODO: Update the production link
- : 'https://buy.stripe.com/test_00gcOhdzucvg1Ko00e';
+// P.S. All of the code below is used in the product payment link
+type PlanCode =
+ | 'one_time_pro_plan_for_1_month'
+ | 'one_time_ultra_plan_for_1_month'
+ | 'monthly_pro_plan_subscription'
+ | 'monthly_ultra_plan_subscription'
+ | 'yearly_pro_plan_subscription'
+ | 'yearly_ultra_plan_subscription';
-// =========== ULTRA PLAN LINKS ===========
-// ULTRA MONTHLY PLAN
-export const ULTRA_MONTHLY_PLAN_PAYMENT_LINK_USD =
- process.env.NEXT_PUBLIC_ENV === 'production'
- ? '' // TODO: Update the production link
- : 'https://buy.stripe.com/test_6oEdSl67266SexafZ9';
+interface MemberShipPlanItem {
+ link: string;
+ price_id: string;
+}
-export const ULTRA_MONTHLY_PLAN_PAYMENT_LINK_TWD =
- process.env.NEXT_PUBLIC_ENV === 'production'
- ? '' // TODO: Update the production link
- : 'https://buy.stripe.com/test_00gcOhbrmgLwbkYdR0';
+interface PlanDetails {
+ plan_code: PlanCode;
+ currencies: {
+ [currency in MemberShipPlanCurrencyType]: MemberShipPlanItem;
+ };
+}
-// ULTRA YEARLY PLAN
-export const ULTRA_YEARLY_PLAN_PAYMENT_LINK_USD =
- process.env.NEXT_PUBLIC_ENV === 'production'
- ? '' // TODO: Update the production link
- : 'https://buy.stripe.com/test_14k7tX7b6brcexa4gs';
+interface MemberShipPlan {
+ pro: {
+ [period in MemberShipPlanPeriodType]: PlanDetails;
+ };
+ ultra: {
+ [period in MemberShipPlanPeriodType]: PlanDetails;
+ };
+}
+interface StripeProduct {
+ MEMBERSHIP_PLAN: MemberShipPlan;
+}
+
+const STRIPE_PRODUCTS_PRODUCTION: StripeProduct = {
+ MEMBERSHIP_PLAN: {
+ pro: {
+ monthly: {
+ // META DATA use in the payment link
+ plan_code: 'monthly_pro_plan_subscription',
+ currencies: {
+ USD: {
+ link: 'https://buy.stripe.com/8wM8Av2DM0u99fWfZ1',
+ price_id: '',
+ },
+ TWD: {
+ link: '',
+ price_id: '',
+ },
+ },
+ },
+ // NOTE: NOT IN USED IN APP
+ 'one-time': {
+ plan_code: 'one_time_pro_plan_for_1_month',
+ currencies: {
+ USD: {
+ link: '',
+ price_id: '',
+ },
+ TWD: {
+ link: '',
+ price_id: '',
+ },
+ },
+ },
+ // NOTE: NOT IN USED IN APP
+ yearly: {
+ plan_code: 'yearly_pro_plan_subscription',
+ currencies: {
+ USD: {
+ link: '',
+ price_id: '',
+ },
+ TWD: {
+ link: '',
+ price_id: '',
+ },
+ },
+ },
+ },
+ ultra: {
+ 'one-time': {
+ plan_code: 'one_time_ultra_plan_for_1_month',
+ currencies: {
+ USD: {
+ link: '',
+ price_id: '',
+ },
+ TWD: {
+ link: '',
+ price_id: '',
+ },
+ },
+ },
+ monthly: {
+ plan_code: 'monthly_ultra_plan_subscription',
+ currencies: {
+ USD: {
+ link: '',
+ price_id: '',
+ },
+ TWD: {
+ link: '',
+ price_id: '',
+ },
+ },
+ },
+ yearly: {
+ plan_code: 'yearly_ultra_plan_subscription',
+ currencies: {
+ USD: {
+ link: '',
+ price_id: '',
+ },
+ TWD: {
+ link: '',
+ price_id: '',
+ },
+ },
+ },
+ },
+ },
+};
+
+const STRIPE_PRODUCTS_STAGING: StripeProduct = {
+ MEMBERSHIP_PLAN: {
+ pro: {
+ // NOTE: NOT IN USED IN APP
+ 'one-time': {
+ plan_code: 'one_time_pro_plan_for_1_month',
+ currencies: {
+ USD: {
+ link: '',
+ price_id: '',
+ },
+ TWD: {
+ link: '',
+ price_id: '',
+ },
+ },
+ },
+ monthly: {
+ // META DATA use in the payment link
+ plan_code: 'monthly_pro_plan_subscription',
+ currencies: {
+ USD: {
+ link: 'https://buy.stripe.com/test_4gw4hLcvq52Odt6fYY',
+ price_id: 'price_1N09fTEEvfd1BzvuJwBCAfg2',
+ },
+ TWD: {
+ link: 'https://buy.stripe.com/test_6oE01v1QM66S74I7sH',
+ price_id: 'price_1PLhJREEvfd1BzvuxCM477DD',
+ },
+ },
+ },
+ // NOTE: NOT IN USED IN APP
+ yearly: {
+ plan_code: 'yearly_pro_plan_subscription',
+ currencies: {
+ USD: {
+ link: '',
+ price_id: '',
+ },
+ TWD: {
+ link: '',
+ price_id: '',
+ },
+ },
+ },
+ },
+ ultra: {
+ // NOTE: NOT IN USED IN APP
+ 'one-time': {
+ plan_code: 'one_time_ultra_plan_for_1_month',
+ currencies: {
+ USD: {
+ link: '',
+ price_id: '',
+ },
+ TWD: {
+ link: '',
+ price_id: '',
+ },
+ },
+ },
+ monthly: {
+ plan_code: 'monthly_ultra_plan_subscription',
+ currencies: {
+ USD: {
+ link: 'https://buy.stripe.com/test_cN29C5dzu8f0dt6fZe',
+ price_id: 'price_1PLhlhEEvfd1Bzvu0UEqwm9y',
+ },
+ TWD: {
+ link: 'https://buy.stripe.com/test_fZe6pT1QM1QC2Os6oF',
+ price_id: 'price_1PLiWBEEvfd1BzvunVr1yZ55',
+ },
+ },
+ },
+ yearly: {
+ plan_code: 'yearly_ultra_plan_subscription',
+ currencies: {
+ USD: {
+ link: 'https://buy.stripe.com/test_3csaG952Y2UG74IfZg',
+ price_id: 'price_1PLiWmEEvfd1BzvuDFmiLKI6',
+ },
+ TWD: {
+ link: 'https://buy.stripe.com/test_8wM9C5fHCan8agUdR9',
+ price_id: 'price_1PLiWVEEvfd1Bzvu7voi21Jw',
+ },
+ },
+ },
+ },
+ },
+};
-export const ULTRA_YEARLY_PLAN_PAYMENT_LINK_TWD =
+export const STRIPE_PRODUCTS =
process.env.NEXT_PUBLIC_ENV === 'production'
- ? '' // TODO: Update the production link
- : 'https://buy.stripe.com/test_eVa15z6720Myexa7sF';
+ ? STRIPE_PRODUCTS_PRODUCTION
+ : STRIPE_PRODUCTS_STAGING;
// =========== TOP UP LINKS ===========
export const GPT4_CREDIT_PURCHASE_LINKS = {
From fb28020a923231138c9fdf808f4eac62ca6b60a4 Mon Sep 17 00:00:00 2001
From: 1orzero
Date: Wed, 29 May 2024 17:46:54 +0800
Subject: [PATCH 047/134] refactor: Add API endpoint for changing subscription
plan
---
hooks/useChangeSubscriptionPlan.ts | 38 ++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100644 hooks/useChangeSubscriptionPlan.ts
diff --git a/hooks/useChangeSubscriptionPlan.ts b/hooks/useChangeSubscriptionPlan.ts
new file mode 100644
index 0000000000..ed858f4d96
--- /dev/null
+++ b/hooks/useChangeSubscriptionPlan.ts
@@ -0,0 +1,38 @@
+import { useSupabaseClient } from '@supabase/auth-helpers-react';
+import { useMutation } from '@tanstack/react-query';
+import { useContext } from 'react';
+
+import HomeContext from '@/components/home/home.context';
+
+export const useChangeSubscriptionPlan = () => {
+ const supabase = useSupabaseClient();
+ const {
+ state: { user },
+ } = useContext(HomeContext);
+
+ const changeSubscriptionPlan = async () => {
+ if (!user) {
+ throw new Error('User is not authenticated');
+ }
+ const accessToken = (await supabase.auth.getSession()).data.session
+ ?.access_token!;
+ const response = await fetch('/api/stripe/change-subscription-plan', {
+ method: 'POST',
+ headers: {
+ 'access-token': accessToken,
+ 'Content-Type': 'application/json',
+ },
+ });
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ const data = await response.json();
+ return data.subscription;
+ };
+
+ return useMutation(changeSubscriptionPlan, {
+ onError: (error) => {
+ console.error('There was a problem with your mutation operation:', error);
+ },
+ });
+};
From 9d89727a757166e1bb9ed45f4ccadde8a3015270 Mon Sep 17 00:00:00 2001
From: 1orzero
Date: Wed, 29 May 2024 17:46:58 +0800
Subject: [PATCH 048/134] refactor: Add API endpoint for changing subscription
plan
---
pages/api/stripe/change-subscription-plan.ts | 66 ++++++++++++++++++++
1 file changed, 66 insertions(+)
create mode 100644 pages/api/stripe/change-subscription-plan.ts
diff --git a/pages/api/stripe/change-subscription-plan.ts b/pages/api/stripe/change-subscription-plan.ts
new file mode 100644
index 0000000000..4e9e8b7855
--- /dev/null
+++ b/pages/api/stripe/change-subscription-plan.ts
@@ -0,0 +1,66 @@
+import { fetchUserProfileWithAccessToken } from '@/utils/server/auth';
+import { fetchSubscriptionIdByUserId } from '@/utils/server/stripe/strip_helper';
+
+import Stripe from 'stripe';
+
+export const config = {
+ runtime: 'edge',
+};
+
+const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
+ apiVersion: '2022-11-15',
+});
+
+const handler = async (req: Request) => {
+ if (req.method !== 'POST') {
+ return new Response('Method Not Allowed', { status: 405 });
+ }
+
+ try {
+ // Step 1: Get User Profile and Subscription ID
+ const userProfile = await fetchUserProfileWithAccessToken(req);
+ if (userProfile.plan !== 'pro' && userProfile.plan !== 'ultra') {
+ throw new Error('User does not have a paid plan');
+ }
+ const subscriptionId = await fetchSubscriptionIdByUserId(userProfile.id);
+ if (!subscriptionId) {
+ throw new Error('User does not have a valid subscription in Stripe');
+ }
+
+ // Step 2: Retrieve Current Subscription
+ const subscription = await stripe.subscriptions.retrieve(subscriptionId);
+ console.log({
+ subscription,
+ });
+
+ // // Step 3: Calculate Proration
+ // const prorationDate = Math.floor(Date.now() / 1000);
+
+ // // Step 4: Update Subscription
+ // const updatedSubscription = await stripe.subscriptions.update(
+ // subscriptionId,
+ // {
+ // items: [
+ // {
+ // id: subscription.items.data[0].id,
+ // price: newPlanId,
+ // },
+ // ],
+ // proration_behavior: 'create_prorations',
+ // proration_date: prorationDate,
+ // },
+ // );
+
+ // Step 5: Notify User
+ // In a real-world application, you might send an email or in-app notification here
+ // For this example, we'll just return the updated subscription details
+ return new Response(JSON.stringify({ subscription: subscription }), {
+ status: 200,
+ });
+ } catch (error) {
+ console.error(error);
+ return new Response('Internal Server Error', { status: 500 });
+ }
+};
+
+export default handler;
From 97406b784cd1fd928fb84b206e30a2422c46c5ac Mon Sep 17 00:00:00 2001
From: 1orzero
Date: Wed, 29 May 2024 17:47:05 +0800
Subject: [PATCH 049/134] refactor: Update handleCustomerSubscriptionDeleted to
use getCustomerEmailByCustomerID from strip_helper
---
utils/server/stripe/handleCustomerSubscriptionDeleted.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/utils/server/stripe/handleCustomerSubscriptionDeleted.ts b/utils/server/stripe/handleCustomerSubscriptionDeleted.ts
index e22af2c2f5..6b74093543 100644
--- a/utils/server/stripe/handleCustomerSubscriptionDeleted.ts
+++ b/utils/server/stripe/handleCustomerSubscriptionDeleted.ts
@@ -1,5 +1,6 @@
-import getCustomerEmailByCustomerID, {
+import {
downgradeUserAccount,
+ getCustomerEmailByCustomerID,
} from './strip_helper';
import Stripe from 'stripe';
From 4505d49909a9152d8275385f58d485a398b7ac11 Mon Sep 17 00:00:00 2001
From: 1orzero
Date: Wed, 29 May 2024 17:47:10 +0800
Subject: [PATCH 050/134] refactor: Update handleCustomerSubscriptionUpdated to
use getCustomerEmailByCustomerID from strip_helper
---
utils/server/stripe/handleCustomerSubscriptionUpdated.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/utils/server/stripe/handleCustomerSubscriptionUpdated.ts b/utils/server/stripe/handleCustomerSubscriptionUpdated.ts
index 4c291f88a1..ef3098e904 100644
--- a/utils/server/stripe/handleCustomerSubscriptionUpdated.ts
+++ b/utils/server/stripe/handleCustomerSubscriptionUpdated.ts
@@ -1,6 +1,7 @@
-import getCustomerEmailByCustomerID, {
+import {
downgradeUserAccount,
extendMembershipByStripeSubscriptionId,
+ getCustomerEmailByCustomerID,
} from './strip_helper';
import dayjs from 'dayjs';
From e72ed64a4e45fc1e0689f9a00efbd3f0c049a6f8 Mon Sep 17 00:00:00 2001
From: 1orzero
Date: Wed, 29 May 2024 17:47:20 +0800
Subject: [PATCH 051/134] refactor: Update getCustomerEmailByCustomerID
function in strip_helper
---
utils/server/stripe/strip_helper.ts | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/utils/server/stripe/strip_helper.ts b/utils/server/stripe/strip_helper.ts
index 04b4b042e4..0246ba770f 100644
--- a/utils/server/stripe/strip_helper.ts
+++ b/utils/server/stripe/strip_helper.ts
@@ -4,7 +4,18 @@ import Stripe from 'stripe';
const supabase = getAdminSupabaseClient();
-export default async function getCustomerEmailByCustomerID(
+export async function fetchSubscriptionIdByUserId(
+ userId: string,
+): Promise {
+ const { data: userProfile } = await supabase
+ .from('profiles')
+ .select('stripe_subscription_id')
+ .eq('id', userId)
+ .single();
+ return userProfile?.stripe_subscription_id;
+}
+
+export async function getCustomerEmailByCustomerID(
customerID: string,
): Promise {
try {
From 5465e0570621d198920acc15178cda3cbceeddae Mon Sep 17 00:00:00 2001
From: 1orzero
Date: Thu, 30 May 2024 10:33:16 +0800
Subject: [PATCH 052/134] refactor: Improve PlanComparison component UI and
readability
---
components/User/Settings/PlanComparison.tsx | 37 ++++++++++++++-------
1 file changed, 25 insertions(+), 12 deletions(-)
diff --git a/components/User/Settings/PlanComparison.tsx b/components/User/Settings/PlanComparison.tsx
index 51fa8284e3..3fe7419418 100644
--- a/components/User/Settings/PlanComparison.tsx
+++ b/components/User/Settings/PlanComparison.tsx
@@ -12,7 +12,6 @@ import { User } from '@/types/user';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import { cn } from '@/lib/utils';
import dayjs from 'dayjs';
const PlanComparison = ({ user }: { user: User | null }) => {
@@ -78,14 +77,9 @@ const ProPlanContent = ({ user }: { user: User | null }) => {
const proPlanIndex = OrderedSubscriptionPlans.indexOf('pro');
return userPlanIndex < proPlanIndex;
}, [user]);
- const isPaidUser = user?.plan === 'pro' || user?.plan === 'ultra';
const { mutate: changeSubscriptionPlan } = useChangeSubscriptionPlan();
const upgradeLinkOnClick = () => {
- if (isPaidUser) {
- changeSubscriptionPlan();
- return;
- }
const paymentLink =
i18n.language === 'zh-Hant' || i18n.language === 'zh'
? STRIPE_PRODUCTS.MEMBERSHIP_PLAN.pro.monthly.currencies.TWD.link
@@ -146,6 +140,18 @@ const ProPlanContent = ({ user }: { user: User | null }) => {
)}
+ {user?.plan === 'ultra' && user.proPlanExpirationDate && (
+
+ )}
{user?.plan === 'pro' && user.proPlanExpirationDate && (
@@ -164,15 +170,9 @@ const UltraPlanContent = ({ user }: { user: User | null }) => {
return userPlanIndex < ultraPlanIndex;
}, [user]);
- const isPaidUser = user?.plan === 'pro' || user?.plan === 'ultra';
const { mutate: changeSubscriptionPlan } = useChangeSubscriptionPlan();
const upgradeLinkOnClick = () => {
- if (isPaidUser) {
- changeSubscriptionPlan();
- return;
- }
-
let paymentLink =
STRIPE_PRODUCTS.MEMBERSHIP_PLAN.ultra.monthly.currencies.USD.link;
if (priceType === 'monthly') {
@@ -250,6 +250,19 @@ const UltraPlanContent = ({ user }: { user: User | null }) => {