From 3668d37619877f0479f19f013c0ffee015a1fd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 30 May 2023 00:34:35 +0200 Subject: [PATCH] feat: change payment method (subscription v2) --- .../ProjectUpdateDisabledTooltip.tsx | 2 +- .../Tier/SubscriptionPaymentMethod.tsx | 179 ++++++++++++++++++ .../Subscription/Tier/SubscriptionTier.tsx | 40 +++- .../BillingSettings/PaymentMethods.tsx | 1 + .../project-subscription-update-mutation.ts | 4 +- .../project-subscription-v2-query.ts | 8 + 6 files changed, 227 insertions(+), 7 deletions(-) create mode 100644 studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionPaymentMethod.tsx diff --git a/studio/components/interfaces/BillingV2/ProjectUpdateDisabledTooltip.tsx b/studio/components/interfaces/BillingV2/ProjectUpdateDisabledTooltip.tsx index e09812349b2..873a8144f68 100644 --- a/studio/components/interfaces/BillingV2/ProjectUpdateDisabledTooltip.tsx +++ b/studio/components/interfaces/BillingV2/ProjectUpdateDisabledTooltip.tsx @@ -11,7 +11,7 @@ const ProjectUpdateDisabledTooltip = ({ }: PropsWithChildren) => { return ( - {children} + {children} {projectUpdateDisabled ? ( diff --git a/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionPaymentMethod.tsx b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionPaymentMethod.tsx new file mode 100644 index 00000000000..7bf7f58814f --- /dev/null +++ b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionPaymentMethod.tsx @@ -0,0 +1,179 @@ +import Panel from 'components/ui/Panel' +import { ProjectSubscriptionResponse } from 'data/subscriptions/project-subscription-v2-query' +import { Button, IconCreditCard, IconFileText, Modal } from 'ui' +import * as Tooltip from '@radix-ui/react-tooltip' +import { useState } from 'react' +import PaymentMethodSelection from './PaymentMethodSelection' +import { useParams } from 'common' +import { + SubscriptionTier, + updateSubscriptionTier, +} from 'data/subscriptions/project-subscription-update-mutation' +import { useStore } from 'hooks' +import { BASE_PATH } from 'lib/constants' + +export interface SubscriptionTierProps { + subscription: ProjectSubscriptionResponse +} + +const SubscriptionPaymentMethod = ({ subscription }: SubscriptionTierProps) => { + const { ref: projectRef } = useParams() + const { ui } = useStore() + + const [isSubmitting, setIsSubmitting] = useState(false) + const [showPaymentEditModal, setShowPaymentEditModal] = useState(false) + const [selectedPaymentMethod, setSelectedPaymentMethod] = useState( + subscription.payment_method_id || '' + ) + + const onConfirmPaymentMethod = async () => { + setIsSubmitting(true) + + if (selectedPaymentMethod) { + if (!projectRef) return console.error('Project ref is required') + if (!selectedPaymentMethod) { + return ui.setNotification({ category: 'error', message: 'Please select a payment method' }) + } + + const selectedTier = + subscription.plan.id === 'pro' && subscription.usage_billing_enabled + ? 'tier_payg' + : `tier_${subscription.plan.id}` + + try { + setIsSubmitting(true) + await updateSubscriptionTier({ + projectRef, + tier: selectedTier as SubscriptionTier, + paymentMethod: selectedPaymentMethod, + }) + + setShowPaymentEditModal(false) + + ui.setNotification({ + category: 'success', + message: `Successfully updated payment method!`, + }) + } catch (error: any) { + ui.setNotification({ + error, + category: 'error', + message: `Unable to update subscription: ${error.message}`, + }) + } finally { + setIsSubmitting(false) + } + } + + setIsSubmitting(false) + } + + if (subscription.payment_method_type === 'none') return null + + return ( + <> + + + {subscription.payment_method_type === 'card' && ( + <> +
+ {subscription.payment_method_card_details?.brand ? ( + Credit card brand + ) : ( + + )} + + **** **** ****{' '} + {subscription?.payment_method_card_details?.last_4_digits || '****'} + +
+
+ + Expires {subscription?.payment_method_card_details?.expiry_month || '-'}/ + {subscription?.payment_method_card_details?.expiry_year?.toString()?.slice(-2) || + '-'} + +
+ + )} + + {subscription?.payment_method_type === 'invoice' && ( + <> +
+
+

Payment via invoice

+

You get a monthly invoice and payment link via email.

+
+
+ + )} + + + +
+ +
+
+ {subscription?.payment_method_type !== 'card' && ( + + + +
+ + Please email support@supbase.io to change your payment method. + +
+
+
+ )} +
+
+
+ + setShowPaymentEditModal(false)} + onConfirm={onConfirmPaymentMethod} + overlayClassName="pointer-events-none" + header={`Change payment method`} + > + +
+

+ Upon clicking confirm, your next and future invoices will be deducted from the + selected payment method. There are no immediate charges. +

+
+ +
+
+
+
+ + ) +} + +export default SubscriptionPaymentMethod diff --git a/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx index 6955be892c6..8f93a5cb0ae 100644 --- a/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/Tier/SubscriptionTier.tsx @@ -4,16 +4,19 @@ import SparkBar from 'components/ui/SparkBar' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' import dayjs from 'dayjs' import { useSubscriptionPageStateSnapshot } from 'state/subscription-page' -import { Alert, Button } from 'ui' +import { Alert, Button, IconExternalLink } from 'ui' import TierUpdateSidePanel from './TierUpdateSidePanel' import Link from 'next/link' -import { useFlag } from 'hooks' +import { useFlag, useStore } from 'hooks' import ProjectUpdateDisabledTooltip from '../../ProjectUpdateDisabledTooltip' +import SubscriptionPaymentMethod from './SubscriptionPaymentMethod' export interface SubscriptionTierProps {} const SubscriptionTier = ({}: SubscriptionTierProps) => { const { ref: projectRef } = useParams() + const { ui } = useStore() + const orgSlug = ui.selectedOrganization?.slug ?? '' const snap = useSubscriptionPageStateSnapshot() const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate') const { data: subscription, isLoading } = useProjectSubscriptionV2Query({ projectRef }) @@ -32,8 +35,33 @@ const SubscriptionTier = ({}: SubscriptionTierProps) => { return ( <>
-
-

Subscription plan

+
+
+

Subscription plan

+
+ To manage your billing address, emails or tax IDs, head to your{' '} + + +

+ organization settings +

+
+ +
+
+

More information

+ +
+
{isLoading ? (
@@ -87,7 +115,7 @@ const SubscriptionTier = ({}: SubscriptionTierProps) => { {!subscription?.usage_billing_enabled && (

- When this project exceeds its included usage quotas, it may become unresponsive. + When this project exceeds its included usage quotas, it may become unresponsive.{' '} {currentPlan?.id === 'free' ? 'If you wish to exceed the included usage, you should upgrade to a paid plan.' : 'You can change the Cost Control settings if you plan on exceeding the included usage quotas.'} @@ -106,6 +134,8 @@ const SubscriptionTier = ({}: SubscriptionTierProps) => { labelBottomClass="!text-scale-1000 pb-1" labelTop={`${daysToCycleEnd} Days left`} /> + + {subscription && }

)}
diff --git a/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx b/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx index c69eea6b4c1..83bb9cd1a88 100644 --- a/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx +++ b/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx @@ -165,6 +165,7 @@ const PaymentMethods: FC = ({