Creates every product in your price list in Stripe and records its shareable payment link on a tab, never twice.

UsesGoogle SheetsStripe

Turn a price list into Stripe payment links

Give it a Google Sheet of what you sell and run it by hand: each line becomes a product in Stripe with a price and a shareable payment link you can send to a customer. Prices are read as everyday figures such as 49.99 and converted to the smallest unit of the currency you name. Nobody is charged until they pay through a link.

Nothing in your price list is ever changed. A 'Payment links' tab is added alongside it and a line is appended for every link published, with the product, price, link and the Stripe ids behind it. A product already on that tab is skipped, so running again never creates a second link for it. The number of links per run is capped so you can check the first link before the rest, a row priced above your ceiling is skipped so a typo cannot go on sale, and a row with no name or usable price is reported and skipped. If a link is created but the tab cannot be written, the run stops at once. If Stripe fails partway through a product, the run is marked as failed and names it, and the next run finishes that product rather than making a second one.

You need Google Sheets and Stripe connected (Stripe's test mode is a safe first try), and you are asked for the sheet's link, the price list tab's name and your currency when you add it. The tab needs a 'Product' column and a 'Price' column.

ValuesSet sheetUrl to blank.Set sheetUrl to blank.
ValuesSet priceListTabName to blank.Set priceListTabName to blank.
ValuesSet currency to blank.Set currency to blank.
ValuesSet paymentLinksTabName to Payment links.Set paymentLinksTabName to Payment links.
ValuesSet maxLinksThisRun to 1.Set maxLinksThisRun to 1.
ValuesSet maxPricePerItem to 1000.Set maxPricePerItem to 1000.
TextMake currency lowercase and save it as currencyCode.Make currency lowercase and save it as currencyCode.
Decisions & LoopsIf the length of currencyCode is not 3If the length of currencyCode is not 3
Yes
Decisions & LoopsStop and report a problem: The 'currency' step must hold a three-letter currency code such as usd, eur or gbp - not a currency symbol or a full name.Stop and report a problem: The 'currency' step must hold a three-letter currency code such as usd, eur or gbp - not a currency symbol or a full name.
No
No steps
ListsCreate a list of [bif, clp, djf, gnf, jpy, kmf, krw, mga, pyg, rwf, ugx, vnd, vuv, xaf, xof, xpf] and save it as zeroDecimalCurrencies.Create a list of [bif, clp, djf, gnf, jpy, kmf, krw, mga, pyg, rwf, ugx, vnd, vuv, xaf, xof, xpf] and save it as zeroDecimalCurrencies.
Decisions & LoopsIf zeroDecimalCurrencies contains currencyCodeIf zeroDecimalCurrencies contains currencyCode
Yes
Decisions & LoopsStop and report a problem: This workflow reads prices written the everyday way (49.99) and multiplies by 100 to give Stripe the amount in cents. That is wrong for currencyCode, which has no smaller unit, so every payment link would ask for a hundred times the price. Nothing has been created.Stop and report a problem: This workflow reads prices written the everyday way (49.99) and multiplies by 100 to give Stripe the amount in cents. That is wrong for currencyCode, which has no smaller unit, so every payment link would ask for a hundred times the price. Nothing has been created.
No
No steps
MathRound maxPricePerItem multiplied by 100 to the nearest integer and save it as maxAmountInSmallestUnit.Round maxPricePerItem multiplied by 100 to the nearest integer and save it as maxAmountInSmallestUnit.
ValuesSet linksTabIsNew to true.Set linksTabIsNew to true.
Decisions & LoopsIf a step below fails, save the error as createLinksTabErrorIf a step below fails, save the error as createLinksTabError
Try
Google SheetsGoogle SheetsCreate the tab paymentLinksTabName in sheetUrl.Create the tab paymentLinksTabName in sheetUrl.
On error
ValuesChange linksTabIsNew to false.Change linksTabIsNew to false.
Decisions & LoopsIf linksTabIsNew is yesIf linksTabIsNew is yes
Yes
Google SheetsGoogle SheetsAdd a row to paymentLinksTabName in sheetUrl.Add a row to paymentLinksTabName in sheetUrl.
No
No steps
Google SheetsGoogle SheetsRead column 'A' of paymentLinksTabName in sheetUrl. Save it as publishedLog.Read column 'A' of paymentLinksTabName in sheetUrl. Save it as publishedLog.
ValuesSet alreadyPublished to an empty list.Set alreadyPublished to an empty list.
Decisions & LoopsFor each loggedProduct in publishedLog.values:For each loggedProduct in publishedLog.values:
Decisions & LoopsIf a step below fails, save the error as loggedProductErrorIf a step below fails, save the error as loggedProductError
Try
ListsAdd loggedProduct in lowercase to the end of alreadyPublished and save it as alreadyPublished.Add loggedProduct in lowercase to the end of alreadyPublished and save it as alreadyPublished.
On error
ValuesShow in the output: Ignored a cell in the '@paymentLinksTabName' tab that is not text.Show in the output: Ignored a cell in the '@paymentLinksTabName' tab that is not text.
ValuesSet publishedThisRun to 0.Set publishedThisRun to 0.
ValuesSet skippedRows to 0.Set skippedRows to 0.
ValuesSet alreadyDoneRows to 0.Set alreadyDoneRows to 0.
Stored ValuesGet the stored value 'stripe_products_awaiting_link'. Save it as rememberedProducts.Get the stored value 'stripe_products_awaiting_link'. Save it as rememberedProducts.
TextUse rememberedProducts.value, or [] if it is empty and save it as productsAwaitingLink.Use rememberedProducts.value, or [] if it is empty and save it as productsAwaitingLink.
ValuesSet stripeFailures to an empty list.Set stripeFailures to an empty list.
Google SheetsGoogle SheetsRead the rows of sheetUrl. Save it as priceList.Read the rows of sheetUrl. Save it as priceList.
Decisions & LoopsFor each row in priceList.rows:For each row in priceList.rows:
Decisions & LoopsIf publishedThisRun is greater than or equal to maxLinksThisRunIf publishedThisRun is greater than or equal to maxLinksThisRun
Yes
Decisions & LoopsStop repeating.Stop repeating.
No
No steps
ValuesSet rowLabel to row row.row_number as text.Set rowLabel to row row.row_number as text.
ValuesSet productName to blank.Set productName to blank.
Decisions & LoopsIf a step below fails, save the error as productNameErrorIf a step below fails, save the error as productNameError
Try
TextTrim row.Product and save it as productName.Trim row.Product and save it as productName.
On error
ValuesChange productName to blank.Change productName to blank.
Decisions & LoopsIf productName is emptyIf productName is empty
Yes
ValuesShow in the output: Skipped rowLabel: the Product column is empty.Show in the output: Skipped rowLabel: the Product column is empty.
ValuesAdd 1 to skippedRows.Add 1 to skippedRows.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
TextMake productName lowercase and save it as productKey.Make productName lowercase and save it as productKey.
Decisions & LoopsIf alreadyPublished contains productKeyIf alreadyPublished contains productKey
Yes
ValuesAdd 1 to alreadyDoneRows.Add 1 to alreadyDoneRows.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet amountInSmallestUnit to 0.Set amountInSmallestUnit to 0.
Decisions & LoopsIf a step below fails, save the error as priceErrorIf a step below fails, save the error as priceError
Try
MathRound row.Price multiplied by 100 to the nearest integer and save it as amountInSmallestUnit.Round row.Price multiplied by 100 to the nearest integer and save it as amountInSmallestUnit.
On error
ValuesChange amountInSmallestUnit to 0.Change amountInSmallestUnit to 0.
Decisions & LoopsIf amountInSmallestUnit is less than or equal to 0If amountInSmallestUnit is less than or equal to 0
Yes
ValuesShow in the output: Skipped rowLabel: the Price column must hold a number above 0, written as a plain figure like 49.99 with no currency symbol.Show in the output: Skipped rowLabel: the Price column must hold a number above 0, written as a plain figure like 49.99 with no currency symbol.
ValuesAdd 1 to skippedRows.Add 1 to skippedRows.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
Decisions & LoopsIf amountInSmallestUnit is greater than maxAmountInSmallestUnitIf amountInSmallestUnit is greater than maxAmountInSmallestUnit
Yes
ValuesShow in the output: Skipped rowLabel: the price is above the 'maxPricePerItem' ceiling of maxPricePerItem as text. Check it is not a typo, then raise the ceiling.Show in the output: Skipped rowLabel: the price is above the 'maxPricePerItem' ceiling of maxPricePerItem as text. Check it is not a typo, then raise the ceiling.
ValuesAdd 1 to skippedRows.Add 1 to skippedRows.
Decisions & LoopsSkip to the next item.Skip to the next item.
No
No steps
ValuesSet stripeProductId to blank.Set stripeProductId to blank.
ListsKeep each item in productsAwaitingLink where item.name is productKey and save it as earlierProduct.Keep each item in productsAwaitingLink where item.name is productKey and save it as earlierProduct.
Decisions & LoopsIf earlierProduct isn't emptyIf earlierProduct isn't empty
Yes
ValuesChange stripeProductId to earlierProduct[0].id.Change stripeProductId to earlierProduct[0].id.
No
Decisions & LoopsIf a step below fails, save the error as createProductErrorIf a step below fails, save the error as createProductError
Try
TextTrim the id from the Stripe “Create a product” step and save it as stripeProductId.Trim the id from the Stripe “Create a product” step and save it as stripeProductId.
On error
ValuesShow in the output: Skipped rowLabel: Stripe did not create a product for it. createProductError as text.Show in the output: Skipped rowLabel: Stripe did not create a product for it. createProductError as text.
ListsAdd productName to the end of stripeFailures and save it as stripeFailures.Add productName to the end of stripeFailures and save it as stripeFailures.
Decisions & LoopsSkip to the next item.Skip to the next item.
ListsAdd a record of name and id to the end of productsAwaitingLink and save it as productsAwaitingLink.Add a record of name and id to the end of productsAwaitingLink and save it as productsAwaitingLink.
Decisions & LoopsIf a step below fails, save the error as rememberErrorIf a step below fails, save the error as rememberError
Try
Stored ValuesSet the stored value 'stripe_products_awaiting_link' to productsAwaitingLink.Set the stored value 'stripe_products_awaiting_link' to productsAwaitingLink.
On error
Decisions & LoopsStop and report a problem: Stripe made the product '@productName' (stripeProductId) but the workflow could not note it down, so this run stopped before going further. If the next run makes a second product with this name, archive the extra one in Stripe. rememberError.message.Stop and report a problem: Stripe made the product '@productName' (stripeProductId) but the workflow could not note it down, so this run stopped before going further. If the next run makes a second product with this name, archive the extra one in Stripe. rememberError.message.
ValuesSet stripePriceId to blank.Set stripePriceId to blank.
Decisions & LoopsIf a step below fails, save the error as createPriceErrorIf a step below fails, save the error as createPriceError
Try
TextTrim the id from the Stripe “Create a price” step and save it as stripePriceId.Trim the id from the Stripe “Create a price” step and save it as stripePriceId.
On error
ValuesShow in the output: Skipped rowLabel: Stripe did not set a price on it. createPriceError as text.Show in the output: Skipped rowLabel: Stripe did not set a price on it. createPriceError as text.
ListsAdd productName to the end of stripeFailures and save it as stripeFailures.Add productName to the end of stripeFailures and save it as stripeFailures.
Decisions & LoopsSkip to the next item.Skip to the next item.
ValuesSet paymentLink to blank.Set paymentLink to blank.
Decisions & LoopsIf a step below fails, save the error as createLinkErrorIf a step below fails, save the error as createLinkError
Try
TextTrim the url from the Stripe “Create a payment link” step and save it as paymentLink.Trim the url from the Stripe “Create a payment link” step and save it as paymentLink.
On error
ValuesShow in the output: Skipped rowLabel: Stripe did not return a payment link. createLinkError as text.Show in the output: Skipped rowLabel: Stripe did not return a payment link. createLinkError as text.
ListsAdd productName to the end of stripeFailures and save it as stripeFailures.Add productName to the end of stripeFailures and save it as stripeFailures.
Decisions & LoopsSkip to the next item.Skip to the next item.
MathSet priceAsWritten to amountInSmallestUnit divided by 100.Set priceAsWritten to amountInSmallestUnit divided by 100.
Decisions & LoopsIf a step below fails, save the error as logWriteErrorIf a step below fails, save the error as logWriteError
Try
Google SheetsGoogle SheetsAdd a row to paymentLinksTabName in sheetUrl.Add a row to paymentLinksTabName in sheetUrl.
On error
Decisions & LoopsStop and report a problem: A payment link was created for '@productName' but it could not be written to the '@paymentLinksTabName' tab, so this run has stopped rather than carry on unable to tell what it has already published. The link is paymentLink - paste it into that tab by hand, then run this again.Stop and report a problem: A payment link was created for '@productName' but it could not be written to the '@paymentLinksTabName' tab, so this run has stopped rather than carry on unable to tell what it has already published. The link is paymentLink - paste it into that tab by hand, then run this again.
ListsAdd productKey to the end of alreadyPublished and save it as alreadyPublished.Add productKey to the end of alreadyPublished and save it as alreadyPublished.
ListsKeep each item in productsAwaitingLink where item.name is not productKey and save it as productsAwaitingLink.Keep each item in productsAwaitingLink where item.name is not productKey and save it as productsAwaitingLink.
ValuesAdd 1 to publishedThisRun.Add 1 to publishedThisRun.
ValuesShow in the output: Published a payment link for '@productName': paymentLink.Show in the output: Published a payment link for '@productName': paymentLink.
Stored ValuesSet the stored value 'stripe_products_awaiting_link' to productsAwaitingLink.Set the stored value 'stripe_products_awaiting_link' to productsAwaitingLink.
ValuesSet summary to Published publishedThisRun as text new payment link(s). Skipped skippedRows as text row(s). alreadyDoneRows as text product(s) already had a link.Set summary to Published publishedThisRun as text new payment link(s). Skipped skippedRows as text row(s). alreadyDoneRows as text product(s) already had a link.
Decisions & LoopsIf publishedThisRun is greater than or equal to maxLinksThisRunIf publishedThisRun is greater than or equal to maxLinksThisRun
Yes
ValuesChange summary to summary This run stopped at the 'maxLinksThisRun' limit - raise it and run again to carry on down the list.Change summary to summary This run stopped at the 'maxLinksThisRun' limit - raise it and run again to carry on down the list.
No
No steps
Decisions & LoopsIf stripeFailures isn't emptyIf stripeFailures isn't empty
Yes
TextJoin stripeFailures with , and save it as failedProducts.Join stripeFailures with , and save it as failedProducts.
Decisions & LoopsStop and report a problem: summary Stripe failed for: failedProducts. Run it again to retry them; a product Stripe already made is reused, not made twice.Stop and report a problem: summary Stripe failed for: failedProducts. Run it again to retry them; a product Stripe already made is reused, not made twice.
No
No steps
Decisions & LoopsFinish the run: summary.Finish the run: summary.