|
|
@@ -0,0 +1,193 @@
|
|
|
+import { PluginCommonModule, VendurePlugin } from '@vendure/core';
|
|
|
+import { gql } from 'apollo-server-core';
|
|
|
+
|
|
|
+import { braintreePaymentMethodHandler } from './braintree.handler';
|
|
|
+import { BraintreeResolver } from './braintree.resolver';
|
|
|
+
|
|
|
+/**
|
|
|
+ * @description
|
|
|
+ * This plugin enables payments to be processed by [Braintree](https://www.braintreepayments.com/), a popular payment provider.
|
|
|
+ *
|
|
|
+ * ## Requirements
|
|
|
+ *
|
|
|
+ * 1. You will need to create a Braintree sandbox account as outlined in https://developers.braintreepayments.com/start/overview.
|
|
|
+ * 2. Then install `braintree` and `@types/braintree` from npm. This plugin was written with `v3.x` of the Braintree lib.
|
|
|
+ * ```shell
|
|
|
+ * yarn add \@vendure/payments-plugin braintree
|
|
|
+ * yarn add -D \@types/braintree
|
|
|
+ * ```
|
|
|
+ * or
|
|
|
+ * ```shell
|
|
|
+ * npm install \@vendure/payments-plugin braintree
|
|
|
+ * npm install -D \@types/braintree
|
|
|
+ * ```
|
|
|
+ *
|
|
|
+ * ## Setup
|
|
|
+ *
|
|
|
+ * 1. Add the plugin to your VendureConfig `plugins` array:
|
|
|
+ * ```TypeScript
|
|
|
+ * import { BraintreePlugin } from '\@vendure/payments-plugin/package/braintree';
|
|
|
+ *
|
|
|
+ * // ...
|
|
|
+ *
|
|
|
+ * plugins: [
|
|
|
+ * BraintreePlugin,
|
|
|
+ * ]
|
|
|
+ * ```
|
|
|
+ * 2. Create a new PaymentMethod in the Admin UI, and select "Braintree payments" as the handler.
|
|
|
+ * 2. Fill in the `Merchant ID`, `Public Key` & `Private Key` from your Braintree sandbox account.
|
|
|
+ *
|
|
|
+ * ## Storefront usage
|
|
|
+ *
|
|
|
+ * The plugin is designed to work with the [Braintree drop-in UI](https://developers.braintreepayments.com/guides/drop-in/overview/javascript/v3).
|
|
|
+ * This is a library provided by Braintree which will handle the payment UI for you. You can install it in your storefront project
|
|
|
+ * with:
|
|
|
+ *
|
|
|
+ * ```shell
|
|
|
+ * yarn add braintree-web-drop-in
|
|
|
+ * # or
|
|
|
+ * npm install braintree-web-drop-in
|
|
|
+ * ```
|
|
|
+ *
|
|
|
+ * The high-level workflow is:
|
|
|
+ * 1. Generate a "client token" on the server by executing the `generateBraintreeClientToken` mutation which is exposed by this plugin.
|
|
|
+ * 2. Use this client token to instantiate the Braintree Dropin UI.
|
|
|
+ * 3. Listen for the `"paymentMethodRequestable"` event which emitted by the Dropin.
|
|
|
+ * 4. Use the Dropin's `requestPaymentMethod()` method to get the required payment metadata.
|
|
|
+ * 5. Pass that metadata to the `addPaymentToOrder` mutation.
|
|
|
+ *
|
|
|
+ * Here is an example of how your storefront code will look. Note that this example is attempting to
|
|
|
+ * be framework-agnostic, so you'll need to adapt it to fit to your framework of choice.
|
|
|
+ *
|
|
|
+ * ```TypeScript
|
|
|
+ * // The Braintree Dropin instance
|
|
|
+ * let dropin: import('braintree-web-drop-in').Dropin;
|
|
|
+ *
|
|
|
+ * // Used to show/hide a "submit" button, which would be bound to the
|
|
|
+ * // `submitPayment()` method below.
|
|
|
+ * let showSubmitButton = false;
|
|
|
+ *
|
|
|
+ * // Used to display a "processing..." spinner
|
|
|
+ * let processing = false;
|
|
|
+ *
|
|
|
+ * //
|
|
|
+ * // This method would be invoked when the payment screen is mounted/created.
|
|
|
+ * //
|
|
|
+ * async function renderDropin(order: Order, clientToken: string) {
|
|
|
+ * // Lazy load braintree dropin because it has a reference
|
|
|
+ * // to `window` which breaks SSR
|
|
|
+ * dropin = await import('braintree-web-drop-in').then((module) =>
|
|
|
+ * module.default.create({
|
|
|
+ * authorization: clientToken,
|
|
|
+ * // This assumes a div in your view with the corresponding ID
|
|
|
+ * container: '#dropin-container',
|
|
|
+ * card: {
|
|
|
+ * cardholderName: {
|
|
|
+ * required: true,
|
|
|
+ * },
|
|
|
+ * overrides: {},
|
|
|
+ * },
|
|
|
+ * // Additional config is passed here depending on
|
|
|
+ * // which payment methods you have enabled in your
|
|
|
+ * // Braintree account.
|
|
|
+ * paypal: {
|
|
|
+ * flow: 'checkout',
|
|
|
+ * amount: order.totalWithTax,
|
|
|
+ * currency: 'GBP',
|
|
|
+ * },
|
|
|
+ * }),
|
|
|
+ * );
|
|
|
+ *
|
|
|
+ * dropin.on('paymentMethodRequestable', (payload) => {
|
|
|
+ * if (payload.type === 'CreditCard') {
|
|
|
+ * showSubmitButton = true;
|
|
|
+ * }
|
|
|
+ * if (payload.type === 'PayPalAccount') {
|
|
|
+ * this.submitPayment();
|
|
|
+ * }
|
|
|
+ * });
|
|
|
+ *
|
|
|
+ * dropin.on('noPaymentMethodRequestable', () => {
|
|
|
+ * // Display an error
|
|
|
+ * });
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * async function generateClientToken(orderId: string) {
|
|
|
+ * const { generateBraintreeClientToken } = await graphQlClient.query(gql`
|
|
|
+ * query GenerateBraintreeClientToken($orderId: ID!) {
|
|
|
+ * generateBraintreeClientToken(orderId: $orderId)
|
|
|
+ * }
|
|
|
+ * `, { orderId });
|
|
|
+ * return generateBraintreeClientToken;
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * async submitPayment() {
|
|
|
+ * if (!dropin.isPaymentMethodRequestable()) {
|
|
|
+ * return;
|
|
|
+ * }
|
|
|
+ * showSubmitButton = false;
|
|
|
+ * processing = true;
|
|
|
+ *
|
|
|
+ * const paymentResult = await dropin.requestPaymentMethod();
|
|
|
+ *
|
|
|
+ * const { addPaymentToOrder } = await graphQlClient.query(gql`
|
|
|
+ * mutation AddPayment($input: PaymentInput!) {
|
|
|
+ * addPaymentToOrder(input: $input) {
|
|
|
+ * ... on Order {
|
|
|
+ * id
|
|
|
+ * payments {
|
|
|
+ * id
|
|
|
+ * amount
|
|
|
+ * errorMessage
|
|
|
+ * method
|
|
|
+ * state
|
|
|
+ * transactionId
|
|
|
+ * createdAt
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * ... on ErrorResult {
|
|
|
+ * errorCode
|
|
|
+ * message
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * }`, {
|
|
|
+ * input: {
|
|
|
+ * method: 'braintree', // The code of you Braintree PaymentMethod
|
|
|
+ * metadata: paymentResult,
|
|
|
+ * },
|
|
|
+ * },
|
|
|
+ * );
|
|
|
+ *
|
|
|
+ * switch (addPaymentToOrder?.__typename) {
|
|
|
+ * case 'Order':
|
|
|
+ * // Adding payment succeeded!
|
|
|
+ * break;
|
|
|
+ * case 'OrderStateTransitionError':
|
|
|
+ * case 'OrderPaymentStateError':
|
|
|
+ * case 'PaymentDeclinedError':
|
|
|
+ * case 'PaymentFailedError':
|
|
|
+ * // Display an error to the customer
|
|
|
+ * dropin.clearSelectedPaymentMethod();
|
|
|
+ * }
|
|
|
+ * }
|
|
|
+ * ```
|
|
|
+ * @docsCategory payments-plugin
|
|
|
+ * @docsPage BraintreePlugin
|
|
|
+ */
|
|
|
+@VendurePlugin({
|
|
|
+ imports: [PluginCommonModule],
|
|
|
+ providers: [],
|
|
|
+ configuration: config => {
|
|
|
+ config.paymentOptions.paymentMethodHandlers.push(braintreePaymentMethodHandler);
|
|
|
+ return config;
|
|
|
+ },
|
|
|
+ shopApiExtensions: {
|
|
|
+ schema: gql`
|
|
|
+ extend type Query {
|
|
|
+ generateBraintreeClientToken(orderId: ID!): String!
|
|
|
+ }
|
|
|
+ `,
|
|
|
+ resolvers: [BraintreeResolver],
|
|
|
+ },
|
|
|
+})
|
|
|
+export class BraintreePlugin {}
|