From 8178130b3afcaad7a69bec2b7252e3b727f0f9e2 Mon Sep 17 00:00:00 2001 From: Kevin He Date: Wed, 15 Mar 2023 18:04:26 -0700 Subject: [PATCH 01/38] render payment form --- .../app/pages/checkout/index.jsx | 4 +-- .../checkout/partials/payment-selection.jsx | 6 ++-- .../app/pages/checkout/partials/payment.jsx | 32 +++++++++++++++---- .../pages/checkout/util/usePaymentForms.js | 11 +++++-- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/checkout/index.jsx b/packages/template-retail-react-app/app/pages/checkout/index.jsx index f94449e309..9a4a1abf1a 100644 --- a/packages/template-retail-react-app/app/pages/checkout/index.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/index.jsx @@ -12,7 +12,7 @@ import {CheckoutProvider, useCheckout} from './util/checkout-context' import ContactInfo from './partials/contact-info' import ShippingAddress from './partials/shipping-address' import ShippingOptions from './partials/shipping-options' -// import Payment from './partials/payment' +import Payment from './partials/payment' import OrderSummary from '../../components/order-summary' import {useCurrentCustomer} from '../../hooks/use-current-customer' import {useCurrentBasket} from '../../hooks/use-current-basket' @@ -61,7 +61,7 @@ const Checkout = () => { - {/* */} + {step === 4 && ( diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/payment-selection.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/payment-selection.jsx index 4429ebd793..46bf137b1d 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/payment-selection.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/payment-selection.jsx @@ -20,14 +20,16 @@ import { Tooltip } from '@chakra-ui/react' import {useForm, Controller} from 'react-hook-form' +import { useCurrentBasket } from '../../../hooks/use-current-basket' +import { useCurrentCustomer } from '../../../hooks/use-current-customer' import {LockIcon, PaypalIcon} from '../../../components/icons' -import {useCheckout} from '../util/checkout-context' import CreditCardFields from '../../../components/forms/credit-card-fields' import CCRadioGroup from './cc-radio-group' const PaymentSelection = ({form, hideSubmitButton, onSubmit = () => null}) => { const {formatMessage} = useIntl() - const {customer, basket} = useCheckout() + const {data: basket} = useCurrentBasket() + const {data: customer} = useCurrentCustomer() const hasSavedCards = customer?.paymentInstruments?.length > 0 diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx index 92757cfde2..2794e085bb 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx @@ -8,6 +8,8 @@ import React, {useEffect} from 'react' import PropTypes from 'prop-types' import {FormattedMessage, useIntl} from 'react-intl' import {Box, Button, Checkbox, Container, Heading, Stack, Text, Divider} from '@chakra-ui/react' +import {usePaymentMethodsForBasket} from 'commerce-sdk-react-preview' +import { useCurrentBasket } from '../../../hooks/use-current-basket' import {useCheckout} from '../util/checkout-context' import usePaymentForms from '../util/usePaymentForms' import {getCreditCardIcon} from '../../../utils/cc-utils' @@ -19,15 +21,19 @@ import {PromoCode, usePromoCode} from '../../../components/promo-code' const Payment = () => { const {formatMessage} = useIntl() + const {data: basket} = useCurrentBasket() + const selectedShippingAddress = basket?.shipments && basket?.shipments[0]?.shippingAddress + const selectedBillingAddress = basket?.billingAddress + const selectedPayment = basket?.paymentInstruments && basket?.paymentInstruments[0] const { step, checkoutSteps, setCheckoutStep, - selectedShippingAddress, - selectedBillingAddress, - selectedPayment, - getPaymentMethods, + // selectedShippingAddress, + // selectedBillingAddress, + // selectedPayment, + // getPaymentMethods, removePayment } = useCheckout() @@ -41,9 +47,21 @@ const Payment = () => { const {removePromoCode, ...promoCodeProps} = usePromoCode() - useEffect(() => { - getPaymentMethods() - }, []) + // const {data: shippingMethods} = useShippingMethodsForShipment( + // { + // parameters: { + // basketId: basket.basketId, + // shipmentId: 'me' + // } + // }, + // { + // enabled: Boolean(basket.basketId) && step === checkoutSteps.ShippingOptions + // } + // ) + + // useEffect(() => { + // getPaymentMethods() + // }, []) return ( { + const {data: basket} = useCurrentBasket() + const selectedShippingAddress = basket?.shipments && basket?.shipments[0]?.shippingAddress + const selectedBillingAddress = basket?.billingAddress + const selectedPayment = basket?.paymentInstruments && basket?.paymentInstruments[0] const { - selectedPayment, - selectedBillingAddress, - selectedShippingAddress, + // selectedPayment, + // selectedBillingAddress, + // selectedShippingAddress, setPayment, setBillingAddress, isBillingSameAsShipping, From e72e43e7ec9e2c7bc2c95945569d3b2a54e0d578 Mon Sep 17 00:00:00 2001 From: Kevin He Date: Fri, 17 Mar 2023 22:58:52 -0700 Subject: [PATCH 02/38] refactor payment form --- .../pages/checkout/partials/payment-form.jsx | 103 +++++++++ .../checkout/partials/payment-selection.jsx | 206 ------------------ .../app/pages/checkout/partials/payment.jsx | 91 +++++++- .../pages/checkout/util/usePaymentForms.js | 47 +++- 4 files changed, 222 insertions(+), 225 deletions(-) create mode 100644 packages/template-retail-react-app/app/pages/checkout/partials/payment-form.jsx delete mode 100644 packages/template-retail-react-app/app/pages/checkout/partials/payment-selection.jsx diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/payment-form.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/payment-form.jsx new file mode 100644 index 0000000000..8d315fba91 --- /dev/null +++ b/packages/template-retail-react-app/app/pages/checkout/partials/payment-form.jsx @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import React from 'react' +import {FormattedMessage, FormattedNumber, useIntl} from 'react-intl' +import PropTypes from 'prop-types' +import { + Box, + Flex, + Radio, + RadioGroup, + Stack, + Text, + Tooltip +} from '@chakra-ui/react' +import {useCurrentBasket} from '../../../hooks/use-current-basket' +import {LockIcon, PaypalIcon} from '../../../components/icons' +import CreditCardFields from '../../../components/forms/credit-card-fields' + +const PaymentForm = ({form, onSubmit}) => { + const {formatMessage} = useIntl() + const {data: basket} = useCurrentBasket() + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) +} + +PaymentForm.propTypes = { + /** The form object returnd from `useForm` */ + form: PropTypes.object, + + /** Callback for form submit */ + onSubmit: PropTypes.func +} + +export default PaymentForm diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/payment-selection.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/payment-selection.jsx deleted file mode 100644 index 46bf137b1d..0000000000 --- a/packages/template-retail-react-app/app/pages/checkout/partials/payment-selection.jsx +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2021, salesforce.com, inc. - * All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - */ -import React, {useState} from 'react' -import {FormattedMessage, FormattedNumber, useIntl} from 'react-intl' -import PropTypes from 'prop-types' -import { - Box, - Button, - Container, - Flex, - Heading, - Radio, - RadioGroup, - Stack, - Text, - Tooltip -} from '@chakra-ui/react' -import {useForm, Controller} from 'react-hook-form' -import { useCurrentBasket } from '../../../hooks/use-current-basket' -import { useCurrentCustomer } from '../../../hooks/use-current-customer' -import {LockIcon, PaypalIcon} from '../../../components/icons' -import CreditCardFields from '../../../components/forms/credit-card-fields' -import CCRadioGroup from './cc-radio-group' - -const PaymentSelection = ({form, hideSubmitButton, onSubmit = () => null}) => { - const {formatMessage} = useIntl() - const {data: basket} = useCurrentBasket() - const {data: customer} = useCurrentCustomer() - - const hasSavedCards = customer?.paymentInstruments?.length > 0 - - const [isEditingPayment, setIsEditingPayment] = useState(!hasSavedCards) - - form = form || useForm() - - const submitForm = async (payment) => { - await onSubmit(payment) - } - - // Acts as our `onChange` handler for paymentInstrumentId radio group. We do this - // manually here so we can toggle off the 'add payment' form as needed. - const onPaymentIdChange = (value) => { - if (value && isEditingPayment) { - togglePaymentEdit() - } - form.reset({paymentInstrumentId: value}) - } - - // Opens/closes the 'add payment' form. Notice that when toggling either state, - // we reset the form so as to remove any payment selection. - const togglePaymentEdit = () => { - form.reset({paymentInstrumentId: ''}) - setIsEditingPayment(!isEditingPayment) - form.trigger() - } - - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - {hasSavedCards && ( - ( - - )} - /> - )} - - {isEditingPayment && ( - - - {hasSavedCards && ( - - - - )} - - - - {!hideSubmitButton && ( - - - - - - )} - - - )} - - - - - - - - - - - - - - -
- ) -} - -PaymentSelection.propTypes = { - /** The form object returnd from `useForm` */ - form: PropTypes.object, - - /** Show or hide the submit button (for controlling the form from outside component) */ - hideSubmitButton: PropTypes.bool, - - /** Callback for form submit */ - onSubmit: PropTypes.func -} - -export default PaymentSelection diff --git a/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx b/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx index 2794e085bb..172887a4ea 100644 --- a/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx +++ b/packages/template-retail-react-app/app/pages/checkout/partials/payment.jsx @@ -4,17 +4,18 @@ * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import React, {useEffect} from 'react' +import React from 'react' import PropTypes from 'prop-types' import {FormattedMessage, useIntl} from 'react-intl' import {Box, Button, Checkbox, Container, Heading, Stack, Text, Divider} from '@chakra-ui/react' -import {usePaymentMethodsForBasket} from 'commerce-sdk-react-preview' -import { useCurrentBasket } from '../../../hooks/use-current-basket' +import {useForm} from 'react-hook-form' +import {useShopperBasketsMutation} from 'commerce-sdk-react-preview' +import {useCurrentBasket} from '../../../hooks/use-current-basket' import {useCheckout} from '../util/checkout-context' import usePaymentForms from '../util/usePaymentForms' -import {getCreditCardIcon} from '../../../utils/cc-utils' +import {getPaymentInstrumentCardType, getCreditCardIcon} from '../../../utils/cc-utils' import {ToggleCard, ToggleCardEdit, ToggleCardSummary} from '../../../components/toggle-card' -import PaymentSelection from './payment-selection' +import PaymentForm from './payment-form' import ShippingAddressSelection from './shipping-address-selection' import AddressDisplay from '../../../components/address-display' import {PromoCode, usePromoCode} from '../../../components/promo-code' @@ -26,6 +27,10 @@ const Payment = () => { const selectedBillingAddress = basket?.billingAddress const selectedPayment = basket?.paymentInstruments && basket?.paymentInstruments[0] + const {mutateAsync: addPaymentInstrumentToBasket} = useShopperBasketsMutation( + 'addPaymentInstrumentToBasket' + ) + const { step, checkoutSteps, @@ -38,15 +43,81 @@ const Payment = () => { } = useCheckout() const { - paymentMethodForm, + // paymentMethodForm, billingAddressForm, billingSameAsShipping, - setBillingSameAsShipping, - reviewOrder + setBillingSameAsShipping + // reviewOrder } = usePaymentForms() const {removePromoCode, ...promoCodeProps} = usePromoCode() + const paymentMethodForm = useForm() + + const onPaymentSubmit = async () => { + console.log('onPaymentSubmit') + const isFormValid = await paymentMethodForm.trigger() + + if (!isFormValid) { + return + } + + const formValue = paymentMethodForm.getValues() + // cardType + // : + // "visa" + // expiry + // : + // "12/26" + // holder + // : + // "test" + // number + // : + // "4111 1111 1111 1111" + // securityCode + // : + // "265" + + // The form gives us the expiration date as `MM/YY` - so we need to split it into + // month and year to submit them as individual fields. + const [expirationMonth, expirationYear] = formValue.expiry.split('/') + + const paymentInstrument = { + paymentMethodId: 'CREDIT_CARD', + paymentCard: { + holder: formValue.holder, + // maskedNumber: formValue.number.replace(/ /g, ''), + // TODO: SCAPI only takes masked cc number ? + maskedNumber: "*********1234", + cardType: getPaymentInstrumentCardType(formValue.cardType), + expirationMonth: parseInt(expirationMonth), + expirationYear: parseInt(`20${expirationYear}`), + + // TODO: These fields are required for saving the card to the customer's + // account. Im not sure what they are for or how to get them, so for now + // we're just passing some values to make it work. Need to investigate. + issueNumber: '', + validFromMonth: 1, + validFromYear: 2020 + } + } + + await addPaymentInstrumentToBasket({parameters: {basketId: basket.basketId}, body: paymentInstrument}) + } + const onBillingSubmit = async () => { + console.log('onBillingSubmit') + console.log() + // function delay(milliseconds) { + // return new Promise(resolve => setTimeout(resolve, milliseconds)); + // } + // await delay(1000) + } + + const onSubmit = async () => { + await onPaymentSubmit() + await onBillingSubmit() + } // const {data: shippingMethods} = useShippingMethodsForShipment( // { // parameters: { @@ -82,7 +153,7 @@ const Payment = () => { {!selectedPayment?.paymentCard ? ( - + ) : ( @@ -148,7 +219,7 @@ const Payment = () => { - -