diff --git a/hosting-holium-com/src/pages/payment.tsx b/hosting-holium-com/src/pages/payment.tsx index d95e062fa0..a105be9841 100644 --- a/hosting-holium-com/src/pages/payment.tsx +++ b/hosting-holium-com/src/pages/payment.tsx @@ -6,7 +6,9 @@ import { OnboardingPage, OnboardingStorage, PaymentDialog, + ThirdEarthPeriodicity, ThirdEarthProduct, + ThirdEarthProductId, ThirdEarthProductType, } from '@holium/shared'; @@ -16,8 +18,7 @@ import { thirdEarthApi } from '../util/thirdEarthApi'; import { useNavigation } from '../util/useNavigation'; type ServerSideProps = { - products: ThirdEarthProduct[]; - // We use underscore to highlight that this is a query param. + product: ThirdEarthProduct | undefined; product_type: ThirdEarthProductType; back_url?: string; }; @@ -28,9 +29,14 @@ export const getServerSideProps: GetServerSideProps = async ({ query }) => { const product_type = (query.product_type ?? 'planet') as ThirdEarthProductType; + const product = products.find((p) => { + if (product_type === 'planet') return p.id === ThirdEarthProductId.PLANET; + else return p.id === ThirdEarthProductId.BYOP_P; + }); + return { props: { - products, + product, product_type, back_url, } as ServerSideProps, @@ -38,7 +44,7 @@ export const getServerSideProps: GetServerSideProps = async ({ query }) => { }; export default function Payment({ - products: unfilteredProducts, + product, product_type, back_url, }: ServerSideProps) { @@ -51,11 +57,7 @@ export default function Payment({ const [stripe, setStripe] = useState(); const [clientSecret, setClientSecret] = useState(); - const products = unfilteredProducts.filter( - (product) => product.product_type === product_type - ); - - const [productId, setProductId] = useState(products[0].id); + const [periodicity, setPeriodicity] = useState(ThirdEarthPeriodicity.MONTH); const [invoiceId, setInvoiceId] = useState(); useEffect(() => { @@ -72,9 +74,17 @@ export default function Payment({ setToken(token); const getSecretAndSetupStripe = async () => { + if (!product) return; + + const priceOption = product.price_options.find( + (po) => po.periodicity === periodicity + ); + if (!priceOption) return; + const response = await thirdEarthApi.stripeMakePayment( token, - productId.toString(), + product.id.toString(), + priceOption.stripe_price_id, // Don't pass serverId for byop-p. product_type !== 'byop-p' && serverId ? serverId : undefined ); @@ -90,7 +100,7 @@ export default function Payment({ } catch (e) { console.error(e); } - }, [productId]); + }); const stripeOptions: StripeElementsOptions = { clientSecret: clientSecret, @@ -112,7 +122,7 @@ export default function Payment({ }; const onNext = async () => { - if (!token || !invoiceId || !productId) return false; + if (!token || !invoiceId || !product) return false; if (product_type === 'byop-p') { await thirdEarthApi.log(token, { @@ -120,7 +130,7 @@ export default function Payment({ type: 'info', subject: 'FRONTEND: payment step (email notify)', message: `Succesful stripe purchase of byop-p by ${email}.`, - productId: productId.toString(), + productId: product.id.toString(), auditTrailCode: 1000, }); @@ -136,7 +146,7 @@ export default function Payment({ // This server call should be non-blocking since the user has already paid. await thirdEarthApi.provisionalShipEntry({ token, - product: productId.toString(), + product: product.id.toString(), invoiceId, shipType: 'provisional', }); @@ -156,7 +166,7 @@ export default function Payment({ type: 'info', subject: 'FRONTEND: payment step (email notify)', message: `Succesful stripe purchase of planet by ${email}.`, - productId: productId.toString(), + productId: product.id.toString(), auditTrailCode: 1000, }); @@ -174,7 +184,7 @@ export default function Payment({ await thirdEarthApi.provisionalShipEntry({ token, patp: serverId, - product: productId.toString(), + product: product.id.toString(), invoiceId, shipType: 'planet', }); @@ -191,14 +201,13 @@ export default function Payment({ return ( diff --git a/shared/src/onboarding/dialogs/Payment/PaymentDialog.tsx b/shared/src/onboarding/dialogs/Payment/PaymentDialog.tsx index 8de9cce14c..900cb52480 100644 --- a/shared/src/onboarding/dialogs/Payment/PaymentDialog.tsx +++ b/shared/src/onboarding/dialogs/Payment/PaymentDialog.tsx @@ -13,32 +13,30 @@ import { Flex, Spinner } from '@holium/design-system/general'; import { OnboardDialog } from '../../components/OnboardDialog'; import { OnboardDialogTitle } from '../../components/OnboardDialog.styles'; import { PaymentIcon } from '../../icons/PaymentIcon'; -import { ThirdEarthProduct, ThirdEarthProductType } from '../../types'; +import { ThirdEarthPeriodicity, ThirdEarthPriceOption } from '../../types'; import { AccountInformation } from './AccountInformation'; import { PaymentForm } from './PaymentForm'; import { ProductCards } from './ProductCards'; type Props = { - productType: ThirdEarthProductType; - products: ThirdEarthProduct[]; - productId: number; - patp: string; + priceOptions: ThirdEarthPriceOption[]; + periodicity: ThirdEarthPeriodicity; + setPeriodicity: (periodicity: ThirdEarthPeriodicity) => void; + patp: string | undefined; email: string; stripe: Stripe | undefined; stripeOptions: StripeElementsOptions | undefined; - setProductId: (productId: number) => void; onBack: () => void; onNext: () => Promise; }; const PaymentDialogPresenter = ({ - productType, - products, - productId, + priceOptions, + periodicity, + setPeriodicity, patp, email, stripeOptions, - setProductId, onBack, onNext, }: Props) => { @@ -84,14 +82,11 @@ const PaymentDialogPresenter = ({ <> Payment - + } @@ -111,14 +106,11 @@ export const PaymentDialog = ({ stripe, stripeOptions, ...props }: Props) => { <> Payment - + diff --git a/shared/src/onboarding/dialogs/Payment/ProductCards.tsx b/shared/src/onboarding/dialogs/Payment/ProductCards.tsx index b06fea12d5..d0435e3109 100644 --- a/shared/src/onboarding/dialogs/Payment/ProductCards.tsx +++ b/shared/src/onboarding/dialogs/Payment/ProductCards.tsx @@ -1,44 +1,40 @@ -import { useEffect } from 'react'; - import { Flex } from '@holium/design-system/general'; -import { ThirdEarthProduct } from '../../types/index'; +import { + ThirdEarthPeriodicity, + ThirdEarthPriceOption, +} from '../../types/index'; import { ProductCard } from './ProductCard'; type Props = { - products: ThirdEarthProduct[]; - productId: number; - setProductId: (productId: number) => void; + priceOptions: ThirdEarthPriceOption[]; + periodicity: ThirdEarthPeriodicity; + setPeriodicity: (periodicity: ThirdEarthPeriodicity) => void; }; -export const ProductCards = ({ products, productId, setProductId }: Props) => { - const byopProduct = products.find( - (product) => product.product_type === 'byop-p' - ); - - useEffect(() => { - if (byopProduct) { - setProductId(byopProduct.id); - } - }, [byopProduct, setProductId]); +export const ProductCards = ({ + priceOptions, + periodicity, + setPeriodicity, +}: Props) => ( + + {/* Assume the cheapest is monthly and the second yearly. */} + {priceOptions.sort().map((priceOption) => { + const isMonthly = priceOption.periodicity === ThirdEarthPeriodicity.MONTH; + const isSelected = priceOption.periodicity === periodicity; - return ( - - {/* Assume the cheapest is monthly and the second yearly. */} - {products.sort().map((product, index) => ( + return ( setProductId(product.id)} + isSelected={isSelected} + onClick={() => setPeriodicity(priceOption.periodicity)} /> - ))} - - ); -}; + ); + })} + +); diff --git a/shared/src/onboarding/dialogs/util.tsx b/shared/src/onboarding/dialogs/util.tsx index 2b82b5c114..673193289c 100644 --- a/shared/src/onboarding/dialogs/util.tsx +++ b/shared/src/onboarding/dialogs/util.tsx @@ -43,6 +43,24 @@ export const thirdEarthMockProduct: ThirdEarthProduct = { long_description: 'Monthly subscription', price_id: '11', subscription_price: 15, + price_options: [ + { + unit: 'usd', + description: 'monthly subscription', + periodicity: 'month', + one_time_price: 0, + recurring_price: 15, + stripe_price_id: 'price_1MDqoIHhoM3uGGuYAZZN23Yr', + }, + { + unit: 'usd', + description: 'yearly subscription', + periodicity: 'year', + one_time_price: 0, + recurring_price: 150, + stripe_price_id: 'price_1MDqoIHhoM3uGGuY00mWc29l', + }, + ], }; export const thirdEarthMockProducts = [ diff --git a/shared/src/onboarding/onboarding.ts b/shared/src/onboarding/onboarding.ts index 61c626890a..2785e7bb38 100644 --- a/shared/src/onboarding/onboarding.ts +++ b/shared/src/onboarding/onboarding.ts @@ -60,7 +60,9 @@ export type { RealmOnboardingStep, ThirdEarthAlert, ThirdEarthPortalSession, + ThirdEarthPriceOption, ThirdEarthProduct, ThirdEarthProductType, ThirdEarthShip, } from './types'; +export { ThirdEarthPeriodicity, ThirdEarthProductId } from './types/index'; diff --git a/shared/src/onboarding/services/ThirdEarthApi.ts b/shared/src/onboarding/services/ThirdEarthApi.ts index 2e91b20a4f..7daa4da7ae 100644 --- a/shared/src/onboarding/services/ThirdEarthApi.ts +++ b/shared/src/onboarding/services/ThirdEarthApi.ts @@ -265,7 +265,12 @@ export class ThirdEarthApi { ); } - stripeMakePayment(token: string, productId: string, patp?: string) { + stripeMakePayment( + token: string, + productId: string, + priceId: string, + patp?: string + ) { return http( `${this.apiBaseUrl}/stripe-make-payment`, { @@ -273,6 +278,7 @@ export class ThirdEarthApi { headers: this.getHeaders(token), body: JSON.stringify({ productId, + priceId, patp, }), } diff --git a/shared/src/onboarding/types/index.ts b/shared/src/onboarding/types/index.ts index 1793430509..3c2537d339 100644 --- a/shared/src/onboarding/types/index.ts +++ b/shared/src/onboarding/types/index.ts @@ -4,6 +4,25 @@ export type ThirdEarthProductType = | 'byop-nk' | 'subscription'; +export enum ThirdEarthPeriodicity { + MONTH = 'month', + YEAR = 'year', +} + +export type ThirdEarthPriceOption = { + unit: string; + description: string; + periodicity: ThirdEarthPeriodicity; + one_time_price: number; + recurring_price: number; + stripe_price_id: string; +}; + +export enum ThirdEarthProductId { + PLANET = 30, + BYOP_P = 101, +} + export type ThirdEarthProduct = { client_id: number; comet_count: string; @@ -16,6 +35,7 @@ export type ThirdEarthProduct = { long_description: string; price_id: string; priority: number; + price_options: ThirdEarthPriceOption[]; product_status: string; product_type: ThirdEarthProductType; subscription_price: number;