Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Set your Stripe Secret key and Reader ID
- const STRIPE_SECRET_KEY = '';
- const READER_ID = '';
- // Create User Selectable Settings for Table and Select Field
- const config = input.config({
- title: 'Stripe Terminal',
- description: 'This script prompts a Stripe reader to shift to its payment acceptance screen to collect payment.',
- items: [
- input.config.table('posTable', {
- label: 'Point-of-sale (POS) table with your product data.'
- }),
- input.config.table('lineItemsTable', {
- label: 'Table where your order line items will be saved.'
- }),
- input.config.table('ordersTable', {
- label: 'Table where your orders will be saved.'
- }),
- input.config.select('readerAction', {
- label: 'Reader Action',
- description: 'Select a reader action.',
- options: [
- {label: 'Checkout', value: 'checkout'},
- {label: 'Display Cart', value: 'displayCart'},
- {label: 'Cancel', value: 'cancel'},
- ]
- }),
- ]
- });
- // Error message if a key or reader ID is missing
- if (!(STRIPE_SECRET_KEY && READER_ID)) {
- throw new Error('Please make sure to set your Stripe secret key AND Reader ID by editing the script.');
- }
- // Get Point-of-Sale grid view
- const table = config.posTable;
- const view = table.getView('Grid view');
- // Orders and Line Items tables
- const ordersTable = config.ordersTable;
- const lineItemsTable = config.lineItemsTable;
- // Get the customer's order from the Point-of-Sale table
- const order = await getOrder();
- const { readerAction } = config;
- switch(readerAction) {
- case 'checkout':
- checkOrderValidity();
- await checkout(order);
- showOrder(order);
- await resetPosQuantities();
- break;
- case 'displayCart':
- checkOrderValidity();
- await displayCart(order);
- showOrder(order)
- break;
- case 'cancel':
- await resetPosQuantities();
- await resetReader();
- break;
- }
- // ----- Reader actions -----
- /**
- * Checkout the customer by creating a Stripe payment and sending it to the reader
- */
- async function checkout(order) {
- // Calculate the total amount based on what's in the order
- const total = order.reduce((x, y) => x + (y.price * y.quantity), 0);
- // Create a Stripe payment
- const { id: paymentIntentId } = await stripePost('payment_intents', {
- 'amount': total,
- 'currency': 'usd',
- 'capture_method': 'manual',
- 'payment_method_types[]': 'card_present'
- });
- // Write to the Orders table so that we can record the transaction
- const orderRecordId = await createOrderRecord({ paymentIntentId, order, total });
- output.text(`Created payment with ID: ${paymentIntentId} for ${total} in Orders table`);
- // Write to the Line Items table to record the individual items in the order
- await createLineItemsRecord({ orderRecordId, order });
- // Sends the payment to the reader
- const reader = await stripePost(`terminal/readers/${READER_ID}/process_payment_intent`, {
- 'payment_intent': paymentIntentId
- });
- }
- /**
- * Display the customer's order on the Stripe reader so that they can see it.
- * Customers can predip their card at this stage.
- */
- async function displayCart(order) {
- // Calculate the total amount based on what's in the order
- const total = order.reduce((x, y) => x + (y.price * y.quantity), 0);
- const lineItems = getLineItems(order);
- const readerDisplay = await stripePost(`terminal/readers/${READER_ID}/set_reader_display`, {
- 'type': 'cart',
- 'cart[currency]': 'usd',
- 'cart[total]': total,
- ...lineItems,
- })
- output.text("Displaying the order on the reader.");
- }
- /** Resets the Stripe reader to its idle state */
- async function resetReader() {
- return await stripePost(`terminal/readers/${READER_ID}/cancel_action`);
- }
- // ---- Helper for making API requests to Stripe -----
- /**
- * Wrapper for using fetch to interact with the Stripe API
- */
- async function stripePost(api, data) {
- const urlencodedData = new URLSearchParams(data);
- const response = await fetch(`https://api.stripe.com/v1/${api}`, {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- "Authorization": `Bearer ${STRIPE_SECRET_KEY}`
- },
- body: urlencodedData
- });
- const json = await response.json();
- return response.ok ? json : Promise.reject(json);
- }
- /**
- * Make a lineItems object out of an order for setting the reader display
- * with the Terminal set_reader_display API endpoint.
- */
- function getLineItems(order) {
- let lineItems = {}
- order.forEach((item, index) => {
- const cartAmount = `cart[line_items][${index}][amount]`;
- const cartDescription = `cart[line_items][${index}][description]`;
- const cartQuantity = `cart[line_items][${index}][quantity]`;
- lineItems = {
- ...lineItems,
- [cartAmount]: item.price,
- [cartDescription]: item.product,
- [cartQuantity]: item.quantity,
- }
- });
- return lineItems;
- }
- // ---- Helpers for managing the in-person cart (order) ----
- /**
- * Gets an order based on what's selected in the POS table.
- * It only adds products that have a quantity greater than one
- */
- async function getOrder() {
- let result = await view.selectRecordsAsync({ fields: ["Product", "Amount", "Quantity", "Record ID (from Products)"] });
- let order = result.records.filter((x) => x.getCellValue("Quantity") > 0).map((x) => {
- return {
- id: x.getCellValueAsString("Record ID (from Products)"),
- product: x.getCellValueAsString("Product"),
- price: x.getCellValue("Amount"),
- quantity: x.getCellValue("Quantity"),
- }
- })
- return order;
- }
- /**
- * Calculate the total
- */
- function calculateTotal(order) {
- return order.reduce((x, y) => (x.price * x.quantity) + y, 0);
- }
- /**
- * Show a table of the order in the Airtable scripts box.
- */
- function showOrder(order) {
- const orderSummary = order.map(({ id, ...otherProps }) => otherProps);
- output.table(orderSummary);
- }
- // ---- Helpers for adding, deleting, modifying Airtable records ---
- /**
- * Write the order to the Orders table
- */
- async function createOrderRecord({ paymentIntentId, order, total }) {
- const orderRecord = await ordersTable.createRecordAsync({
- "ID": paymentIntentId,
- "Status": {
- "name": "Created"
- }
- });
- return orderRecord;
- }
- /**
- * Write the line items to the Line items table
- */
- async function createLineItemsRecord({ orderRecordId, order }) {
- const lineItems = order.map((x) => {
- return {
- fields: {
- "Order": [{ "id": orderRecordId}],
- "Product": [{"id": x.id}],
- "Quantity": x.quantity,
- }
- }
- });
- const lineItemsRecords = await lineItemsTable.createRecordsAsync(lineItems);
- return lineItemsRecords;
- }
- /**
- * Resets all Point-of-Sale items to have 0 quantity
- */
- async function resetPosQuantities() {
- let result = await view.selectRecordsAsync({ fields: ["Quantity"] });
- const recordsReset = result.records.map((x) => {
- return {
- id: x.id,
- fields: {
- "Quantity": 0
- }
- }
- });
- await table.updateRecordsAsync(recordsReset);
- }
- function checkOrderValidity() {
- // Make sure there are actually things in the order
- if (order.length == 0 ) {
- 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');
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment