Skip to content

Commit

Permalink
Refactor/buy subscription and handle response (#767)
Browse files Browse the repository at this point in the history
  • Loading branch information
chavda-bhavik committed Aug 27, 2024
2 parents 2413edc + 0f0be86 commit 135c2ae
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 82 deletions.
3 changes: 3 additions & 0 deletions apps/api/src/app/user/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DeleteUserPaymentMethod } from './delete-user-payment-method/delete-use
import { GetTransactionHistory } from './get-transaction-history/get-transaction-history.usecase';
import { ApplyCoupon } from './apply-coupon/apply-coupon.usecase';
import { Checkout } from './checkout/checkout.usecase';
import { Subscription } from './subscription/subscription.usecase';

export const USE_CASES = [
GetImportCounts,
Expand All @@ -20,6 +21,7 @@ export const USE_CASES = [
GetTransactionHistory,
ApplyCoupon,
Checkout,
Subscription,
//
];

Expand All @@ -34,4 +36,5 @@ export {
GetTransactionHistory,
ApplyCoupon,
Checkout,
Subscription,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { PaymentAPIService } from '@impler/services';
import { Injectable } from '@nestjs/common';

@Injectable()
export class Subscription {
constructor(private paymentApiService: PaymentAPIService) {}

async execute({
email,
planCode,
selectedPaymentMethod,
couponCode,
}: {
email: string;
planCode: string;
selectedPaymentMethod: string;
couponCode?: string;
}) {
return await this.paymentApiService.subscribe({
planCode,
email,
selectedPaymentMethod,
couponCode,
});
}
}
24 changes: 22 additions & 2 deletions apps/api/src/app/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
GetTransactionHistory,
ApplyCoupon,
Checkout,
Subscription,
} from './usecases';
import { JwtAuthGuard } from '@shared/framework/auth.gaurd';
import { IJwtPayload, ACCESS_KEY_NAME } from '@impler/shared';
Expand All @@ -32,7 +33,8 @@ export class UserController {
private confirmIntentId: ConfirmIntentId,
private getTransactionHistory: GetTransactionHistory,
private retrivePaymentMethods: RetrievePaymentMethods,
private deleteUserPaymentMethod: DeleteUserPaymentMethod
private deleteUserPaymentMethod: DeleteUserPaymentMethod,
private subscription: Subscription
) {}

@Get('/import-count')
Expand Down Expand Up @@ -71,7 +73,7 @@ export class UserController {
@ApiOperation({
summary: 'Setup User Payment Intent',
})
async setupEMandateIntent(@UserSession() user: IJwtPayload, @Param('paymentId') paymentId: string) {
async setupEmandateIntent(@UserSession() user: IJwtPayload, @Param('paymentId') paymentId: string) {
return this.updatePaymentMethod.execute(user.email, paymentId);
}

Expand Down Expand Up @@ -137,4 +139,22 @@ export class UserController {
couponCode: couponCode,
});
}

@Get('/subscribe')
@ApiOperation({
summary: 'Make successful Plan Purchase and begin subscription',
})
async newSubscriptionRoute(
@Query('planCode') planCode: string,
@UserSession() user: IJwtPayload,
@Query('paymentMethodId') paymentMethodId: string,
@Query('couponCode') couponCode?: string
) {
return await this.subscription.execute({
email: user.email,
planCode,
selectedPaymentMethod: paymentMethodId,
couponCode,
});
}
}
15 changes: 0 additions & 15 deletions apps/web/components/home/PlanDetails/PlanDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@ import { SelectCardModal } from '@components/settings';
import { usePlanDetails } from '@hooks/usePlanDetails';
import TooltipLink from '@components/TooltipLink/TooltipLink';
import { PlansModal } from '@components/UpgradePlan/PlansModal';
import { ConfirmationModal } from '@components/ConfirmationModal';
import { CONSTANTS, MODAL_KEYS, ROUTES, colors, DOCUMENTATION_REFERENCE_LINKS } from '@config';

export function PlanDetails() {
const router = useRouter();
const status = router.query?.status;
const planName = router.query?.plan;

const { profile } = useApp();
const { [CONSTANTS.PLAN_CODE_QUERY_KEY]: selectedPlan } = router.query;
Expand All @@ -41,18 +38,6 @@ export function PlanDetails() {
withCloseButton: true,
});
};
useEffect(() => {
if (planName && status) {
modals.open({
title:
status === CONSTANTS.PAYMENT_SUCCCESS_CODE
? CONSTANTS.SUBSCRIPTION_ACTIVATED_TITLE
: CONSTANTS.SUBSCRIPTION_FAILED_TITLE,
children: <ConfirmationModal status={status as string} />,
});
router.push(ROUTES.HOME, {}, { shallow: true });
}
}, [planName, status, router]);

