Skip to content

Commit 515aafb

Browse files
committed
feat(stripe): implement replaceProductPrice function to manage default price updates
1 parent 04991b2 commit 515aafb

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

@@ -330,11 +330,10 @@ export async function updateService(
330330
});
331331

332332
if (cents !== undefined) {
333-
const { priceId } = await createPrice(row.stripeProductId, cents);
334-
const oldPriceIds = (await listActivePriceIds(row.stripeProductId)).filter(
335-
(id) => id !== priceId,
336-
);
337-
await deactivatePrices(oldPriceIds);
333+
const current = await getStripeServiceData(row.stripeProductId);
334+
if (current?.priceCents !== cents) {
335+
await replaceProductPrice(row.stripeProductId, cents);
336+
}
338337
}
339338

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

lib/stripe.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,57 @@ export async function createPrice(
182182
unit_amount: amountCents,
183183
currency: "cad",
184184
});
185-
await stripe.products.update(productId, { default_price: price.id });
185+
186186
return { priceId: price.id };
187187
}
188188

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

0 commit comments

Comments
 (0)