Search code examples
node.jsaws-lambdaaccess-tokenbraintree

How to implement Braintree with AWS Lambda Node.js server and generate a client token with a promise?


I am trying to generate Braintree's clientToken that I am supposed to get from the Braintree gateway that I need my lambda to connect to when triggered by my UI according to the docs found here: https://developers.braintreepayments.com/start/hello-server/node

I am trying to implement this within a Lambda function that I need to export for use by the rest of my serverless application. Furthermore, I am trying to use ES6 import statements as needed to import the Braintree functionality. Although, I get the same error even if I use require statements.

Here is my Lambda function:

import * as dynamoDbLib from "./libs/dynamodb-lib";
import { success, failure } from "./libs/response-lib";
import braintree from "braintree";

export async function main(event, context) {
    /*
            All secrets are declared in the serverless template for the specific function
            and stored in AWS SecretsManger
    */
    // create a Braintree gateway
    let gateway = braintree.connect({
        environment: braintree.Environment.Sandbox, // either Sandbox or Production
        merchantId: process.env.merchantId, // these come from the Lambda's environmental variables
        publicKey: process.env.publicKey,
        privateKey: process.env.privateKey
    });

    /*************************************************
    * Braintree related functions based on documentation at
    * https://developers.braintreepayments.com/start/hello-server/node
    */
    /////////////////////////////////////
    // get a client token from Braintree and send it to the client
    /////////////////////////////////////

    const getClientToken = (options = {}) => {

        console.log("getting client token: ", gateway); // console.logs will show up in AWS Cloudwatch

        let customerId = options && options.hasOwnProperty("customerID")
          ? options.customerID
          : null;

        // THIS IS THE ONLY THING THAT PRINTS TO CONSOLE!!!
        console.log("This is customerID: ", customerId);

        // NONE OF THIS WORKS!!!???
        gateway.clientToken.generate({})
            .then((res) => {
                console.log("maybe result: ", res); // <---- None of these print!?!?
                console.log("Maybe success/fail: ", res.success);
                console.log("maybe token: ", res.clientToken);
                // Code here
            })
            .catch (err => {
                console.log("ERROR CAUGHT: ", err);
                failure({
                  status: false,
                  error: "did it trigger 2: " + err
                });
            }
        );
    };

  // try to execute API calls
  try {
    switch (event.pathParameters.txnMethod) {

      case "get-client-token":
        // call getClientToken with the parsed version of optional body if present, otherwise call it with nothing
        getClientToken(
          event.hasOwnProperty("body")
            ? JSON.parse(event.body) 
            : null
        );
        break;

      default:
        failure({
          status: false,
          error: "invalid query string"
        });
        break;
    }
  } catch (error) {
    failure({
      status: false,
      error: "Did it trigger 1: " + error
    });
  }
}

Only the first console statements actually print anything, after that everything fails from gateway.clientToken.generate({}) on down and most importantly it fails without any error thrown....

When I output console.log("getting client token: ", gateway);

I get a data structure like this:

