diff --git a/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/__tests__/__snapshots__/usePaymentMethods.spec.js.snap b/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/__tests__/__snapshots__/usePaymentMethods.spec.js.snap index f4ddf7ee4a..8427e95511 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/__tests__/__snapshots__/usePaymentMethods.spec.js.snap +++ b/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/__tests__/__snapshots__/usePaymentMethods.spec.js.snap @@ -8,6 +8,7 @@ Object { }, ], "currentSelectedPaymentMethod": "currentSelectedPaymentMethod", + "handlePaymentMethodSelection": [Function], "initialSelectedMethod": "availablePaymentMethod", "isLoading": false, } diff --git a/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/__tests__/usePaymentMethods.spec.js b/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/__tests__/usePaymentMethods.spec.js index 743ce9e071..04cca81fd3 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/__tests__/usePaymentMethods.spec.js +++ b/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/__tests__/usePaymentMethods.spec.js @@ -18,14 +18,21 @@ jest.mock( } ); +const mockSetPaymentMethodOnCartMutation = jest.fn(); + jest.mock('@apollo/client', () => { return { + useMutation: jest.fn(() => [ + mockSetPaymentMethodOnCartMutation, + { loading: true } + ]), useQuery: jest.fn().mockReturnValue({ data: { cart: { available_payment_methods: [ { code: 'availablePaymentMethod' } - ] + ], + selected_payment_method: { code: 'braintree' } } }, loading: false @@ -34,9 +41,18 @@ jest.mock('@apollo/client', () => { }); jest.mock('../paymentMethods.gql', () => ({ - getPaymentMethodsQuery: 'paymentMethodsQuery' + getPaymentMethodsQuery: 'paymentMethodsQuery', + setPaymentMethodOnCartMutation: 'setPaymentMethod' })); +const getPaymentMethodsQuery = 'getPaymentMethodsQuery'; +const setPaymentMethodOnCartMutation = 'setPaymentMethodOnCartMutation'; + +const operations = { + getPaymentMethodsQuery, + setPaymentMethodOnCartMutation +}; + const Component = props => { const talonProps = usePaymentMethods(props); @@ -58,7 +74,9 @@ const getTalonProps = props => { }; it('returns the correct shape', () => { - const { talonProps } = getTalonProps(); + const { talonProps } = getTalonProps({ + operations + }); expect(talonProps).toMatchSnapshot(); }); @@ -71,3 +89,38 @@ it('returns an empty array for availablePaymentMethods when there is no data', ( expect(talonProps.availablePaymentMethods).toEqual([]); expect(talonProps.initialSelectedMethod).toBeNull(); }); + +it('default payment is selected when there is one available payment method', () => { + useQuery.mockReturnValueOnce({ + data: { + cart: { + available_payment_methods: [{ code: 'availablePaymentMethod' }], + selected_payment_method: { code: null } + } + } + }); + + const { talonProps } = getTalonProps(); + + expect(talonProps.availablePaymentMethods.length).toEqual(1); + expect(talonProps.initialSelectedMethod).toEqual('availablePaymentMethod'); +}); + +it('default payment is not selected when there is more than one available payment method', () => { + useQuery.mockReturnValueOnce({ + data: { + cart: { + available_payment_methods: [ + { code: 'availablePaymentMethod1' }, + { code: 'availablePaymentMethod2' } + ], + selected_payment_method: { code: null } + } + } + }); + + const { talonProps } = getTalonProps(); + + expect(talonProps.availablePaymentMethods.length).toEqual(2); + expect(talonProps.initialSelectedMethod).toBeNull(); +}); diff --git a/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/paymentMethods.gql.js b/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/paymentMethods.gql.js index b003f0b945..b3583f957f 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/paymentMethods.gql.js +++ b/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/paymentMethods.gql.js @@ -8,10 +8,33 @@ export const GET_PAYMENT_METHODS = gql` code title } + selected_payment_method { + code + } + } + } +`; + +export const SET_PAYMENT_METHOD_ON_CART = gql` + mutation setPaymentMethodOnCart( + $cartId: String! + $paymentMethod: PaymentMethodInput! + ) { + setPaymentMethodOnCart( + input: { cart_id: $cartId, payment_method: $paymentMethod } + ) { + cart { + id + selected_payment_method { + code + title + } + } } } `; export default { - getPaymentMethodsQuery: GET_PAYMENT_METHODS + getPaymentMethodsQuery: GET_PAYMENT_METHODS, + setPaymentMethodOnCartMutation: SET_PAYMENT_METHOD_ON_CART }; diff --git a/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/usePaymentMethods.js b/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/usePaymentMethods.js index c8810abd8f..49b4c69c57 100644 --- a/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/usePaymentMethods.js +++ b/packages/peregrine/lib/talons/CheckoutPage/PaymentInformation/usePaymentMethods.js @@ -1,4 +1,5 @@ -import { useQuery } from '@apollo/client'; +import { useCallback } from 'react'; +import { useMutation, useQuery } from '@apollo/client'; import useFieldState from '@magento/peregrine/lib/hooks/hook-wrappers/useInformedFieldStateWrapper'; import DEFAULT_OPERATIONS from './paymentMethods.gql'; import mergeOperations from '@magento/peregrine/lib/util/shallowMerge'; @@ -7,7 +8,12 @@ import { useCartContext } from '../../../context/cart'; export const usePaymentMethods = props => { const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations); - const { getPaymentMethodsQuery } = operations; + const { + getPaymentMethodsQuery, + setPaymentMethodOnCartMutation + } = operations; + + const [setPaymentMethod] = useMutation(setPaymentMethodOnCartMutation); const [{ cartId }] = useCartContext(); @@ -23,13 +29,43 @@ export const usePaymentMethods = props => { const availablePaymentMethods = (data && data.cart.available_payment_methods) || []; - const initialSelectedMethod = + // If there is one payment method, select it by default. + // If more than one, none should be selected by default. + const defaultPaymentCode = (availablePaymentMethods.length && availablePaymentMethods[0].code) || null; + const selectedPaymentCode = + (data && data.cart.selected_payment_method.code) || null; + + const initialSelectedMethod = + availablePaymentMethods.length > 1 + ? selectedPaymentCode + : defaultPaymentCode; + + const handlePaymentMethodSelection = useCallback( + element => { + const value = element.target.value; + + setPaymentMethod({ + variables: { + cartId, + paymentMethod: { + code: value, + braintree: { + payment_method_nonce: value, + is_active_payment_token_enabler: false + } + } + } + }); + }, + [cartId, setPaymentMethod] + ); return { availablePaymentMethods, currentSelectedPaymentMethod, + handlePaymentMethodSelection, initialSelectedMethod, isLoading: loading }; diff --git a/packages/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.js b/packages/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.js index 286f08e59a..175743844c 100644 --- a/packages/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.js +++ b/packages/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.js @@ -28,6 +28,7 @@ const PaymentMethods = props => { const { availablePaymentMethods, currentSelectedPaymentMethod, + handlePaymentMethodSelection, initialSelectedMethod, isLoading } = talonProps; @@ -65,6 +66,7 @@ const PaymentMethods = props => { label: classes.radio_label }} checked={isSelected} + onChange={handlePaymentMethodSelection} /> {renderedComponent} diff --git a/packages/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.module.css b/packages/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.module.css index 871733791f..97021147a1 100644 --- a/packages/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.module.css +++ b/packages/venia-ui/lib/components/CheckoutPage/PaymentInformation/paymentMethods.module.css @@ -1,7 +1,7 @@ .root { composes: grid from global; composes: p-md from global; - composes: pb-xs from global; + composes: pb-s from global; } .radio_group { @@ -14,8 +14,6 @@ composes: border-subtle from global; composes: pb-xs from global; composes: pt-xs from global; - - composes: last_pt-0 from global; } /* TODO @TW: cannot compose */