Skip to content

Commit 2c7e8b5

Browse files
committed
feat(stripe): implement replaceProductPrice function to manage default price updates
1 parent d44d3a7 commit 2c7e8b5

2 files changed

Lines changed: 54 additions & 8 deletions

File tree

app/(authenticated)/services/actions.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import { cadStringToCents } from "@/lib/money";
1010
import {
1111
createPrice,
1212
createProduct,
13-
deactivatePrices,
14-
listActivePriceIds,
13+
getStripeServiceData,
14+
replaceProductPrice,
1515
updateProduct,
1616
} from "@/lib/stripe";
1717

@@ -322,11 +322,10 @@ export async function updateService(
322322
});
323323

324324
if (cents !== undefined) {
325-
const { priceId } = await createPrice(row.stripeProductId, cents);
326-
const oldPriceIds = (await listActivePriceIds(row.stripeProductId)).filter(
327-
(id) => id !== priceId,
328-
);
329-
await deactivatePrices(oldPriceIds);
325+
const current = await getStripeServiceData(row.stripeProductId);
326+
if (current?.priceCents !== cents) {
327+
await replaceProductPrice(row.stripeProductId, cents);
328+
}
330329
}
331330

332331
const dbPatch: Partial<typeof services.$inferInsert> = {};

lib/stripe.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,57 @@ export async function createPrice(
171171
unit_amount: amountCents,
172172
currency: "cad",
173173
});
174-
await stripe.products.update(productId, { default_price: price.id });
174+
175175
return { priceId: price.id };
176176
}
177177

178+
export async function replaceProductPrice(
179+
productId: string,
180+
amountCents: number,
181+
): Promise<{ priceId: string }> {
182+
// get the current default price for the product
183+
const product = await stripe.products.retrieve(productId, {
184+
expand: ["default_price"],
185+
});
186+
187+
const currentDefaultPrice =
188+
typeof product.default_price === "string"
189+
? product.default_price
190+
: product.default_price?.id;
191+
192+
// create the new price FIRST (rlly important)
193+
const newPrice = await stripe.prices.create({
194+
product: productId,
195+
unit_amount: amountCents,
196+
currency: "cad",
197+
});
198+
199+
// set the new price as the product default
200+
await stripe.products.update(productId, {
201+
default_price: newPrice.id,
202+
});
203+
204+
// archive old active prices but NEVER archive the new default price
205+
const prices = await stripe.prices.list({
206+
product: productId,
207+
active: true,
208+
limit: 100,
209+
});
210+
211+
for (const p of prices.data) {
212+
if (p.id !== newPrice.id && p.id !== currentDefaultPrice) {
213+
await stripe.prices.update(p.id, { active: false });
214+
}
215+
}
216+
217+
// archive the old default price after default_price has been swapped
218+
if (currentDefaultPrice && currentDefaultPrice !== newPrice.id) {
219+
await stripe.prices.update(currentDefaultPrice, { active: false });
220+
}
221+
222+
return { priceId: newPrice.id };
223+
}
224+
178225
export async function deactivateActivePricesForProduct(
179226
productId: string,
180227
): Promise<void> {

0 commit comments

Comments
 (0)