4

This SO answer correctly explains that since the require Node/JS library is not supported by Google Apps Script, the following code changes must be made to get Stripe to work properly in a GAS project:

from
const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
(async () => {
  const product = await stripe.products.create({
    name: 'My SaaS Platform',
    type: 'service',
  });
})();
to
function myFunction() {
  var url = "https://api.stripe.com/v1/products";
  var params = {
    method: "post",
    headers: {Authorization: "Basic " + Utilities.base64Encode("sk_test_4eC39HqLyjWDarjtT1zdp7dc:")},
    payload: {name: "My SaaS Platform", type: "service"}
  };
  var res = UrlFetchApp.fetch(url, params);
  Logger.log(res.getContentText())
}

Now, I want to convert the following code into the Google Apps Script friendly version.

from https://stripe.com/docs/payments/checkout/accept-a-payment#create-checkout-session
const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');

const session = await stripe.checkout.sessions.create({
  payment_method_types: ['card', 'ideal'],
  line_items: [{
    price_data: {
      currency: 'eur',
      product_data: {
        name: 'T-shirt',
      },
      unit_amount: 2000,
    },
    quantity: 1,
  }],
  mode: 'payment',
  success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://example.com/cancel',
});

So, I'm trying the following.

to
function myFunction() {
  var url = "https://api.stripe.com/v1/checkout/sessions";
  var params = {
    method: "post",
    headers: {
      Authorization:
        "Basic " + Utilities.base64Encode("sk_test_4eC39HqLyjWDarjtT1zdp7dc:"),
    },
    payload: {
      payment_method_types: ["card", "ideal"],
      line_items: [
        {
          price_data: {
            currency: "eur",
            product_data: {
              name: "T-shirt",
            },
            unit_amount: 2000,
          },
          quantity: 1,
        },
      ],
      mode: "payment",
      success_url:
        "https://example.com/success?session_id={CHECKOUT_SESSION_ID}",
      cancel_url: "https://example.com/cancel",
    },
  };
  var res = UrlFetchApp.fetch(url, params);
  Logger.log(res.getContentText());
}

However, instead of getting the expected response object returned, I get the following error:

Exception: Request failed for https://api.stripe.com returned code 400. Truncated server response:

Log.error
{
  error: {
    message: "Invalid array",
    param: "line_items",
    type: "invalid_request_error",
  },
}

What am I doing wrong? And how can I generalize the first example? What is the specific documentation I need that I'm not seeing?

Edit:

After stringifying the payload per the suggestion from the comments, now I get the following error:

Exception: Request failed for https://api.stripe.com returned code 400. Truncated server response:

Log.error
{
  "error": {
    "code": "parameter_unknown",
    "doc_url": "https://stripe.com/docs/error-codes/parameter-unknown",
    "message": "Received unknown parameter: {\"payment_method_types\":. Did you mean payment_method_types?",
    "param": "{\"payment_method_types\":",
    "type": "invalid_request_error"
  }
}
Let Me Tink About It
  • 15,156
  • 21
  • 98
  • 207
  • 2
    Stringify the payload – TheMaster Jul 21 '20 at 12:55
  • Link the official page of the documentation you're referring to – TheMaster Jul 21 '20 at 16:23
  • @TheMaster: When I stringify the payload, I get a different error — as described in the edit. That error suggests the stringification is creating the problem. That error is shown in the log and in a popup window of the script editor so there is no link available to a URL. Also, in the first example, there was no need to stringify the payload as the first example produced the expected behavior by returning the requested object from the server. You can test in your script editor with a simple copy and paste. No special setup is required. The API key is public for testing purposes. – Let Me Tink About It Jul 21 '20 at 19:30
  • I mean the page you got sample. Documentation of stripe-api – TheMaster Jul 21 '20 at 19:32
  • @TheMaster: Oh, sorry, here's the second one of them. I also edited the question to include it. https://stripe.com/docs/payments/checkout/accept-a-payment#create-checkout-session I'll look for the other and add it too. – Let Me Tink About It Jul 21 '20 at 19:51
  • See the curl part. That should be how the paylood should look: `payload: { "payment_method_types[]": "card",`... and so on. Research x-www-urlformencoded and see how it looks like. – TheMaster Jul 21 '20 at 20:28
  • I proposed a modified script as an answer. Could you please confirm it? Unfortunately, I cannot test it. So when this modified script didn't work, I apologize. – Tanaike Jul 21 '20 at 22:27
  • For others who might have this question in the future, here's a library/script... https://gist.github.com/lastguest/1fd181a9c9db0550a847 ... although it might need some modification to make it applicable to this situation. – Let Me Tink About It Jul 26 '20 at 08:05