useEffect(() => {
if (selectedPlan && profile) {
Expand Down
83 changes: 18 additions & 65 deletions apps/web/components/settings/AddCard/SelectCardModal.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
import { useState } from 'react';
import getConfig from 'next/config';
import { useRouter } from 'next/router';
import { modals } from '@mantine/modals';
import { useQuery } from '@tanstack/react-query';
import { Flex, Loader, Divider, Title, Stack } from '@mantine/core';

import { notify } from '@libs/notify';
import { Button } from '@ui/button';
import { commonApi } from '@libs/api';

import Coupon from './Coupon/Coupon';
import { useCheckout } from '@hooks/useCheckout';
import { CheckoutDetails } from '../Checkout/CheckoutDetails';

import { ICardData, IErrorObject } from '@impler/shared';
import { API_KEYS, CONSTANTS, NOTIFICATION_KEYS, ROUTES } from '@config';
import { useSubscribe } from '@hooks/useSubscribe';
import { PaymentMethods } from './PaymentMethods/PaymentMethods';
import Coupon from './Coupon/Coupon';

const { publicRuntimeConfig } = getConfig();

interface SelectCardModalProps {
email: string;
Expand All @@ -25,65 +14,29 @@ interface SelectCardModalProps {
paymentMethodId?: string;
}

export function SelectCardModal({ email, planCode, onClose, paymentMethodId }: SelectCardModalProps) {
const router = useRouter();
// const theme = useMantineTheme();

const gatewayURL = publicRuntimeConfig.NEXT_PUBLIC_PAYMENT_GATEWAY_URL;
const isCouponFeatureEnabled = publicRuntimeConfig.NEXT_PUBLIC_COUPON_ENABLED;
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string | undefined>(paymentMethodId);
const [appliedCouponCode, setAppliedCouponCode] = useState<string>();
export function SelectCardModal({ email, planCode, paymentMethodId }: SelectCardModalProps) {
const {
data: paymentMethods,
isLoading: isPaymentMethodsLoading,
isFetching: isPaymentMethodsFetching,
} = useQuery<unknown, IErrorObject, ICardData[], [string]>(
[API_KEYS.PAYMENT_METHOD_LIST],
() => commonApi<ICardData[]>(API_KEYS.PAYMENT_METHOD_LIST as any, {}),
{
onSuccess(data) {
if (data?.length === 0) {
notify(NOTIFICATION_KEYS.NO_PAYMENT_METHOD_FOUND, {
title: 'No Cards Found!',
message: 'Please Add your Card first. Redirecting you to cards!',
color: 'red',
});
modals.closeAll();
router.push(ROUTES.ADD_CARD + `&${CONSTANTS.PLAN_CODE_QUERY_KEY}=${planCode}`);
} else {
setSelectedPaymentMethod(data[0].paymentMethodId);
}
},
}
);
appliedCouponCode,
setAppliedCouponCode,
handlePaymentMethodChange,
handleProceed,
isCouponFeatureEnabled,
isPaymentMethodsFetching,
isPaymentMethodsLoading,
selectedPaymentMethod,
paymentMethods,
} = useSubscribe({
email,
planCode,
paymentMethodId,
});

const { checkoutData, isCheckoutDataLoading } = useCheckout({
couponCode: appliedCouponCode,
paymentMethodId: selectedPaymentMethod,
planCode,
});

const handleProceed = () => {
modals.closeAll();
onClose();
if (selectedPaymentMethod) {
let url =
`${gatewayURL}/api/v1/plans/${planCode}/buy/${email}/redirect?paymentMethodId=${selectedPaymentMethod}`.replaceAll(
'"',
''
);

if (appliedCouponCode) {
url += `&couponCode=${appliedCouponCode}`;
}

window.location.href = url;
}
};
const handlePaymentMethodChange = (methodId: string) => {
setSelectedPaymentMethod(methodId);
};

return (
<>
{isPaymentMethodsLoading || isPaymentMethodsFetching ? (
Expand Down
2 changes: 2 additions & 0 deletions apps/web/config/constants.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const API_KEYS = {

APPLY_COUPON_CODE: 'APPLY_COUPON_CODE',

SUBSCRIBE: 'SUBSCRIBE',
TRANSACTION_HISTORY: 'TRANSACTION_HISTORY',

PAYMENT_METHOD_DELETE: 'PAYMENT_METHOD_DELETE',
Expand Down Expand Up @@ -151,6 +152,7 @@ export const NOTIFICATION_KEYS = {

CARD_ADDED: 'CARD_ADDED',
CARD_REMOVED: 'CARD_REMOVED',
PURCHASE_FAILED: 'PURCHASE_FAILED',

COLUMN_ERRROR: 'COLUMN_ERRROR',
};
Expand Down
123 changes: 123 additions & 0 deletions apps/web/hooks/useSubscribe.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { useState } from 'react';
import getConfig from 'next/config';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { notify } from '@libs/notify';
import { commonApi } from '@libs/api';
import { API_KEYS, CONSTANTS, NOTIFICATION_KEYS, ROUTES } from '@config';
import { modals } from '@mantine/modals';
import { ICardData, IErrorObject } from '@impler/shared';
import { ConfirmationModal } from '@components/ConfirmationModal';

const { publicRuntimeConfig } = getConfig();

interface UseSubscribeProps {
email: string;
planCode: string;
paymentMethodId?: string;
}

interface ISubscribeResponse {
url: string;
status: string;
success: boolean;
}

export const useSubscribe = ({ email, planCode, paymentMethodId }: UseSubscribeProps) => {
const queryClient = useQueryClient();
const router = useRouter();
const isCouponFeatureEnabled = publicRuntimeConfig.NEXT_PUBLIC_COUPON_ENABLED;
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string | undefined>(paymentMethodId);
const [appliedCouponCode, setAppliedCouponCode] = useState<string | undefined>(undefined);

const {
data: paymentMethods,
isLoading: isPaymentMethodsLoading,
isFetching: isPaymentMethodsFetching,
} = useQuery<unknown, IErrorObject, ICardData[], [string]>(
[API_KEYS.PAYMENT_METHOD_LIST],
() => commonApi<ICardData[]>(API_KEYS.PAYMENT_METHOD_LIST as any, {}),
{
onSuccess(data) {
if (data?.length === 0) {
notify(NOTIFICATION_KEYS.NO_PAYMENT_METHOD_FOUND, {
title: 'No Cards Found!',
message: 'Please Add your Card first. Redirecting you to cards!',
color: 'red',
});
modals.closeAll();
router.push(ROUTES.ADD_CARD + `&${CONSTANTS.PLAN_CODE_QUERY_KEY}=${planCode}`);
} else {
setSelectedPaymentMethod(data[0].paymentMethodId);
}
},
}
);
const { mutate: subscribe, isLoading: isPurchaseLoading } = useMutation<
ISubscribeResponse,
IErrorObject,
ISubscribeData
>(
[API_KEYS.SUBSCRIBE, selectedPaymentMethod, email, planCode],
() =>
commonApi<ISubscribeResponse>(API_KEYS.SUBSCRIBE as any, {
query: { paymentMethodId: selectedPaymentMethod, email, planCode },
}),
{
onSuccess: (response) => {
queryClient.invalidateQueries([API_KEYS.FETCH_ACTIVE_SUBSCRIPTION, email]);
if (response && response.status) {
modals.open({
title:
response.status === CONSTANTS.PAYMENT_SUCCCESS_CODE
? CONSTANTS.SUBSCRIPTION_ACTIVATED_TITLE
: CONSTANTS.SUBSCRIPTION_FAILED_TITLE,
children: <ConfirmationModal status={response.status as string} />,
});
}
},
onError: (error: IErrorObject) => {
notify(NOTIFICATION_KEYS.PURCHASE_FAILED, {
title: 'Purchase Failed',
message: error.message,
color: 'red',
});
queryClient.invalidateQueries([API_KEYS.FETCH_ACTIVE_SUBSCRIPTION, email]);
if (error && error.statusCode) {
modals.open({
title: CONSTANTS.SUBSCRIPTION_FAILED_TITLE,
children: <ConfirmationModal status={String(error.statusCode)} />,
});
}
},
}
);

const handleProceed = () => {
modals.closeAll();
if (selectedPaymentMethod) {
subscribe({
email,
planCode,
selectedPaymentMethod,
});
}
};

const handlePaymentMethodChange = (paymnentMethodId: string) => {
setSelectedPaymentMethod(paymnentMethodId);
};

return {
paymentMethods,
isPaymentMethodsFetching,
isPaymentMethodsLoading,
handleProceed,
handlePaymentMethodChange,
appliedCouponCode,
setAppliedCouponCode,
selectedPaymentMethod,
isPurchaseLoading,
isCouponFeatureEnabled,
};
};
5 changes: 5 additions & 0 deletions apps/web/libs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ const routes: Record<string, Route> = {
method: 'PUT',
},

[API_KEYS.SUBSCRIBE]: {
url: () => `/v1/user/subscribe`,
method: 'GET',
},

[API_KEYS.FETCH_ACTIVE_SUBSCRIPTION]: {
url: () => `/v1/user/subscription`,
method: 'GET',
Expand Down
Loading

0 comments on commit 135c2ae

Please sign in to comment.