| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- import { LanguageCode, PluginCommonModule, Type, VendurePlugin } from '@vendure/core';
- import { gql } from 'graphql-tag';
- import { braintreePaymentMethodHandler } from './braintree.handler';
- import { BraintreeResolver } from './braintree.resolver';
- import { BRAINTREE_PLUGIN_OPTIONS } from './constants';
- import { BraintreePluginOptions } from './types';
- /**
- * @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:
- * ```ts
- * import { BraintreePlugin } from '\@vendure/payments-plugin/package/braintree';
- * import { Environment } from 'braintree';
- *
- * // ...
- *
- * plugins: [
- * BraintreePlugin.init({
- * environment: Environment.Sandbox,
- * // This allows saving customer payment
- * // methods with Braintree (see "vaulting"
- * // section below for details)
- * storeCustomersInBraintree: true,
- * }),
- * ]
- * ```
- * 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. The metadata should be an object of type `{ nonce: string; }`
- *
- * 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.
- *
- * ```ts
- * // 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 / 100,
- * currency: 'GBP',
- * },
- * }),
- * );
- *
- * // If you are using the `storeCustomersInBraintree` option, then the
- * // customer might already have a stored payment method selected as
- * // soon as the dropin script loads. In this case, show the submit
- * // button immediately.
- * if (dropin.isPaymentMethodRequestable()) {
- * showSubmitButton = true;
- * }
- *
- * 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() {
- * const { generateBraintreeClientToken } = await graphQlClient.query(gql`
- * query GenerateBraintreeClientToken {
- * generateBraintreeClientToken
- * }
- * `);
- * 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();
- * }
- * }
- * ```
- *
- * ## Storing payment details (vaulting)
- *
- * Braintree has a [vault feature](https://developer.paypal.com/braintree/articles/control-panel/vault/overview) which allows the secure storage
- * of customer's payment information. Using the vault allows you to offer a faster checkout for repeat customers without needing to worry about
- * how to securely store payment details.
- *
- * To enable this feature, set the `storeCustomersInBraintree` option to `true`.
- *
- * ```ts
- * BraintreePlugin.init({
- * environment: Environment.Sandbox,
- * storeCustomersInBraintree: true,
- * }),
- * ```
- *
- * Since v1.8, it is possible to override vaulting on a per-payment basis by passing `includeCustomerId: false` to the `generateBraintreeClientToken`
- * mutation:
- *
- * ```GraphQL
- * const { generateBraintreeClientToken } = await graphQlClient.query(gql`
- * query GenerateBraintreeClientToken($includeCustomerId: Boolean) {
- * generateBraintreeClientToken(includeCustomerId: $includeCustomerId)
- * }
- * `, { includeCustomerId: false });
- * ```
- *
- * as well as in the metadata of the `addPaymentToOrder` mutation:
- *
- * ```ts
- * const { addPaymentToOrder } = await graphQlClient.query(gql`
- * mutation AddPayment($input: PaymentInput!) {
- * addPaymentToOrder(input: $input) {
- * ...Order
- * ...ErrorResult
- * }
- * }`, {
- * input: {
- * method: 'braintree',
- * metadata: {
- * ...paymentResult,
- * includeCustomerId: false,
- * },
- * }
- * );
- * ```
- *
- * @docsCategory core plugins/PaymentsPlugin
- * @docsPage BraintreePlugin
- */
- @VendurePlugin({
- imports: [PluginCommonModule],
- providers: [
- {
- provide: BRAINTREE_PLUGIN_OPTIONS,
- useFactory: () => BraintreePlugin.options,
- },
- ],
- configuration: config => {
- config.paymentOptions.paymentMethodHandlers.push(braintreePaymentMethodHandler);
- if (BraintreePlugin.options.storeCustomersInBraintree === true) {
- config.customFields.Customer.push({
- name: 'braintreeCustomerId',
- type: 'string',
- label: [{ languageCode: LanguageCode.en, value: 'Braintree Customer ID' }],
- nullable: true,
- public: false,
- readonly: true,
- });
- }
- return config;
- },
- shopApiExtensions: {
- schema: gql`
- extend type Query {
- generateBraintreeClientToken(orderId: ID, includeCustomerId: Boolean): String!
- }
- `,
- resolvers: [BraintreeResolver],
- },
- compatibility: '^2.0.0',
- })
- export class BraintreePlugin {
- static options: BraintreePluginOptions = {};
- static init(options: BraintreePluginOptions): Type<BraintreePlugin> {
- this.options = options;
- return BraintreePlugin;
- }
- }
|