diff --git a/packages/api-client/src/api/getBraintreeToken/index.ts b/packages/api-client/src/api/getBraintreeToken/index.ts new file mode 100644 index 00000000..584b0ef1 --- /dev/null +++ b/packages/api-client/src/api/getBraintreeToken/index.ts @@ -0,0 +1,28 @@ +import axios from 'axios'; +import { ApiContext, BraintreeToken } from '../../types'; +import getCurrentBearerOrCartToken from '../authentication/getCurrentBearerOrCartToken'; +import getAuthorizationHeaders from '../authentication/getAuthorizationHeaders'; +import { Logger } from '@vue-storefront/core'; + +export default async function getBraintreeToken({ client, config }: ApiContext, methodId: number): Promise { + try { + const token = await getCurrentBearerOrCartToken({ client, config }); + const currency = await config.internationalization.getCurrency(); + const endpoint = config.backendUrl.concat('/api/v2/braintree_client_token'); + const response = await axios.post( + endpoint, { + currency: currency, + payment_method_id: methodId + }, + { + headers: getAuthorizationHeaders(token) + } + ); + return { + clientToken: response.data.client_token + }; + } catch (e) { + Logger.error(e); + throw e; + } +} diff --git a/packages/api-client/src/api/savePaymentMethod/index.ts b/packages/api-client/src/api/savePaymentMethod/index.ts index 3cf1c886..b943e024 100644 --- a/packages/api-client/src/api/savePaymentMethod/index.ts +++ b/packages/api-client/src/api/savePaymentMethod/index.ts @@ -10,7 +10,8 @@ export default async function savePaymentMethod({ client, config }: ApiContext, order: { payments_attributes: [ { - payment_method_id: methodId.toString() + payment_method_id: methodId.toString(), + braintree_nonce: payload.braintree_nonce.toString() } ] }, diff --git a/packages/api-client/src/index.server.ts b/packages/api-client/src/index.server.ts index 9e80a188..4a08af1f 100644 --- a/packages/api-client/src/index.server.ts +++ b/packages/api-client/src/index.server.ts @@ -14,6 +14,7 @@ import deleteWishlist from './api/deleteWishlist'; import forgotPassword from './api/forgotPassword'; import getAddresses from './api/getAddresses'; import getAvailableCountries from './api/getAvailableCountries'; +import getBraintreeToken from './api/getBraintreeToken'; import getCMSPage from './api/getCMSPage'; import getCart from './api/getCart'; import getCategory from './api/getCategory'; @@ -131,7 +132,8 @@ const { createApiClient } = apiClientFactory({ changeCurrency, deleteAddress, getMenus, - getCMSPage + getCMSPage, + getBraintreeToken }, extensions: [tokenExtension] }); diff --git a/packages/api-client/src/types/braintreetoken.ts b/packages/api-client/src/types/braintreetoken.ts new file mode 100644 index 00000000..50021cd7 --- /dev/null +++ b/packages/api-client/src/types/braintreetoken.ts @@ -0,0 +1,3 @@ +export type BraintreeToken = { + clientToken: string; +}; diff --git a/packages/api-client/src/types/index.ts b/packages/api-client/src/types/index.ts index 0d06b39b..7faaea8a 100644 --- a/packages/api-client/src/types/index.ts +++ b/packages/api-client/src/types/index.ts @@ -11,6 +11,7 @@ export * from './wishlist'; export * from './user'; export * from './menu'; export * from './page'; +export * from './braintreetoken'; export type CategoryFilter = Record; export type ShippingMethod = Record; diff --git a/packages/theme/components/Checkout/PaymentMethod/Braintree.vue b/packages/theme/components/Checkout/PaymentMethod/Braintree.vue new file mode 100644 index 00000000..b8794f4b --- /dev/null +++ b/packages/theme/components/Checkout/PaymentMethod/Braintree.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/packages/theme/components/Checkout/VsfPaymentProvider.vue b/packages/theme/components/Checkout/VsfPaymentProvider.vue index 2aa879b1..f24db305 100644 --- a/packages/theme/components/Checkout/VsfPaymentProvider.vue +++ b/packages/theme/components/Checkout/VsfPaymentProvider.vue @@ -28,6 +28,7 @@ import { SfButton, SfRadio } from '@storefront-ui/vue'; import { ref, onMounted, computed } from '@nuxtjs/composition-api'; import { useVSFContext } from '@vue-storefront/core'; import Stripe from '~/components/Checkout/PaymentMethod/Stripe'; +import Braintree from '~/components/Checkout/PaymentMethod/Braintree'; import Check from '~/components/Checkout/PaymentMethod/Check'; import { Logger } from '@vue-storefront/core'; @@ -38,6 +39,7 @@ export default { SfButton, SfRadio, Stripe, + Braintree, Check }, @@ -55,6 +57,8 @@ export default { switch (selectedMethod.value) { case 'Spree::Gateway::StripeElementsGateway': return 'Stripe'; + case 'Spree::Gateway::BraintreeVzeroDropInUi': + return 'Braintree'; case 'Spree::PaymentMethod::Check': return 'Check'; } diff --git a/packages/theme/package.json b/packages/theme/package.json index 103fca3c..e28a2c8b 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -11,8 +11,8 @@ "test": "jest" }, "dependencies": { - "@nuxtjs/pwa": "^3.3.5", "@nuxtjs/google-fonts": "^1.3.0", + "@nuxtjs/pwa": "^3.3.5", "@nuxtjs/style-resources": "^1.2.1", "@storefront-ui/vue": "^0.11.0", "@stripe/stripe-js": "^1.20.3", @@ -20,17 +20,19 @@ "@vue-storefront/nuxt": "^2.5.0", "@vue-storefront/nuxt-theme": "^2.5.0", "@vue-storefront/spree": "1.3.0", + "braintree-web-drop-in": "^1.33.4", "cookie-universal-nuxt": "^2.1.5", "nuxt": "^2.15.8", "nuxt-i18n": "^6.28.0", - "vue-server-renderer": "^2.6.14", "vee-validate": "^3.4.13", "vue-demi": "latest", - "vue-scrollto": "^2.20.0" + "vue-scrollto": "^2.20.0", + "vue-server-renderer": "^2.6.14" }, "devDependencies": { "@nuxt/types": "latest", "@nuxt/typescript-build": "latest", + "@types/braintree-web-drop-in": "^1.28.0", "@vue/test-utils": "^1.2.2", "babel-core": "7.0.0-bridge.0", "babel-jest": "^27.3.0", diff --git a/yarn.lock b/yarn.lock index b43819a3..ba0dd6bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1125,6 +1125,53 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@braintree/asset-loader@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@braintree/asset-loader/-/asset-loader-0.4.4.tgz#9a5eda24c3627bfd5c7f7483cd48f0e411dd2f09" + integrity sha512-uVhXC5dydmngmNVuDiKgfXSlz4kv4x5ytIJodI8N5SY16mRh13m/UmbQ7yH+o8DQqp50qPZ45MUHIZkXKPg85w== + dependencies: + promise-polyfill "^8.1.3" + +"@braintree/browser-detection@1.14.0", "@braintree/browser-detection@^1.12.1": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@braintree/browser-detection/-/browser-detection-1.14.0.tgz#d1b397b00ccbc7cac12f6cec27c0a413d740332a" + integrity sha512-OsqU+28RhNvSw8Y5JEiUHUrAyn4OpYazFkjSJe8ZVZfkAaRXQc6hsV38MMEpIlkPMig+A68buk/diY+0O8/dMQ== + +"@braintree/class-list@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@braintree/class-list/-/class-list-0.2.0.tgz#4c4352ac19c262f61526f93d07d248244b399ec4" + integrity sha512-iLXJT51jnBFuGvyTAQqZ2uwyEVwdyapyz52F5MK1Uoh2ZOiPJ5hoqI0wncyCP2KfqrgyCpOkkEaLMLb/94unGA== + +"@braintree/event-emitter@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@braintree/event-emitter/-/event-emitter-0.4.1.tgz#204eaad8cf84eb7bf81fb288a359d34eda85a396" + integrity sha512-X41357O3OXUDlnwMvS1m0GQEn3zB3s3flOBeg2J5OBvLvdJEIAVpPkblABPtsPrlciDSvfv1aSG5ixHPgFH0Zg== + +"@braintree/extended-promise@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@braintree/extended-promise/-/extended-promise-0.4.1.tgz#b44f8e6236ddb43434be11924f00fa69f8782a36" + integrity sha512-00n7m4z+swWHoFQLHLvrIBIEoxnGUBsl3ogvX79ITpcn8CHczDwtxYy5+RhMoAraRdfN3oB+8QIpN3KOxs2Q7w== + +"@braintree/iframer@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@braintree/iframer/-/iframer-1.1.0.tgz#7e59b975c2a48bd92616f653367a5214fc2ddd4b" + integrity sha512-tVpr7U6u6bqeQlHreEjYMNtnHX62vLnNWziY2kQLqkWhvusPuY5DfuGEIPpWqsd+V/a1slyTQaxK6HWTlH6A/Q== + +"@braintree/sanitize-url@6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.0.tgz#fe364f025ba74f6de6c837a84ef44bdb1d61e68f" + integrity sha512-mgmE7XBYY/21erpzhexk4Cj1cyTQ9LzvnTxtzM17BJ7ERMNE6W72mQRo0I1Ud8eFJ+RVVIcBNhLFZ3GX4XFz5w== + +"@braintree/uuid@0.1.0", "@braintree/uuid@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@braintree/uuid/-/uuid-0.1.0.tgz#ab9355015a7fb0e25cf3c2ff9cd32ece8ea304b0" + integrity sha512-YvZJdlNcK5EnR+7M8AjgEAf4Qx696+FOSYlPfy5ePn80vODtVAUU0FxHnzKZC0og1VbDNQDDiwhthR65D4Na0g== + +"@braintree/wrap-promise@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@braintree/wrap-promise/-/wrap-promise-2.1.0.tgz#7e27ffc5dacd2d71533b0c42506eea8e7c2e50fa" + integrity sha512-UIrJB+AfKU0CCfbMoWrsGpd2D/hBpY/SGgFI6WRHPOwhaZ3g9rz1weiJ6eb6L9KgVyunT7s2tckcPkbHw+NzeA== + "@cld-apis/utils@^0.1.0": version "0.1.3" resolved "https://registry.yarnpkg.com/@cld-apis/utils/-/utils-0.1.3.tgz#a12d26bef6955d4c3e501fb84ad4bf544f07aa10" @@ -3195,6 +3242,11 @@ dependencies: anymatch "*" +"@types/applepayjs@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/applepayjs/-/applepayjs-3.0.4.tgz#9806a4b3ccd73dcf169c61a34be7a39f91d77540" + integrity sha512-RqaVZWy1Kj4e1PoUoOI8uA+4UuuLpicQFxfU9Y/xWJFZFT6mFB4PiiY911iDxFk7pdvaj5HKH7VsWRisRca1Rg== + "@types/autoprefixer@9.7.2": version "9.7.2" resolved "https://registry.yarnpkg.com/@types/autoprefixer/-/autoprefixer-9.7.2.tgz#64b3251c9675feef5a631b7dd34cfea50a8fdbcc" @@ -3255,6 +3307,24 @@ "@types/connect" "*" "@types/node" "*" +"@types/braintree-web-drop-in@^1.28.0": + version "1.28.0" + resolved "https://registry.yarnpkg.com/@types/braintree-web-drop-in/-/braintree-web-drop-in-1.28.0.tgz#db145f5a50633a088b3c49dccafbd41127087f39" + integrity sha512-rVrRUvHt7fR7GCl8ysFqnMN4Y4gNpVgG3LF7RzZCP5f7+YUQu0SE2+71bXQQ5/Ksdad6QYIFrTOIcVKxMV2/vQ== + dependencies: + "@types/applepayjs" "*" + "@types/braintree-web" "*" + "@types/googlepay" "*" + "@types/paypal-checkout-components" "*" + +"@types/braintree-web@*": + version "3.75.20" + resolved "https://registry.yarnpkg.com/@types/braintree-web/-/braintree-web-3.75.20.tgz#d6c9755bb2474687cdbaa7efc37a378cf11604dc" + integrity sha512-Z7zCPdg3tpf+mqZlDpwOpQjgPEFhMPuZPtLEDO3z0vwF8iGRS21haLaxRR0K3IJZGZfXzR5PWtKjrZrDRxt2tw== + dependencies: + "@types/googlepay" "*" + "@types/paypal-checkout-components" "*" + "@types/browserslist@*": version "4.15.0" resolved "https://registry.yarnpkg.com/@types/browserslist/-/browserslist-4.15.0.tgz#ba0265b33003a2581df1fc5f483321a30205f2d2" @@ -3352,6 +3422,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/googlepay@*": + version "0.6.4" + resolved "https://registry.yarnpkg.com/@types/googlepay/-/googlepay-0.6.4.tgz#e0b06ae1f12496c4fa43e52d35e76411dbbee135" + integrity sha512-PTt/UCllzl8z5HmhymPpSj6uENZvVKZvCBYdDVmbBVJnLStitxtWrterAOQZkKGlqVdzxNXYeif5hOAMNMS5mw== + "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -3488,6 +3563,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/paypal-checkout-components@*": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/paypal-checkout-components/-/paypal-checkout-components-4.0.3.tgz#6eb7e090548bf58f2df662a473445ff74540b785" + integrity sha512-i4Hmw7lEyThjhMxfrwtaPdtca8e5CaoIZpZrgpFMnszODBz7PDqJxHHJsrVEtmRdRPpknFuUJbSSirUwl6ObyA== + "@types/prettier@^2.1.5": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" @@ -4912,6 +4992,41 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +braintree-web-drop-in@^1.33.4: + version "1.33.4" + resolved "https://registry.yarnpkg.com/braintree-web-drop-in/-/braintree-web-drop-in-1.33.4.tgz#29b5dccd5dfc016d136156a93d0677692410842d" + integrity sha512-/Q8Fe8SG8URzaNA0cAR0Z5k0QMPp6gEPKJJHAipWEqJeVM/N0kK9WRlhxhkMpdAu9GUaygl0gAeuak1FBxz4vg== + dependencies: + "@braintree/asset-loader" "0.4.4" + "@braintree/browser-detection" "1.14.0" + "@braintree/class-list" "0.2.0" + "@braintree/event-emitter" "0.4.1" + "@braintree/uuid" "0.1.0" + "@braintree/wrap-promise" "2.1.0" + braintree-web "3.86.0" + promise-polyfill "8.2.3" + +braintree-web@3.86.0: + version "3.86.0" + resolved "https://registry.yarnpkg.com/braintree-web/-/braintree-web-3.86.0.tgz#3d61fde3a1a3266a7a8114bbd2c90cfa1e981982" + integrity sha512-Z7t6Sq06cpiwCIwuREyprla/pKxRlVs6Cw4AV8ORn0dyxao6U2rt8IobYkwuQmrc0MzdjJ5BLOu/cn5PMZhz+A== + dependencies: + "@braintree/asset-loader" "0.4.4" + "@braintree/browser-detection" "1.14.0" + "@braintree/class-list" "0.2.0" + "@braintree/event-emitter" "0.4.1" + "@braintree/extended-promise" "0.4.1" + "@braintree/iframer" "1.1.0" + "@braintree/sanitize-url" "6.0.0" + "@braintree/uuid" "0.1.0" + "@braintree/wrap-promise" "2.1.0" + card-validator "8.1.1" + credit-card-type "9.1.0" + framebus "5.1.2" + inject-stylesheet "5.0.0" + promise-polyfill "8.2.3" + restricted-input "3.0.5" + brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -5337,6 +5452,13 @@ capture-exit@^2.0.0: dependencies: rsvp "^4.8.4" +card-validator@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/card-validator/-/card-validator-8.1.1.tgz#418f5f32435553fb9ca2a02634ad413bb38697a9" + integrity sha512-cN4FsKwoTfTFnqPwVc7TQLSsH/QMDB3n/gWm0XelcApz4sKipnOQ6k33sa3bWsNnnIpgs7eXOF+mUV2UQAX2Sw== + dependencies: + credit-card-type "^9.1.0" + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -6166,6 +6288,11 @@ create-require@^1.1.0, create-require@^1.1.1: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +credit-card-type@9.1.0, credit-card-type@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/credit-card-type/-/credit-card-type-9.1.0.tgz#54dd96c93b6579623e9c8656e6798fc2b93f5f05" + integrity sha512-CpNFuLxiPFxuZqhSKml3M+t0K/484pMAnfYWH14JoD7OZMnmC0Lmo+P7JX9SobqFpRoo7ifA18kOHdxJywYPEA== + cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -7977,6 +8104,13 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" +framebus@5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/framebus/-/framebus-5.1.2.tgz#cb38cf6a282d405411227cfaab4a1095ca9e8e15" + integrity sha512-Z/y6/0gHVx4Td4c0jkDiASBo0pXlJ2fKOP6CynSFnxTzqojG9xOKOFOqoYkcBHlz1vP4t4yHHR6Esp+GsYIh/Q== + dependencies: + "@braintree/uuid" "^0.1.0" + fresh@0.5.2, fresh@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -9055,6 +9189,11 @@ init-package-json@^1.10.3: validate-npm-package-license "^3.0.1" validate-npm-package-name "^3.0.0" +inject-stylesheet@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/inject-stylesheet/-/inject-stylesheet-5.0.0.tgz#bb34acf05ca6ed86e5763d886cd6c9b19f360ab1" + integrity sha512-GzncrJP8E/pavMQzoO93CXoYCfTttwVm2cX2TyXJdgtVE0cCvWSFCn1/uMsM6ZkEg7LUsOcKuamcLiGWlv2p9A== + inquirer@6.5.2, inquirer@^6.2.0: version "6.5.2" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" @@ -13623,6 +13762,11 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise-polyfill@8.2.3, promise-polyfill@^8.1.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.2.3.tgz#2edc7e4b81aff781c88a0d577e5fe9da822107c6" + integrity sha512-Og0+jCRQetV84U8wVjMNccfGCnMQ9mGs9Hv78QFe+pSDD3gWTpz0y+1QCuxy5d/vBFuZ3iwP2eycAkvqIMPmWg== + promise-retry@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d" @@ -14413,6 +14557,13 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +restricted-input@3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/restricted-input/-/restricted-input-3.0.5.tgz#c43d279df36be0a11085daa3b1ae1d28044c44d8" + integrity sha512-lUuXZ3wUnHURRarj5/0C8vomWIfWJO+p7T6RYwB46v7Oyuyr3yyupU+i7SjqUv4S6RAeAAZt1C/QCLJ9xhQBow== + dependencies: + "@braintree/browser-detection" "^1.12.1" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"