@graphcommerce/magento-payment-multisafepay 4.14.0-canary.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ # @graphcommerce/magento-payment-multisafepay
2
+
3
+ ## 4.14.0-canary.3
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1729](https://github.com/graphcommerce-org/graphcommerce/pull/1729) [`366b05a7d`](https://github.com/graphcommerce-org/graphcommerce/commit/366b05a7da174a8a7c665b44e11422d8c873e4ed) - MultiSafePay Payment gateway support ([@paales](https://github.com/paales))
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # MultiSafePay
2
+
3
+ ## Requirements
4
+
5
+ - MultiSafePay Magento module: https://github.com/MultiSafepay/magento2
6
+ - MultiSafePay Magento GraphQL module:
7
+ https://github.com/MultiSafepay/magento2-graphql
8
+
9
+ ## Installation
10
+
11
+ 1. Find current version of your `@graphcommerce/magento-cart-payment-method` in
12
+ your package.json.
13
+ 2. `yarn add @graphcommerce/magento-payment-multisafepay@1.2.3` (replace 1.2.3
14
+ with the version of the step above)
15
+ 3. Sales -> MultiSafePay -> General Settings -> Advanced Settings (this should
16
+ be configured per storeview)
17
+ - _Use custom return urls for PWA storefront integration_: Yes
18
+ - _Custom redirect url after cancelling the payment_:
19
+ `https://$domain/$locale/checkout/payment?locked=1&success=0&cart_id={{quote.masked_id}}&method={{payment.code}}`
20
+ - _Custom "Success page" url_:
21
+ `https://$domain/$locale/checkout/payment?locked=1&success=1&cart_id={{quote.masked_id}}&method={{payment.code}}`
@@ -0,0 +1,29 @@
1
+ import { Image } from '@graphcommerce/image'
2
+ import { PaymentMethodActionCardProps } from '@graphcommerce/magento-cart-payment-method'
3
+ import { ActionCard, responsiveVal as rv } from '@graphcommerce/next-ui'
4
+ import { Trans } from '@lingui/react'
5
+
6
+ export function MSPPaymentActionCard(props: PaymentMethodActionCardProps) {
7
+ const { child, multisafepay_additional_data } = props
8
+ const iconSize = rv(30, 44)
9
+
10
+ return (
11
+ <ActionCard
12
+ {...props}
13
+ details={child === 'ideal' && <Trans id='Pay with iDEAL' />}
14
+ image={
15
+ multisafepay_additional_data?.image && (
16
+ <Image
17
+ layout='fixed'
18
+ width={100}
19
+ height={100}
20
+ sx={{ width: iconSize, height: iconSize, objectFit: 'contain' }}
21
+ sizes={iconSize}
22
+ unoptimized
23
+ src={multisafepay_additional_data?.image}
24
+ />
25
+ )
26
+ }
27
+ />
28
+ )
29
+ }
@@ -0,0 +1,3 @@
1
+ mutation MSPPaymentHandler($cartId: String!) {
2
+ getPaymentMeta(input: { cart_id: $cartId })
3
+ }
@@ -0,0 +1,57 @@
1
+ import { useMutation } from '@graphcommerce/graphql'
2
+ import { useAssignCurrentCartId, useClearCurrentCartId } from '@graphcommerce/magento-cart'
3
+ import { PaymentHandlerProps } from '@graphcommerce/magento-cart-payment-method'
4
+ import { ErrorSnackbar } from '@graphcommerce/next-ui'
5
+ import { Trans } from '@lingui/react'
6
+ import { useRouter } from 'next/router'
7
+ import { useEffect } from 'react'
8
+ import { useMSPCartLock } from '../../hooks/useMSPCartLock'
9
+ import { MSPPaymentHandlerDocument } from './MSPPaymentHandler.gql'
10
+
11
+ export const MSPPaymentHandler = (props: PaymentHandlerProps) => {
12
+ const { code } = props
13
+ const { push } = useRouter()
14
+ const [lockStatus, , unlock] = useMSPCartLock()
15
+ const assignCurrentCartId = useAssignCurrentCartId()
16
+ const clearCurrentCartId = useClearCurrentCartId()
17
+
18
+ const [restore, { error }] = useMutation(MSPPaymentHandlerDocument)
19
+
20
+ const { justLocked, success, cart_id: cartId, locked, method } = lockStatus
21
+
22
+ const canProceed = !(justLocked || !locked || !cartId || method !== code)
23
+
24
+ // When the payment has failed we restore the current cart
25
+ const shouldRestore = canProceed && success !== '1'
26
+ useEffect(() => {
27
+ if (!shouldRestore) return
28
+
29
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
30
+ restore({ variables: { cartId } }).then(({ data }) => {
31
+ if (!data?.getPaymentMeta) return
32
+
33
+ assignCurrentCartId(data.getPaymentMeta)
34
+ unlock({ success: null })
35
+ })
36
+ }, [assignCurrentCartId, cartId, restore, shouldRestore, unlock])
37
+
38
+ // If successfull we clear it's cart and redirect to the success page.
39
+ const shouldRedirect = canProceed && success === '1'
40
+ useEffect(() => {
41
+ if (!shouldRedirect) return
42
+
43
+ // We're done with the current cart, we clear the current cartId.
44
+ clearCurrentCartId()
45
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
46
+ push({ pathname: '/checkout/success', query: { cart_id: cartId } })
47
+ }, [cartId, clearCurrentCartId, push, shouldRedirect])
48
+
49
+ if (error) {
50
+ return (
51
+ <ErrorSnackbar open>
52
+ <Trans id='Payment has not completed succesfully, please try again.' />
53
+ </ErrorSnackbar>
54
+ )
55
+ }
56
+ return null
57
+ }
@@ -0,0 +1,18 @@
1
+ mutation MSPPaymentOptionsAndPlaceOrder($cartId: String!, $paymentMethod: PaymentMethodInput!) {
2
+ setPaymentMethodOnCart(input: { cart_id: $cartId, payment_method: $paymentMethod }) {
3
+ cart {
4
+ selected_payment_method {
5
+ ...SelectedPaymentMethod
6
+ }
7
+ }
8
+ }
9
+ placeOrder(input: { cart_id: $cartId }) {
10
+ order {
11
+ order_number
12
+ multisafepay_payment_url {
13
+ error
14
+ payment_url
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,88 @@
1
+ import {
2
+ useFormCompose,
3
+ useFormPersist,
4
+ useFormValidFields,
5
+ SelectElement,
6
+ } from '@graphcommerce/ecommerce-ui'
7
+ import { useFormGqlMutationCart } from '@graphcommerce/magento-cart'
8
+ import {
9
+ PaymentOptionsProps,
10
+ usePaymentMethodContext,
11
+ } from '@graphcommerce/magento-cart-payment-method'
12
+ import { filterNonNullableKeys, FormRow } from '@graphcommerce/next-ui'
13
+ import { useRouter } from 'next/router'
14
+ import { useMSPCartLock } from '../../hooks/useMSPCartLock'
15
+ import {
16
+ MSPPaymentOptionsAndPlaceOrderMutation,
17
+ MSPPaymentOptionsAndPlaceOrderMutationVariables,
18
+ MSPPaymentOptionsAndPlaceOrderDocument,
19
+ } from './MSPPaymentOptionsAndPlaceOrder.gql'
20
+
21
+ /** It sets the selected payment method on the cart. */
22
+ export function MSPPaymentOptionsAndPlaceOrder(props: PaymentOptionsProps) {
23
+ const { code, step, multisafepay_available_issuers } = props
24
+
25
+ const [, lock] = useMSPCartLock()
26
+ const { selectedMethod } = usePaymentMethodContext()
27
+ const { push } = useRouter()
28
+
29
+ /**
30
+ * In the this folder you'll also find a PaymentMethodOptionsNoop.graphql document that is
31
+ * imported here and used as the basis for the form below.
32
+ */
33
+ const form = useFormGqlMutationCart<
34
+ MSPPaymentOptionsAndPlaceOrderMutation,
35
+ MSPPaymentOptionsAndPlaceOrderMutationVariables
36
+ >(MSPPaymentOptionsAndPlaceOrderDocument, {
37
+ onComplete: async (result) => {
38
+ const url = result.data?.placeOrder?.order.multisafepay_payment_url
39
+
40
+ if (result.errors || !selectedMethod?.code || url?.error || !url?.payment_url) return
41
+
42
+ lock({ method: selectedMethod.code })
43
+ await push(url.payment_url)
44
+ },
45
+ })
46
+
47
+ const { handleSubmit, muiRegister, formState, required, register, control } = form
48
+
49
+ const submit = handleSubmit(() => {})
50
+
51
+ const key = `PaymentMethodOptions_${code}`
52
+ useFormPersist({
53
+ form,
54
+ name: key,
55
+ persist: ['paymentMethod.multisafepay_ideal.issuer_id'],
56
+ storage: 'localStorage',
57
+ })
58
+
59
+ const valid = useFormValidFields(form, required)
60
+
61
+ /** To use an external Pay button we register the current form to be handled there as well. */
62
+ useFormCompose({ form, step, submit, key })
63
+
64
+ const issuers = filterNonNullableKeys(multisafepay_available_issuers, ['code', 'description'])
65
+
66
+ /** This is the form that the user can fill in. In this case we don't wat the user to fill in anything. */
67
+ return (
68
+ <form key={key} onSubmit={submit} noValidate>
69
+ <input type='hidden' value={code} {...register('paymentMethod.code')} />
70
+
71
+ {code === 'multisafepay_ideal' && issuers.length && (
72
+ <FormRow>
73
+ <SelectElement
74
+ control={control}
75
+ name='paymentMethod.multisafepay_ideal.issuer_id'
76
+ required
77
+ validation={{ required: { message: 'Please provide an issuer', value: true } }}
78
+ variant='outlined'
79
+ color='secondary'
80
+ select
81
+ label='Select your bank'
82
+ options={issuers.map(({ code: id, description: label }) => ({ id, label }))}
83
+ />
84
+ </FormRow>
85
+ )}
86
+ </form>
87
+ )
88
+ }
@@ -0,0 +1,11 @@
1
+ fragment MSPAvailablePaymentMethod on AvailablePaymentMethod
2
+ @inject(into: ["AvailablePaymentMethod"]) {
3
+ multisafepay_additional_data {
4
+ image
5
+ is_preselected
6
+ }
7
+ multisafepay_available_issuers {
8
+ code
9
+ description
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ import { CartLockState, useCartLock } from '@graphcommerce/magento-cart-payment-method'
2
+
3
+ type MSPLockState = CartLockState & {
4
+ success?: string | null
5
+ }
6
+
7
+ /**
8
+ * The cart lock situation is a bit odd since are unable to actually influence the return URL we
9
+ * can't safely remember the cart ID.
10
+ *
11
+ * This is a potential bug because when the customer is returning from an icognito session, the cart
12
+ * ID is not available.
13
+ */
14
+ export const useMSPCartLock = () => useCartLock<MSPLockState>()
package/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './methods'
package/methods.ts ADDED
@@ -0,0 +1,100 @@
1
+ import { PaymentModule } from '@graphcommerce/magento-cart-payment-method'
2
+ import { MSPPaymentActionCard } from './components/MSPPaymentActionCard/MSPPaymentActionCard'
3
+ import { MSPPaymentHandler } from './components/MSPPaymentHandler/MSPPaymentHandler'
4
+ import { MSPPaymentOptionsAndPlaceOrder } from './components/MSPPaymentOptionsAndPlaceOrder/MSPPaymentOptionsAndPlaceOrder'
5
+
6
+ const mspModule: PaymentModule = {
7
+ PaymentOptions: MSPPaymentOptionsAndPlaceOrder,
8
+ PaymentActionCard: MSPPaymentActionCard,
9
+ PaymentHandler: MSPPaymentHandler,
10
+ PaymentPlaceOrder: () => null,
11
+ }
12
+
13
+ /**
14
+ * List is extracted from https://github.com/MultiSafepay/magento2-core/blob/master/etc/config.xml
15
+ *
16
+ * All payment methods are enabled except the ones that require additional imputs:
17
+ *
18
+ * - `multisafepay_afterpay`
19
+ * - `multisafepay_directbanktransfer`
20
+ * - `multisafepay_directdebit`
21
+ * - `multisafepay_einvoicing`
22
+ * - `multisafepay_in3`
23
+ * - `multisafepay_payafter`
24
+ *
25
+ * Excluded taken from:
26
+ * https://github.com/MultiSafepay/magento2-graphql/blob/53db83f1b3382c8e11c22a1fa2170b8b17ecb7d7/etc/schema.graphqls#L36-L41
27
+ */
28
+ export const multisafepay: Record<string, PaymentModule> = {
29
+ multisafepay_ideal: mspModule,
30
+ multisafepay_ideal_recurring: mspModule,
31
+ multisafepay_ideal_vault: mspModule,
32
+ // multisafepay_payafter: mspModule,
33
+ multisafepay_klarna: mspModule,
34
+ // multisafepay_afterpay: mspModule,
35
+ multisafepay_bancontact: mspModule,
36
+ multisafepay_amex: mspModule,
37
+ multisafepay_applepay: mspModule,
38
+ multisafepay_belfius: mspModule,
39
+ multisafepay_creditcard: mspModule,
40
+ multisafepay_creditcard_recurring: mspModule,
41
+ multisafepay_creditcard_vault: mspModule,
42
+ multisafepay_visa_recurring: mspModule,
43
+ multisafepay_visa_vault: mspModule,
44
+ multisafepay_mastercard_recurring: mspModule,
45
+ multisafepay_mastercard_vault: mspModule,
46
+ multisafepay_amex_recurring: mspModule,
47
+ multisafepay_amex_vault: mspModule,
48
+ multisafepay_dotpay: mspModule,
49
+ multisafepay_eps: mspModule,
50
+ multisafepay_giropay: mspModule,
51
+ multisafepay_googlepay: mspModule,
52
+ multisafepay_idealqr: mspModule,
53
+ multisafepay_maestro: mspModule,
54
+ multisafepay_maestro_recurring: mspModule,
55
+ multisafepay_maestro_vault: mspModule,
56
+ multisafepay_mastercard: mspModule,
57
+ multisafepay_mybank: mspModule,
58
+ multisafepay_paysafecard: mspModule,
59
+ multisafepay_sofort: mspModule,
60
+ multisafepay_trustpay: mspModule,
61
+ multisafepay_visa: mspModule,
62
+ multisafepay_alipay: mspModule,
63
+ multisafepay_alipayplus: mspModule,
64
+ // multisafepay_directbanktransfer: mspModule,
65
+ multisafepay_banktransfer: mspModule,
66
+ multisafepay_santander: mspModule,
67
+ multisafepay_cbc: mspModule,
68
+ // multisafepay_directdebit: mspModule,
69
+ // multisafepay_directdebit_recurring: mspModule,
70
+ // multisafepay_directdebit_vault: mspModule,
71
+ // multisafepay_einvoicing: mspModule,
72
+ multisafepay_inghomepay: mspModule,
73
+ multisafepay_kbc: mspModule,
74
+ multisafepay_paypal: mspModule,
75
+ multisafepay_trustly: mspModule,
76
+ // multisafepay_in3: mspModule,
77
+ multisafepay_genericgateway: mspModule,
78
+ multisafepay_babygiftcard: mspModule,
79
+ multisafepay_beautyandwellness: mspModule,
80
+ multisafepay_boekenbon: mspModule,
81
+ multisafepay_fashioncheque: mspModule,
82
+ multisafepay_fashiongiftcard: mspModule,
83
+ multisafepay_fietsenbon: mspModule,
84
+ multisafepay_edenred: mspModule,
85
+ multisafepay_gezondheidsbon: mspModule,
86
+ multisafepay_givacard: mspModule,
87
+ multisafepay_good4fun: mspModule,
88
+ multisafepay_goodcard: mspModule,
89
+ multisafepay_nationaletuinbon: mspModule,
90
+ multisafepay_parfumcadeaukaart: mspModule,
91
+ multisafepay_podiumcadeaukaart: mspModule,
92
+ multisafepay_sportenfit: mspModule,
93
+ multisafepay_vvvcadeaukaart: mspModule,
94
+ multisafepay_webshopgiftcard: mspModule,
95
+ multisafepay_wechatpay: mspModule,
96
+ multisafepay_wellnessgiftcard: mspModule,
97
+ multisafepay_wijncadeau: mspModule,
98
+ multisafepay_winkelcheque: mspModule,
99
+ multisafepay_yourgift: mspModule,
100
+ }
package/next-env.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/types/global" />
3
+ /// <reference types="next/image-types/global" />
4
+ /// <reference types="@graphcommerce/next-ui/types" />
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@graphcommerce/magento-payment-multisafepay",
3
+ "homepage": "https://www.graphcommerce.org/",
4
+ "repository": "github:graphcommerce-org/graphcommerce",
5
+ "version": "4.14.0-canary.3",
6
+ "sideEffects": false,
7
+ "prettier": "@graphcommerce/prettier-config-pwa",
8
+ "eslintConfig": {
9
+ "extends": "@graphcommerce/eslint-config-pwa",
10
+ "parserOptions": {
11
+ "project": "./tsconfig.json"
12
+ }
13
+ },
14
+ "devDependencies": {
15
+ "@graphcommerce/next-ui": "4.31.0-canary.2",
16
+ "@graphcommerce/eslint-config-pwa": "^4.1.11",
17
+ "@graphcommerce/magento-cart-shipping-address": "4.14.0-canary.3",
18
+ "@graphcommerce/magento-product": "4.14.0-canary.3",
19
+ "@graphcommerce/magento-product-configurable": "4.14.0-canary.3",
20
+ "@graphcommerce/prettier-config-pwa": "^4.0.7",
21
+ "@graphcommerce/typescript-config-pwa": "^4.0.5",
22
+ "@playwright/test": "^1.21.1",
23
+ "type-fest": "^2.12.2"
24
+ },
25
+ "dependencies": {
26
+ "@graphcommerce/graphql": "4.31.0-canary.2",
27
+ "@graphcommerce/graphql-mesh": "4.31.0-canary.2",
28
+ "@graphcommerce/image": "4.31.0-canary.2",
29
+ "@graphcommerce/magento-cart": "4.14.0-canary.3",
30
+ "@graphcommerce/magento-cart-payment-method": "4.14.0-canary.3",
31
+ "@graphcommerce/magento-store": "4.14.0-canary.3",
32
+ "@graphcommerce/next-ui": "4.31.0-canary.2",
33
+ "@graphcommerce/react-hook-form": "4.31.0-canary.2"
34
+ },
35
+ "peerDependencies": {
36
+ "@lingui/react": "^3.13.2",
37
+ "@lingui/core": "^3.13.2",
38
+ "@mui/material": "5.5.3",
39
+ "next": "^12.1.2",
40
+ "react": "^17.0.1",
41
+ "react-dom": "^17.0.1"
42
+ }
43
+ }
@@ -0,0 +1,13 @@
1
+ import { PaymentMethodContextProviderProps } from '@graphcommerce/magento-cart-payment-method'
2
+ import type { PluginProps } from '@graphcommerce/next-config'
3
+ import { multisafepay } from '../methods'
4
+
5
+ export const component = 'PaymentMethodContextProvider'
6
+ export const exported = '@graphcommerce/magento-cart-payment-method'
7
+
8
+ function AddMultisafepayMethods(props: PluginProps<PaymentMethodContextProviderProps>) {
9
+ const { modules, Component } = props
10
+ return <Component {...props} modules={{ ...modules, ...multisafepay }} />
11
+ }
12
+
13
+ export const Plugin = AddMultisafepayMethods
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "exclude": ["node_modules"],
3
+ "include": ["**/*.ts", "**/*.tsx"],
4
+ "extends": "@graphcommerce/typescript-config-pwa/nextjs.json"
5
+ }