automateallthethings

Main dashboard

Jul 8th, 2022
576
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Set your Stripe Secret key and Reader ID
  2. const STRIPE_SECRET_KEY = '';
  3. const READER_ID = '';
  4.  
  5. // Create User Selectable Settings for Table and Select Field
  6. const config = input.config({
  7.   title: 'Stripe Terminal',
  8.   description: 'This script prompts a Stripe reader to shift to its payment acceptance screen to collect payment.',
  9.   items: [
  10.       input.config.table('posTable', {
  11.           label: 'Point-of-sale (POS) table with your product data.'
  12.       }),
  13.       input.config.table('lineItemsTable', {
  14.           label: 'Table where your order line items will be saved.'
  15.       }),
  16.       input.config.table('ordersTable', {
  17.           label: 'Table where your orders will be saved.'
  18.       }),
  19.       input.config.select('readerAction', {
  20.         label: 'Reader Action',
  21.         description: 'Select a reader action.',
  22.         options: [
  23.             {label: 'Checkout', value: 'checkout'},
  24.             {label: 'Display Cart', value: 'displayCart'},
  25.             {label: 'Cancel', value: 'cancel'},
  26.         ]
  27.     }),
  28.   ]
  29. });
  30.  
  31. // Error message if a key or reader ID is missing
  32. if (!(STRIPE_SECRET_KEY && READER_ID)) {
  33.     throw new Error('Please make sure to set your Stripe secret key AND Reader ID by editing the script.');
  34. }
  35.  
  36. // Get Point-of-Sale grid view
  37. const table = config.posTable;
  38. const view = table.getView('Grid view');
  39.  
  40. // Orders and Line Items tables
  41. const ordersTable = config.ordersTable;
  42. const lineItemsTable  = config.lineItemsTable;
  43.  
  44. // Get the customer's order from the Point-of-Sale table
  45. const order = await getOrder();
  46.  
  47. const { readerAction } = config;
  48. switch(readerAction) {
  49.     case 'checkout':
  50.         checkOrderValidity();
  51.         await checkout(order);
  52.         showOrder(order);
  53.         await resetPosQuantities();
  54.         break;
  55.     case 'displayCart':
  56.         checkOrderValidity();
  57.         await displayCart(order);
  58.         showOrder(order)
  59.         break;
  60.     case 'cancel':
  61.         await resetPosQuantities();
  62.         await resetReader();
  63.         break;
  64. }
  65.  
  66.  
  67. // ----- Reader actions -----
  68.  
  69. /**
  70.  * Checkout the customer by creating a Stripe payment and sending it to the reader
  71.  */
  72. async function checkout(order) {
  73.     // Calculate the total amount based on what's in the order
  74.     const total = order.reduce((x, y) => x + (y.price * y.quantity), 0);
  75.    
  76.     // Create a Stripe payment
  77.     const { id: paymentIntentId } = await stripePost('payment_intents', {
  78.         'amount': total,
  79.         'currency': 'usd',
  80.         'capture_method': 'manual',
  81.         'payment_method_types[]': 'card_present'
  82.     });
  83.  
  84.     // Write to the Orders table so that we can record the transaction
  85.     const orderRecordId = await createOrderRecord({ paymentIntentId, order, total });
  86.     output.text(`Created payment with ID: ${paymentIntentId} for ${total} in Orders table`);
  87.  
  88.     // Write to the Line Items table to record the individual items in the order
  89.     await createLineItemsRecord({ orderRecordId, order });
  90.  
  91.     // Sends the payment to the reader
  92.     const reader = await stripePost(`terminal/readers/${READER_ID}/process_payment_intent`, {
  93.         'payment_intent': paymentIntentId
  94.     });
  95. }
  96.  
  97. /**
  98.  * Display the customer's order on the Stripe reader so that they can see it.
  99.  * Customers can predip their card at this stage.
  100.  */
  101. async function displayCart(order) {
  102.     // Calculate the total amount based on what's in the order
  103.     const total = order.reduce((x, y) => x + (y.price * y.quantity), 0);
  104.     const lineItems = getLineItems(order);
  105.     const readerDisplay = await stripePost(`terminal/readers/${READER_ID}/set_reader_display`, {
  106.         'type': 'cart',
  107.         'cart[currency]': 'usd',
  108.         'cart[total]': total,
  109.         ...lineItems,
  110.     })
  111.     output.text("Displaying the order on the reader.");
  112. }
  113.  
  114. /**  Resets the Stripe reader to its idle state */
  115. async function resetReader() {
  116.     return await stripePost(`terminal/readers/${READER_ID}/cancel_action`);
  117. }
  118.  
  119. // ---- Helper for making API requests to Stripe -----
  120.  
  121. /**
  122.  * Wrapper for using fetch to interact with the Stripe API
  123.  */
  124. async function stripePost(api, data) {
  125.     const urlencodedData = new URLSearchParams(data);
  126.     const response = await fetch(`https://api.stripe.com/v1/${api}`, {
  127.         method: "POST",
  128.         headers: {
  129.             "Content-Type": "application/x-www-form-urlencoded",
  130.             "Authorization": `Bearer ${STRIPE_SECRET_KEY}`
  131.         },
  132.         body: urlencodedData
  133.     });
  134.     const json = await response.json();
  135.     return response.ok ? json : Promise.reject(json);
  136. }
  137.  
  138. /**
  139.  * Make a lineItems object out of an order for setting the reader display
  140.  * with the Terminal set_reader_display API endpoint.
  141.  */
  142. function getLineItems(order) {
  143.     let  lineItems = {}
  144.     order.forEach((item, index) => {
  145.         const cartAmount = `cart[line_items][${index}][amount]`;
  146.         const cartDescription = `cart[line_items][${index}][description]`;
  147.         const cartQuantity = `cart[line_items][${index}][quantity]`;
  148.         lineItems = {
  149.             ...lineItems,
  150.             [cartAmount]: item.price,
  151.             [cartDescription]: item.product,
  152.             [cartQuantity]: item.quantity,
  153.         }
  154.     });
  155.     return lineItems;  
  156. }
  157.  
  158. // ---- Helpers for managing the in-person cart (order) ----
  159.  
  160. /**
  161.  * Gets an order based on what's selected in the POS table.
  162.  * It only adds products that have a quantity greater than one
  163.  */
  164. async function getOrder() {
  165.     let result = await view.selectRecordsAsync({ fields: ["Product", "Amount", "Quantity", "Record ID (from Products)"] });
  166.    
  167.     let order = result.records.filter((x) => x.getCellValue("Quantity") > 0).map((x) => {
  168.         return {
  169.             id: x.getCellValueAsString("Record ID (from Products)"),
  170.             product: x.getCellValueAsString("Product"),
  171.             price: x.getCellValue("Amount"),
  172.             quantity: x.getCellValue("Quantity"),
  173.         }
  174.     })
  175.     return order;
  176. }
  177.  
  178. /**
  179.  * Calculate the total
  180.  */
  181. function calculateTotal(order) {
  182.     return order.reduce((x, y) =>  (x.price * x.quantity) + y, 0);
  183. }
  184.  
  185. /**
  186.  * Show a table of the order in the Airtable scripts box.
  187.  */
  188. function showOrder(order) {
  189.     const orderSummary = order.map(({ id, ...otherProps }) => otherProps);
  190.     output.table(orderSummary);
  191. }
  192.  
  193. // ---- Helpers for adding, deleting, modifying Airtable records ---
  194.  
  195. /**
  196.  * Write the order to the Orders table
  197.  */
  198. async function createOrderRecord({ paymentIntentId, order, total }) {
  199.     const orderRecord = await ordersTable.createRecordAsync({
  200.         "ID": paymentIntentId,
  201.         "Status": {
  202.             "name": "Created"
  203.         }
  204.     });
  205.     return orderRecord;
  206. }
  207.  
  208. /**
  209.  * Write the line items to the Line items table
  210.  */
  211. async function createLineItemsRecord({ orderRecordId, order }) {
  212.     const lineItems = order.map((x) => {
  213.         return {
  214.             fields: {
  215.                 "Order": [{ "id": orderRecordId}],
  216.                 "Product": [{"id": x.id}],
  217.                 "Quantity": x.quantity,
  218.             }
  219.         }
  220.     });  
  221.     const lineItemsRecords = await lineItemsTable.createRecordsAsync(lineItems);
  222.     return lineItemsRecords;
  223. }
  224.  
  225.  
  226. /**
  227.  * Resets all Point-of-Sale items to have 0 quantity
  228.  */
  229. async function resetPosQuantities() {
  230.     let result = await view.selectRecordsAsync({ fields: ["Quantity"] });
  231.     const recordsReset = result.records.map((x) => {
  232.         return {
  233.             id: x.id,
  234.             fields: {
  235.                 "Quantity": 0
  236.             }
  237.         }
  238.     });
  239.     await table.updateRecordsAsync(recordsReset);
  240. }
  241.  
  242. function checkOrderValidity() {
  243.     // Make sure there are actually things in the order
  244.     if (order.length == 0 ) {
  245.         throw new Error('Please make sure you have items with a quantity greater than 0 in your POS table before using the checkout or display cart action');
  246.     }
  247. }
  248.  
Advertisement
Add Comment
Please, Sign In to add comment