2 Answers2

4

I modified the script mentioned in the comments and now have a one that works for this purpose.

// [ BEGIN ] utilities library

/**
 * Returns encoded form suitable for HTTP POST: x-www-urlformencoded
 * @see code: https://gist.github.com/lastguest/1fd181a9c9db0550a847
 * @see context: https://stackoverflow.com/a/63024022
 * @param { Object } element a data object needing to be encoded
 * @param { String } key not necessary for initial call, but used for recursive call
 * @param { Object } result recursively populated return object
 * @returns { Object } a stringified object
 * @example
 *  `{
        "cancel_url": "https://example.com/cancel",
        "line_items[0][price_data][currency]": "eur",
        "line_items[0][price_data][product_data][name]": "T-shirt",
        "line_items[0][price_data][unit_amount]": "2000",
        "line_items[0][quantity]": "1",
        "mode": "payment",
        "payment_method_types[0]": "card",
        "payment_method_types[1]": "ideal",
        "success_url": "https://example.com/success?session_id={CHECKOUT_SESSION_ID}"
      }`
 */
const json2urlEncoded = ( element, key, result={}, ) => {
  const OBJECT = 'object';
  const typeOfElement = typeof( element );
  if( typeOfElement === OBJECT ){
    for ( const index in element ) {
      const elementParam = element[ index ];
      const keyParam = key ? `${ key }[${ index }]` : index;
      json2urlEncoded( elementParam, keyParam, result, );
    }
  } else {
    result[ key ] = element.toString();
  }
  return result;
}
// // test
// const json2urlEncoded_test = () => {
//   const data = {
//     time : +new Date,
//     users : [
//       { id: 100 , name: 'Alice'   , } ,
//       { id: 200 , name: 'Bob'     , } ,
//       { id: 300 , name: 'Charlie' , } ,
//     ],  
//   };
//   const test = json2urlEncoded( data, );
//   // Logger.log( 'test\n%s', test, );
//   return test;
//   // Output:
//   // users[0][id]=100&users[0][name]=Stefano&users[1][id]=200&users[1][name]=Lucia&users[2][id]=300&users[2][name]=Franco&time=1405014230183
// }
// // quokka
// const test = json2urlEncoded_test();
// const typeOfTest = typeof test;
// typeOfTest
// test

// [ END ] utilities library
Let Me Tink About It
  • 15,156
  • 21
  • 98
  • 207
2

From this question and this sample script, I thought that in this case, the values are required to be sent as the form data. So how about the following modification?

Modified script:

function myFunction() {
  var url = "https://httpbin.org/anything";
  var params = {
    method: "post",
    headers: {Authorization: "Basic " + Utilities.base64Encode("sk_test_4eC39HqLyjWDarjtT1zdp7dc:")},
    payload: {
      "cancel_url": "https://example.com/cancel",
      "line_items[0][price_data][currency]": "eur",
      "line_items[0][price_data][product_data][name]": "T-shirt",
      "line_items[0][price_data][unit_amount]": "2000",
      "line_items[0][quantity]": "1",
      "mode": "payment",
      "payment_method_types[0]": "card",
      "payment_method_types[1]": "ideal",
      "success_url": "https://example.com/success?session_id={CHECKOUT_SESSION_ID}"
    }
  };
  var res = UrlFetchApp.fetch(url, params);
  Logger.log(res.getContentText());  // or console.log(res.getContentText())
}
  • I think that point might be payment_method_types: ["card", "ideal"] is required to be sent as "payment_method_types[0]": "card" and "payment_method_types[1]": "ideal".

References:

Tanaike
  • 181,128
  • 11
  • 97
  • 165
  • Thank you. +1. Do you know of any GAS libraries that will convert a regular JSON object into this type of format? – Let Me Tink About It Jul 21 '20 at 23:29
  • @Let Me Tink About It Thank you for replying. I'm glad your issue was resolved. Unfortunately, I cannot know about it. I'm sorry for this. – Tanaike Jul 21 '20 at 23:30