@graphcommerce/magento-payment-braintree 3.0.6 → 3.0.7
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 +12 -0
- package/hooks/useBraintreeCartLock.ts +7 -0
- package/hooks/useBraintreeLocalPayment.ts +1 -1
- package/methods/braintree/PaymentMethodOptions.tsx +0 -7
- package/methods/braintree_local_payments/PaymentHandler.tsx +3 -0
- package/methods/braintree_local_payments/PaymentMethodOptions.tsx +106 -39
- package/methods/braintree_local_payments/index.ts +2 -0
- package/package.json +10 -10
- package/test/braintree.playwright.ts +1 -1
- package/test/braintree_local_payments.playwright.ts +9 -14
- package/utils/isBraintreeError.ts +11 -0
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 3.0.7
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- 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)]:
|
8
|
+
- @graphcommerce/next-ui@4.6.0
|
9
|
+
- @graphcommerce/magento-cart@4.2.4
|
10
|
+
- @graphcommerce/magento-cart-payment-method@3.1.0
|
11
|
+
- @graphcommerce/react-hook-form@3.1.0
|
12
|
+
- @graphcommerce/image@3.1.4
|
13
|
+
- @graphcommerce/magento-store@4.1.6
|
14
|
+
|
3
15
|
## 3.0.6
|
4
16
|
|
5
17
|
### Patch Changes
|
@@ -12,7 +12,7 @@ type StartPaymentPayload = {
|
|
12
12
|
type LocalPayment = {
|
13
13
|
closeWindow(): void
|
14
14
|
focusWindow(): void
|
15
|
-
hasTokenizationParams():
|
15
|
+
hasTokenizationParams(): boolean
|
16
16
|
startPayment(options: StartPaymentOptions): Promise<StartPaymentPayload>
|
17
17
|
teardown(): Promise<void>
|
18
18
|
tokenize(params: {
|
@@ -2,16 +2,9 @@
|
|
2
2
|
import { useFormGqlMutationCart } from '@graphcommerce/magento-cart'
|
3
3
|
import { PaymentOptionsProps } from '@graphcommerce/magento-cart-payment-method'
|
4
4
|
import { useFormCompose } from '@graphcommerce/react-hook-form'
|
5
|
-
// import { BraintreeError } from 'braintree-web'
|
6
5
|
import { BraintreePaymentMethodOptionsDocument } from '../../BraintreePaymentMethodOptions.gql'
|
7
6
|
import { useBraintreeHostedFields } from '../../hooks/useBraintreeHostedFields'
|
8
7
|
|
9
|
-
// const errorTypes = ['CUSTOMER', 'MERCHANT', 'NETWORK', 'INTERNAL', 'UNKNOWN']
|
10
|
-
|
11
|
-
// function isBraintreeError(e: any | BraintreeError): e is BraintreeError {
|
12
|
-
// return errorTypes.includes((e as BraintreeError).type)
|
13
|
-
// }
|
14
|
-
|
15
8
|
/** It sets the selected payment method on the cart. */
|
16
9
|
export function PaymentMethodOptions(props: PaymentOptionsProps) {
|
17
10
|
const { code, step, Container } = props
|
@@ -1,10 +1,63 @@
|
|
1
1
|
import { useCartQuery, useFormGqlMutationCart } from '@graphcommerce/magento-cart'
|
2
|
-
import {
|
2
|
+
import {
|
3
|
+
PaymentOptionsProps,
|
4
|
+
usePaymentMethodContext,
|
5
|
+
} from '@graphcommerce/magento-cart-payment-method'
|
3
6
|
import { useFormCompose } from '@graphcommerce/react-hook-form'
|
7
|
+
import { useEffect } from 'react'
|
4
8
|
import { BraintreePaymentMethodOptionsDocument } from '../../BraintreePaymentMethodOptions.gql'
|
5
9
|
import { StartPaymentOptions } from '../../hooks/useBraintree'
|
10
|
+
import { useBraintreeCartLock } from '../../hooks/useBraintreeCartLock'
|
6
11
|
import { useBraintreeLocalPayment } from '../../hooks/useBraintreeLocalPayment'
|
7
|
-
import {
|
12
|
+
import { isBraintreeError } from '../../utils/isBraintreeError'
|
13
|
+
import {
|
14
|
+
BraintreeLocalPaymentsCartDocument,
|
15
|
+
BraintreeLocalPaymentsCartQuery,
|
16
|
+
} from './BraintreeLocalPaymentsCart.gql'
|
17
|
+
|
18
|
+
function validateAndBuildStartPaymentParams(cartData: BraintreeLocalPaymentsCartQuery): Partial {
|
19
|
+
const cart = cartData?.cart
|
20
|
+
|
21
|
+
const { email } = cart ?? {}
|
22
|
+
if (!email) throw Error('Please provide an email address')
|
23
|
+
const { value: amount, currency: currencyCode } = cart?.prices?.grand_total ?? {}
|
24
|
+
|
25
|
+
if (!cart?.shipping_addresses?.[0]) throw Error('Please provide a shipping method')
|
26
|
+
if (!amount || !currencyCode) throw Error('Grand total was not set')
|
27
|
+
|
28
|
+
const {
|
29
|
+
telephone: phone,
|
30
|
+
firstname: givenName,
|
31
|
+
lastname: surname,
|
32
|
+
street,
|
33
|
+
city: locality,
|
34
|
+
postcode: postalCode,
|
35
|
+
region: regionObj,
|
36
|
+
country,
|
37
|
+
} = cart?.shipping_addresses?.[0] ?? {}
|
38
|
+
|
39
|
+
const [streetAddress, ...rest] = street ?? []
|
40
|
+
const extendedAddress = rest.join('\n')
|
41
|
+
if (!streetAddress) throw Error('Please provide a street address')
|
42
|
+
|
43
|
+
const region = regionObj?.code ?? ''
|
44
|
+
if (!postalCode) throw Error('Please provide postalCode')
|
45
|
+
|
46
|
+
const { code: countryCode } = country ?? {}
|
47
|
+
if (!countryCode) throw Error('Please provide countryCode')
|
48
|
+
|
49
|
+
return {
|
50
|
+
amount: amount.toString(),
|
51
|
+
|
52
|
+
currencyCode,
|
53
|
+
shippingAddressRequired: false,
|
54
|
+
email: cart?.email ?? '',
|
55
|
+
phone,
|
56
|
+
givenName,
|
57
|
+
surname,
|
58
|
+
address: { streetAddress, extendedAddress, locality, postalCode, region, countryCode },
|
59
|
+
}
|
60
|
+
}
|
8
61
|
|
9
62
|
/** It sets the selected payment method on the cart. */
|
10
63
|
export function PaymentMethodOptions(props: PaymentOptionsProps) {
|
@@ -13,6 +66,24 @@ export function PaymentMethodOptions(props: PaymentOptionsProps) {
|
|
13
66
|
const { code, step, child } = props
|
14
67
|
const paymentType = child as StartPaymentOptions['paymentType']
|
15
68
|
const { data: cartData } = useCartQuery(BraintreeLocalPaymentsCartDocument)
|
69
|
+
const [lockState, lock, unlock] = useBraintreeCartLock()
|
70
|
+
const { selectedMethod } = usePaymentMethodContext()
|
71
|
+
|
72
|
+
useEffect(() => {
|
73
|
+
if (lockState.locked && !lockState.justLocked) {
|
74
|
+
const params = unlock({ payment_id: null })
|
75
|
+
|
76
|
+
// // eslint-disable-next-line @typescript-eslint/no-floating-promises
|
77
|
+
// ;(async () => {
|
78
|
+
// const localPayment = await localPaymentPromise
|
79
|
+
// if (localPayment.hasTokenizationParams()) {
|
80
|
+
// await localPayment.tokenize(({}) => {
|
81
|
+
// // do stuff;
|
82
|
+
// })
|
83
|
+
// }
|
84
|
+
// })()
|
85
|
+
}
|
86
|
+
}, [lockState.justLocked, lockState.locked, unlock])
|
16
87
|
|
17
88
|
/**
|
18
89
|
* In the this folder you'll also find a PaymentMethodOptionsNoop.graphql document that is
|
@@ -21,46 +92,40 @@ export function PaymentMethodOptions(props: PaymentOptionsProps) {
|
|
21
92
|
const form = useFormGqlMutationCart(BraintreePaymentMethodOptionsDocument, {
|
22
93
|
defaultValues: { code },
|
23
94
|
onBeforeSubmit: async () => {
|
24
|
-
if (!cartData
|
95
|
+
if (!cartData?.cart?.id) throw Error('Cart id is missing')
|
96
|
+
if (!paymentType) throw Error('Could not resolve payment type')
|
97
|
+
if (!selectedMethod?.code) throw Error('Selected method not found')
|
98
|
+
const options = validateAndBuildStartPaymentParams(cartData)
|
25
99
|
|
26
|
-
|
100
|
+
lock({ payment_id: null, method: selectedMethod?.code })
|
27
101
|
|
28
102
|
const localPayment = await localPaymentPromise
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
},
|
56
|
-
})
|
57
|
-
|
58
|
-
return {
|
59
|
-
cartId: cartData.cart?.id as string,
|
60
|
-
deviceData: '',
|
61
|
-
isTokenEnabler: false,
|
62
|
-
nonce: paymentResult.nonce,
|
63
|
-
code,
|
103
|
+
try {
|
104
|
+
const paymentResult = await localPayment.startPayment({
|
105
|
+
paymentType,
|
106
|
+
...options,
|
107
|
+
fallback: {
|
108
|
+
buttonText: 'Return to website',
|
109
|
+
url: window.location.href,
|
110
|
+
},
|
111
|
+
onPaymentStart: ({ paymentId }, next) => {
|
112
|
+
lock({ payment_id: paymentId, method: selectedMethod?.code })
|
113
|
+
next()
|
114
|
+
},
|
115
|
+
})
|
116
|
+
|
117
|
+
await localPayment.teardown()
|
118
|
+
|
119
|
+
return {
|
120
|
+
cartId: cartData?.cart?.id,
|
121
|
+
deviceData: '',
|
122
|
+
isTokenEnabler: false,
|
123
|
+
nonce: paymentResult.nonce,
|
124
|
+
code,
|
125
|
+
}
|
126
|
+
} catch (e) {
|
127
|
+
if (isBraintreeError(e)) unlock({ payment_id: null })
|
128
|
+
throw e
|
64
129
|
}
|
65
130
|
},
|
66
131
|
})
|
@@ -78,3 +143,5 @@ export function PaymentMethodOptions(props: PaymentOptionsProps) {
|
|
78
143
|
</form>
|
79
144
|
)
|
80
145
|
}
|
146
|
+
|
147
|
+
type Partial = Omit<StartPaymentOptions, 'paymentType' | 'fallback'>
|
@@ -2,11 +2,13 @@ import {
|
|
2
2
|
PaymentMethodPlaceOrderNoop,
|
3
3
|
PaymentModule,
|
4
4
|
} from '@graphcommerce/magento-cart-payment-method'
|
5
|
+
import { PaymentHandler } from './PaymentHandler'
|
5
6
|
import { PaymentMethodOptions } from './PaymentMethodOptions'
|
6
7
|
import { expandMethods } from './expandMethods'
|
7
8
|
|
8
9
|
export const braintree_local_payment = {
|
9
10
|
PaymentOptions: PaymentMethodOptions,
|
10
11
|
PaymentPlaceOrder: PaymentMethodPlaceOrderNoop,
|
12
|
+
PaymentHandler,
|
11
13
|
expandMethods,
|
12
14
|
} as PaymentModule
|
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": "3.0.
|
5
|
+
"version": "3.0.7",
|
6
6
|
"sideEffects": false,
|
7
7
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
8
8
|
"eslintConfig": {
|
@@ -13,9 +13,9 @@
|
|
13
13
|
},
|
14
14
|
"devDependencies": {
|
15
15
|
"@graphcommerce/eslint-config-pwa": "^4.1.4",
|
16
|
-
"@graphcommerce/magento-cart-shipping-address": "3.0.
|
17
|
-
"@graphcommerce/magento-product": "4.1.
|
18
|
-
"@graphcommerce/magento-product-configurable": "4.0.
|
16
|
+
"@graphcommerce/magento-cart-shipping-address": "3.0.7",
|
17
|
+
"@graphcommerce/magento-product": "4.1.5",
|
18
|
+
"@graphcommerce/magento-product-configurable": "4.0.9",
|
19
19
|
"@graphcommerce/prettier-config-pwa": "^4.0.5",
|
20
20
|
"@graphcommerce/typescript-config-pwa": "^4.0.2",
|
21
21
|
"@playwright/test": "^1.20.1",
|
@@ -24,12 +24,12 @@
|
|
24
24
|
},
|
25
25
|
"dependencies": {
|
26
26
|
"@graphcommerce/graphql": "3.0.7",
|
27
|
-
"@graphcommerce/image": "3.1.
|
28
|
-
"@graphcommerce/magento-cart": "4.2.
|
29
|
-
"@graphcommerce/magento-cart-payment-method": "3.0
|
30
|
-
"@graphcommerce/magento-store": "4.1.
|
31
|
-
"@graphcommerce/next-ui": "4.
|
32
|
-
"@graphcommerce/react-hook-form": "3.0
|
27
|
+
"@graphcommerce/image": "3.1.4",
|
28
|
+
"@graphcommerce/magento-cart": "4.2.4",
|
29
|
+
"@graphcommerce/magento-cart-payment-method": "3.1.0",
|
30
|
+
"@graphcommerce/magento-store": "4.1.6",
|
31
|
+
"@graphcommerce/next-ui": "4.6.0",
|
32
|
+
"@graphcommerce/react-hook-form": "3.1.0",
|
33
33
|
"braintree-web": "^3.85.2"
|
34
34
|
},
|
35
35
|
"peerDependencies": {
|
@@ -9,7 +9,7 @@ import { expect } from '@playwright/test'
|
|
9
9
|
test('place order', async ({ page, productURL }) => {
|
10
10
|
await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
|
11
11
|
|
12
|
-
await page.click('
|
12
|
+
await page.click('#view-shopping-cart-button')
|
13
13
|
|
14
14
|
await page.click('a[href="/checkout"]:last-of-type')
|
15
15
|
|
@@ -1,33 +1,28 @@
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
2
2
|
import { waitForGraphQlResponse } from '@graphcommerce/graphql/test/apolloClient.fixture'
|
3
3
|
import { PaymentMethodPlaceOrderNoopDocument } from '@graphcommerce/magento-cart-payment-method/PaymentMethodPlaceOrderNoop/PaymentMethodPlaceOrderNoop.gql'
|
4
|
-
import {
|
4
|
+
import { goToPayment } from '@graphcommerce/magento-cart-payment-method/test/goToPayment'
|
5
5
|
import { addConfigurableProductToCart } from '@graphcommerce/magento-product-configurable/test/addConfigurableProductToCart'
|
6
6
|
import { test } from '@graphcommerce/magento-product/test/productURL.fixture'
|
7
7
|
import { expect } from '@playwright/test'
|
8
8
|
|
9
|
-
test('place order', async ({ page, productURL }) => {
|
10
|
-
|
11
|
-
|
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')
|
9
|
+
test('place order ideal', async ({ page, productURL, apolloClient, locale }) => {
|
10
|
+
test.skip(locale !== 'nl', 'Skip test for non-nl locale')
|
18
11
|
|
19
|
-
await
|
20
|
-
|
21
|
-
await page.click('button[value=flatrate-flatrate]')
|
22
|
-
await page.click('#next')
|
12
|
+
await addConfigurableProductToCart(page, productURL.ConfigurableProduct)
|
13
|
+
await goToPayment(page, apolloClient)
|
23
14
|
|
24
15
|
await page.click('button[value=braintree_local_payment___ideal]')
|
25
16
|
|
17
|
+
await page.pause()
|
18
|
+
|
26
19
|
const [braintreePopup] = await Promise.all([
|
27
20
|
page.waitForEvent('popup'),
|
28
21
|
page.click('#place-order'),
|
29
22
|
])
|
30
23
|
|
24
|
+
await page.pause()
|
25
|
+
|
31
26
|
await braintreePopup.click('text=Proceed with Sandbox Purchase')
|
32
27
|
const result = await waitForGraphQlResponse(page, PaymentMethodPlaceOrderNoopDocument)
|
33
28
|
expect(result.errors).toBeUndefined()
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { BraintreeError } from 'braintree-web'
|
2
|
+
|
3
|
+
const errorTypes = ['CUSTOMER', 'MERCHANT', 'NETWORK', 'INTERNAL', 'UNKNOWN']
|
4
|
+
|
5
|
+
export function isBraintreeError(e: unknown): e is BraintreeError {
|
6
|
+
return errorTypes.includes((e as BraintreeError).type) && e instanceof Error
|
7
|
+
}
|
8
|
+
|
9
|
+
export function isBraintreeCustomerError(e: unknown): e is BraintreeError {
|
10
|
+
return isBraintreeError(e) && e.type === 'CUSTOMER'
|
11
|
+
}
|