diff --git a/packages/common/src/controllers/CheckoutController.ts b/packages/common/src/controllers/CheckoutController.ts index c1a5b3d7d..c75540db4 100644 --- a/packages/common/src/controllers/CheckoutController.ts +++ b/packages/common/src/controllers/CheckoutController.ts @@ -224,6 +224,8 @@ export default class CheckoutController { return response.responseData; }; + finalizeStripePpvPayment = (paymentIntent: string) => this.checkoutService?.finalizeStripePpvPayment?.({ paymentIntent }); + paypalPayment = async ({ successUrl, waitingUrl, diff --git a/packages/common/src/services/integrations/CheckoutService.ts b/packages/common/src/services/integrations/CheckoutService.ts index 483422073..ecce38568 100644 --- a/packages/common/src/services/integrations/CheckoutService.ts +++ b/packages/common/src/services/integrations/CheckoutService.ts @@ -7,6 +7,7 @@ import type { GetDirectPostCardPayment, GetEntitlements, GetFinalizeAdyenPayment, + GetFinalizeStripePpvPayment, GetInitialAdyenPayment, GetOffer, GetOffers, @@ -54,6 +55,8 @@ export default abstract class CheckoutService { abstract finalizeAdyenPayment?: GetFinalizeAdyenPayment; + abstract finalizeStripePpvPayment?: GetFinalizeStripePpvPayment; + abstract updatePaymentMethodWithPayPal?: UpdatePaymentWithPayPal; abstract deletePaymentMethod?: DeletePaymentMethod; diff --git a/packages/common/src/services/integrations/cleeng/CleengCheckoutService.ts b/packages/common/src/services/integrations/cleeng/CleengCheckoutService.ts index ae4338076..3d8b0046b 100644 --- a/packages/common/src/services/integrations/cleeng/CleengCheckoutService.ts +++ b/packages/common/src/services/integrations/cleeng/CleengCheckoutService.ts @@ -162,6 +162,8 @@ export default class CleengCheckoutService extends CheckoutService { finalizeAdyenPayment: GetFinalizeAdyenPayment = async (payload) => this.cleengService.post('/connectors/adyen/initial-payment/finalize', JSON.stringify(payload), { authenticate: true }); + finalizeStripePpvPayment: undefined; + updatePaymentMethodWithPayPal: UpdatePaymentWithPayPal = async (payload) => { return this.cleengService.post('/connectors/paypal/v1/payment_details/tokens', JSON.stringify(payload), { authenticate: true }); }; diff --git a/packages/common/src/services/integrations/jwp/JWPCheckoutService.ts b/packages/common/src/services/integrations/jwp/JWPCheckoutService.ts index 6a3e4a178..2445eaf06 100644 --- a/packages/common/src/services/integrations/jwp/JWPCheckoutService.ts +++ b/packages/common/src/services/integrations/jwp/JWPCheckoutService.ts @@ -306,6 +306,14 @@ export default class JWPCheckoutService extends CheckoutService { } }; + finalizeStripePpvPayment = async ({ paymentIntent }: { paymentIntent: string }) => { + try { + await this.apiService.post('/payments', { pi_id: paymentIntent }, { withAuthentication: true }); + } catch { + throw new Error('Failed to confirm payment'); + } + }; + getSubscriptionSwitches = undefined; getOrder = undefined; diff --git a/packages/common/types/checkout.ts b/packages/common/types/checkout.ts index c286f4ec6..3df79281d 100644 --- a/packages/common/types/checkout.ts +++ b/packages/common/types/checkout.ts @@ -94,10 +94,12 @@ export type Order = { requiredPaymentDetails: boolean; }; +export type PaymentProvider = 'stripe' | 'adyen'; + export type PaymentMethod = { id: number; methodName: 'card' | 'paypal'; - provider?: 'stripe' | 'adyen'; + provider?: PaymentProvider; paymentGateway?: 'adyen' | 'paypal'; // @todo: merge with provider logoUrl: string; }; @@ -383,6 +385,7 @@ export type GetEntitlements = EnvironmentServiceRequest; export type GetInitialAdyenPayment = EnvironmentServiceRequest; export type GetFinalizeAdyenPayment = EnvironmentServiceRequest; +export type GetFinalizeStripePpvPayment = PromiseRequest<{ paymentIntent: string }, void>; export type UpdatePaymentWithPayPal = EnvironmentServiceRequest; export type DeletePaymentMethod = EnvironmentServiceRequest; export type AddAdyenPaymentDetails = EnvironmentServiceRequest; diff --git a/packages/ui-react/src/components/FinalizePayment/FinalizeAdyenPayment.tsx b/packages/ui-react/src/components/FinalizePayment/FinalizeAdyenPayment.tsx new file mode 100644 index 000000000..05722fe11 --- /dev/null +++ b/packages/ui-react/src/components/FinalizePayment/FinalizeAdyenPayment.tsx @@ -0,0 +1,84 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useLocation, useNavigate } from 'react-router'; +import { useSearchParams } from 'react-router-dom'; +import { getModule } from '@jwp/ott-common/src/modules/container'; +import { useConfigStore } from '@jwp/ott-common/src/stores/ConfigStore'; +import AccountController from '@jwp/ott-common/src/controllers/AccountController'; +import CheckoutController from '@jwp/ott-common/src/controllers/CheckoutController'; +import { ACCESS_MODEL } from '@jwp/ott-common/src/constants'; +import useEventCallback from '@jwp/ott-hooks-react/src/useEventCallback'; + +import Button from '../Button/Button'; +import { modalURLFromLocation } from '../../utils/location'; +import { useAriaAnnouncer } from '../../containers/AnnouncementProvider/AnnoucementProvider'; + +import styles from './FinalizePayment.module.scss'; + +type FinalizeAdyenPaymentProps = { + onError: () => void; +}; + +const FinalizeAdyenPayment = ({ onError }: FinalizeAdyenPaymentProps) => { + const accountController = getModule(AccountController); + const checkoutController = getModule(CheckoutController); + + const { t } = useTranslation('account'); + const announce = useAriaAnnouncer(); + const navigate = useNavigate(); + const location = useLocation(); + + const { accessModel } = useConfigStore(({ accessModel }) => ({ accessModel })); + const [searchParams] = useSearchParams(); + const redirectResult = searchParams.get('redirectResult'); + const orderIdQueryParam = searchParams.get('orderId'); + + const [errorMessage, setErrorMessage] = useState(); + + const paymentSuccessUrl = useMemo(() => { + return modalURLFromLocation(location, accessModel === ACCESS_MODEL.SVOD ? 'welcome' : null); + }, [accessModel, location]); + + const checkPaymentResult = useEventCallback(async (redirectResult: string) => { + const orderId = orderIdQueryParam ? parseInt(orderIdQueryParam, 10) : undefined; + + try { + await checkoutController.finalizeAdyenPayment({ redirectResult: decodeURI(redirectResult) }, orderId); + await accountController.reloadSubscriptions({ retry: 10 }); + + announce(t('checkout.payment_success'), 'success'); + navigate(paymentSuccessUrl); + } catch (error: unknown) { + if (error instanceof Error) { + setErrorMessage(error.message); + onError(); + } + } + }); + + useEffect(() => { + if (!redirectResult) return; + + checkPaymentResult(redirectResult); + }, [checkPaymentResult, redirectResult]); + + return ( + <> + {errorMessage && ( + <> +

{errorMessage}

+