@graphcommerce/mollie-magento-payment 3.0.6 → 3.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # Change Log
2
2
 
3
+ ## 3.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1379](https://github.com/graphcommerce-org/graphcommerce/pull/1379) [`7c1b45348`](https://github.com/graphcommerce-org/graphcommerce/commit/7c1b45348bcf99a0dd16d2079893680c5cb6ffc9) Thanks [@paales](https://github.com/paales)! - Support for mollie credit cards and playwright tests
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), [`6ebe9d12d`](https://github.com/graphcommerce-org/graphcommerce/commit/6ebe9d12db9fcaa2af67a475e64a08d63e232b46)]:
14
+ - @graphcommerce/next-ui@4.6.0
15
+ - @graphcommerce/magento-cart@4.2.4
16
+ - @graphcommerce/magento-cart-payment-method@3.1.0
17
+ - @graphcommerce/react-hook-form@3.1.0
18
+ - @graphcommerce/image@3.1.4
19
+ - @graphcommerce/magento-store@4.1.6
20
+
21
+ ## 3.1.1
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/graphql@3.0.7
29
+ - @graphcommerce/image@3.1.3
30
+ - @graphcommerce/magento-cart@4.2.3
31
+ - @graphcommerce/magento-cart-payment-method@3.0.7
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.1.0
37
+
38
+ ### Minor Changes
39
+
40
+ - [#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
41
+
42
+ ### Patch Changes
43
+
44
+ - [#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
45
+
46
+ - 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), [`892018809`](https://github.com/graphcommerce-org/graphcommerce/commit/8920188093d0422ec50580e408dc28ac5f93e46a)]:
47
+ - @graphcommerce/graphql@3.0.6
48
+ - @graphcommerce/next-ui@4.5.0
49
+ - @graphcommerce/image@3.1.2
50
+ - @graphcommerce/magento-cart@4.2.2
51
+ - @graphcommerce/magento-cart-payment-method@3.0.6
52
+ - @graphcommerce/magento-store@4.1.4
53
+ - @graphcommerce/react-hook-form@3.0.6
54
+
3
55
  ## 3.0.6
4
56
 
5
57
  ### Patch Changes
@@ -2,11 +2,7 @@ fragment MollieAvailablePaymentMethod on AvailablePaymentMethod
2
2
  @inject(into: ["AvailablePaymentMethod"]) {
3
3
  mollie_available_issuers {
4
4
  code
5
- image
6
5
  name
7
6
  svg
8
7
  }
9
- mollie_meta {
10
- image
11
- }
12
8
  }
@@ -1,6 +1,27 @@
1
1
  import { PaymentOptionsProps } from '@graphcommerce/magento-cart-payment-method'
2
+ import { Trans } from '@lingui/macro'
3
+ import { Box } from '@mui/material'
2
4
  import { MollieIssuerOptions } from './MollieIssuerOptions'
3
5
 
4
6
  export function MollieIdealOptions(props: PaymentOptionsProps) {
5
- return <MollieIssuerOptions label='Choose your bank' {...props} />
7
+ return (
8
+ <MollieIssuerOptions label='Choose your bank' {...props}>
9
+ <Box
10
+ component='ul'
11
+ sx={(theme) => ({ typography: 'body2', paddingLeft: theme.spacings.xs, margin: 0 })}
12
+ >
13
+ <li>
14
+ <Trans>Choose your bank, and place your order.</Trans>
15
+ </li>
16
+ <li>
17
+ <Trans>Complete the payment on your bank’s website.</Trans>
18
+ </li>
19
+ <li>
20
+ <Trans>
21
+ As soon as the payment is completed, you will automatically return to the webshop.
22
+ </Trans>
23
+ </li>
24
+ </Box>
25
+ </MollieIssuerOptions>
26
+ )
6
27
  }
@@ -6,18 +6,15 @@ import { Trans } from '@lingui/macro'
6
6
  import { Box, TextField, Typography } from '@mui/material'
7
7
  import { SetMolliePaymentMethodIssuerOnCartDocument } from './SetMolliePaymentMethodIssuerOnCart.gql'
8
8
 
9
- type MollieIssuerOptionsProps = PaymentOptionsProps & { label: string }
9
+ type MollieIssuerOptionsProps = PaymentOptionsProps & { label: string; children?: React.ReactNode }
10
10
 
11
11
  const compName = 'MollieIssuerOptions' as const
12
- const parts = ['root', 'list'] as const
13
- const { classes } = extendableComponent(compName, parts)
14
12
 
15
13
  export function MollieIssuerOptions(props: MollieIssuerOptionsProps) {
16
- const { mollie_available_issuers = [] } = props
14
+ const { mollie_available_issuers = [], children } = props
17
15
  const { code, step, Container, label, title = '' } = props
18
16
 
19
17
  const form = useFormGqlMutationCart(SetMolliePaymentMethodIssuerOnCartDocument, {
20
- mode: 'onChange',
21
18
  defaultValues: { code },
22
19
  })
23
20
 
@@ -25,7 +22,13 @@ export function MollieIssuerOptions(props: MollieIssuerOptionsProps) {
25
22
  const submit = handleSubmit(() => {})
26
23
  const valid = useFormValidFields(form, required)
27
24
 
28
- useFormPersist({ form, name: `PaymentMethodOptions_${code}` })
25
+ // Since the issuer isn't retrievable from Magento we persist this value.
26
+ useFormPersist({
27
+ form,
28
+ name: `PaymentMethodOptions_${code}`,
29
+ persist: ['issuer'],
30
+ storage: 'localStorage',
31
+ })
29
32
  useFormCompose({ form, step, submit, key: `PaymentMethodOptions_${code}` })
30
33
 
31
34
  return (
@@ -44,14 +47,15 @@ export function MollieIssuerOptions(props: MollieIssuerOptionsProps) {
44
47
  helperText={formState.isSubmitted && formState.errors.issuer?.message}
45
48
  label={label}
46
49
  required={required.issuer}
47
- {...muiRegister('issuer', { required: required.issuer })}
50
+ {...muiRegister('issuer', {
51
+ required: { value: required.issuer, message: 'Please provide an issuer' },
52
+ })}
48
53
  InputProps={{
49
54
  endAdornment: <InputCheckmark show={valid.issuer} select />,
50
55
  }}
51
56
  >
52
57
  {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */}
53
58
  <option value='' />
54
- {/* <MenuItem value='' /> */}
55
59
  {mollie_available_issuers?.map((issuer) => {
56
60
  if (!issuer?.code || !issuer.name) return null
57
61
 
@@ -60,39 +64,11 @@ export function MollieIssuerOptions(props: MollieIssuerOptionsProps) {
60
64
  {issuer.name}
61
65
  </option>
62
66
  )
63
- // return (
64
- // <MenuItem key={issuer.code} value={issuer.code}>
65
- // <ListItemIcon>
66
- // <IconSvg src={issuer.svg} alt={issuer.name} size='small' />
67
- // </ListItemIcon>
68
- // <Typography variant='inherit'>{issuer.name}</Typography>
69
- // </MenuItem>
70
- // )
71
67
  })}
72
68
  </TextField>
73
69
  </FormRow>
74
- <Box
75
- component='ul'
76
- className={classes.list}
77
- sx={(theme) => ({
78
- typography: 'body2',
79
- paddingLeft: theme.spacings.xs,
80
- margin: 0,
81
- })}
82
- >
83
- <li>
84
- <Trans>Choose your bank, and place your order.</Trans>
85
- </li>
86
- <li>
87
- <Trans>Complete the payment on your bank’s website.</Trans>
88
- </li>
89
- <li>
90
- <Trans>
91
- As soon as the payment is completed, you will automatically return to the webshop.
92
- </Trans>
93
- </li>
94
- </Box>
95
70
  </form>
71
+ {children}
96
72
  </Container>
97
73
  )
98
74
  }
@@ -20,7 +20,6 @@ export function MollieCreditCardOptions(props: PaymentOptionsProps) {
20
20
  const conf = useQuery(StoreConfigDocument)
21
21
 
22
22
  const form = useFormGqlMutationCart(SetMolliePaymentMethodTokenOnCartDocument, {
23
- mode: 'onChange',
24
23
  defaultValues: { code },
25
24
  onBeforeSubmit: async (variables) => {
26
25
  const result = await mollie?.createToken()
@@ -0,0 +1,8 @@
1
+ mutation MolliePaymentHandler($mollie_payment_token: String!) {
2
+ mollieProcessTransaction(input: { payment_token: $mollie_payment_token }) {
3
+ cart {
4
+ id
5
+ }
6
+ paymentStatus
7
+ }
8
+ }
@@ -1,34 +1,102 @@
1
+ import { PaymentStatusEnum, useMutation } from '@graphcommerce/graphql'
1
2
  import { ApolloCartErrorFullPage, useClearCurrentCartId } from '@graphcommerce/magento-cart'
3
+ import {
4
+ PaymentHandlerProps,
5
+ usePaymentMethodContext,
6
+ } from '@graphcommerce/magento-cart-payment-method'
7
+ import { ErrorSnackbar } from '@graphcommerce/next-ui'
8
+ import { Trans } from '@lingui/macro'
2
9
  import { Button, Dialog } from '@mui/material'
3
10
  import { useRouter } from 'next/router'
4
- import React from 'react'
5
- import { useMolliePaymentTokenHandler } from '../../hooks/useMolliePaymentTokenHandler'
11
+ import { useEffect } from 'react'
12
+ import { useCartLockWithToken } from '../../hooks/useCartLockWithToken'
13
+ import { MolliePaymentHandlerDocument } from './MolliePaymentHandler.gql'
14
+ import { MollieRecoverCartDocument } from './MollieRecoverCart.gql'
6
15
 
7
- export function MolliePaymentHandler() {
8
- const { error } = useMolliePaymentTokenHandler()
9
- const clear = useClearCurrentCartId()
16
+ const successStatusses: PaymentStatusEnum[] = ['AUTHORIZED', 'COMPLETED', 'PAID', 'SHIPPING']
17
+
18
+ export function MolliePaymentHandler({ code }: PaymentHandlerProps) {
10
19
  const router = useRouter()
20
+ const method = usePaymentMethodContext()
21
+ const clear = useClearCurrentCartId()
22
+
23
+ const [lockState] = useCartLockWithToken()
11
24
 
12
- const handle = () => {
13
- clear()
25
+ const isActive = method.selectedMethod?.code === code || lockState.method === code
26
+
27
+ const [handle, handleResult] = useMutation(MolliePaymentHandlerDocument)
28
+ const [recoverCart, recoverResult] = useMutation(MollieRecoverCartDocument)
29
+
30
+ const { called, error, data } = handleResult
31
+
32
+ useEffect(() => {
14
33
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
15
- router.push('/')
16
- }
34
+ ;(async () => {
35
+ if (!isActive) return
36
+ if (lockState.locked && lockState.justLocked) return
37
+
38
+ if (!lockState.mollie_payment_token) return
39
+ if (called || error) return
40
+
41
+ const result = await handle({
42
+ variables: { mollie_payment_token: lockState.mollie_payment_token },
43
+ })
44
+
45
+ const paymentStatus = result.data?.mollieProcessTransaction?.paymentStatus
46
+ let returnedCartId = result.data?.mollieProcessTransaction?.cart?.id
47
+
48
+ if (paymentStatus === 'OPEN' && lockState.cart_id) {
49
+ const res = await recoverCart({ variables: { cartId: lockState.cart_id } })
50
+ returnedCartId = res.data?.mollieRestoreCart?.cart.id
51
+ }
52
+
53
+ if (result.errors || !paymentStatus) return
54
+
55
+ if (successStatusses.includes(paymentStatus)) {
56
+ clear()
57
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
58
+ await router.push({ pathname: '/checkout/success', query: { cart_id: lockState.cart_id } })
59
+ } else if (returnedCartId) {
60
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
61
+ router.replace('/checkout/payment')
62
+ }
63
+ })()
64
+ }, [called, clear, error, handle, isActive, lockState, recoverCart, router])
65
+
66
+ const paymentStatus = data?.mollieProcessTransaction?.paymentStatus
67
+ if (paymentStatus)
68
+ return (
69
+ <ErrorSnackbar open>
70
+ <Trans>Payment failed with status: {paymentStatus}</Trans>
71
+ </ErrorSnackbar>
72
+ )
73
+
74
+ if (!error || recoverResult.loading) return null
17
75
 
18
- if (!error) return null
19
76
  return (
20
77
  <Dialog open fullWidth>
21
78
  <ApolloCartErrorFullPage
22
79
  error={error}
23
80
  disableMargin
24
81
  button={
25
- <Button variant='contained' color='primary' size='large' onClick={handle}>
26
- Reset Cart and Return to home
82
+ <Button
83
+ variant='contained'
84
+ color='primary'
85
+ size='large'
86
+ onClick={() => {
87
+ clear()
88
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
89
+ router.push('/')
90
+ }}
91
+ >
92
+ <Trans>Reset Cart and Return to home</Trans>
27
93
  </Button>
28
94
  }
29
95
  >
30
- If you’ve successfully paid your order, the order <strong>will</strong> come through, but
31
- there is a communication error with the website.
96
+ <Trans>
97
+ If you’ve successfully paid your order, the order <strong>will</strong> come through, but
98
+ there is a communication error with the website.
99
+ </Trans>
32
100
  </ApolloCartErrorFullPage>
33
101
  </Dialog>
34
102
  )
@@ -0,0 +1,7 @@
1
+ mutation MollieRecoverCart($cartId: String!) {
2
+ mollieRestoreCart(input: { cart_id: $cartId }) {
3
+ cart {
4
+ id
5
+ }
6
+ }
7
+ }
@@ -1,16 +1,7 @@
1
- import { Image } from '@graphcommerce/image'
2
1
  import { PaymentToggleProps } from '@graphcommerce/magento-cart-payment-method'
3
2
 
4
3
  export function PaymentToggle(props: PaymentToggleProps) {
5
- const { mollie_meta, title } = props
4
+ const { title } = props
6
5
 
7
- return (
8
- <>
9
- {mollie_meta?.image && (
10
- <Image layout='fixed' src={mollie_meta?.image} unoptimized alt={title} />
11
- )}
12
- &nbsp;
13
- {title}
14
- </>
15
- )
6
+ return <>{title}</>
16
7
  }
@@ -1,53 +1,54 @@
1
+ import { useCurrentCartId, useFormGqlMutationCart } from '@graphcommerce/magento-cart'
1
2
  import {
2
- useClearCurrentCartId,
3
- useCurrentCartId,
4
- useFormGqlMutationCart,
5
- } from '@graphcommerce/magento-cart'
6
- import { PaymentPlaceOrderProps, useCartLock } from '@graphcommerce/magento-cart-payment-method'
3
+ PaymentPlaceOrderProps,
4
+ usePaymentMethodContext,
5
+ } from '@graphcommerce/magento-cart-payment-method'
7
6
  import { useFormCompose } from '@graphcommerce/react-hook-form'
8
7
  import { useRouter } from 'next/router'
9
8
  import { useEffect } from 'react'
9
+ import { useCartLockWithToken } from '../../hooks/useCartLockWithToken'
10
10
  import { MolliePlaceOrderDocument } from './MolliePlaceOrder.gql'
11
11
 
12
12
  export function MolliePlaceOrder(props: PaymentPlaceOrderProps) {
13
13
  const { step, code } = props
14
- const router = useRouter()
14
+ const { push } = useRouter()
15
15
  const cartId = useCurrentCartId()
16
- const clear = useClearCurrentCartId()
17
- const { lock } = useCartLock()
16
+ const [, lock] = useCartLockWithToken()
17
+ const { selectedMethod } = usePaymentMethodContext()
18
18
 
19
- const form = useFormGqlMutationCart(MolliePlaceOrderDocument, { mode: 'onChange' })
19
+ const form = useFormGqlMutationCart(MolliePlaceOrderDocument)
20
20
 
21
21
  const { handleSubmit, data, error, register, setValue } = form
22
22
 
23
23
  useEffect(() => {
24
24
  const current = new URL(window.location.href.replace(window.location.hash, ''))
25
- current.searchParams.append('payment_token', 'PAYMENT_TOKEN')
25
+ // current.searchParams.append('locked', '1')
26
+ current.searchParams.set('cart_id', cartId ?? '')
27
+ current.searchParams.set('mollie_payment_token', 'PAYMENT_TOKEN')
28
+ current.searchParams.set('method', selectedMethod?.code ?? '')
29
+ current.searchParams.set('locked', '1')
26
30
  const replaced = current.toString().replace('PAYMENT_TOKEN', '{{payment_token}}')
27
31
  setValue('returnUrl', replaced)
28
- }, [setValue])
32
+ }, [cartId, selectedMethod?.code, setValue])
29
33
 
30
34
  const submit = handleSubmit(() => {})
31
35
 
32
36
  useEffect(() => {
33
37
  if (!data?.placeOrder?.order || error || !cartId) return
34
38
  const redirectUrl = data?.placeOrder?.order.mollie_redirect_url
35
- const molliePaymentToken = data?.placeOrder?.order.mollie_payment_token
39
+ const mollie_payment_token = data?.placeOrder?.order.mollie_payment_token
36
40
 
37
- if (redirectUrl) {
38
- lock(true)
41
+ // When redirecting to the payment gateway
42
+ if (redirectUrl && mollie_payment_token) {
43
+ lock({
44
+ mollie_payment_token,
45
+ method: selectedMethod?.code ?? null,
46
+ })
39
47
 
40
- setTimeout(() => {
41
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
42
- router.push(redirectUrl)
43
- }, 500)
44
- }
45
- if (!redirectUrl && molliePaymentToken) {
46
- clear()
47
48
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
48
- router.push({ pathname: '/checkout/success', query: { cartId, molliePaymentToken } })
49
+ push(redirectUrl)
49
50
  }
50
- }, [cartId, clear, data?.placeOrder?.order, error, lock, router])
51
+ }, [cartId, data?.placeOrder?.order, error, lock, push, selectedMethod?.code])
51
52
 
52
53
  useFormCompose({ form, step, submit, key: `PaymentMethodPlaceOrder_${code}` })
53
54
 
@@ -0,0 +1,5 @@
1
+ import { CartLockState, useCartLock } from '@graphcommerce/magento-cart-payment-method'
2
+
3
+ type MollieLockState = CartLockState & { mollie_payment_token: string | null }
4
+
5
+ export const useCartLockWithToken = () => useCartLock<MollieLockState>()
package/index.ts CHANGED
@@ -1,3 +1,2 @@
1
- export * from './hooks'
2
1
  export * as mollie_methods from './methods'
3
2
  export * from './components/MolliePaymentToggle/MolliePaymentToggle'
@@ -1,10 +1,11 @@
1
- import { PaymentModule } from '@graphcommerce/magento-cart-payment-method'
2
- import { MollieCreditCardOptions } from '../components/MollieOptionsToken/MollieCreditCardOptions'
1
+ import { PaymentMethodOptionsNoop, PaymentModule } from '@graphcommerce/magento-cart-payment-method'
2
+ import { MolliePaymentHandler } from '../components/MolliePaymentHandler/MolliePaymentHandler'
3
3
  import { PaymentToggle } from '../components/MolliePaymentToggle/MolliePaymentToggle'
4
4
  import { MolliePlaceOrder } from '../components/MolliePlaceOrder/MolliePlaceOrder'
5
5
 
6
6
  export const mollie_methods_creditcard: PaymentModule = {
7
7
  PaymentToggle,
8
- PaymentOptions: MollieCreditCardOptions,
8
+ PaymentOptions: PaymentMethodOptionsNoop,
9
9
  PaymentPlaceOrder: MolliePlaceOrder,
10
+ PaymentHandler: MolliePaymentHandler,
10
11
  }
@@ -1,9 +1,7 @@
1
1
  import { PaymentMethodOptionsNoop, PaymentModule } from '@graphcommerce/magento-cart-payment-method'
2
- import { PaymentToggle } from '../components/MolliePaymentToggle/MolliePaymentToggle'
3
2
  import { MolliePlaceOrder } from '../components/MolliePlaceOrder/MolliePlaceOrder'
4
3
 
5
4
  export const mollie_methods_klarnapaylater: PaymentModule = {
6
- PaymentToggle,
7
5
  PaymentOptions: PaymentMethodOptionsNoop,
8
6
  PaymentPlaceOrder: MolliePlaceOrder,
9
7
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/mollie-magento-payment",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "3.0.6",
5
+ "version": "3.2.0",
6
6
  "sideEffects": false,
7
7
  "engines": {
8
8
  "node": "14.x"
@@ -15,29 +15,29 @@
15
15
  }
16
16
  },
17
17
  "devDependencies": {
18
- "@graphcommerce/eslint-config-pwa": "^4.1.2",
19
- "@graphcommerce/magento-cart-shipping-address": "^3.0.4",
20
- "@graphcommerce/magento-product": "^4.1.2",
21
- "@graphcommerce/magento-product-configurable": "^4.0.6",
22
- "@graphcommerce/prettier-config-pwa": "^4.0.3",
18
+ "@graphcommerce/eslint-config-pwa": "^4.1.4",
19
+ "@graphcommerce/magento-cart-shipping-address": "3.0.7",
20
+ "@graphcommerce/magento-product": "4.1.5",
21
+ "@graphcommerce/magento-product-configurable": "4.0.9",
22
+ "@graphcommerce/prettier-config-pwa": "^4.0.5",
23
23
  "@graphcommerce/typescript-config-pwa": "^4.0.2",
24
- "@playwright/test": "^1.19.2"
24
+ "@playwright/test": "^1.20.1",
25
+ "type-fest": "2.12.1"
25
26
  },
26
27
  "dependencies": {
27
- "@graphcommerce/graphql": "^3.0.4",
28
- "@graphcommerce/image": "^3.1.1",
29
- "@graphcommerce/magento-cart": "^4.2.1",
30
- "@graphcommerce/magento-cart-payment-method": "^3.0.5",
31
- "@graphcommerce/magento-store": "^4.1.2",
32
- "@graphcommerce/next-ui": "^4.4.0",
33
- "@graphcommerce/react-hook-form": "^3.0.4",
34
- "type-fest": "^2.12.0"
28
+ "@graphcommerce/graphql": "3.0.7",
29
+ "@graphcommerce/image": "3.1.4",
30
+ "@graphcommerce/magento-cart": "4.2.4",
31
+ "@graphcommerce/magento-cart-payment-method": "3.1.0",
32
+ "@graphcommerce/magento-store": "4.1.6",
33
+ "@graphcommerce/next-ui": "4.6.0",
34
+ "@graphcommerce/react-hook-form": "3.1.0"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@lingui/macro": "^3.13.2",
38
- "@mui/material": "^5.4.1",
38
+ "@mui/material": "5.5.3",
39
39
  "framer-motion": "^6.2.4",
40
- "next": "^12.0.10",
40
+ "next": "12.1.2",
41
41
  "react": "^17.0.1",
42
42
  "react-dom": "^17.0.1"
43
43
  }
@@ -0,0 +1,55 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import { selectPaymentMethod } from '@graphcommerce/magento-cart-payment-method/test'
3
+ import { goToPayment } from '@graphcommerce/magento-cart-payment-method/test/goToPayment'
4
+ import { addConfigurableProductToCart } from '@graphcommerce/magento-product-configurable/test/addConfigurableProductToCart'
5
+ import { test } from '@graphcommerce/magento-product/test/productURL.fixture'
6
+ import { expect } from '@playwright/test'
7
+ import { placeOrderOffsite } from './utils'
8
+
9
+ const method = 'mollie_methods_creditcard'
10
+
11
+ test.describe.only('mollie creditcard place order', () => {
12
+ test('Should be able to place an order and return on the success page', async ({
13
+ page,
14
+ productURL,
15
+ apolloClient,
16
+ }) => {
17
+ await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
18
+ await goToPayment(page, apolloClient)
19
+ await selectPaymentMethod(page, method)
20
+
21
+ await placeOrderOffsite(page, 'paid')
22
+ expect(await page.locator('#back-to-home').innerText()).toBeDefined()
23
+ })
24
+
25
+ test('Should be possible to cancel an order and then place the order', async ({
26
+ page,
27
+ productURL,
28
+ apolloClient,
29
+ }) => {
30
+ await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
31
+ await goToPayment(page, apolloClient)
32
+ await selectPaymentMethod(page, method)
33
+ await placeOrderOffsite(page, 'canceled')
34
+ await placeOrderOffsite(page, 'paid')
35
+ expect(await page.locator('#back-to-home').innerText()).toBeDefined()
36
+ })
37
+
38
+ test.only('Should be possible to press back on the payment gateway and then place the order', async ({
39
+ page,
40
+ productURL,
41
+ apolloClient,
42
+ }) => {
43
+ await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
44
+ await goToPayment(page, apolloClient)
45
+ await selectPaymentMethod(page, method)
46
+ await Promise.all([page.waitForNavigation(), page.click('#place-order')])
47
+
48
+ await page.goBack()
49
+
50
+ expect(await page.locator('text=Payment failed with status: OPEN').innerText()).toBeDefined()
51
+
52
+ await placeOrderOffsite(page, 'paid')
53
+ expect(await page.locator('#back-to-home').innerText()).toBeDefined()
54
+ })
55
+ })
@@ -0,0 +1,91 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import { goToPayment, selectPaymentMethod } from '@graphcommerce/magento-cart-payment-method/test'
3
+ import { addConfigurableProductToCart } from '@graphcommerce/magento-product-configurable/test/addConfigurableProductToCart'
4
+ import { test } from '@graphcommerce/magento-product/test/productURL.fixture'
5
+ import { expect } from '@playwright/test'
6
+ import { handleOffsitePayment, placeOrderOffsite, selectIssuer } from './utils'
7
+
8
+ const method = 'mollie_methods_ideal'
9
+
10
+ test.describe(method, () => {
11
+ test('Should be able to place an order and return on the success page', async ({
12
+ page,
13
+ productURL,
14
+ apolloClient,
15
+ }) => {
16
+ await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
17
+ await goToPayment(page, apolloClient)
18
+ await selectPaymentMethod(page, method)
19
+
20
+ await selectIssuer(page, 'ideal_RABONL2U')
21
+ await placeOrderOffsite(page, 'paid')
22
+ expect(await page.locator('#back-to-home').innerText()).toBeDefined()
23
+ })
24
+
25
+ test("Should not allow submission when all fields aren't filled yet", async ({
26
+ page,
27
+ productURL,
28
+ apolloClient,
29
+ }) => {
30
+ await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
31
+ await goToPayment(page, apolloClient)
32
+ await selectPaymentMethod(page, method)
33
+ await page.click('#place-order')
34
+
35
+ await expect(page.locator('select[name="issuer"]')).toHaveAttribute('aria-invalid', 'true')
36
+ })
37
+
38
+ test('Should be possible to cancel an order and then place the order', async ({
39
+ page,
40
+ productURL,
41
+ apolloClient,
42
+ }) => {
43
+ await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
44
+ await goToPayment(page, apolloClient)
45
+ await selectPaymentMethod(page, method)
46
+ await selectIssuer(page, 'ideal_RABONL2U')
47
+ await placeOrderOffsite(page, 'canceled')
48
+ await placeOrderOffsite(page, 'paid')
49
+ expect(await page.locator('#back-to-home').innerText()).toBeDefined()
50
+ })
51
+
52
+ test('Should be possible to press back on the payment gateway and then place the order', async ({
53
+ page,
54
+ productURL,
55
+ apolloClient,
56
+ }) => {
57
+ await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
58
+ await goToPayment(page, apolloClient)
59
+ await selectPaymentMethod(page, method)
60
+ await selectIssuer(page, 'ideal_RABONL2U')
61
+ await Promise.all([page.waitForNavigation(), page.click('#place-order')])
62
+
63
+ await page.goBack()
64
+
65
+ expect(await page.locator('text=Payment failed with status: OPEN').innerText()).toBeDefined()
66
+
67
+ await placeOrderOffsite(page, 'paid')
68
+ expect(await page.locator('#back-to-home').innerText()).toBeDefined()
69
+ })
70
+
71
+ test('Should be possible to place the order even though there is a completely separate session', async ({
72
+ page,
73
+ productURL,
74
+ apolloClient,
75
+ context,
76
+ }) => {
77
+ await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
78
+ await goToPayment(page, apolloClient)
79
+ await selectPaymentMethod(page, method)
80
+ await selectIssuer(page, 'ideal_RABONL2U')
81
+ await Promise.all([page.waitForNavigation(), page.click('#place-order')])
82
+
83
+ const page2 = await context.newPage()
84
+ await Promise.all([page2.waitForNavigation(), page2.goto(page.url())])
85
+ await page.close()
86
+
87
+ await handleOffsitePayment(page2, 'paid')
88
+
89
+ expect(await page.locator('#back-to-home').innerText()).toBeDefined()
90
+ })
91
+ })
package/test/utils.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { waitForGraphQlResponse } from '@graphcommerce/graphql/test/apolloClient.fixture'
2
+ import { Page, expect } from '@playwright/test'
3
+ import { MolliePaymentHandlerDocument } from '../components/MolliePaymentHandler/MolliePaymentHandler.gql'
4
+
5
+ type Statuses = 'paid' | 'failed' | 'canceled' | 'open' | 'expired'
6
+
7
+ export const handleOffsitePayment = async (page: Page, status: Statuses) => {
8
+ await page.click(`input[name="final_state"][value=${status}]`)
9
+
10
+ await Promise.all([page.waitForNavigation(), page.click('.footer button')])
11
+
12
+ const result = await waitForGraphQlResponse(page, MolliePaymentHandlerDocument)
13
+ expect(result.errors).toBeUndefined()
14
+ expect(result.data?.mollieProcessTransaction?.paymentStatus).toBe(status.toUpperCase())
15
+ }
16
+
17
+ export const placeOrderOffsite = async (page: Page, status: Statuses) => {
18
+ await Promise.all([page.waitForNavigation(), page.click('#place-order')])
19
+
20
+ await handleOffsitePayment(page, status)
21
+ }
22
+
23
+ export const selectIssuer = async (page: Page, issuer: string) => {
24
+ await page.locator('select[name="issuer"]').selectOption(issuer)
25
+ }
@@ -1,6 +0,0 @@
1
- fragment MollieSelectedPaymentMethod on SelectedPaymentMethod
2
- @inject(into: ["SelectedPaymentMethod"]) {
3
- mollie_meta {
4
- image
5
- }
6
- }
@@ -1,101 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
- import { waitForGraphQlResponse } from '@graphcommerce/graphql/_playwright/apolloClient.fixture'
3
- import { fillShippingAddressForm } from '@graphcommerce/magento-cart-shipping-address/_playwright/fillShippingAddressForm'
4
- import { fillCartAgreementsForm } from '@graphcommerce/magento-cart/_playwright/fillCartAgreementsForm'
5
- import { addConfigurableProductToCart } from '@graphcommerce/magento-product-configurable/_playwright/addConfigurableProductToCart'
6
- import { test } from '@graphcommerce/magento-product/_playwright/productURL.fixture'
7
- import { expect, Page } from '@playwright/test'
8
- import { MolliePlaceOrderDocument } from '../components/MolliePlaceOrder/MolliePlaceOrder.gql'
9
- import { UseMolliePaymentTokenHandlerDocument } from '../hooks/UseMolliePaymentTokenHandler.gql'
10
-
11
- const goToPayment = async (page: Page) => {
12
- await page.click('a:has-text("View shopping cart")')
13
-
14
- await page.click('a[href="/checkout"]:last-of-type')
15
-
16
- await page.click('input[name="email"]')
17
- await page.fill('input[name="email"]', 'test@test.com')
18
-
19
- await fillShippingAddressForm(page)
20
-
21
- await page.click('button[value=flatrate-flatrate]')
22
- await page.click('button:has-text("Next")')
23
-
24
- // Select the iDEAL option
25
- await page.click('button[value=mollie_methods_ideal___]')
26
-
27
- // Select Rabobank
28
- await page.selectOption('select[name="issuer"]', 'ideal_RABONL2U')
29
-
30
- await fillCartAgreementsForm(page)
31
- }
32
-
33
- test.describe('mollie ideal place order', () => {
34
- test('CANCELED', async ({ page, productURL }) => {
35
- test.fixme()
36
- await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
37
- await goToPayment(page)
38
-
39
- // Place the order and wait for the the redirect to the new page.
40
- await Promise.all([page.waitForNavigation(), page.click('button[name="placeOrder"]')])
41
-
42
- // Let the order fail
43
- await page.click('input[name="final_state"][value=canceled]')
44
-
45
- // Return to the website.
46
- await Promise.all([page.waitForNavigation(), page.click('.footer button')])
47
-
48
- const result = await waitForGraphQlResponse(page, UseMolliePaymentTokenHandlerDocument)
49
- expect(result.errors).toBeUndefined()
50
- expect(result.data?.mollieProcessTransaction?.paymentStatus).toBe('CANCELED')
51
-
52
- // Select Rabobank
53
- await page.selectOption('select[name="issuer"]', 'ideal_RABONL2U')
54
-
55
- // Place the order and wait for the the redirect to the new page.
56
- await Promise.all([page.waitForNavigation(), page.click('button[name="placeOrder"]')])
57
-
58
- const placeOrder = await waitForGraphQlResponse(page, MolliePlaceOrderDocument)
59
- expect(placeOrder.errors).toBeUndefined()
60
- })
61
-
62
- test('PAID', async ({ page, productURL }) => {
63
- await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
64
- await goToPayment(page)
65
-
66
- await page.pause()
67
-
68
- // Place the order and wait for the the redirect to the new page.
69
- await Promise.all([page.waitForNavigation(), page.click('button[name="placeOrder"]')])
70
-
71
- // Let the order fail
72
- await page.click('input[name="final_state"][value=paid]')
73
-
74
- // Return to the website.
75
- await Promise.all([page.waitForNavigation(), page.click('.footer button')])
76
-
77
- const result = await waitForGraphQlResponse(page, UseMolliePaymentTokenHandlerDocument)
78
- expect(result.errors).toBeUndefined()
79
- expect(result.data?.mollieProcessTransaction?.paymentStatus).toBe('PAID')
80
-
81
- await page.waitForNavigation()
82
- })
83
- })
84
-
85
- // test('place order failed', async ({ page, productURL }) => {
86
- // await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
87
- // await goToPayment(page)
88
-
89
- // // Place the order and wait for the the redirect to the new page.
90
- // await Promise.all([page.waitForNavigation(), page.click('button[name="placeOrder"]')])
91
-
92
- // // Let the order fail
93
- // await page.click('input[name="final_state"][value=failed]')
94
-
95
- // // Return to the website.
96
- // await Promise.all([page.waitForNavigation(), page.click('.footer button')])
97
-
98
- // const result = await waitForGraphQlResponse(page, UseMolliePaymentTokenHandlerDocument)
99
- // expect(result.errors).toBeUndefined()
100
- // expect(result.data?.mollieProcessTransaction?.paymentStatus).toBeDefined()
101
- // })
@@ -1,8 +0,0 @@
1
- mutation UseMolliePaymentTokenHandler($paymentToken: String!) {
2
- mollieProcessTransaction(input: { payment_token: $paymentToken }) {
3
- cart {
4
- id
5
- }
6
- paymentStatus
7
- }
8
- }
package/hooks/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from './useMolliePaymentTokenHandler'
@@ -1,65 +0,0 @@
1
- import { useMutation , PaymentStatusEnum } from '@graphcommerce/graphql'
2
- import { useClearCurrentCartId, useCurrentCartId } from '@graphcommerce/magento-cart'
3
- import { useCartLock, usePaymentMethodContext } from '@graphcommerce/magento-cart-payment-method'
4
- import { useRouter } from 'next/router'
5
- import { useEffect } from 'react'
6
- import { UseMolliePaymentTokenHandlerDocument } from './UseMolliePaymentTokenHandler.gql'
7
-
8
- const successStatusses: PaymentStatusEnum[] = ['AUTHORIZED', 'COMPLETED', 'PAID', 'SHIPPING']
9
-
10
- export function useMolliePaymentTokenHandler() {
11
- const router = useRouter()
12
- const method = usePaymentMethodContext()
13
- const cartId = useCurrentCartId()
14
- const clear = useClearCurrentCartId()
15
-
16
- const isMollie = method.selectedMethod?.code.startsWith('mollie_methods')
17
-
18
- const paymentToken = router.query.payment_token as string | undefined
19
- // const orderHash = router.query.orderHash
20
- const [handlePaymentToken, res] = useMutation(UseMolliePaymentTokenHandlerDocument, {
21
- errorPolicy: 'all',
22
- })
23
- const { lock, locked } = useCartLock()
24
-
25
- useEffect(() => {
26
- if (locked && !paymentToken && isMollie) lock(false)
27
- }, [isMollie, lock, locked, paymentToken])
28
-
29
- useEffect(() => {
30
- if (!paymentToken || res.called || res.error)
31
- return // eslint-disable-next-line @typescript-eslint/no-floating-promises
32
- ;(async () => {
33
- const result = await handlePaymentToken({ variables: { paymentToken } })
34
-
35
- const paymentStatus = result.data?.mollieProcessTransaction?.paymentStatus
36
- const returnedCartId = result.data?.mollieProcessTransaction?.cart?.id
37
-
38
- if (result.errors || !paymentStatus) return
39
-
40
- if (successStatusses.includes(paymentStatus)) {
41
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
42
- await router.push({ pathname: '/checkout/success', query: { cartId } })
43
- clear()
44
- } else if (returnedCartId) {
45
- lock(false)
46
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
47
- router.replace('/checkout/payment')
48
- } else {
49
- throw Error('Mollie backend error occured')
50
- }
51
- })()
52
- }, [
53
- cartId,
54
- clear,
55
- handlePaymentToken,
56
- isMollie,
57
- lock,
58
- paymentToken,
59
- res.called,
60
- res.error,
61
- router,
62
- ])
63
-
64
- return res
65
- }