-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(payments-plugin): Add multi currency support for braintree plugin #3239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: minor
Are you sure you want to change the base?
Changes from 3 commits
c859cd1
097e150
8c68d8f
f3b1fe6
7746af4
c2049ce
acdbd2f
2eb2629
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| import { AdminUiPlugin } from '@vendure/admin-ui-plugin'; | ||
| import { | ||
| ChannelService, | ||
| configureDefaultOrderProcess, | ||
| DefaultLogger, | ||
| LanguageCode, | ||
| Logger, | ||
| LogLevel, | ||
| mergeConfig, | ||
| OrderService, | ||
| RequestContext, | ||
| } from '@vendure/core'; | ||
| import { | ||
| createTestEnvironment, | ||
| registerInitializer, | ||
| SimpleGraphQLClient, | ||
| SqljsInitializer, | ||
| testConfig, | ||
| } from '@vendure/testing'; | ||
| import gql from 'graphql-tag'; | ||
| import path from 'path'; | ||
|
|
||
| import { initialData } from '../../../e2e-common/e2e-initial-data'; | ||
| import { BraintreePlugin } from '../src/braintree'; | ||
| import { braintreePaymentMethodHandler } from '../src/braintree/braintree.handler'; | ||
|
|
||
| /* eslint-disable */ | ||
| import { CREATE_PAYMENT_METHOD } from './graphql/admin-queries'; | ||
| import { | ||
| CreatePaymentMethodMutation, | ||
| CreatePaymentMethodMutationVariables, | ||
| } from './graphql/generated-admin-types'; | ||
| import { | ||
| AddItemToOrderMutation, | ||
| AddItemToOrderMutationVariables, | ||
| AddPaymentToOrderMutation, | ||
| AddPaymentToOrderMutationVariables, | ||
| } from './graphql/generated-shop-types'; | ||
| import { ADD_ITEM_TO_ORDER, ADD_PAYMENT } from './graphql/shop-queries'; | ||
| import { GENERATE_BRAINTREE_CLIENT_TOKEN, proceedToArrangingPayment, setShipping } from './payment-helpers'; | ||
| import braintree, { Environment, Test } from 'braintree'; | ||
| import { BraintreeTestPlugin } from './fixtures/braintree-checkout-test.plugin'; | ||
|
|
||
| export let clientToken: string; | ||
| export let exposedShopClient: SimpleGraphQLClient; | ||
|
|
||
| /** | ||
| * The actual starting of the dev server | ||
| */ | ||
| (async () => { | ||
| require('dotenv').config(); | ||
|
|
||
| registerInitializer('sqljs', new SqljsInitializer(path.join(__dirname, '__data__'))); | ||
| const config = mergeConfig(testConfig, { | ||
| authOptions: { | ||
| tokenMethod: ['bearer', 'cookie'], | ||
| cookieOptions: { | ||
| secret: 'cookie-secret', | ||
| }, | ||
| }, | ||
| plugins: [ | ||
| ...testConfig.plugins, | ||
| AdminUiPlugin.init({ | ||
| route: 'admin', | ||
| port: 5001, | ||
| }), | ||
| BraintreePlugin.init({ | ||
| storeCustomersInBraintree: false, | ||
| environment: Environment.Sandbox, | ||
| merchantAccountIds: { | ||
| USD: process.env.BRAINTREE_MERCHANT_ACCOUNT_ID_USD, | ||
| EUR: process.env.BRAINTREE_MERCHANT_ACCOUNT_ID_EUR, | ||
| }, | ||
| }), | ||
| BraintreeTestPlugin, | ||
| ], | ||
| logger: new DefaultLogger({ level: LogLevel.Debug }), | ||
| }); | ||
| const { server, shopClient, adminClient } = createTestEnvironment(config as any); | ||
| exposedShopClient = shopClient; | ||
| await server.init({ | ||
| initialData, | ||
| productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'), | ||
| customerCount: 1, | ||
| }); | ||
| // Create method | ||
| await adminClient.asSuperAdmin(); | ||
| await adminClient.query<CreatePaymentMethodMutation, CreatePaymentMethodMutationVariables>( | ||
| CREATE_PAYMENT_METHOD, | ||
| { | ||
| input: { | ||
| code: 'braintree-payment-method', | ||
| enabled: true, | ||
| translations: [ | ||
| { | ||
| name: 'Braintree', | ||
| description: 'This is a Braintree test payment method', | ||
| languageCode: LanguageCode.en, | ||
| }, | ||
| ], | ||
| handler: { | ||
| code: braintreePaymentMethodHandler.code, | ||
| arguments: [ | ||
| { name: 'privateKey', value: process.env.BRAINTREE_PRIVATE_KEY! }, | ||
| { name: 'publicKey', value: process.env.BRAINTREE_PUBLIC_KEY! }, | ||
| { name: 'merchantId', value: process.env.BRAINTREE_MERCHANT_ID! }, | ||
| ], | ||
| }, | ||
| }, | ||
| }, | ||
| ); | ||
| // Prepare order for payment | ||
| await shopClient.asUserWithCredentials('[email protected]', 'test'); | ||
| await shopClient.query<AddItemToOrderMutation, AddItemToOrderMutationVariables>(ADD_ITEM_TO_ORDER, { | ||
| productVariantId: 'T_1', | ||
| quantity: 1, | ||
| }); | ||
| const ctx = new RequestContext({ | ||
| apiType: 'admin', | ||
| isAuthorized: true, | ||
| authorizedAsOwnerOnly: false, | ||
| channel: await server.app.get(ChannelService).getDefaultChannel(), | ||
| }); | ||
| await server.app.get(OrderService).addSurchargeToOrder(ctx, 1, { | ||
| description: 'Negative test surcharge', | ||
| listPrice: -20000, | ||
| }); | ||
| await setShipping(shopClient); | ||
| const { generateBraintreeClientToken } = await shopClient.query(GENERATE_BRAINTREE_CLIENT_TOKEN); | ||
| clientToken = generateBraintreeClientToken; | ||
| Logger.info('http://localhost:3050/checkout', 'Braintree DevServer'); | ||
| })(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| /* eslint-disable */ | ||
| import { Controller, Res, Get, Post, Body } from '@nestjs/common'; | ||
| import { PluginCommonModule, VendurePlugin } from '@vendure/core'; | ||
| import { Response } from 'express'; | ||
|
|
||
| import { clientToken, exposedShopClient } from '../braintree-dev-server'; | ||
| import { proceedToArrangingPayment } from '../payment-helpers'; | ||
| import { | ||
| AddPaymentToOrderMutation, | ||
| AddPaymentToOrderMutationVariables, | ||
| } from '../graphql/generated-shop-types'; | ||
| import { ADD_PAYMENT } from '../graphql/shop-queries'; | ||
| /** | ||
| * This test controller returns the Braintree drop-in checkout page | ||
| * with the client secret generated by the dev-server | ||
| */ | ||
| @Controller() | ||
| export class BraintreeTestCheckoutController { | ||
| @Get('checkout') | ||
| async client(@Res() res: Response): Promise<void> { | ||
| res.send(` | ||
| <head> | ||
| <title>Checkout</title> | ||
| <script src="https://js.braintreegateway.com/web/dropin/1.33.3/js/dropin.min.js"></script> | ||
| </head> | ||
| <html> | ||
|
|
||
| <div id="dropin-container"></div> | ||
| <button id="submit-button">Purchase</button> | ||
| <div id="result"/> | ||
|
|
||
| <script> | ||
| var submitButton = document.querySelector('#submit-button'); | ||
| braintree.dropin.create({ | ||
| authorization: "${clientToken}", | ||
| container: '#dropin-container', | ||
| dataCollector: true, | ||
| paypal: { | ||
| flow: 'checkout', | ||
| amount: 100, | ||
| currency: 'GBP', | ||
| }, | ||
| }, function (err, dropinInstance) { | ||
|
|
||
| submitButton.addEventListener('click', function () { | ||
| dropinInstance.requestPaymentMethod(async function (err, payload) { | ||
| sendPayloadToServer(payload) | ||
| }); | ||
| }); | ||
|
|
||
| if (dropinInstance.isPaymentMethodRequestable()) { | ||
| // This will be true if you generated the client token | ||
| // with a customer ID and there is a saved payment method | ||
| // available to tokenize with that customer. | ||
| submitButton.removeAttribute('disabled'); | ||
| } | ||
|
|
||
| dropinInstance.on('paymentMethodRequestable', function (event) { | ||
| console.log(event.type); // The type of Payment Method, e.g 'CreditCard', 'PayPalAccount'. | ||
| console.log(event.paymentMethodIsSelected); // true if a customer has selected a payment method when paymentMethodRequestable fires | ||
| submitButton.removeAttribute('disabled'); | ||
| }); | ||
|
|
||
| dropinInstance.on('noPaymentMethodRequestable', function () { | ||
| submitButton.setAttribute('disabled', true); | ||
| }); | ||
| }); | ||
|
|
||
| async function sendPayloadToServer(payload) { | ||
| const response = await fetch('checkout', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'Accept': 'application/json', | ||
| 'Credentials': 'include', | ||
| }, | ||
| body: JSON.stringify(payload) | ||
| }) | ||
| .then(res => res.json()) | ||
| .catch(err => console.error(err)) | ||
|
|
||
| document.querySelector('#result').innerHTML = JSON.stringify(response) | ||
| console.log(response) | ||
|
|
||
| } | ||
| </script> | ||
|
|
||
| </html> | ||
| `); | ||
| } | ||
| @Post('checkout') | ||
| async test(@Body() body: Request, @Res() res: Response): Promise<void> { | ||
| await proceedToArrangingPayment(exposedShopClient); | ||
| const { addPaymentToOrder } = await exposedShopClient.query< | ||
| AddPaymentToOrderMutation, | ||
| AddPaymentToOrderMutationVariables | ||
| >(ADD_PAYMENT, { | ||
| input: { | ||
| method: 'braintree-payment-method', | ||
| metadata: body, | ||
| }, | ||
| }); | ||
| console.log(addPaymentToOrder); | ||
dlhck marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| res.send(addPaymentToOrder); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Test plugin for serving the Stripe intent checkout page | ||
| */ | ||
| @VendurePlugin({ | ||
| imports: [PluginCommonModule], | ||
| controllers: [BraintreeTestCheckoutController], | ||
| }) | ||
| export class BraintreeTestPlugin {} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ import { Controller, Res, Get } from '@nestjs/common'; | |
| import { PluginCommonModule, VendurePlugin } from '@vendure/core'; | ||
| import { Response } from 'express'; | ||
|
|
||
| import { clientSecret } from './stripe-dev-server'; | ||
| import { clientSecret } from '../stripe-dev-server'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Circular-dependency causes Because the plugin is imported by -import { clientSecret } from '../stripe-dev-server';
+// Keep a live reference to the export to avoid stale value
+import * as StripeDevServer from '../stripe-dev-server';Then replace the template interpolation later in the file: - clientSecret: '${clientSecret}',
+ clientSecret: '${StripeDevServer.clientSecret}',This keeps a single object reference and avoids the stale-value bug. 🤖 Prompt for AI Agents |
||
|
|
||
| /** | ||
| * This test controller returns the Stripe intent checkout page | ||
|
|
@@ -18,8 +18,8 @@ export class StripeTestCheckoutController { | |
| <title>Checkout</title> | ||
| <script src="https://js.stripe.com/v3/"></script> | ||
| </head> | ||
| <html> | ||
|
|
||
| <html> | ||
| <form id="payment-form"> | ||
| <div id="payment-element"> | ||
| <!-- Elements will create form elements here --> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation for required environment variables.
The required Braintree credentials are used without validation. Consider adding checks to ensure they're properly configured.
🤖 Prompt for AI Agents