@graphcommerce/magento-cart-payment-method 3.0.5 → 3.1.0

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.
@@ -19,6 +19,8 @@ export type PaymentButtonProps = PaymentMethod & { buttonProps: LinkOrButtonProp
19
19
  export type PaymentOptionsProps = PaymentMethod & PaymentMethodOptionsProps
20
20
 
21
21
  export type PaymentToggleProps = PaymentMethod
22
+ export type PaymentHandlerProps = { code: string }
23
+
22
24
  export type ExpandPaymentMethods = (
23
25
  available: AvailablePaymentMethodFragment,
24
26
  context: PaymentMethodContextFragment,
@@ -32,7 +34,7 @@ export interface PaymentModule {
32
34
  PaymentButton?: React.VFC<PaymentButtonProps>
33
35
  PaymentToggle?: React.VFC<PaymentToggleProps>
34
36
  expandMethods?: ExpandPaymentMethods
35
- PaymentHandler?: React.VFC
37
+ PaymentHandler?: React.VFC<PaymentHandlerProps>
36
38
  }
37
39
 
38
40
  export type PaymentMethodModules = { [code: string]: PaymentModule }
@@ -1,4 +1,5 @@
1
- fragment PaymentMethodContext on Cart @injectable {
1
+ fragment PaymentMethodContext on Cart @injectable @inject(into: ["ShippingMethodSelected"]) {
2
+ id
2
3
  available_payment_methods {
3
4
  ...AvailablePaymentMethod
4
5
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # Change Log
2
2
 
3
+ ## 3.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1379](https://github.com/graphcommerce-org/graphcommerce/pull/1379) [`6ebe9d12d`](https://github.com/graphcommerce-org/graphcommerce/commit/6ebe9d12db9fcaa2af67a475e64a08d63e232b46) Thanks [@paales](https://github.com/paales)! - Added a `code` prop to the PaymentHandler so the handler can be reused by multiple methods without interfering.
8
+
9
+ ### Patch Changes
10
+
11
+ - [#1379](https://github.com/graphcommerce-org/graphcommerce/pull/1379) [`b8d04130a`](https://github.com/graphcommerce-org/graphcommerce/commit/b8d04130a1b1cb8fc85308939235140288744465) Thanks [@paales](https://github.com/paales)! - Removed unused trigger: onChange for useForm handler
12
+
13
+ - Updated dependencies [[`3c801f45c`](https://github.com/graphcommerce-org/graphcommerce/commit/3c801f45c7df55131acf30ae2fe0d2344830d480), [`b8d04130a`](https://github.com/graphcommerce-org/graphcommerce/commit/b8d04130a1b1cb8fc85308939235140288744465), [`3192fab82`](https://github.com/graphcommerce-org/graphcommerce/commit/3192fab82560e2211dfcacadc3b0b305260527d8), [`104abd14e`](https://github.com/graphcommerce-org/graphcommerce/commit/104abd14e1585ef0d8de77937d25156b8fa1e201), [`0e425e85e`](https://github.com/graphcommerce-org/graphcommerce/commit/0e425e85ee8fed280349317ee0440c7bceea5823), [`2a125b1f9`](https://github.com/graphcommerce-org/graphcommerce/commit/2a125b1f98bb9272d96c3577f21d6c984caad892), [`8a354d1cd`](https://github.com/graphcommerce-org/graphcommerce/commit/8a354d1cd4757497ddfc9b1969a0addbc8ff616b)]:
14
+ - @graphcommerce/next-ui@4.6.0
15
+ - @graphcommerce/magento-cart@4.2.4
16
+ - @graphcommerce/react-hook-form@3.1.0
17
+ - @graphcommerce/image@3.1.4
18
+ - @graphcommerce/framer-scroller@2.1.5
19
+ - @graphcommerce/magento-store@4.1.6
20
+
21
+ ## 3.0.7
22
+
23
+ ### Patch Changes
24
+
25
+ - [#1378](https://github.com/graphcommerce-org/graphcommerce/pull/1378) [`b610a6e40`](https://github.com/graphcommerce-org/graphcommerce/commit/b610a6e4049e8c9e8b5d2aeff31b8e1bfc24abe5) Thanks [@paales](https://github.com/paales)! - Pin all versions internally so we can’t end up in an unfixable state for the user
26
+
27
+ - Updated dependencies [[`b610a6e40`](https://github.com/graphcommerce-org/graphcommerce/commit/b610a6e4049e8c9e8b5d2aeff31b8e1bfc24abe5), [`22ff9df16`](https://github.com/graphcommerce-org/graphcommerce/commit/22ff9df1677742ae8e07d9b7e5b12fbb487580dc)]:
28
+ - @graphcommerce/framer-scroller@2.1.4
29
+ - @graphcommerce/graphql@3.0.7
30
+ - @graphcommerce/image@3.1.3
31
+ - @graphcommerce/magento-cart@4.2.3
32
+ - @graphcommerce/magento-store@4.1.5
33
+ - @graphcommerce/next-ui@4.5.1
34
+ - @graphcommerce/react-hook-form@3.0.7
35
+
36
+ ## 3.0.6
37
+
38
+ ### Patch Changes
39
+
40
+ - [#1369](https://github.com/graphcommerce-org/graphcommerce/pull/1369) [`ae6449502`](https://github.com/graphcommerce-org/graphcommerce/commit/ae64495024a455bbe5188588604368c1542840c9) Thanks [@paales](https://github.com/paales)! - Upgraded dependencies
41
+
42
+ * [#1368](https://github.com/graphcommerce-org/graphcommerce/pull/1368) [`892018809`](https://github.com/graphcommerce-org/graphcommerce/commit/8920188093d0422ec50580e408dc28ac5f93e46a) Thanks [@paales](https://github.com/paales)! - Fix issue where the cart couldn't be properly restored when a payment was cancelled
43
+
44
+ * Updated dependencies [[`892018809`](https://github.com/graphcommerce-org/graphcommerce/commit/8920188093d0422ec50580e408dc28ac5f93e46a), [`892018809`](https://github.com/graphcommerce-org/graphcommerce/commit/8920188093d0422ec50580e408dc28ac5f93e46a), [`ae6449502`](https://github.com/graphcommerce-org/graphcommerce/commit/ae64495024a455bbe5188588604368c1542840c9), [`892018809`](https://github.com/graphcommerce-org/graphcommerce/commit/8920188093d0422ec50580e408dc28ac5f93e46a), [`892018809`](https://github.com/graphcommerce-org/graphcommerce/commit/8920188093d0422ec50580e408dc28ac5f93e46a)]:
45
+ - @graphcommerce/graphql@3.0.6
46
+ - @graphcommerce/next-ui@4.5.0
47
+ - @graphcommerce/framer-scroller@2.1.3
48
+ - @graphcommerce/image@3.1.2
49
+ - @graphcommerce/magento-cart@4.2.2
50
+ - @graphcommerce/magento-store@4.1.4
51
+ - @graphcommerce/react-hook-form@3.0.6
52
+
3
53
  ## 3.0.5
4
54
 
5
55
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import { ApolloCartErrorAlert } from '@graphcommerce/magento-cart'
1
+ import { ApolloCartErrorSnackbar } from '@graphcommerce/magento-cart'
2
2
  import { LinkOrButton, LinkOrButtonProps } from '@graphcommerce/next-ui'
3
3
  import { ComposedSubmit, ComposedSubmitRenderComponentProps } from '@graphcommerce/react-hook-form'
4
4
  import { usePaymentMethodContext } from '../PaymentMethodContext/PaymentMethodContext'
@@ -11,19 +11,17 @@ function PaymentMethodButtonRenderer(
11
11
  const { buttonProps, error, buttonState, submit } = props
12
12
  const { selectedMethod, selectedModule } = usePaymentMethodContext()
13
13
 
14
- const btnProps = { ...buttonProps, name: 'placeOrder' }
15
-
16
14
  const PaymentButton = selectedModule?.PaymentButton
17
15
 
18
16
  return (
19
17
  <>
20
18
  {!PaymentButton || !selectedMethod?.code ? (
21
19
  <LinkOrButton
22
- {...btnProps}
20
+ {...buttonProps}
23
21
  onClick={submit}
24
22
  loading={buttonState.isSubmitting || (buttonState.isSubmitSuccessful && !error)}
25
23
  >
26
- {btnProps.children}
24
+ {buttonProps.children}
27
25
  {selectedMethod?.title && (
28
26
  <>
29
27
  {' '}
@@ -35,7 +33,7 @@ function PaymentMethodButtonRenderer(
35
33
  <PaymentButton
36
34
  {...selectedMethod}
37
35
  buttonProps={{
38
- ...btnProps,
36
+ ...buttonProps,
39
37
  onClick: submit,
40
38
  loading: buttonState.isSubmitting || (buttonState.isSubmitSuccessful && !error),
41
39
  }}
@@ -65,7 +63,7 @@ export function PaymentMethodButton(props: PaymentMethodButtonProps) {
65
63
  ) : (
66
64
  <>
67
65
  {button}
68
- <ApolloCartErrorAlert key='error' error={errorVal} />
66
+ <ApolloCartErrorSnackbar key='error' error={errorVal} />
69
67
  </>
70
68
  )
71
69
  }}
@@ -20,7 +20,7 @@ const paymentMethodContext = React.createContext<PaymentMethodContextProps>({
20
20
  })
21
21
  paymentMethodContext.displayName = 'PaymentMethodContext'
22
22
 
23
- type PaymentMethodContextProviderProps = PropsWithChildren<{ modules: PaymentMethodModules }>
23
+ export type PaymentMethodContextProviderProps = PropsWithChildren<{ modules: PaymentMethodModules }>
24
24
 
25
25
  export function PaymentMethodContextProvider(props: PaymentMethodContextProviderProps) {
26
26
  const { modules, children } = props
@@ -32,36 +32,28 @@ export function PaymentMethodContextProvider(props: PaymentMethodContextProvider
32
32
  const [selectedMethod, setSelectedMethod] = useState<PaymentMethod>()
33
33
  const [selectedModule, setSelectedModule] = useState<PaymentModule>()
34
34
 
35
+ const availableMethods = useMemo(() => {
36
+ const allMethods = cartContext?.available_payment_methods ?? []
37
+ const free = allMethods.find((method) => method?.code === 'free')
38
+
39
+ return free ? [free] : allMethods
40
+ }, [cartContext?.available_payment_methods])
41
+
35
42
  // Expand the payment methods
36
43
  useEffect(() => {
37
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
44
+ if (!cartContext) return // eslint-disable-next-line @typescript-eslint/no-floating-promises
38
45
  ;(async () => {
39
- if (!cartContext) return
40
-
41
- const freeMethod = cartContext.available_payment_methods?.find(
42
- (method) => method?.code === 'free',
46
+ const promises = availableMethods.map(async (method) =>
47
+ method
48
+ ? modules[method.code]?.expandMethods?.(method, cartContext) ?? [{ ...method, child: '' }]
49
+ : Promise.resolve([]),
43
50
  )
44
51
 
45
- const promises =
46
- [...(freeMethod ? [freeMethod] : cartContext.available_payment_methods ?? [])].map(
47
- async (method) =>
48
- method
49
- ? modules?.[method.code]?.expandMethods?.(method, cartContext) ?? [
50
- { ...method, child: '' },
51
- ]
52
- : Promise.resolve([]),
53
- ) ?? []
54
-
55
52
  const loaded = (await Promise.all(promises)).flat(1).sort((a) => (a.preferred ? 1 : 0))
56
- const sortedMethods = loaded.sort((a, b) => {
57
- if (!modules?.[a?.code]) return 0
58
- if (!modules?.[b?.code]) return -1
59
- return 1
60
- })
61
53
 
62
- setMethods(sortedMethods)
54
+ setMethods(loaded)
63
55
  })()
64
- }, [cartContext, modules])
56
+ }, [availableMethods, cartContext, modules])
65
57
 
66
58
  const value = useMemo(
67
59
  () => ({
@@ -77,11 +69,9 @@ export function PaymentMethodContextProvider(props: PaymentMethodContextProvider
77
69
 
78
70
  return (
79
71
  <paymentMethodContext.Provider value={value}>
80
- {Object.entries(modules).map(([method, module]) => {
81
- const { PaymentHandler } = module
82
- if (!PaymentHandler) return null
83
- return <PaymentHandler key={method} />
84
- })}
72
+ {Object.entries(modules).map(([code, module]) =>
73
+ module.PaymentHandler ? <module.PaymentHandler key={code} code={code} /> : null,
74
+ )}
85
75
  {children}
86
76
  </paymentMethodContext.Provider>
87
77
  )
@@ -14,7 +14,7 @@ export function PaymentMethodPlaceOrderNoop(props: PaymentPlaceOrderProps) {
14
14
  const clearCurrentCartId = useClearCurrentCartId()
15
15
 
16
16
  const cartId = useCurrentCartId()
17
- const form = useFormGqlMutationCart(PaymentMethodPlaceOrderNoopDocument, { mode: 'onChange' })
17
+ const form = useFormGqlMutationCart(PaymentMethodPlaceOrderNoopDocument)
18
18
 
19
19
  const { handleSubmit, data, error } = form
20
20
  const { push } = useRouter()
@@ -23,7 +23,7 @@ export function PaymentMethodPlaceOrderNoop(props: PaymentPlaceOrderProps) {
23
23
  if (!data?.placeOrder?.order || error || !cartId) return
24
24
  clearCurrentCartId()
25
25
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
26
- push({ pathname: '/checkout/success', query: { cartId } })
26
+ push({ pathname: '/checkout/success', query: { cart_id: cartId } })
27
27
  }, [cartId, clearCurrentCartId, data?.placeOrder?.order, error, push])
28
28
 
29
29
  const submit = handleSubmit(() => {})
@@ -16,10 +16,11 @@ import {
16
16
  UseFormComposeOptions,
17
17
  useFormPersist,
18
18
  } from '@graphcommerce/react-hook-form'
19
- import { t } from '@lingui/macro'
19
+ import { select, t } from '@lingui/macro'
20
20
  import { Box, FormControl, FormHelperText, SxProps, Theme } from '@mui/material'
21
21
  import { useEffect } from 'react'
22
22
  import { usePaymentMethodContext } from '../PaymentMethodContext/PaymentMethodContext'
23
+ import { useCartLock } from '../hooks/useCartLock'
23
24
 
24
25
  export type PaymentMethodTogglesProps = Pick<UseFormComposeOptions, 'step'> & {
25
26
  sx?: SxProps<Theme>
@@ -52,10 +53,11 @@ export function PaymentMethodToggles(props: PaymentMethodTogglesProps) {
52
53
  const { methods, selectedMethod, setSelectedMethod, setSelectedModule, modules } =
53
54
  usePaymentMethodContext()
54
55
 
55
- const form = useForm<{ code: string; paymentMethod?: string }>({
56
- mode: 'onChange',
57
- defaultValues: { code: selectedMethod?.code },
56
+ const [lockState] = useCartLock()
57
+ const form = useForm<{ code: string | null; paymentMethod?: string }>({
58
+ defaultValues: { code: lockState.method },
58
59
  })
60
+
59
61
  useFormPersist({ form, name: 'PaymentMethodToggle' })
60
62
 
61
63
  const { control, handleSubmit, watch, register, setValue, formState } = form
@@ -64,6 +66,10 @@ export function PaymentMethodToggles(props: PaymentMethodTogglesProps) {
64
66
 
65
67
  useFormCompose({ form, step, submit: submitHandler, key: 'PaymentMethodToggles' })
66
68
 
69
+ useEffect(() => {
70
+ if (selectedMethod?.code) setValue('code', selectedMethod.code)
71
+ }, [selectedMethod?.code, setValue])
72
+
67
73
  const paymentMethod = watch('paymentMethod')
68
74
  useEffect(() => {
69
75
  const [code, child] = paymentMethod?.split('___') ?? ['']
@@ -141,7 +147,10 @@ export function PaymentMethodToggles(props: PaymentMethodTogglesProps) {
141
147
  }}
142
148
  >
143
149
  {methods?.map((pm) => {
144
- const buttonValue = `${pm.code}___${pm.child}`
150
+ const buttonValue = pm.child ? `${pm.code}___${pm.child}` : pm.code
151
+
152
+ if (process.env.NODE_ENV === 'production' && !modules?.[pm.code]) return null
153
+
145
154
  return (
146
155
  <ToggleButton
147
156
  name={name}
@@ -1,7 +1,12 @@
1
- import { useApolloClient } from '@graphcommerce/graphql'
2
- import { useCartQuery, useCurrentCartId } from '@graphcommerce/magento-cart'
3
- import { useCallback } from 'react'
4
- import { UseCartLockDocument } from './UseCartLock.gql'
1
+ import { useCurrentCartId } from '@graphcommerce/magento-cart'
2
+ import { useUrlQuery } from '@graphcommerce/next-ui'
3
+ import { useState } from 'react'
4
+
5
+ export type CartLockState = {
6
+ cart_id: string | null
7
+ locked: string | null
8
+ method: string | null
9
+ }
5
10
 
6
11
  /**
7
12
  * Locking a cart might is usefull in the following cases: We want to disable cart modifications
@@ -9,25 +14,31 @@ import { UseCartLockDocument } from './UseCartLock.gql'
9
14
  *
10
15
  * Todo: Block all operations on the cart while the cart is being blocked.
11
16
  */
12
- export function useCartLock() {
13
- const { cache } = useApolloClient()
14
- const cartId = useCurrentCartId()
17
+ export function useCartLock<E extends CartLockState>() {
18
+ const currentCartId = useCurrentCartId()
19
+ const [justLocked, setJustLocked] = useState(false)
20
+ const [queryState, setRouterQuery] = useUrlQuery<E>()
15
21
 
16
- const locked = useCartQuery(UseCartLockDocument, { allowUrl: true }).data?.cart?.locked ?? false
22
+ const lock = (params: Omit<E, 'locked' | 'cart_id'>) => {
23
+ if (!currentCartId) return
24
+ setJustLocked(true)
25
+ setRouterQuery({
26
+ locked: '1',
27
+ cart_id: currentCartId,
28
+ ...params,
29
+ } as E)
30
+ }
17
31
 
18
- const lock = useCallback(
19
- (locking: boolean) => {
20
- if (!cartId) return
32
+ const unlock = (params: Omit<E, 'locked' | 'cart_id' | 'method'>) => {
33
+ setRouterQuery({ cart_id: null, locked: null, method: null, ...params } as E)
34
+ return queryState
35
+ }
21
36
 
22
- cache.writeQuery({
23
- query: UseCartLockDocument,
24
- data: { cart: { __typename: 'Cart', id: cartId, locked: locking } },
25
- variables: { cartId },
26
- broadcast: true,
27
- })
28
- },
29
- [cache, cartId],
30
- )
37
+ const resulting: Omit<E, 'locked'> & { locked: boolean; justLocked: boolean } = {
38
+ ...queryState,
39
+ locked: queryState.locked === '1',
40
+ justLocked,
41
+ }
31
42
 
32
- return { locked, lock }
43
+ return [resulting, lock, unlock] as const
33
44
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/magento-cart-payment-method",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "3.0.5",
5
+ "version": "3.1.0",
6
6
  "sideEffects": false,
7
7
  "engines": {
8
8
  "node": "14.x"
@@ -15,26 +15,26 @@
15
15
  }
16
16
  },
17
17
  "devDependencies": {
18
- "@graphcommerce/eslint-config-pwa": "^4.0.5",
19
- "@graphcommerce/prettier-config-pwa": "^4.0.3",
18
+ "@graphcommerce/eslint-config-pwa": "^4.1.4",
19
+ "@graphcommerce/prettier-config-pwa": "^4.0.5",
20
20
  "@graphcommerce/typescript-config-pwa": "^4.0.2",
21
- "@playwright/test": "^1.19.2"
21
+ "@playwright/test": "^1.20.1",
22
+ "type-fest": "2.12.1"
22
23
  },
23
24
  "dependencies": {
24
- "@graphcommerce/framer-scroller": "^2.0.6",
25
- "@graphcommerce/graphql": "^3.0.4",
26
- "@graphcommerce/image": "^3.1.1",
27
- "@graphcommerce/magento-cart": "^4.1.4",
28
- "@graphcommerce/magento-store": "^4.1.2",
29
- "@graphcommerce/next-ui": "^4.2.4",
30
- "@graphcommerce/react-hook-form": "^3.0.4",
31
- "type-fest": "^2.12.0"
25
+ "@graphcommerce/framer-scroller": "2.1.5",
26
+ "@graphcommerce/graphql": "3.0.7",
27
+ "@graphcommerce/image": "3.1.4",
28
+ "@graphcommerce/magento-cart": "4.2.4",
29
+ "@graphcommerce/magento-store": "4.1.6",
30
+ "@graphcommerce/next-ui": "4.6.0",
31
+ "@graphcommerce/react-hook-form": "3.1.0"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@lingui/macro": "^3.13.2",
35
- "@mui/material": "^5.4.1",
35
+ "@mui/material": "5.5.3",
36
36
  "framer-motion": "^6.2.4",
37
- "next": "^12.0.10",
37
+ "next": "12.1.2",
38
38
  "react": "^17.0.1",
39
39
  "react-dom": "^17.0.1"
40
40
  }
@@ -0,0 +1,24 @@
1
+ import { ApolloClient, NormalizedCacheObject } from '@graphcommerce/graphql'
2
+ import { fillShippingAddressForm } from '@graphcommerce/magento-cart-shipping-address/test/fillShippingAddressForm'
3
+ import { fillCartAgreementsForm } from '@graphcommerce/magento-cart/test/fillCartAgreementsForm'
4
+ import { Page } from '@playwright/test'
5
+
6
+ export const goToPayment = async (
7
+ page: Page,
8
+ apolloClient: ApolloClient<NormalizedCacheObject>,
9
+ ) => {
10
+ await page.locator('#view-shopping-cart-button').click()
11
+ await page.locator('#cart-start-checkout').click()
12
+
13
+ const email = page.locator('input[name="email"]')
14
+ await email.click()
15
+ await email.fill('test@test.com')
16
+
17
+ await fillShippingAddressForm(page)
18
+
19
+ await page.click('button[value=flatrate-flatrate]')
20
+ await page.click('#next')
21
+
22
+ // Fill in the agreements
23
+ await fillCartAgreementsForm(page, apolloClient)
24
+ }
package/test/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './goToPayment'
2
+ export * from './selectPaymentMethod'
@@ -0,0 +1,8 @@
1
+ import { Page } from '@playwright/test'
2
+
3
+ export const selectPaymentMethod = async (page: Page, method: string) => {
4
+ const locator = page.locator(`button[value=${method}]`)
5
+ await locator.click()
6
+
7
+ return locator
8
+ }
@@ -1,7 +0,0 @@
1
- query UseCartLock($cartId: String!) {
2
- cart(cart_id: $cartId) @client {
3
- __typename
4
- id
5
- locked
6
- }
7
- }
@@ -1,7 +0,0 @@
1
- extend type Mutation {
2
- lockCart(lock: Boolean): Boolean
3
- }
4
-
5
- extend type Cart {
6
- locked: Boolean!
7
- }