getting client token:  BraintreeGateway {
  config:
   Config {
     timeout: 60000,
     apiVersion: '5',
     graphQLApiVersion: '2018-09-10',
     publicKey: 'sbxkeys',
     privateKey: 'sbxkeys',
     merchantId: 'sbxkeys',
     environment:
      Environment {
        server: 'api.sandbox.braintreegateway.com',
        port: '443',
        authUrl: 'https://auth.sandbox.venmo.com',
        ssl: true,
        graphQLServer: 'payments.sandbox.braintree-api.com',
        graphQLPort: '443' } },
  graphQLClient: GraphQLClient { _service: GraphQL { config: [Config] } },
  http:
   Http {
     config:
      Config {
        timeout: 60000,
        apiVersion: '5',
        graphQLApiVersion: '2018-09-10',
        publicKey: 'sbxkeys',
        privateKey: 'sbxkeys',
        merchantId: 'sbxkeys',
        environment: [Environment] } },
  addOn:
   AddOnGateway {
     gateway: [Circular],
     config:
      Config {
        timeout: 60000,
        apiVersion: '5',
        graphQLApiVersion: '2018-09-10',
        publicKey: 'sbxkeys',
        privateKey: 'sbxkeys',
        merchantId: 'sbxkeys',
        environment: [Environment] } },
  address:
   AddressGateway {
     gateway: [Circular],
     config:
      Config {
        timeout: 60000,
        apiVersion: '5',
        graphQLApiVersion: '2018-09-10',
        publicKey: 'sbxkeys',
        privateKey: 'sbxkeys',
        merchantId: 'sbxkeys',
        environment: [Environment] } },
  clientToken:
   ClientTokenGateway {
     gateway: [Circular],
     config:
      Config {
        timeout: 60000,
        apiVersion: '5',
        graphQLApiVersion: '2018-09-10',
        publicKey: 'sbxkeys',
        privateKey: 'sbxkeys',
        merchantId: 'sbxkeys',
        environment: [Environment] } },
  creditCard:
   CreditCardGateway {
     gateway: [Circular],
     config:
      Config {
        timeout: 60000,
        apiVersion: '5',
        graphQLApiVersion: '2018-09-10',
        publicKey: 'sbxkeys',
        privateKey: 'sbxkeys',
        merchantId: 'sbxkeys',
        environment: [Environment] } },
  creditCardVerification:


...........



So i try to change this: gateway.clientToken.generate({}) to this:

  • gateway.BraintreeGateway.clientToken.generate({}) in the hopes that the `generate() method is associate to that structure and the following is the error that I receive when I test it:
TypeError: Cannot read property 'clientToken' of undefined"

Im assuming there is nothing there.

How do I get the Braintree token to generate in my serverless lambda function using ES6 import statements??


Solution

  • I realized that being that these lambda's are async functions and that since we have to wait for the response back from Braintree, we therefore have to declare the right combination of nested async/await statements.

    The code below is the final implementation I committed with the correct promise chain implementation to handle the async process correctly:

    import * as dynamoDbLib from "./libs/dynamodb-lib";
    import { success, failure } from "./libs/response-lib";
    import braintree from "braintree";
    
    export async function main(event, context) {
        /*
                All secrets are declared in the serverless template for the specific function
                and stored in AWS SecretsManger
        */
        // create a Braintree gateway
        let gateway = braintree.connect({
            environment: braintree.Environment.Sandbox, // either Sandbox or Production
            merchantId: process.env.merchantId, // these come from the Lambda's environmental variables
            publicKey: process.env.publicKey,
            privateKey: process.env.privateKey
        });
    
        /*************************************************
        * Braintree related functions based on documentation at
        * https://developers.braintreepayments.com/start/hello-server/node
        */
        /////////////////////////////////////
        // get a client token from Braintree and send it to the client
        /////////////////////////////////////
        const getClientToken = async (options = {}) => {
            console.log("Getting client token...");
            //console.log("hasOwnProps: ", options.hasOwnProperty("customerID") );
    
            let customerId = options && options.hasOwnProperty("customerID")
              ? options.customerID
              : null;
    
            console.log("This is customerID: ", customerId);
    
            return await gateway.clientToken.generate({});
        };
    
        /////////////////////////////////////
        // purchase an item using Braintree's transaction method
        /////////////////////////////////////
        const purchaseItem = async (purchaseInformation) => {
            /* console.log(
              "purchaseInformation: ",
              util.inspect(purchaseInformation, { showHidden: false, depth: null })
            ); */
            return await gateway.transaction.sale(
                {
                    amount: purchaseInformation.amount,
                    paymentMethodNonce: purchaseInformation.nonce,
                    options: {
                        submitForSettlement: true
                    }
                }
            );
        };
    
      /*************************************************
       * Enter here
       */
      // view the event that was received
      console.log("event: ", event);
      let result;
    
      // try to execute API calls
      try {
        switch (event.pathParameters.txnMethod) {
          case "get-client-token":
            // call getClientToken with the parsed version of optional body if present, otherwise call it with nothing
            await getClientToken(
                event.hasOwnProperty("body")
                    ? JSON.parse(event.body) 
                    : null)
                .then((res) => {
                    //console.log("maybe result: ", res);
                    //console.log("Maybe success/fail: ", typeof(res.success));
                    //console.log("maybe token: ", res.clientToken);
                    // Code here
                    if(res.success.toString() === "true"){
    
                        //console.log("SUCCESS token: ", res.clientToken);
                        return context.succeed(success({
                            status: true,
                            clientToken: res.clientToken
                        }));
                    } else {
    
                        return failure({
                            status: false,
                            error: "Braintree ClientToken Failure."
                        });
                    }
                })
                .catch (err => {
                    console.log("ERROR CAUGHT: ", err);
    
                    return failure({
                      status: false,
                      error: "did it trigger 2: " + err
                    });
                });
            break;
    
          case "purchase-item":
            console.log("purchasing item");
            const data = JSON.parse(event.body);
            await purchaseItem(data)
                .then((res) => {
                    // Code here
                    if(res.success === true){
                        console.log("~~~~~~~~~~~");
                        console.log("This is RES: ", res);
                        console.log("This is ERR>RES: ", res.ErrorResponse);
                        return context.succeed(success({
                            status: true,
                            TxnAuth: res
                        }));
                    } else if (res.success === false) {
                        console.log("#################");
                        console.log(res.result);
                        return failure({
                            status: false,
                            error: "Error validating payment server information"
                        });
                    } else {
    
                        return failure({
                            status: false,
                            error: "Braintree Server Failure."
                        });
                    }
                })
                .catch (err => {
                    console.log("ERROR CAUGHT: ", err);
                    console.log("*********");
                    return failure({
                        status: false,
                        error: "did it trigger 3pmt: " + err
                    });
                });
            break;
    
          default:
            return failure({
              status: false,
              error: "invalid query string"
            });
            break;
        }
      } catch (error) {
        return failure({
          status: false,
          error: "API Call to Braintree Failed: " + error
        });
      }
    }