0

We are using authorize.net Node SDK to process payments. We have a Firebase callable function to handle the request to process a payment but we can't seem to be able to get the response of the transaction.

The problem is in the following code.

  try { 

  // MAKE GLOBAL VARIABLE TO HOLD RESPONSE? -> (problems with async callback)
  let RESPONSE_FOR_CLIENT;


  await ctrl.execute(async function () {

      var apiResponse = ctrl.getResponse();

      var response = await new ApiContracts.CreateTransactionResponse(apiResponse);

      RESPONSE_FOR_CLIENT = response;

      if (response != null) {
        if (response.getMessages().getResultCode() == ApiContracts.MessageTypeEnum.OK) {
          if (response.getTransactionResponse().getMessages() != null) {

              // ... do stuff

          }
          else {
            console.log('Failed Transaction.');
            if (response.getTransactionResponse().getErrors() != null) {

              // ... do stuff

            }
          }
        }
        else {
          console.log('Failed Transaction. ');

      }
});


return RESPONSE_FOR_CLIENT;

} catch (error) {
  throw new functions.https.HttpsError('unknown', error);
}

Yes, I know the problem is that the ctrl.execute is a callback function and I'm really confused why authorize.net implemented it this way. The java and python SDKs all run them synchronously so you could easily send the response back to the user.

So, I think there must be a way to return the response, I just don't know how. Thanks.

Marcus Gallegos
  • 1,532
  • 1
  • 16
  • 31
  • 1
    You will have to "promisify" the callback into a new promise that you can return to the client that resolves with the data you want to send. Await will not do anything if their API doesn't return a promise. – Doug Stevenson Nov 02 '19 at 07:35

3 Answers3

1

The problem is that the provided SDK does not return a promise so there is no way to wait for anything to return. Our solution was to ditch the Authorize.net's SDK and build from scratch. Luckily we don't have to consider every endpoint of their API, just the parts we need. I found this question very illuminating for the firebase callable functions piece.

We also raised an issue on the github repository so hopefully we will get the go ahead to make changes on the SDK.

Node/Firebase onCall asynchronous function return

Marcus Gallegos
  • 1,532
  • 1
  • 16
  • 31
0

Use below code. Its working for me.

// MAKE GLOBAL VARIABLE TO HOLD RESPONSE? -> (problems with async callback)
  let RESPONSE_FOR_CLIENT;

    return new Promise(function (resolve, reject) {
  ctrl.execute(function () {

      var apiResponse = ctrl.getResponse();

      var response =  new ApiContracts.CreateTransactionResponse(apiResponse);

      RESPONSE_FOR_CLIENT = response;

      if (response != null) {
        if (response.getMessages().getResultCode() == ApiContracts.MessageTypeEnum.OK) {
          if (response.getTransactionResponse().getMessages() != null) {

              return resolve(response)

          }
          else {
            console.log('Failed Transaction.');
            if (response.getTransactionResponse().getErrors() != null) {

               throw reject(response.getTransactionResponse().getErrors())

            }
          }
        }
        else {
          throw reject(''Error Message)

          }
});
    }); 
Pritesh Mahajan
  • 4,974
  • 8
  • 39
  • 64
  • Code dumps do not make for good answers. You should explain *how* and *why* this solves their problem. I recommend reading, "[How do I write a good answer?"](http://stackoverflow.com/help/how-to-answer) – John Conde Dec 03 '19 at 13:10
0

Pritesh Mahajan's answer was on the right track, but I don't know how it was working for him, as the throws and returns break the code. Here is a more complete example with the ctrl.execute converted into a promise for use with await.

This example also does away with the need for a global variable which should never be used for a transactional value in Node.

I also use the to() function from the togo package that implements the to functionality from the go language, eliminating the need for large try / catch blocks.

This example is a fully working example for a node project using ES6 modules running NodeJS 14 or later.

This is setup for use with Accept.js using the Authorize.Net hosted payment information form to collect the card information in a PCI-DSS SAQ A compliant way.

See https://developer.authorize.net/api/reference/features/acceptjs.html#Using_the_Hosted_Payment_Information_Form for more details.

The only data needed for a successful transaction is the authData and the paymentData. The shipping data is included as an example, if you need any of the other data that the Authorize.NET API accommodates, it would be easy to modify this function to support it.

import authorizenet from "authorizenet";
const ApiContracts = authorizenet.APIContracts;
const ApiControllers = authorizenet.APIControllers;
const SDKConstants = authorizenet.Constants;

function to (promise)
{
    return promise
        .then(val => [null, val])
        .catch(err => [err]);
}

async function chargeCustomer (params = {}) {

    /*
    params = {
        authData: {
            api_login_id,
            transaction_key,
            endpoint
        }
        paymentData: {
            opaqueData: {
                dataDescriptor,
                dataValue
            },
            amount
        }
        shipTo: {
            firstName,
            lastName,
            company,
            address,
            city,
            state,
            zip,
            country
        }
    }
    */

    if ((!params?.authData?.api_login_id) || (!params?.authData?.transaction_key)) {
        throw "missing credentials";
    }

    let merchantAuthenticationType = new ApiContracts.MerchantAuthenticationType();
    merchantAuthenticationType.setName(params.authData.api_login_id);
    merchantAuthenticationType.setTransactionKey(params.authData.transaction_key);

    let opaqueData = new ApiContracts.OpaqueDataType();
    opaqueData.setDataDescriptor(params.opaqueData.dataDescriptor);
    opaqueData.setDataValue(params.opaqueData.dataValue);

    let paymentType = new ApiContracts.PaymentType();
    paymentType.setOpaqueData(opaqueData);

    let shipTo = new ApiContracts.CustomerAddressType();
    shipTo.setFirstName(params?.shipTo?.firstName || null);
    shipTo.setLastName(params?.shipTo?.last_name || null);
    shipTo.setCompany(params?.shipTo?.company || null);
    shipTo.setAddress(params?.shipTo?.address || null);
    shipTo.setCity(params?.shipTo?.city || null);
    shipTo.setState(params?.shipTo?.state || null);
    shipTo.setZip(params?.shipTo?.zip || null);
    shipTo.setCountry(params?.shipTo?.country || null);

    let transactionRequestType = new ApiContracts.TransactionRequestType();
    transactionRequestType.setTransactionType(ApiContracts.TransactionTypeEnum.AUTHCAPTURETRANSACTION);
    transactionRequestType.setPayment(paymentType);
    transactionRequestType.setAmount(params?.paymentData?.amount);
    transactionRequestType.setShipTo(shipTo);

    let createRequest = new ApiContracts.CreateTransactionRequest();
    createRequest.setMerchantAuthentication(merchantAuthenticationType);
    createRequest.setTransactionRequest(transactionRequestType);

    let ctrl = new ApiControllers.CreateTransactionController(createRequest.getJSON());
    if (params?.authData?.endpoint === "production") {
        ctrl.setEnvironment(SDKConstants.endpoint.production);
    }

    let [err, response] = await to(new Promise((resolve, reject) => {
        ctrl.execute( () => {
            let apiResponse = ctrl.getResponse();

            let response = new ApiContracts.CreateTransactionResponse(apiResponse);

            if (response != null) {
                if (response.getMessages().getResultCode() == ApiContracts.MessageTypeEnum.OK) {
                    if (response.getTransactionResponse().getMessages() != null) {
                        console.log(JSON.stringify(response));
                        resolve(response);
                    } else {
                        console.debug('Failed Transaction.');
                        if (response.getTransactionResponse().getErrors() != null) {
                            reject(response.getTransactionResponse().getErrors())
                        }
                    }
                } else {
                    reject('null response from Authorize.Net');
                }
            };
        });
    }));
    if (err) {
        throw err;
    }
    return response;
}
Three D Fish
  • 144
  • 8