@graphcommerce/magento-payment-braintree 8.1.0-canary.2 → 8.1.0-canary.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +68 -1
- package/hooks/useBraintreeHostedFields.ts +49 -39
- package/hooks/useBraintreePaypal.ts +39 -0
- package/methods/braintree/PaymentMethodOptions.tsx +281 -22
- package/methods/braintree/index.ts +2 -2
- package/methods/braintree_local_payments/BraintreeLocalPaymentsCart.graphql +1 -2
- package/methods/braintree_local_payments/PaymentMethodOptions.tsx +4 -5
- package/methods/braintree_local_payments/index.ts +2 -2
- package/methods/braintree_paypal/PaymentMethodOptions.tsx +90 -0
- package/methods/braintree_paypal/index.ts +10 -0
- package/package.json +14 -14
- package/plugins/AddBraintreeMethods.tsx +12 -1
    
        package/CHANGELOG.md
    CHANGED
    
    | @@ -1,6 +1,73 @@ | |
| 1 1 | 
             
            # Change Log
         | 
| 2 2 |  | 
| 3 | 
            -
            ## 8.1.0-canary. | 
| 3 | 
            +
            ## 8.1.0-canary.5
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## 8.0.6-canary.4
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ## 8.0.6-canary.3
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ## 8.0.6-canary.2
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ### Patch Changes
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            - [#2234](https://github.com/graphcommerce-org/graphcommerce/pull/2234) [`43bd04a`](https://github.com/graphcommerce-org/graphcommerce/commit/43bd04a777c5800cc7e01bee1e123a5aad82f194) - Prevent BillingPage query from rerunning on each mutation
         | 
| 14 | 
            +
              ([@FrankHarland](https://github.com/FrankHarland))
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            ## 8.0.6-canary.1
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            ## 8.0.6-canary.0
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            ## 8.0.5
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            ### Patch Changes
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            - [#2188](https://github.com/graphcommerce-org/graphcommerce/pull/2188) [`eec7498`](https://github.com/graphcommerce-org/graphcommerce/commit/eec7498213f34f0f850123b577b77bf678e3c80b) - Braintree Credit Card: Hosted payment fields now have proper styling and all focus/blur and error states are correctly handled.
         | 
| 25 | 
            +
              ([@paales](https://github.com/paales))
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ## 8.0.5-canary.10
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            ## 8.0.5-canary.9
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            ## 8.0.5-canary.8
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ## 8.0.5-canary.7
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            ## 8.0.5-canary.6
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            ## 8.0.5-canary.5
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            ### Patch Changes
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            - [#2188](https://github.com/graphcommerce-org/graphcommerce/pull/2188) [`eec7498`](https://github.com/graphcommerce-org/graphcommerce/commit/eec7498213f34f0f850123b577b77bf678e3c80b) - Braintree Credit Card: Hosted payment fields now have proper styling and all focus/blur and error states are correctly handled.
         | 
| 42 | 
            +
              ([@paales](https://github.com/paales))
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            ## 8.0.5-canary.4
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            ## 8.0.5-canary.3
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            ## 8.0.5-canary.2
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            ## 8.0.5-canary.1
         | 
| 51 | 
            +
             | 
| 52 | 
            +
            ## 8.0.5-canary.0
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            ## 8.0.4
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            ## 8.0.4-canary.1
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            ## 8.0.4-canary.0
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            ## 8.0.3
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            ## 8.0.3-canary.6
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            ## 8.0.3-canary.5
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            ## 8.0.3-canary.4
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            ## 8.0.3-canary.3
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            ## 8.0.3-canary.2
         | 
| 4 71 |  | 
| 5 72 | 
             
            ## 8.0.3-canary.1
         | 
| 6 73 |  | 
| @@ -1,46 +1,56 @@ | |
| 1 | 
            -
            import braintree, { HostedFields } from 'braintree-web'
         | 
| 2 | 
            -
            import {  | 
| 1 | 
            +
            import braintree, { HostedFields, ThreeDSecure } from 'braintree-web'
         | 
| 2 | 
            +
            import { useEffect, useState } from 'react'
         | 
| 3 3 | 
             
            import { useBraintreeClient } from './useBraintree'
         | 
| 4 4 |  | 
| 5 | 
            -
            let  | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 5 | 
            +
            let teardownPromise: Promise<void> | undefined | void
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            export function useBraintreeHostedFields() {
         | 
| 8 | 
            +
              const braintreePromise = useBraintreeClient()
         | 
| 9 | 
            +
              const [hostedFields, setHostedFields] = useState<
         | 
| 10 | 
            +
                [HostedFields, ThreeDSecure] | [undefined, undefined]
         | 
| 11 | 
            +
              >([undefined, undefined])
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              useEffect(() => {
         | 
| 14 | 
            +
                if (!hostedFields[0] && !teardownPromise) {
         | 
| 9 15 | 
             
                  // eslint-disable-next-line @typescript-eslint/no-floating-promises
         | 
| 10 | 
            -
                   | 
| 11 | 
            -
                     | 
| 12 | 
            -
                       | 
| 13 | 
            -
             | 
| 14 | 
            -
                      resolve(
         | 
| 15 | 
            -
                        await braintree.hostedFields.create({
         | 
| 16 | 
            -
                          client,
         | 
| 17 | 
            -
                          fields: {
         | 
| 18 | 
            -
                            number: {
         | 
| 19 | 
            -
                              container: '#card-number',
         | 
| 20 | 
            -
                              // placeholder: '4111 1111 1111 1111',
         | 
| 21 | 
            -
                            },
         | 
| 22 | 
            -
                            cvv: {
         | 
| 23 | 
            -
                              container: '#cvv',
         | 
| 24 | 
            -
                              // placeholder: '123',
         | 
| 25 | 
            -
                            },
         | 
| 26 | 
            -
                            expirationDate: {
         | 
| 27 | 
            -
                              container: '#expiration-date',
         | 
| 28 | 
            -
                              // placeholder: '10/2022',
         | 
| 29 | 
            -
                            },
         | 
| 30 | 
            -
                          },
         | 
| 31 | 
            -
                        }),
         | 
| 32 | 
            -
                      )
         | 
| 33 | 
            -
                    } catch (e) {
         | 
| 34 | 
            -
                      reject(e)
         | 
| 16 | 
            +
                  braintreePromise.then(async (client) => {
         | 
| 17 | 
            +
                    if (teardownPromise) {
         | 
| 18 | 
            +
                      await teardownPromise
         | 
| 19 | 
            +
                      teardownPromise = undefined
         | 
| 35 20 | 
             
                    }
         | 
| 36 | 
            -
                  })()
         | 
| 37 | 
            -
                })
         | 
| 38 | 
            -
              }
         | 
| 39 21 |  | 
| 40 | 
            -
             | 
| 41 | 
            -
            }
         | 
| 22 | 
            +
                    const threeDSecure = await braintree.threeDSecure.create({ client, version: 2 })
         | 
| 42 23 |  | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 24 | 
            +
                    const hosted = await braintree.hostedFields.create({
         | 
| 25 | 
            +
                      client,
         | 
| 26 | 
            +
                      styles: {
         | 
| 27 | 
            +
                        input: {
         | 
| 28 | 
            +
                          // change input styles to match
         | 
| 29 | 
            +
                          // bootstrap styles
         | 
| 30 | 
            +
                          'font-size': '1rem',
         | 
| 31 | 
            +
                          color: '#495057',
         | 
| 32 | 
            +
                          'padding-left': '16px',
         | 
| 33 | 
            +
                          'padding-right': '16px',
         | 
| 34 | 
            +
                        },
         | 
| 35 | 
            +
                      },
         | 
| 36 | 
            +
                      fields: {
         | 
| 37 | 
            +
                        number: { container: '#card-number' },
         | 
| 38 | 
            +
                        cvv: { container: '#cvv' },
         | 
| 39 | 
            +
                        expirationDate: { container: '#expiration-date' },
         | 
| 40 | 
            +
                        // cardholderName: { container: '#cardholder-name' },
         | 
| 41 | 
            +
                        // postalCode: { container: '#postal-code' },
         | 
| 42 | 
            +
                      },
         | 
| 43 | 
            +
                    })
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    setHostedFields([hosted, threeDSecure])
         | 
| 46 | 
            +
                  })
         | 
| 47 | 
            +
                }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                return () => {
         | 
| 50 | 
            +
                  teardownPromise = hostedFields[0]?.teardown()
         | 
| 51 | 
            +
                  hostedFields[1]?.teardown()
         | 
| 52 | 
            +
                }
         | 
| 53 | 
            +
              }, [])
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              return teardownPromise ? [undefined, undefined] : hostedFields
         | 
| 46 56 | 
             
            }
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            import braintree, { PayPalCheckout } from 'braintree-web'
         | 
| 2 | 
            +
            import { useEffect, useState } from 'react'
         | 
| 3 | 
            +
            import { useBraintreeClient } from './useBraintree'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            let teardownPromise: Promise<void> | undefined | void
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            export function useBraintreePaypal() {
         | 
| 8 | 
            +
              const braintreePromise = useBraintreeClient()
         | 
| 9 | 
            +
              const [paypalCheckout, setPaypalCheckout] = useState<PayPalCheckout | undefined>(undefined)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              useEffect(() => {
         | 
| 12 | 
            +
                if (!paypalCheckout && !teardownPromise) {
         | 
| 13 | 
            +
                  // eslint-disable-next-line @typescript-eslint/no-floating-promises
         | 
| 14 | 
            +
                  braintreePromise.then(async (client) => {
         | 
| 15 | 
            +
                    if (teardownPromise) {
         | 
| 16 | 
            +
                      await teardownPromise
         | 
| 17 | 
            +
                      teardownPromise = undefined
         | 
| 18 | 
            +
                    }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    const checkout = await braintree.paypalCheckout.create({ client })
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    const loaded = await checkout.loadPayPalSDK({
         | 
| 23 | 
            +
                      debug: true,
         | 
| 24 | 
            +
                      currency: 'EUR',
         | 
| 25 | 
            +
                      locale: 'en-US',
         | 
| 26 | 
            +
                      intent: 'capture',
         | 
| 27 | 
            +
                      vault: false,
         | 
| 28 | 
            +
                    })
         | 
| 29 | 
            +
                    setPaypalCheckout(loaded)
         | 
| 30 | 
            +
                  })
         | 
| 31 | 
            +
                }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                return () => {
         | 
| 34 | 
            +
                  teardownPromise = paypalCheckout?.teardown()
         | 
| 35 | 
            +
                }
         | 
| 36 | 
            +
              }, [braintreePromise, paypalCheckout])
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              return teardownPromise ? undefined : paypalCheckout
         | 
| 39 | 
            +
            }
         | 
| @@ -1,33 +1,252 @@ | |
| 1 1 | 
             
            /* eslint-disable jsx-a11y/label-has-associated-control */
         | 
| 2 | 
            -
            import { useFormGqlMutationCart } from '@graphcommerce/magento-cart'
         | 
| 3 | 
            -
            import { PaymentOptionsProps } from '@graphcommerce/magento-cart-payment-method'
         | 
| 4 | 
            -
            import {  | 
| 5 | 
            -
            import {  | 
| 2 | 
            +
            import { useCartQuery, useFormGqlMutationCart } from '@graphcommerce/magento-cart'
         | 
| 3 | 
            +
            import { PaymentOptionsProps, useCartLock } from '@graphcommerce/magento-cart-payment-method'
         | 
| 4 | 
            +
            import { BillingPageDocument } from '@graphcommerce/magento-cart-checkout'
         | 
| 5 | 
            +
            import { ErrorSnackbar, FormRow, FullPageMessage } from '@graphcommerce/next-ui'
         | 
| 6 | 
            +
            import {
         | 
| 7 | 
            +
              FieldValues,
         | 
| 8 | 
            +
              FormProvider,
         | 
| 9 | 
            +
              Path,
         | 
| 10 | 
            +
              UseControllerProps,
         | 
| 11 | 
            +
              useController,
         | 
| 12 | 
            +
              useFormCompose,
         | 
| 13 | 
            +
            } from '@graphcommerce/react-hook-form'
         | 
| 14 | 
            +
            import { i18n } from '@lingui/core'
         | 
| 15 | 
            +
            import { Trans } from '@lingui/react'
         | 
| 16 | 
            +
            import { Box, CircularProgress, TextField } from '@mui/material'
         | 
| 17 | 
            +
            import { HostedFields } from 'braintree-web'
         | 
| 18 | 
            +
            import { HostedFieldsEvent, HostedFieldsHostedFieldsFieldName } from 'braintree-web/hosted-fields'
         | 
| 19 | 
            +
            import React, { useEffect, useState } from 'react'
         | 
| 20 | 
            +
            import {
         | 
| 21 | 
            +
              BraintreePaymentMethodOptionsDocument,
         | 
| 22 | 
            +
              BraintreePaymentMethodOptionsMutation,
         | 
| 23 | 
            +
              BraintreePaymentMethodOptionsMutationVariables,
         | 
| 24 | 
            +
            } from '../../BraintreePaymentMethodOptions.gql'
         | 
| 6 25 | 
             
            import { useBraintreeHostedFields } from '../../hooks/useBraintreeHostedFields'
         | 
| 26 | 
            +
            import { isBraintreeError } from '../../utils/isBraintreeError'
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            const Field = React.forwardRef<any, { ownerState: unknown; as: string }>((props, ref) => {
         | 
| 29 | 
            +
              const { ownerState, as, ...rest } = props
         | 
| 30 | 
            +
              return <Box {...rest} ref={ref} sx={{ height: 54, width: '100%' }} />
         | 
| 31 | 
            +
            })
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            export function BraintreeField<T extends FieldValues>(
         | 
| 34 | 
            +
              props: {
         | 
| 35 | 
            +
                hostedFields: HostedFields | undefined
         | 
| 36 | 
            +
                id: string
         | 
| 37 | 
            +
                label: string
         | 
| 38 | 
            +
                name: HostedFieldsHostedFieldsFieldName
         | 
| 39 | 
            +
              } & Omit<UseControllerProps<T>, 'name' | 'defaultValue'>,
         | 
| 40 | 
            +
            ) {
         | 
| 41 | 
            +
              const { hostedFields, id, label, name, control, disabled } = props
         | 
| 42 | 
            +
              const scopedName: HostedFieldsHostedFieldsFieldName = name
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              const {
         | 
| 45 | 
            +
                field: { ref, ...field },
         | 
| 46 | 
            +
                fieldState,
         | 
| 47 | 
            +
              } = useController({
         | 
| 48 | 
            +
                name: name as Path<T>,
         | 
| 49 | 
            +
                control,
         | 
| 50 | 
            +
                disabled: Boolean(!hostedFields || disabled),
         | 
| 51 | 
            +
                // shouldUnregister: true,
         | 
| 52 | 
            +
                rules: {
         | 
| 53 | 
            +
                  validate: () => {
         | 
| 54 | 
            +
                    console.log('validate')
         | 
| 55 | 
            +
                    if (!hostedFields) return false
         | 
| 56 | 
            +
                    const hostedField = hostedFields.getState().fields[name]
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    if (hostedField.isEmpty) return i18n._(/* i18n */ 'This field is required')
         | 
| 59 | 
            +
                    if (!hostedField.isPotentiallyValid) return i18n._(/* i18n */ 'This field is invalid')
         | 
| 60 | 
            +
                    if (!hostedField.isValid) return i18n._(/* i18n */ 'This field is invalid')
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    return true
         | 
| 63 | 
            +
                  },
         | 
| 64 | 
            +
                },
         | 
| 65 | 
            +
              })
         | 
| 66 | 
            +
             | 
| 67 | 
            +
              const { invalid, error } = fieldState
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              const [focused, setFocused] = useState(false)
         | 
| 70 | 
            +
              const [shrink, setShrink] = useState(false)
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              // Manual ref handling
         | 
| 73 | 
            +
              ref({
         | 
| 74 | 
            +
                focus: () => {
         | 
| 75 | 
            +
                  hostedFields?.focus(scopedName)
         | 
| 76 | 
            +
                  setFocused(true)
         | 
| 77 | 
            +
                },
         | 
| 78 | 
            +
              })
         | 
| 79 | 
            +
             | 
| 80 | 
            +
              useEffect(() => {
         | 
| 81 | 
            +
                if (!hostedFields) return () => {}
         | 
| 82 | 
            +
                const handleBlur = (event: HostedFieldsEvent) => {
         | 
| 83 | 
            +
                  if (event.emittedBy !== name) return
         | 
| 84 | 
            +
                  setShrink(!event.fields[name].isEmpty)
         | 
| 85 | 
            +
                  setFocused(false)
         | 
| 86 | 
            +
                }
         | 
| 87 | 
            +
                const handleFocus = (event: HostedFieldsEvent) => {
         | 
| 88 | 
            +
                  if (event.emittedBy !== name) return
         | 
| 89 | 
            +
                  setShrink(true)
         | 
| 90 | 
            +
                  setFocused(true)
         | 
| 91 | 
            +
                }
         | 
| 92 | 
            +
                const handleNotEmpty = (event: HostedFieldsEvent) => {
         | 
| 93 | 
            +
                  if (event.emittedBy !== name) return
         | 
| 94 | 
            +
                  setShrink(true)
         | 
| 95 | 
            +
                }
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                try {
         | 
| 98 | 
            +
                  hostedFields.on('focus', handleFocus)
         | 
| 99 | 
            +
                  hostedFields.on('blur', handleBlur)
         | 
| 100 | 
            +
                  hostedFields.on('notEmpty', handleNotEmpty)
         | 
| 101 | 
            +
                } catch {
         | 
| 102 | 
            +
                  // swallow error, sometimes due to timing issue this gets called after the hostedFields.teardown() is called.
         | 
| 103 | 
            +
                }
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                return () => {
         | 
| 106 | 
            +
                  hostedFields.off('focus', handleFocus)
         | 
| 107 | 
            +
                  hostedFields.off('blur', handleBlur)
         | 
| 108 | 
            +
                  hostedFields.off('blur', handleNotEmpty)
         | 
| 109 | 
            +
                }
         | 
| 110 | 
            +
              }, [hostedFields, name])
         | 
| 111 | 
            +
             | 
| 112 | 
            +
              return (
         | 
| 113 | 
            +
                <TextField
         | 
| 114 | 
            +
                  id={id}
         | 
| 115 | 
            +
                  label={label}
         | 
| 116 | 
            +
                  error={invalid}
         | 
| 117 | 
            +
                  helperText={error?.message}
         | 
| 118 | 
            +
                  focused={focused}
         | 
| 119 | 
            +
                  InputProps={{ slots: { input: Field } }}
         | 
| 120 | 
            +
                  InputLabelProps={{ shrink }}
         | 
| 121 | 
            +
                  {...field}
         | 
| 122 | 
            +
                />
         | 
| 123 | 
            +
              )
         | 
| 124 | 
            +
            }
         | 
| 7 125 |  | 
| 8 126 | 
             
            /** It sets the selected payment method on the cart. */
         | 
| 9 127 | 
             
            export function PaymentMethodOptions(props: PaymentOptionsProps) {
         | 
| 10 128 | 
             
              const { code, step, Container } = props
         | 
| 11 | 
            -
              const  | 
| 129 | 
            +
              const [hostedFields, threeDSecure] = useBraintreeHostedFields()
         | 
| 130 | 
            +
              const cart = useCartQuery(BillingPageDocument)
         | 
| 131 | 
            +
              const [lockstate, lock, unlock] = useCartLock()
         | 
| 132 | 
            +
             | 
| 133 | 
            +
              useEffect(() => {
         | 
| 134 | 
            +
                // eslint-disable-next-line @typescript-eslint/no-floating-promises
         | 
| 135 | 
            +
                if (!lockstate.justLocked && lockstate.locked) unlock({})
         | 
| 136 | 
            +
              }, [lockstate.justLocked, lockstate.locked, unlock])
         | 
| 137 | 
            +
             | 
| 138 | 
            +
              useEffect(() => {
         | 
| 139 | 
            +
                if (!threeDSecure) return
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                threeDSecure.on('lookup-complete', (data, next) => {
         | 
| 142 | 
            +
                  console.log('lookup-complete', data)
         | 
| 143 | 
            +
                  next?.()
         | 
| 144 | 
            +
                })
         | 
| 145 | 
            +
                threeDSecure.on('customer-canceled', (data, next) => {
         | 
| 146 | 
            +
                  console.log('customer-canceled', data)
         | 
| 147 | 
            +
                  next?.()
         | 
| 148 | 
            +
                })
         | 
| 149 | 
            +
              }, [threeDSecure])
         | 
| 12 150 |  | 
| 13 151 | 
             
              /**
         | 
| 14 152 | 
             
               * In the this folder you'll also find a PaymentMethodOptionsNoop.graphql document that is
         | 
| 15 153 | 
             
               * imported here and used as the basis for the form below.
         | 
| 16 154 | 
             
               */
         | 
| 17 | 
            -
              const form = useFormGqlMutationCart | 
| 155 | 
            +
              const form = useFormGqlMutationCart<
         | 
| 156 | 
            +
                BraintreePaymentMethodOptionsMutation,
         | 
| 157 | 
            +
                BraintreePaymentMethodOptionsMutationVariables & {
         | 
| 158 | 
            +
                  [K in HostedFieldsHostedFieldsFieldName]?: string
         | 
| 159 | 
            +
                }
         | 
| 160 | 
            +
              >(BraintreePaymentMethodOptionsDocument, {
         | 
| 18 161 | 
             
                defaultValues: { code },
         | 
| 162 | 
            +
                experimental_useV2: true,
         | 
| 19 163 | 
             
                onBeforeSubmit: async (variables) => {
         | 
| 164 | 
            +
                  if (!hostedFields) throw new Error('Hosted fields not available')
         | 
| 165 | 
            +
                  if (!threeDSecure) throw new Error('3D Secure not available')
         | 
| 166 | 
            +
                  if (!cart.data?.cart?.prices?.grand_total?.value) throw Error('Cart total not found')
         | 
| 167 | 
            +
             | 
| 20 168 | 
             
                  try {
         | 
| 21 | 
            -
                    const  | 
| 22 | 
            -
             | 
| 23 | 
            -
                     | 
| 24 | 
            -
                       | 
| 25 | 
            -
                       | 
| 26 | 
            -
                       | 
| 27 | 
            -
                       | 
| 169 | 
            +
                    const tokenResult = await hostedFields.tokenize()
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                    const verifyResult = await threeDSecure.verifyCard({
         | 
| 172 | 
            +
                      nonce: tokenResult.nonce,
         | 
| 173 | 
            +
                      amount: String(cart.data.cart.prices.grand_total.value),
         | 
| 174 | 
            +
                      bin: tokenResult.details.bin,
         | 
| 175 | 
            +
                      collectDeviceData: true,
         | 
| 176 | 
            +
                      billingAddress: {
         | 
| 177 | 
            +
                        givenName: cart.data.cart.billing_address?.firstname,
         | 
| 178 | 
            +
                        surname: cart.data.cart.billing_address?.lastname,
         | 
| 179 | 
            +
                        countryCodeAlpha2: cart.data.cart.billing_address?.country?.code,
         | 
| 180 | 
            +
                        streetAddress: cart.data.cart.billing_address?.street?.join(' '),
         | 
| 181 | 
            +
                        postalCode: cart.data.cart.billing_address?.postcode ?? undefined,
         | 
| 182 | 
            +
                        phoneNumber: cart.data.cart.billing_address?.telephone ?? undefined,
         | 
| 183 | 
            +
                        region: cart.data.cart.billing_address?.region?.code ?? undefined,
         | 
| 184 | 
            +
                        locality: cart.data.cart.billing_address?.city,
         | 
| 185 | 
            +
                      },
         | 
| 186 | 
            +
                      email: cart.data.cart.email ?? undefined,
         | 
| 187 | 
            +
                      mobilePhoneNumber: cart.data.cart.billing_address?.telephone ?? undefined,
         | 
| 188 | 
            +
                    })
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                    if (!verifyResult.threeDSecureInfo.liabilityShifted) {
         | 
| 191 | 
            +
                      throw Error('Liability not shifted')
         | 
| 28 192 | 
             
                    }
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                    await lock({ method: code })
         | 
| 195 | 
            +
                    return { ...variables, deviceData: '', nonce: verifyResult.nonce, isTokenEnabler: false }
         | 
| 29 196 | 
             
                  } catch (e) {
         | 
| 30 | 
            -
                     | 
| 197 | 
            +
                    if (isBraintreeError(e)) {
         | 
| 198 | 
            +
                      switch (e.code) {
         | 
| 199 | 
            +
                        case 'HOSTED_FIELDS_FIELDS_EMPTY':
         | 
| 200 | 
            +
                          // occurs when none of the fields are filled in
         | 
| 201 | 
            +
                          console.error('All fields are empty! Please fill out the form.')
         | 
| 202 | 
            +
                          break
         | 
| 203 | 
            +
                        case 'HOSTED_FIELDS_FIELDS_INVALID':
         | 
| 204 | 
            +
                          // occurs when certain fields do not pass client side validation
         | 
| 205 | 
            +
                          // console.error('Some fields are invalid:', tokenizeErr.details.invalidFieldKeys)
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                          // you can also programtically access the field containers for the invalid fields
         | 
| 208 | 
            +
                          // e.details.invalidFields.forEach((fieldContainer) => {
         | 
| 209 | 
            +
                          //   form.setError(fieldContainer.fieldKey, {})
         | 
| 210 | 
            +
                          //   fieldContainer.className = 'invalid'
         | 
| 211 | 
            +
                          // })
         | 
| 212 | 
            +
                          break
         | 
| 213 | 
            +
                        case 'HOSTED_FIELDS_TOKENIZATION_FAIL_ON_DUPLICATE':
         | 
| 214 | 
            +
                          // occurs when:
         | 
| 215 | 
            +
                          //   * the client token used for client authorization was generated
         | 
| 216 | 
            +
                          //     with a customer ID and the fail on duplicate payment method
         | 
| 217 | 
            +
                          //     option is set to true
         | 
| 218 | 
            +
                          //   * the card being tokenized has previously been vaulted (with any customer)
         | 
| 219 | 
            +
                          // See: https://developers.braintreepayments.com/reference/request/client-token/generate/#options.fail_on_duplicate_payment_method
         | 
| 220 | 
            +
                          console.error('This payment method already exists in your vault.')
         | 
| 221 | 
            +
                          break
         | 
| 222 | 
            +
                        case 'HOSTED_FIELDS_TOKENIZATION_CVV_VERIFICATION_FAILED':
         | 
| 223 | 
            +
                          // occurs when:
         | 
| 224 | 
            +
                          //   * the client token used for client authorization was generated
         | 
| 225 | 
            +
                          //     with a customer ID and the verify card option is set to true
         | 
| 226 | 
            +
                          //     and you have credit card verification turned on in the Braintree
         | 
| 227 | 
            +
                          //     control panel
         | 
| 228 | 
            +
                          //   * the cvv does not pass verfication (https://developers.braintreepayments.com/reference/general/testing/#avs-and-cvv/cid-responses)
         | 
| 229 | 
            +
                          // See: https://developers.braintreepayments.com/reference/request/client-token/generate/#options.verify_card
         | 
| 230 | 
            +
                          console.error('CVV did not pass verification')
         | 
| 231 | 
            +
                          break
         | 
| 232 | 
            +
                        case 'HOSTED_FIELDS_FAILED_TOKENIZATION':
         | 
| 233 | 
            +
                          // occurs for any other tokenization error on the server
         | 
| 234 | 
            +
                          console.error('Tokenization failed server side. Is the card valid?')
         | 
| 235 | 
            +
                          break
         | 
| 236 | 
            +
                        case 'HOSTED_FIELDS_TOKENIZATION_NETWORK_ERROR':
         | 
| 237 | 
            +
                          // occurs when the Braintree gateway cannot be contacted
         | 
| 238 | 
            +
                          console.error('Network error occurred when tokenizing.')
         | 
| 239 | 
            +
                          break
         | 
| 240 | 
            +
                        default:
         | 
| 241 | 
            +
                          console.error('Something bad happened!', e)
         | 
| 242 | 
            +
                      }
         | 
| 243 | 
            +
                    } else if (e instanceof Error) {
         | 
| 244 | 
            +
                      form.setError('nonce', {
         | 
| 245 | 
            +
                        message:
         | 
| 246 | 
            +
                          'Could not verify your Credit Card, please check your information and try again.',
         | 
| 247 | 
            +
                      })
         | 
| 248 | 
            +
                    }
         | 
| 249 | 
            +
                    await unlock({})
         | 
| 31 250 | 
             
                    throw e
         | 
| 32 251 | 
             
                  }
         | 
| 33 252 | 
             
                },
         | 
| @@ -39,20 +258,60 @@ export function PaymentMethodOptions(props: PaymentOptionsProps) { | |
| 39 258 | 
             
              /** To use an external Pay button we register the current form to be handled there as well. */
         | 
| 40 259 | 
             
              useFormCompose({ form, step, submit, key: `PaymentMethodOptions_${code}` })
         | 
| 41 260 |  | 
| 261 | 
            +
              const nonce = form.getFieldState('nonce')
         | 
| 262 | 
            +
             | 
| 263 | 
            +
              const loading = !hostedFields
         | 
| 264 | 
            +
             | 
| 42 265 | 
             
              /** This is the form that the user can fill in. In this case we don't wat the user to fill in anything. */
         | 
| 43 266 | 
             
              return (
         | 
| 44 | 
            -
                < | 
| 267 | 
            +
                <FormProvider {...form}>
         | 
| 45 268 | 
             
                  <form onSubmit={submit}>
         | 
| 269 | 
            +
                    <ErrorSnackbar open={nonce.invalid} onClose={() => form.clearErrors('nonce')}>
         | 
| 270 | 
            +
                      <>{nonce.error?.message}</>
         | 
| 271 | 
            +
                    </ErrorSnackbar>
         | 
| 272 | 
            +
             | 
| 46 273 | 
             
                    <input type='hidden' {...register('code')} />
         | 
| 47 | 
            -
                    <label htmlFor='card-number'>Card Number</label>
         | 
| 48 | 
            -
                    <div id='card-number' />
         | 
| 49 274 |  | 
| 50 | 
            -
                     | 
| 51 | 
            -
             | 
| 275 | 
            +
                    {loading && (
         | 
| 276 | 
            +
                      <FullPageMessage
         | 
| 277 | 
            +
                        icon={<CircularProgress />}
         | 
| 278 | 
            +
                        title={<Trans id='Loading' />}
         | 
| 279 | 
            +
                        disableMargin
         | 
| 280 | 
            +
                        sx={{ mb: 0 }}
         | 
| 281 | 
            +
                      />
         | 
| 282 | 
            +
                    )}
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                    <Box sx={[loading && { display: 'none' }]}>
         | 
| 285 | 
            +
                      <Container>
         | 
| 286 | 
            +
                        <FormRow sx={[]}>
         | 
| 287 | 
            +
                          <BraintreeField
         | 
| 288 | 
            +
                            control={form.control}
         | 
| 289 | 
            +
                            name='number'
         | 
| 290 | 
            +
                            hostedFields={hostedFields}
         | 
| 291 | 
            +
                            id='card-number'
         | 
| 292 | 
            +
                            label='Card Number'
         | 
| 293 | 
            +
                          />
         | 
| 294 | 
            +
                        </FormRow>
         | 
| 52 295 |  | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 296 | 
            +
                        <FormRow>
         | 
| 297 | 
            +
                          <BraintreeField
         | 
| 298 | 
            +
                            control={form.control}
         | 
| 299 | 
            +
                            name='expirationDate'
         | 
| 300 | 
            +
                            hostedFields={hostedFields}
         | 
| 301 | 
            +
                            id='expiration-date'
         | 
| 302 | 
            +
                            label='Expiration Date (MM/YYYY)'
         | 
| 303 | 
            +
                          />
         | 
| 304 | 
            +
                          <BraintreeField
         | 
| 305 | 
            +
                            hostedFields={hostedFields}
         | 
| 306 | 
            +
                            id='cvv'
         | 
| 307 | 
            +
                            control={form.control}
         | 
| 308 | 
            +
                            name='cvv'
         | 
| 309 | 
            +
                            label='CVV'
         | 
| 310 | 
            +
                          />
         | 
| 311 | 
            +
                        </FormRow>
         | 
| 312 | 
            +
                      </Container>
         | 
| 313 | 
            +
                    </Box>
         | 
| 55 314 | 
             
                  </form>
         | 
| 56 | 
            -
                </ | 
| 315 | 
            +
                </FormProvider>
         | 
| 57 316 | 
             
              )
         | 
| 58 317 | 
             
            }
         | 
| @@ -4,7 +4,7 @@ import { | |
| 4 4 | 
             
            } from '@graphcommerce/magento-cart-payment-method'
         | 
| 5 5 | 
             
            import { PaymentMethodOptions } from './PaymentMethodOptions'
         | 
| 6 6 |  | 
| 7 | 
            -
            export const braintree = {
         | 
| 7 | 
            +
            export const braintree: PaymentModule = {
         | 
| 8 8 | 
             
              PaymentOptions: PaymentMethodOptions,
         | 
| 9 9 | 
             
              PaymentPlaceOrder: PaymentMethodPlaceOrderNoop,
         | 
| 10 | 
            -
            } | 
| 10 | 
            +
            }
         | 
| @@ -48,11 +48,10 @@ function validateAndBuildStartPaymentParams(cartData: BraintreeLocalPaymentsCart | |
| 48 48 |  | 
| 49 49 | 
             
              return {
         | 
| 50 50 | 
             
                amount: amount.toString(),
         | 
| 51 | 
            -
             | 
| 52 51 | 
             
                currencyCode,
         | 
| 53 52 | 
             
                shippingAddressRequired: false,
         | 
| 54 53 | 
             
                email: cart?.email ?? '',
         | 
| 55 | 
            -
                phone,
         | 
| 54 | 
            +
                phone: phone ?? '',
         | 
| 56 55 | 
             
                givenName,
         | 
| 57 56 | 
             
                surname,
         | 
| 58 57 | 
             
                address: { streetAddress, extendedAddress, locality, postalCode, region, countryCode },
         | 
| @@ -98,7 +97,7 @@ export function PaymentMethodOptions(props: PaymentOptionsProps) { | |
| 98 97 | 
             
                  if (!selectedMethod?.code) throw Error('Selected method not found')
         | 
| 99 98 | 
             
                  const options = validateAndBuildStartPaymentParams(cartData)
         | 
| 100 99 |  | 
| 101 | 
            -
                  lock({ payment_id: null, method: selectedMethod?.code })
         | 
| 100 | 
            +
                  await lock({ payment_id: null, method: selectedMethod?.code })
         | 
| 102 101 |  | 
| 103 102 | 
             
                  const localPayment = await localPaymentPromise
         | 
| 104 103 | 
             
                  try {
         | 
| @@ -109,8 +108,8 @@ export function PaymentMethodOptions(props: PaymentOptionsProps) { | |
| 109 108 | 
             
                        buttonText: 'Return to website',
         | 
| 110 109 | 
             
                        url: window.location.href,
         | 
| 111 110 | 
             
                      },
         | 
| 112 | 
            -
                      onPaymentStart: ({ paymentId }, next) => {
         | 
| 113 | 
            -
                        lock({ payment_id: paymentId, method: selectedMethod?.code })
         | 
| 111 | 
            +
                      onPaymentStart: async ({ paymentId }, next) => {
         | 
| 112 | 
            +
                        await lock({ payment_id: paymentId, method: selectedMethod?.code })
         | 
| 114 113 | 
             
                        next()
         | 
| 115 114 | 
             
                      },
         | 
| 116 115 | 
             
                    })
         | 
| @@ -6,9 +6,9 @@ import { PaymentHandler } from './PaymentHandler' | |
| 6 6 | 
             
            import { PaymentMethodOptions } from './PaymentMethodOptions'
         | 
| 7 7 | 
             
            import { expandMethods } from './expandMethods'
         | 
| 8 8 |  | 
| 9 | 
            -
            export const braintree_local_payment = {
         | 
| 9 | 
            +
            export const braintree_local_payment: PaymentModule = {
         | 
| 10 10 | 
             
              PaymentOptions: PaymentMethodOptions,
         | 
| 11 11 | 
             
              PaymentPlaceOrder: PaymentMethodPlaceOrderNoop,
         | 
| 12 12 | 
             
              PaymentHandler,
         | 
| 13 13 | 
             
              expandMethods,
         | 
| 14 | 
            -
            } | 
| 14 | 
            +
            }
         | 
| @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            import { useCartQuery, useFormGqlMutationCart } from '@graphcommerce/magento-cart'
         | 
| 2 | 
            +
            import {
         | 
| 3 | 
            +
              PaymentOptionsProps,
         | 
| 4 | 
            +
              usePaymentMethodContext,
         | 
| 5 | 
            +
            } from '@graphcommerce/magento-cart-payment-method'
         | 
| 6 | 
            +
            import { useFormCompose } from '@graphcommerce/react-hook-form'
         | 
| 7 | 
            +
            // import { PayPalCheckoutCreatePaymentOptions } from 'braintree-web/paypal-checkout'
         | 
| 8 | 
            +
            import type { FlowType, Intent } from 'paypal-checkout-components'
         | 
| 9 | 
            +
            import { useEffect } from 'react'
         | 
| 10 | 
            +
            import { BraintreePaymentMethodOptionsDocument } from '../../BraintreePaymentMethodOptions.gql'
         | 
| 11 | 
            +
            import { StartPaymentOptions } from '../../hooks/useBraintree'
         | 
| 12 | 
            +
            import { useBraintreeCartLock } from '../../hooks/useBraintreeCartLock'
         | 
| 13 | 
            +
            import { useBraintreePaypal } from '../../hooks/useBraintreePaypal'
         | 
| 14 | 
            +
            import { isBraintreeError } from '../../utils/isBraintreeError'
         | 
| 15 | 
            +
            import { BraintreeLocalPaymentsCartDocument } from '../braintree_local_payments/BraintreeLocalPaymentsCart.gql'
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            export function PaymentMethodOptions(props: PaymentOptionsProps) {
         | 
| 18 | 
            +
              const paypal = useBraintreePaypal()
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              useEffect(() => {})
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              const { code, step, child } = props
         | 
| 23 | 
            +
              const { data: cartData } = useCartQuery(BraintreeLocalPaymentsCartDocument)
         | 
| 24 | 
            +
              const [lockState, lock, unlock] = useBraintreeCartLock()
         | 
| 25 | 
            +
              const { selectedMethod } = usePaymentMethodContext()
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              useEffect(() => {
         | 
| 28 | 
            +
                if (lockState.locked && !lockState.justLocked) {
         | 
| 29 | 
            +
                  // eslint-disable-next-line @typescript-eslint/no-floating-promises
         | 
| 30 | 
            +
                  unlock({ payment_id: null })
         | 
| 31 | 
            +
                }
         | 
| 32 | 
            +
              }, [lockState.justLocked, lockState.locked, unlock])
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              const form = useFormGqlMutationCart(BraintreePaymentMethodOptionsDocument, {
         | 
| 35 | 
            +
                defaultValues: { code },
         | 
| 36 | 
            +
                onBeforeSubmit: async () => {
         | 
| 37 | 
            +
                  if (!cartData?.cart?.id) throw Error('Cart id is missing')
         | 
| 38 | 
            +
                  if (!cartData.cart.prices?.grand_total?.value) throw Error("Cart doesn't have a total")
         | 
| 39 | 
            +
                  if (!cartData.cart.prices.grand_total.currency) throw Error("Cart doesn't have a total")
         | 
| 40 | 
            +
                  if (!selectedMethod?.code) throw Error('Selected method not found')
         | 
| 41 | 
            +
                  if (!paypal) throw Error('Paypal not loaded')
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  await lock({ payment_id: null, method: selectedMethod?.code })
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  const address = cartData.cart.shipping_addresses?.[0]
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  console.log('hoi')
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  const nonce = await paypal.createPayment({
         | 
| 50 | 
            +
                    flow: 'checkout' as FlowType, // Required
         | 
| 51 | 
            +
                    amount: cartData.cart.prices.grand_total.value,
         | 
| 52 | 
            +
                    currency: cartData.cart.prices.grand_total.currency,
         | 
| 53 | 
            +
                    intent: 'authorize' as Intent,
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    enableShippingAddress: true,
         | 
| 56 | 
            +
                    shippingAddressEditable: false,
         | 
| 57 | 
            +
                    shippingAddressOverride: {
         | 
| 58 | 
            +
                      recipientName: `${address?.firstname} ${address?.lastname}`,
         | 
| 59 | 
            +
                      line1: address?.street.join(' ') ?? '',
         | 
| 60 | 
            +
                      // line2: 'Unit 1',
         | 
| 61 | 
            +
                      city: address?.city ?? '',
         | 
| 62 | 
            +
                      countryCode: address?.country.code ?? '',
         | 
| 63 | 
            +
                      postalCode: address?.postcode ?? '',
         | 
| 64 | 
            +
                      state: address?.region?.code ?? '',
         | 
| 65 | 
            +
                      phone: address?.telephone ?? '',
         | 
| 66 | 
            +
                    },
         | 
| 67 | 
            +
                  })
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  try {
         | 
| 70 | 
            +
                    return { cartId: cartData?.cart?.id, deviceData: '', isTokenEnabler: false, nonce, code }
         | 
| 71 | 
            +
                  } catch (e) {
         | 
| 72 | 
            +
                    if (isBraintreeError(e)) await unlock({ payment_id: null })
         | 
| 73 | 
            +
                    throw e
         | 
| 74 | 
            +
                  }
         | 
| 75 | 
            +
                },
         | 
| 76 | 
            +
              })
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              const { handleSubmit, register } = form
         | 
| 79 | 
            +
              const submit = handleSubmit(() => {})
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              /** To use an external Pay button we register the current form to be handled there as well. */
         | 
| 82 | 
            +
              useFormCompose({ form, step, submit, key: `PaymentMethodOptions_${code}` })
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              /** This is the form that the user can fill in. In this case we don't wat the user to fill in anything. */
         | 
| 85 | 
            +
              return (
         | 
| 86 | 
            +
                <form onSubmit={submit}>
         | 
| 87 | 
            +
                  <input type='hidden' {...register('code')} />
         | 
| 88 | 
            +
                </form>
         | 
| 89 | 
            +
              )
         | 
| 90 | 
            +
            }
         | 
| @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            import {
         | 
| 2 | 
            +
              PaymentMethodPlaceOrderNoop,
         | 
| 3 | 
            +
              PaymentModule,
         | 
| 4 | 
            +
            } from '@graphcommerce/magento-cart-payment-method'
         | 
| 5 | 
            +
            import { PaymentMethodOptions } from './PaymentMethodOptions'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            export const braintree_paypal: PaymentModule = {
         | 
| 8 | 
            +
              PaymentOptions: PaymentMethodOptions,
         | 
| 9 | 
            +
              PaymentPlaceOrder: PaymentMethodPlaceOrderNoop,
         | 
| 10 | 
            +
            }
         | 
    
        package/package.json
    CHANGED
    
    | @@ -2,7 +2,7 @@ | |
| 2 2 | 
             
              "name": "@graphcommerce/magento-payment-braintree",
         | 
| 3 3 | 
             
              "homepage": "https://www.graphcommerce.org/",
         | 
| 4 4 | 
             
              "repository": "github:graphcommerce-org/graphcommerce",
         | 
| 5 | 
            -
              "version": "8.1.0-canary. | 
| 5 | 
            +
              "version": "8.1.0-canary.5",
         | 
| 6 6 | 
             
              "sideEffects": false,
         | 
| 7 7 | 
             
              "prettier": "@graphcommerce/prettier-config-pwa",
         | 
| 8 8 | 
             
              "eslintConfig": {
         | 
| @@ -18,19 +18,19 @@ | |
| 18 18 | 
             
                "braintree-web": "^3.99.0"
         | 
| 19 19 | 
             
              },
         | 
| 20 20 | 
             
              "peerDependencies": {
         | 
| 21 | 
            -
                "@graphcommerce/eslint-config-pwa": "^8.1.0-canary. | 
| 22 | 
            -
                "@graphcommerce/graphql": "^8.1.0-canary. | 
| 23 | 
            -
                "@graphcommerce/image": "^8.1.0-canary. | 
| 24 | 
            -
                "@graphcommerce/magento-cart": "^8.1.0-canary. | 
| 25 | 
            -
                "@graphcommerce/magento-cart-payment-method": "^8.1.0-canary. | 
| 26 | 
            -
                "@graphcommerce/magento-cart-shipping-address": "^8.1.0-canary. | 
| 27 | 
            -
                "@graphcommerce/magento-product": "^8.1.0-canary. | 
| 28 | 
            -
                "@graphcommerce/magento-product-configurable": "^8.1.0-canary. | 
| 29 | 
            -
                "@graphcommerce/magento-store": "^8.1.0-canary. | 
| 30 | 
            -
                "@graphcommerce/next-ui": "^8.1.0-canary. | 
| 31 | 
            -
                "@graphcommerce/prettier-config-pwa": "^8.1.0-canary. | 
| 32 | 
            -
                "@graphcommerce/react-hook-form": "^8.1.0-canary. | 
| 33 | 
            -
                "@graphcommerce/typescript-config-pwa": "^8.1.0-canary. | 
| 21 | 
            +
                "@graphcommerce/eslint-config-pwa": "^8.1.0-canary.5",
         | 
| 22 | 
            +
                "@graphcommerce/graphql": "^8.1.0-canary.5",
         | 
| 23 | 
            +
                "@graphcommerce/image": "^8.1.0-canary.5",
         | 
| 24 | 
            +
                "@graphcommerce/magento-cart": "^8.1.0-canary.5",
         | 
| 25 | 
            +
                "@graphcommerce/magento-cart-payment-method": "^8.1.0-canary.5",
         | 
| 26 | 
            +
                "@graphcommerce/magento-cart-shipping-address": "^8.1.0-canary.5",
         | 
| 27 | 
            +
                "@graphcommerce/magento-product": "^8.1.0-canary.5",
         | 
| 28 | 
            +
                "@graphcommerce/magento-product-configurable": "^8.1.0-canary.5",
         | 
| 29 | 
            +
                "@graphcommerce/magento-store": "^8.1.0-canary.5",
         | 
| 30 | 
            +
                "@graphcommerce/next-ui": "^8.1.0-canary.5",
         | 
| 31 | 
            +
                "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.5",
         | 
| 32 | 
            +
                "@graphcommerce/react-hook-form": "^8.1.0-canary.5",
         | 
| 33 | 
            +
                "@graphcommerce/typescript-config-pwa": "^8.1.0-canary.5",
         | 
| 34 34 | 
             
                "@lingui/core": "^4.2.1",
         | 
| 35 35 | 
             
                "@lingui/macro": "^4.2.1",
         | 
| 36 36 | 
             
                "@lingui/react": "^4.2.1",
         | 
| @@ -2,13 +2,24 @@ import { PaymentMethodContextProviderProps } from '@graphcommerce/magento-cart-p | |
| 2 2 | 
             
            import type { PluginProps } from '@graphcommerce/next-config'
         | 
| 3 3 | 
             
            import { braintree } from '../methods/braintree'
         | 
| 4 4 | 
             
            import { braintree_local_payment } from '../methods/braintree_local_payments'
         | 
| 5 | 
            +
            import { braintree_paypal } from '../methods/braintree_paypal'
         | 
| 5 6 |  | 
| 6 7 | 
             
            export const component = 'PaymentMethodContextProvider'
         | 
| 7 8 | 
             
            export const exported = '@graphcommerce/magento-cart-payment-method'
         | 
| 8 9 |  | 
| 9 10 | 
             
            function AddBrainTreeMethods(props: PluginProps<PaymentMethodContextProviderProps>) {
         | 
| 10 11 | 
             
              const { modules, Prev, ...rest } = props
         | 
| 11 | 
            -
              return  | 
| 12 | 
            +
              return (
         | 
| 13 | 
            +
                <Prev
         | 
| 14 | 
            +
                  {...rest}
         | 
| 15 | 
            +
                  modules={{
         | 
| 16 | 
            +
                    ...modules,
         | 
| 17 | 
            +
                    braintree,
         | 
| 18 | 
            +
                    braintree_local_payment,
         | 
| 19 | 
            +
                    // braintree_paypal,
         | 
| 20 | 
            +
                  }}
         | 
| 21 | 
            +
                />
         | 
| 22 | 
            +
              )
         | 
| 12 23 | 
             
            }
         | 
| 13 24 |  | 
| 14 25 | 
             
            export const Plugin = AddBrainTreeMethods
         |