From b9b3e6c26b330d083d2724750d8dc73e03a0d33a Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Sun, 18 May 2025 00:22:47 +0700 Subject: [PATCH] feat: WIP stripe billing refactor update for 2025 --- .../consumers/admin-get-consumer-by-token.ts | 8 +- apps/api/src/api-v1/index.ts | 69 ++++++---- apps/api/src/db/schema/project.ts | 14 +-- apps/api/src/db/schema/types.ts | 49 ++++---- apps/api/src/db/schema/utils.ts | 19 +-- .../src/lib/billing/upsert-stripe-pricing.ts | 118 +++++++++--------- 6 files changed, 153 insertions(+), 124 deletions(-) diff --git a/apps/api/src/api-v1/consumers/admin-get-consumer-by-token.ts b/apps/api/src/api-v1/consumers/admin-get-consumer-by-token.ts index d9d339a6..4331a0a4 100644 --- a/apps/api/src/api-v1/consumers/admin-get-consumer-by-token.ts +++ b/apps/api/src/api-v1/consumers/admin-get-consumer-by-token.ts @@ -13,9 +13,9 @@ import { assert, parseZodSchema } from '@/lib/utils' import { consumerTokenParamsSchema, populateConsumerSchema } from './schemas' const route = createRoute({ - description: 'Gets a consumer', - tags: ['consumers'], - operationId: 'getConsumer', + description: 'Gets a consumer by API token', + tags: ['admin', 'consumers'], + operationId: 'adminGetConsumerByToken', method: 'get', path: 'admin/consumers/tokens/{token}', security: openapiAuthenticatedSecuritySchemas, @@ -51,7 +51,7 @@ export function registerV1AdminConsumersGetConsumerByToken( ...Object.fromEntries(populate.map((field) => [field, true])) } }) - assert(consumer, 404, `Consumer token not found "${token}"`) + assert(consumer, 404, `API token not found "${token}"`) return c.json(parseZodSchema(schema.consumerSelectSchema, consumer)) }) diff --git a/apps/api/src/api-v1/index.ts b/apps/api/src/api-v1/index.ts index 6870d0c9..a2aa93dd 100644 --- a/apps/api/src/api-v1/index.ts +++ b/apps/api/src/api-v1/index.ts @@ -43,34 +43,34 @@ apiV1.openAPIRegistry.registerComponent('securitySchemes', 'Bearer', { }) // Public routes -const pub = new OpenAPIHono() +const publicRouter = new OpenAPIHono() // Private, authenticated routes -const pri = new OpenAPIHono() +const privateRouter = new OpenAPIHono() -registerHealthCheck(pub) +registerHealthCheck(publicRouter) // Users crud -registerV1UsersGetUser(pri) -registerV1UsersUpdateUser(pri) +registerV1UsersGetUser(privateRouter) +registerV1UsersUpdateUser(privateRouter) // Teams crud -registerV1TeamsCreateTeam(pri) -registerV1TeamsListTeams(pri) -registerV1TeamsGetTeam(pri) -registerV1TeamsDeleteTeam(pri) -registerV1TeamsUpdateTeam(pri) +registerV1TeamsCreateTeam(privateRouter) +registerV1TeamsListTeams(privateRouter) +registerV1TeamsGetTeam(privateRouter) +registerV1TeamsDeleteTeam(privateRouter) +registerV1TeamsUpdateTeam(privateRouter) // Team members crud -registerV1TeamsMembersCreateTeamMember(pri) -registerV1TeamsMembersUpdateTeamMember(pri) -registerV1TeamsMembersDeleteTeamMember(pri) +registerV1TeamsMembersCreateTeamMember(privateRouter) +registerV1TeamsMembersUpdateTeamMember(privateRouter) +registerV1TeamsMembersDeleteTeamMember(privateRouter) // Projects crud -registerV1ProjectsCreateProject(pri) -registerV1ProjectsListProjects(pri) -registerV1ProjectsGetProject(pri) -registerV1ProjectsUpdateProject(pri) +registerV1ProjectsCreateProject(privateRouter) +registerV1ProjectsListProjects(privateRouter) +registerV1ProjectsGetProject(privateRouter) +registerV1ProjectsUpdateProject(privateRouter) // TODO // pub.get('/projects/alias/:alias(.+)', require('./projects').readByAlias) @@ -84,17 +84,42 @@ registerV1ProjectsUpdateProject(pri) // ) // Consumers crud -registerV1ConsumersGetConsumer(pri) +registerV1ConsumersGetConsumer(privateRouter) // Webhook event handlers -registerV1StripeWebhook(pub) +registerV1StripeWebhook(publicRouter) // Admin routes -registerV1AdminConsumersGetConsumerByToken(pri) +registerV1AdminConsumersGetConsumerByToken(privateRouter) // Setup routes and middleware -apiV1.route('/', pub) +apiV1.route('/', publicRouter) apiV1.use(middleware.authenticate) apiV1.use(middleware.team) apiV1.use(middleware.me) -apiV1.route('/', pri) +apiV1.route('/', privateRouter) + +// API route types to be used by Hono's RPC client. +// Should include all routes except for internal and admin routes. +export type ApiRoutes = + | ReturnType + // Users + | ReturnType + | ReturnType + // Teams + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + // Team members + | ReturnType + | ReturnType + | ReturnType + // Projects + | ReturnType + | ReturnType + | ReturnType + | ReturnType + // Consumers + | ReturnType diff --git a/apps/api/src/db/schema/project.ts b/apps/api/src/db/schema/project.ts index fbde2f6f..77ad1d46 100644 --- a/apps/api/src/db/schema/project.ts +++ b/apps/api/src/db/schema/project.ts @@ -94,9 +94,9 @@ export const projects = pgTable( // .notNull(), // Stripe billing Products associated with this project across deployments, - // mapping from PricingPlanMetric **slug** to Stripe Product id. + // mapping from PricingPlanLineItem **slug** to Stripe Product id. // NOTE: This map uses slugs as keys, unlike `_stripePriceIdMap`, because - // Stripe Products are agnostic to the PricingPlanMetric config. This is + // Stripe Products are agnostic to the PricingPlanLineItem config. This is // important for them to be shared across deployments even if the pricing // details change. _stripeProductIdMap: jsonb() @@ -105,16 +105,16 @@ export const projects = pgTable( .notNull(), // Stripe billing Prices associated with this project, mapping from unique - // PricingPlanMetric **hash** to Stripe Price id. + // PricingPlanLineItem **hash** to Stripe Price id. // NOTE: This map uses hashes as keys, because Stripe Prices are dependent - // on the PricingPlanMetric config. This is important for them to be shared + // on the PricingPlanLineItem config. This is important for them to be shared // across deployments even if the pricing details change. _stripePriceIdMap: jsonb().$type().default({}).notNull(), - // Stripe billing Metrics associated with this project, mapping from unique - // PricingPlanMetric **slug** to Stripe Meter id. + // Stripe billing LineItems associated with this project, mapping from unique + // PricingPlanLineItem **slug** to Stripe Meter id. // NOTE: This map uses slugs as keys, unlike `_stripePriceIdMap`, because - // Stripe Products are agnostic to the PricingPlanMetric config. This is + // Stripe Products are agnostic to the PricingPlanLineItem config. This is // important for them to be shared across deployments even if the pricing // details change. _stripeMeterIdMap: jsonb().$type().default({}).notNull(), diff --git a/apps/api/src/db/schema/types.ts b/apps/api/src/db/schema/types.ts index 59a7fd9a..dca4dcf1 100644 --- a/apps/api/src/db/schema/types.ts +++ b/apps/api/src/db/schema/types.ts @@ -89,29 +89,29 @@ export const pricingIntervalSchema = z .openapi('PricingInterval') export type PricingInterval = z.infer -export const pricingPlanMetricHashSchema = z +export const pricingPlanLineItemHashSchema = z .string() .nonempty() - .describe('Internal PricingPlanMetric hash') + .describe('Internal PricingPlanLineItem hash') -export const pricingPlanMetricSlugSchema = z +export const pricingPlanLineItemSlugSchema = z .string() .nonempty() - .describe('PricingPlanMetric slug') + .describe('PricingPlanLineItem slug') export const stripePriceIdMapSchema = z - .record(pricingPlanMetricHashSchema, z.string().describe('Stripe Price id')) - .describe('Map from internal PricingPlanMetric **hash** to Stripe Price id') + .record(pricingPlanLineItemHashSchema, z.string().describe('Stripe Price id')) + .describe('Map from internal PricingPlanLineItem **hash** to Stripe Price id') .openapi('StripePriceIdMap') export type StripePriceIdMap = z.infer export const stripeMeterIdMapSchema = z - .record(pricingPlanMetricHashSchema, z.string().describe('Stripe Meter id')) - .describe('Map from internal PricingPlanMetric **slug** to Stripe Meter id') + .record(pricingPlanLineItemHashSchema, z.string().describe('Stripe Meter id')) + .describe('Map from internal PricingPlanLineItem **slug** to Stripe Meter id') .openapi('StripeMeterIdMap') export type StripeMeterIdMap = z.infer -const commonPricingPlanMetricSchema = z.object({ +const commonPricingPlanLineItemSchema = z.object({ /** * Slugs act as the primary key for metrics. They should be lower and * kebab-cased ("base", "requests", "image-transformations"). @@ -132,21 +132,21 @@ const commonPricingPlanMetricSchema = z.object({ }) /** - * PricingPlanMetrics represent a single line-item in a Stripe Subscription. + * PricingPlanLineItems represent a single line-item in a Stripe Subscription. * * They map to a Stripe billing `Price` and possibly a corresponding Stripe - * `Metric` for metered usage. + * `Meter` for metered usage. */ -export const pricingPlanMetricSchema = z +export const pricingPlanLineItemSchema = z .discriminatedUnion('usageType', [ - commonPricingPlanMetricSchema.merge( + commonPricingPlanLineItemSchema.merge( z.object({ usageType: z.literal('licensed'), amount: z.number().nonnegative() }) ), - commonPricingPlanMetricSchema.merge( + commonPricingPlanLineItemSchema.merge( z.object({ usageType: z.literal('metered'), unitLabel: z.string().optional(), @@ -189,7 +189,7 @@ export const pricingPlanMetricSchema = z defaultAggregation: z .object({ /** - * Specifies how events are aggregated for a Stripe Metric. + * Specifies how events are aggregated for a Stripe Meter. * Allowed values are `count` to count the number of events, `sum` * to sum each event's value and `last` to take the last event's * value in the window. @@ -235,14 +235,14 @@ export const pricingPlanMetricSchema = z return data }) .describe( - 'PricingPlanMetrics represent a single line-item in a Stripe Subscription. They map to a Stripe billing `Price` and possibly a corresponding Stripe `Metric` for metered usage.' + 'PricingPlanLineItems represent a single line-item in a Stripe Subscription. They map to a Stripe billing `Price` and possibly a corresponding Stripe `Meter` for metered usage.' ) - .openapi('PricingPlanMetric') -export type PricingPlanMetric = z.infer + .openapi('PricingPlanLineItem') +export type PricingPlanLineItem = z.infer /** * Represents the config for a Stripe subscription with one or more - * PricingPlanMetrics as line-items. + * PricingPlanLineItems as line-items. */ export const pricingPlanSchema = z .object({ @@ -261,7 +261,7 @@ export const pricingPlanSchema = z trialPeriodDays: z.number().nonnegative().optional(), metricsMap: z - .record(pricingPlanMetricSlugSchema, pricingPlanMetricSchema) + .record(pricingPlanLineItemSlugSchema, pricingPlanLineItemSchema) .refine((metricsMap) => { // Stripe Checkout currently supports a max of 20 line items per // subscription. @@ -279,14 +279,17 @@ export const pricingPlanSchema = z return data }) .describe( - 'Represents the config for a Stripe subscription with one or more PricingPlanMetrics as line-items.' + 'Represents the config for a Stripe subscription with one or more PricingPlanLineItems as line-items.' ) .openapi('PricingPlan') export type PricingPlan = z.infer export const stripeProductIdMapSchema = z - .record(pricingPlanMetricSlugSchema, z.string().describe('Stripe Product id')) - .describe('Map from PricingPlanMetric **slug** to Stripe Product id') + .record( + pricingPlanLineItemSlugSchema, + z.string().describe('Stripe Product id') + ) + .describe('Map from PricingPlanLineItem **slug** to Stripe Product id') .openapi('StripeProductIdMap') export type StripeProductIdMap = z.infer diff --git a/apps/api/src/db/schema/utils.ts b/apps/api/src/db/schema/utils.ts index dae9ecec..390d5443 100644 --- a/apps/api/src/db/schema/utils.ts +++ b/apps/api/src/db/schema/utils.ts @@ -19,8 +19,8 @@ import type { RawProject } from '../types' import type { PricingInterval, PricingPlan, - PricingPlanMap, - PricingPlanMetric + PricingPlanLineItem, + PricingPlanMap } from './types' const usernameAndTeamSlugLength = 64 as const @@ -128,18 +128,19 @@ export const { createInsertSchema, createSelectSchema, createUpdateSchema } = }) /** - * Gets the hash used to uniquely map a PricingPlanMetric to its corresponding - * Stripe Price in a stable way across deployments within a project. + * Gets the hash used to uniquely map a PricingPlanLineItem to its + * corresponding Stripe Price in a stable way across deployments within a + * project. * * This hash is used as the key for the `Project._stripePriceIdMap`. */ -export function getPricingPlanMetricHashForStripePrice({ +export function getPricingPlanLineItemHashForStripePrice({ pricingPlan, - pricingPlanMetric, + pricingPlanLineItem, project }: { pricingPlan: PricingPlan - pricingPlanMetric: PricingPlanMetric + pricingPlanLineItem: PricingPlanLineItem project: RawProject }) { // TODO: use pricingPlan.slug as well here? @@ -154,13 +155,13 @@ export function getPricingPlanMetricHashForStripePrice({ // - 'price:requests:' const hash = hashObject({ - ...pricingPlanMetric, + ...pricingPlanLineItem, projectId: project.id, stripeAccountId: project._stripeAccountId, currency: project.pricingCurrency }) - return `price:${pricingPlan.slug}:${pricingPlanMetric.slug}:${hash}` + return `price:${pricingPlan.slug}:${pricingPlanLineItem.slug}:${hash}` } export function getPricingPlansByInterval({ diff --git a/apps/api/src/lib/billing/upsert-stripe-pricing.ts b/apps/api/src/lib/billing/upsert-stripe-pricing.ts index 36292305..6f92f3f6 100644 --- a/apps/api/src/lib/billing/upsert-stripe-pricing.ts +++ b/apps/api/src/lib/billing/upsert-stripe-pricing.ts @@ -4,10 +4,10 @@ import pAll from 'p-all' import { db, eq, type RawDeployment, type RawProject, schema } from '@/db' import { getLabelForPricingInterval, - getPricingPlanMetricHashForStripePrice, + getPricingPlanLineItemHashForStripePrice, getPricingPlansByInterval, type PricingPlan, - type PricingPlanMetric + type PricingPlanLineItem } from '@/db/schema' import { stripe } from '@/lib/stripe' import { assert } from '@/lib/utils' @@ -46,31 +46,31 @@ export async function upsertStripePricing({ : [] let dirty = false - async function upsertStripeResourcesForPricingPlanMetric({ + async function upsertStripeResourcesForPricingPlanLineItem({ pricingPlan, - pricingPlanMetric + pricingPlanLineItem }: { pricingPlan: PricingPlan - pricingPlanMetric: PricingPlanMetric + pricingPlanLineItem: PricingPlanLineItem }) { const { slug: pricingPlanSlug } = pricingPlan - const { slug: pricingPlanMetricSlug } = pricingPlanMetric + const { slug: pricingPlanLineItemSlug } = pricingPlanLineItem // Upsert the Stripe Product - if (!project._stripeProductIdMap[pricingPlanMetricSlug]) { + if (!project._stripeProductIdMap[pricingPlanLineItemSlug]) { const productParams: Stripe.ProductCreateParams = { - name: `${project.id} ${pricingPlanMetricSlug}`, + name: `${project.id} ${pricingPlanLineItemSlug}`, type: 'service', metadata: { projectId: project.id, - pricingPlanMetricSlug + pricingPlanLineItemSlug } } - if (pricingPlanMetric.usageType === 'licensed') { - productParams.unit_label = pricingPlanMetric.label + if (pricingPlanLineItem.usageType === 'licensed') { + productParams.unit_label = pricingPlanLineItem.label } else { - productParams.unit_label = pricingPlanMetric.unitLabel + productParams.unit_label = pricingPlanLineItem.unitLabel } const product = await stripe.products.create( @@ -78,23 +78,23 @@ export async function upsertStripePricing({ ...stripeConnectParams ) - project._stripeProductIdMap[pricingPlanMetricSlug] = product.id + project._stripeProductIdMap[pricingPlanLineItemSlug] = product.id dirty = true } - assert(project._stripeProductIdMap[pricingPlanMetricSlug]) + assert(project._stripeProductIdMap[pricingPlanLineItemSlug]) - if (pricingPlanMetric.usageType === 'metered') { + if (pricingPlanLineItem.usageType === 'metered') { // Upsert the Stripe Meter - if (!project._stripeMeterIdMap[pricingPlanMetricSlug]) { + if (!project._stripeMeterIdMap[pricingPlanLineItemSlug]) { const stripeMeter = await stripe.billing.meters.create( { - display_name: `${project.id} ${pricingPlanMetric.label || pricingPlanMetricSlug}`, - event_name: `meter-${project.id}-${pricingPlanMetricSlug}`, + display_name: `${project.id} ${pricingPlanLineItem.label || pricingPlanLineItemSlug}`, + event_name: `meter-${project.id}-${pricingPlanLineItemSlug}`, // TODO: This currently isn't taken into account for the slug, so if it // changes across deployments, the meter will not be updated. default_aggregation: { - formula: pricingPlanMetric.defaultAggregation?.formula ?? 'sum' + formula: pricingPlanLineItem.defaultAggregation?.formula ?? 'sum' }, customer_mapping: { event_payload_key: 'stripe_customer_id', @@ -107,37 +107,37 @@ export async function upsertStripePricing({ ...stripeConnectParams ) - project._stripeMeterIdMap[pricingPlanMetricSlug] = stripeMeter.id + project._stripeMeterIdMap[pricingPlanLineItemSlug] = stripeMeter.id dirty = true } - assert(project._stripeMeterIdMap[pricingPlanMetricSlug]) + assert(project._stripeMeterIdMap[pricingPlanLineItemSlug]) } else { - assert(pricingPlanMetric.usageType === 'licensed', 400) + assert(pricingPlanLineItem.usageType === 'licensed', 400) assert( - !project._stripeMeterIdMap[pricingPlanMetricSlug], + !project._stripeMeterIdMap[pricingPlanLineItemSlug], 400, - `Invalid pricing plan metric "${pricingPlanMetricSlug}" for pricing plan "${pricingPlanSlug}": licensed pricing plan metrics cannot replace a previous metered pricing plan metric. Use a different pricing plan metric slug for the new licensed plan.` + `Invalid pricing plan metric "${pricingPlanLineItemSlug}" for pricing plan "${pricingPlanSlug}": licensed pricing plan metrics cannot replace a previous metered pricing plan metric. Use a different pricing plan metric slug for the new licensed plan.` ) } - const pricingPlanMetricHashForStripePrice = - getPricingPlanMetricHashForStripePrice({ + const pricingPlanLineItemHashForStripePrice = + getPricingPlanLineItemHashForStripePrice({ pricingPlan, - pricingPlanMetric, + pricingPlanLineItem, project }) // Upsert the Stripe Price - if (!project._stripePriceIdMap[pricingPlanMetricHashForStripePrice]) { + if (!project._stripePriceIdMap[pricingPlanLineItemHashForStripePrice]) { const interval = - pricingPlanMetric.interval ?? project.defaultPricingInterval + pricingPlanLineItem.interval ?? project.defaultPricingInterval const nickname = [ 'price', project.id, - pricingPlanMetricSlug, + pricingPlanLineItemSlug, getLabelForPricingInterval(interval) ] .filter(Boolean) @@ -145,7 +145,7 @@ export async function upsertStripePricing({ const priceParams: Stripe.PriceCreateParams = { nickname, - product: project._stripeProductIdMap[pricingPlanMetricSlug], + product: project._stripeProductIdMap[pricingPlanLineItemSlug], currency: project.pricingCurrency, recurring: { interval, @@ -153,35 +153,35 @@ export async function upsertStripePricing({ // TODO: support this interval_count: 1, - usage_type: pricingPlanMetric.usageType, + usage_type: pricingPlanLineItem.usageType, - meter: project._stripeMeterIdMap[pricingPlanMetricSlug] + meter: project._stripeMeterIdMap[pricingPlanLineItemSlug] }, metadata: { projectId: project.id, - pricingPlanMetricSlug + pricingPlanLineItemSlug } } - if (pricingPlanMetric.usageType === 'licensed') { - priceParams.unit_amount_decimal = pricingPlanMetric.amount.toFixed(12) + if (pricingPlanLineItem.usageType === 'licensed') { + priceParams.unit_amount_decimal = pricingPlanLineItem.amount.toFixed(12) } else { - priceParams.billing_scheme = pricingPlanMetric.billingScheme + priceParams.billing_scheme = pricingPlanLineItem.billingScheme - if (pricingPlanMetric.billingScheme === 'tiered') { + if (pricingPlanLineItem.billingScheme === 'tiered') { assert( - pricingPlanMetric.tiers?.length, + pricingPlanLineItem.tiers?.length, 400, - `Invalid pricing plan metric "${pricingPlanMetricSlug}" for pricing plan "${pricingPlanSlug}": tiered billing schemes must have at least one tier.` + `Invalid pricing plan metric "${pricingPlanLineItemSlug}" for pricing plan "${pricingPlanSlug}": tiered billing schemes must have at least one tier.` ) assert( - !pricingPlanMetric.transformQuantity, + !pricingPlanLineItem.transformQuantity, 400, - `Invalid pricing plan metric "${pricingPlanMetricSlug}" for pricing plan "${pricingPlanSlug}": tiered billing schemes cannot have transformQuantity.` + `Invalid pricing plan metric "${pricingPlanLineItemSlug}" for pricing plan "${pricingPlanSlug}": tiered billing schemes cannot have transformQuantity.` ) - priceParams.tiers_mode = pricingPlanMetric.tiersMode - priceParams.tiers = pricingPlanMetric.tiers!.map((tierData) => { + priceParams.tiers_mode = pricingPlanLineItem.tiersMode + priceParams.tiers = pricingPlanLineItem.tiers!.map((tierData) => { const tier: Stripe.PriceCreateParams.Tier = { up_to: tierData.upTo } @@ -198,28 +198,28 @@ export async function upsertStripePricing({ }) } else { assert( - pricingPlanMetric.billingScheme === 'per_unit', + pricingPlanLineItem.billingScheme === 'per_unit', 400, - `Invalid pricing plan metric "${pricingPlanMetricSlug}" for pricing plan "${pricingPlanSlug}": invalid billing scheme.` + `Invalid pricing plan metric "${pricingPlanLineItemSlug}" for pricing plan "${pricingPlanSlug}": invalid billing scheme.` ) assert( - pricingPlanMetric.unitAmount !== undefined, + pricingPlanLineItem.unitAmount !== undefined, 400, - `Invalid pricing plan metric "${pricingPlanMetricSlug}" for pricing plan "${pricingPlanSlug}": unitAmount is required for per_unit billing schemes.` + `Invalid pricing plan metric "${pricingPlanLineItemSlug}" for pricing plan "${pricingPlanSlug}": unitAmount is required for per_unit billing schemes.` ) assert( - !pricingPlanMetric.tiers, + !pricingPlanLineItem.tiers, 400, - `Invalid pricing plan metric "${pricingPlanMetricSlug}" for pricing plan "${pricingPlanSlug}": per_unit billing schemes cannot have tiers.` + `Invalid pricing plan metric "${pricingPlanLineItemSlug}" for pricing plan "${pricingPlanSlug}": per_unit billing schemes cannot have tiers.` ) priceParams.unit_amount_decimal = - pricingPlanMetric.unitAmount.toFixed(12) + pricingPlanLineItem.unitAmount.toFixed(12) - if (pricingPlanMetric.transformQuantity) { + if (pricingPlanLineItem.transformQuantity) { priceParams.transform_quantity = { - divide_by: pricingPlanMetric.transformQuantity.divideBy, - round: pricingPlanMetric.transformQuantity.round + divide_by: pricingPlanLineItem.transformQuantity.divideBy, + round: pricingPlanLineItem.transformQuantity.round } } } @@ -230,12 +230,12 @@ export async function upsertStripePricing({ ...stripeConnectParams ) - project._stripePriceIdMap[pricingPlanMetricHashForStripePrice] = + project._stripePriceIdMap[pricingPlanLineItemHashForStripePrice] = stripePrice.id dirty = true } - assert(project._stripePriceIdMap[pricingPlanMetricHashForStripePrice]) + assert(project._stripePriceIdMap[pricingPlanLineItemHashForStripePrice]) } const upserts: Array<() => Promise> = [] @@ -259,11 +259,11 @@ export async function upsertStripePricing({ } for (const pricingPlan of Object.values(deployment.pricingPlanMap)) { - for (const pricingPlanMetric of Object.values(pricingPlan.metricsMap)) { + for (const pricingPlanLineItem of Object.values(pricingPlan.metricsMap)) { upserts.push(() => - upsertStripeResourcesForPricingPlanMetric({ + upsertStripeResourcesForPricingPlanLineItem({ pricingPlan, - pricingPlanMetric + pricingPlanLineItem }) ) }