using legacy stripe versions

This commit is contained in:
John Yeo
2025-08-16 15:37:36 -07:00
parent 0380862ed8
commit 85ff0e9804
13 changed files with 95 additions and 72 deletions

View File

@@ -85,7 +85,7 @@
"react": "^18.2.0",
"recaseai": "^0.0.37",
"resend": "^4.1.1",
"stripe": "18.4.0",
"stripe": "^18.4.0",
"svix": "^1.45.1",
"tsc-alias": "^1.8.16",
"ws": "^8.18.0",
@@ -3025,8 +3025,6 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@types/bun/bun-types": ["bun-types@1.2.20", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-pxTnQYOrKvdOwyiyd/7sMt9yFOenN004Y6O4lCcCUoKVej48FS5cvTw9geRaEcB9TsDZaJKAxPTVvi8tFsVuXA=="],
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],

View File

@@ -16,6 +16,7 @@ fi
# 'tests/attach/addOn/*.ts'
$MOCHA_CMD \
'tests/attach/checkout/*.ts' \
'tests/attach/entities/*.ts' \
'tests/attach/free/*.ts'\
'tests/attach/checkout/*.ts'
# \
# 'tests/attach/entities/*.ts' \
# 'tests/attach/free/*.ts'\

View File

@@ -340,7 +340,7 @@ export class AutumnInt {
rewards = {
create: async (reward: any) => {
const data = await this.post(`/rewards`, reward);
const data = await this.post(`/rewards?legacyStripe=true`, reward);
return data;
},

View File

@@ -57,7 +57,7 @@ export const deleteCouponFromCus = async ({
try {
let stripeCus = (await stripeCli.customers.retrieve(
stripeCusId,
stripeCusId
)) as Stripe.Customer;
if (stripeCus.discount?.id === discountId) {
await stripeCli.customers.deleteDiscount(stripeCusId, discountId);

View File

@@ -9,8 +9,10 @@ import {
Product,
UsagePriceConfig,
RewardType,
AppEnv,
} from "@autumn/shared";
import { Stripe } from "stripe";
import { createStripeCli } from "../utils.js";
const couponToStripeDuration = (coupon: Reward) => {
let discountConfig = coupon.discount_config;
@@ -62,19 +64,27 @@ const couponToStripeValue = ({
export const createStripeCoupon = async ({
reward,
stripeCli,
org,
env,
prices,
logger,
legacyVersion,
}: {
reward: Reward;
stripeCli: Stripe;
org: Organization;
env: AppEnv;
prices: (Price & { product: Product })[];
logger: any;
legacyVersion?: boolean;
}) => {
let discountConfig = reward.discount_config;
const stripeCli = createStripeCli({
org,
env,
legacyVersion,
});
try {
await stripeCli.coupons.del(reward.id);
} catch (error) {}

View File

@@ -16,9 +16,13 @@ import Stripe from "stripe";
export const createStripeCli = ({
org,
env,
// apiVersion,
legacyVersion,
}: {
org: Organization;
env: AppEnv;
// apiVersion?: string;
legacyVersion?: boolean;
}) => {
let encrypted =
env == AppEnv.Sandbox
@@ -34,7 +38,11 @@ export const createStripeCli = ({
}
let decrypted = decryptData(encrypted);
return new Stripe(decrypted);
return new Stripe(decrypted, {
apiVersion: legacyVersion
? ("2025-02-24.acacia" as any)
: "2025-07-30.basil",
});
};
export const calculateMetered1Price = ({

View File

@@ -18,6 +18,7 @@ import { Decimal } from "decimal.js";
import { generateId } from "@/utils/genUtils.js";
import { addMonths } from "date-fns";
import { getStripeNow } from "@/utils/scriptUtils/testClockUtils.js";
import { invoiceToSubId } from "../stripeInvoiceUtils.js";
export const handleInvoicePaidDiscount = async ({
db,
@@ -39,7 +40,7 @@ export const handleInvoicePaidDiscount = async ({
}
let stripeCus = await stripeCli.customers.retrieve(
expandedInvoice.customer as string,
expandedInvoice.customer as string
);
try {
@@ -86,7 +87,7 @@ export const handleInvoicePaidDiscount = async ({
const curAmount = discount.coupon.amount_off;
const amountUsed = totalDiscountAmounts?.find(
(item) => item.discount === discount.id,
(item) => item.discount === discount.id
)?.amount;
const newAmount = new Decimal(curAmount!).sub(amountUsed!).toNumber();
@@ -103,16 +104,17 @@ export const handleInvoicePaidDiscount = async ({
});
let expired = curExpiresAt && curExpiresAt < now;
const subId = invoiceToSubId({ invoice: expandedInvoice });
if (discountFinished || expired) {
logger.info(
`Coupon ${couponId}, stripeCus: ${stripeCus.id}: credits used up or expired. discountFinished: ${discountFinished}, expired: ${expired}`,
`Coupon ${couponId}, stripeCus: ${stripeCus.id}: credits used up or expired. discountFinished: ${discountFinished}, expired: ${expired}`
);
if (expandedInvoice.subscription) {
if (subId) {
await deleteCouponFromCus({
stripeCli,
stripeSubId: expandedInvoice.subscription as string,
stripeSubId: subId,
stripeCusId: expandedInvoice.customer as string,
discountId: discount.id,
logger,
@@ -123,7 +125,7 @@ export const handleInvoicePaidDiscount = async ({
}
logger.info(
`Coupon ${couponId}, stripeCus: ${stripeCus.id}, updating amount from ${curAmount} to ${newAmount}`,
`Coupon ${couponId}, stripeCus: ${stripeCus.id}, updating amount from ${curAmount} to ${newAmount}`
);
// Set expiry date
@@ -147,18 +149,30 @@ export const handleInvoicePaidDiscount = async ({
},
});
await stripeCli.customers.update(expandedInvoice.customer as string, {
coupon: newCoupon.id,
const legacyStripeCli = createStripeCli({
org,
env,
legacyVersion: true,
});
await legacyStripeCli.rawRequest(
"POST",
`/v1/customers/${expandedInvoice.customer}/discounts`,
{
coupon: newCoupon.id,
}
);
await stripeCli.coupons.del(newCoupon.id);
await deleteCouponFromSub({
stripeCli,
stripeSubId: expandedInvoice.subscription as string,
discountId: discount.id,
logger,
});
if (subId) {
await deleteCouponFromSub({
stripeCli,
stripeSubId: subId,
discountId: discount.id,
logger,
});
}
}
} catch (error) {
logger.error("invoice.paid: error updating coupon");

View File

@@ -8,7 +8,6 @@ import { OrgService } from "@/internal/orgs/OrgService.js";
import { createStripeCoupon } from "@/external/stripe/stripeCouponUtils/stripeCouponUtils.js";
import { RewardService } from "@/internal/rewards/RewardService.js";
import { PriceService } from "@/internal/products/prices/PriceService.js";
import { createStripePriceIFNotExist } from "@/external/stripe/createStripePrice/createStripePrice.js";
import { EntitlementService } from "@/internal/products/entitlements/EntitlementService.js";
import {
constructReward,
@@ -54,30 +53,6 @@ rewardRouter.post("", async (req: any, res: any) => {
}),
]);
// Initialize prices
// if (!discountConfig!.apply_to_all) {
// // Create stripe prices if not exists
// const batchSize = 5;
// // for (let i = 0; i < prices.length; i += batchSize) {
// // const batch = prices.slice(i, i + batchSize);
// // const batchPriceCreate = batch.map((price) =>
// // createStripePriceIFNotExist({
// // stripeCli,
// // price,
// // entitlements,
// // org,
// // logger,
// // db,
// // product: price.product,
// // })
// // );
// // await Promise.all(batchPriceCreate);
// // }
// }
await initRewardStripePrices({
db,
prices,
@@ -88,10 +63,11 @@ rewardRouter.post("", async (req: any, res: any) => {
await createStripeCoupon({
reward: newReward,
stripeCli,
org,
env,
prices,
logger,
legacyVersion: req.query.legacyStripe === "true",
});
}
@@ -206,10 +182,11 @@ rewardRouter.post("/:internalId", async (req: any, res: any) => {
if (rewardCat == RewardCategory.Discount) {
await createStripeCoupon({
reward: rewardBody,
stripeCli,
org,
env,
prices,
logger,
legacyVersion: req.query.legacyStripe === "true",
});
}

View File

@@ -1,24 +1,14 @@
import { freeTrialToStripeTimestamp } from "@/internal/products/free-trials/freeTrialUtils.js";
import RecaseError from "@/utils/errorUtils.js";
import {
Customer,
FreeTrial,
Organization,
ErrCode,
BillingInterval,
Reward,
IntervalConfig,
} from "@autumn/shared";
import { ErrCode, Reward, IntervalConfig } from "@autumn/shared";
import Stripe from "stripe";
import { getCusPaymentMethod } from "@/external/stripe/stripeCusUtils.js";
import { SubService } from "@/internal/subscriptions/SubService.js";
import { formatUnixToDateTime, generateId } from "@/utils/genUtils.js";
import { ItemSet } from "@/utils/models/ItemSet.js";
import { generateId } from "@/utils/genUtils.js";
import { DrizzleCli } from "@/db/initDrizzle.js";
import { getAlignedIntervalUnix } from "@/internal/products/prices/billingIntervalUtils.js";
import { getEarliestPeriodEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
import { getSmallestInterval } from "@/internal/products/prices/priceUtils/priceIntervalUtils.js";
// Get payment method
@@ -122,6 +112,15 @@ export const createStripeSub2 = async ({
// coupon: reward ? reward.id : undefined,
discounts: reward ? [{ coupon: reward.id }] : undefined,
expand: ["latest_invoice"],
trial_settings:
freeTrial && !freeTrial.card_required
? {
end_behavior: {
missing_payment_method: "cancel",
},
}
: undefined,
});
// console.log("Latest invoice:", subscription.latest_invoice);

View File

@@ -135,7 +135,7 @@ export const updateStripeSub = async ({
let { replaceables } = await createAndFilterContUseItems({
attachParams,
curMainProduct: curMainProduct!,
stripeSubs,
sub: curSub,
interval,
intervalCount,
logger,

View File

@@ -12,7 +12,6 @@ import { createStripeCli } from "@/external/stripe/utils.js";
import { handleDeleteCustomer } from "./handlers/cusDeleteHandlers.js";
import { handleUpdateBalances } from "./handlers/handleUpdateBalances.js";
import { handleUpdateEntitlement } from "./handlers/handleUpdateEntitlement.js";
import { handleCusProductExpired } from "./handlers/handleCusProductExpired.js";
import { handleAddCouponToCus } from "./handlers/handleAddCouponToCus.js";
import { handlePostCustomerRequest } from "./handlers/handlePostCustomer.js";
import { entityRouter } from "../api/entities/entityRouter.js";

View File

@@ -44,7 +44,12 @@ export const handleAddCouponToCus = async (req: any, res: any) => {
});
}
const stripeCli = createStripeCli({ org, env });
const stripeCli = createStripeCli({
org,
env,
// apiVersion: "2025-02-24.acacia",
legacyVersion: true,
});
await createStripeCusIfNotExists({
db,
@@ -55,9 +60,22 @@ export const handleAddCouponToCus = async (req: any, res: any) => {
});
// Attach coupon to customer
await stripeCli.customers.update(customer.processor.id, {
coupon: coupon.id,
});
// curl https://api.stripe.com/v1/customers/cus_123456/discounts \
// -u sk_test_your_key: \
// -d coupon=COUPON_ID
await stripeCli.rawRequest(
"POST",
`/v1/customers/${customer.processor.id}`,
{
coupon: coupon.id,
}
);
// await stripeCli.customers.update(customer.processor.id, {
// coupon: coupon.id,
// discounts: [{ coupon: coupon.id }],
// });
res.status(200).json({ customer, coupon });
} catch (error) {

View File

@@ -1,6 +1,6 @@
import { AutumnInt } from "@/external/autumn/autumnCli.js";
import { initCustomer } from "@/utils/scriptUtils/initCustomer.js";
import { APIVersion, AppEnv, Organization } from "@autumn/shared";
import { AppEnv, Organization } from "@autumn/shared";
import chalk from "chalk";
import Stripe from "stripe";
import { DrizzleCli } from "@/db/initDrizzle.js";
@@ -16,7 +16,6 @@ import { TestFeature } from "tests/setup/v2Features.js";
import { completeCheckoutForm } from "tests/utils/stripeUtils.js";
import { timeout } from "@/utils/genUtils.js";
import { expectProductAttached } from "tests/utils/expectUtils/expectProductAttached.js";
import { expectFeaturesCorrect } from "tests/utils/expectUtils/expectFeaturesCorrect.js";
import { expect } from "chai";
import { getBasePrice } from "tests/utils/testProductUtils/testProductUtils.js";