Files
overleaf-cep/services/web/scripts/stripe/helpers.mjs
Antoine Clausse 9e41d9afdb Add script to create Stripe prices from CSV (#31144)
* Extract currency conversion and rate limiting functions to helpers

* Add script to create prices from CSV for Stripe integration

* Add tests for create prices from CSV script

* Add usage documentation for create_prices_from_csv script

* Add `planCode` metadata

* Temp: Plans CSV example

* Revert "Temp: Plans CSV example"

This reverts commit 810b1ed67052f7a1a0deb20b70f14507a282fcf1.

* Various improvements to price and product creation script

---------

Co-authored-by: Tim Down <158919+timdown@users.noreply.github.com>
GitOrigin-RevId: c015c6dd904db3143781581db4210cef282a4070
2026-02-10 09:05:28 +00:00

131 lines
3.5 KiB
JavaScript

/* eslint-disable @overleaf/require-script-runner */
// This file contains helper functions used by other scripts.
// The scripts that import these helpers should use Script Runner.
/**
* @import Stripe from 'stripe'
* @import { getRegionClient } from '../../modules/subscriptions/app/src/StripeClient.mjs'
*/
/**
* @export
* @typedef {Object} CSVSubscriptionChange
* @property {string} subscription_id
* @property {string} current_lookup_key
* @property {string} new_lookup_key
* @property {string} current_add_on_lookup_key
* @property {string} new_add_on_lookup_key
*/
/**
* @export
* @typedef {'renewal' | 'now'} Timeframe
*/
/**
* @export
* @typedef {ReturnType<typeof getRegionClient>} StripeClient
*/
/**
* Custom error class for reportable errors that should be written to CSV output
*/
export class ReportError extends Error {
/**
* @param {string} status - The error status code for CSV output
* @param {string} message - The error message
*/
constructor(status, message) {
super(message)
this.status = status
}
}
/**
* Gets the product ID from a Stripe Subscription Item
*
* @param {Stripe.SubscriptionItem | Stripe.SubscriptionSchedule.Phase.Item} item
* @returns {string}
*/
export function getProductIdFromItem(item) {
const product =
typeof item.price === 'string'
? null
: 'product' in item.price
? item.price.product
: null
return typeof product === 'string' ? product : (product?.id ?? '')
}
/**
* Gets the price ID from a Stripe Subscription Item
*
* @param {Stripe.SubscriptionItem | Stripe.SubscriptionSchedule.Phase.Item} item
* @returns {string}
*/
export function getPriceIdFromItem(item) {
return typeof item.price === 'string' ? item.price : (item.price?.id ?? '')
}
/**
* Gets the product ID from a Stripe Price object
*
* @param {Stripe.Price} price
* @returns {string}
*/
export function getProductIdFromPrice(price) {
return typeof price.product === 'string'
? price.product
: (price.product?.id ?? '')
}
/**
* Sleep function to respect Stripe rate limits (100 requests per second)
*/
export async function rateLimitSleep() {
return new Promise(resolve => setTimeout(resolve, 50))
}
/**
* Convert amount to minor units (cents for most currencies)
* Some currencies like JPY, KRW, CLP, VND don't have cents
*
* Copied from services/web/frontend/js/shared/utils/currency.ts
*
* @param {number} amount - Amount in major units (dollars, euros, etc.)
* @param {string} currency - Currency code (lowercase)
* @returns {number} Amount in minor units
*/
export function convertToMinorUnits(amount, currency) {
const isNoCentsCurrency = ['clp', 'jpy', 'krw', 'vnd'].includes(
currency.toLowerCase()
)
// Determine the multiplier based on currency
let multiplier = 100 // default for most currencies (2 decimal places)
if (isNoCentsCurrency) {
multiplier = 1 // no decimal places
}
// Convert and round to an integer
return Math.round(amount * multiplier)
}
/**
* Convert amount from minor units (cents for most currencies)
* Some currencies like JPY, KRW, CLP, VND don't have cents
*
* Copied from services/web/modules/subscriptions/app/src/StripeClient.mjs
*
* @param {number} amount - price in the smallest currency unit (e.g. dollar cents, CLP units, ...)
* @param {StripeCurrencyCode} currency - currency code
* @return {number}
*/
export function convertFromMinorUnits(amount, currency) {
const isNoCentsCurrency = ['clp', 'jpy', 'krw', 'vnd'].includes(
currency.toLowerCase()
)
return isNoCentsCurrency ? amount : amount / 100
}