Revert "feat: 🎸 stripe managed payments"
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"setup": ["bun i", "bun dw setup"],
|
"setup": ["bun dw setup"],
|
||||||
"run": ["bun dw run"],
|
"run": ["bun dw run"],
|
||||||
"teardown": ["bun dw teardown"]
|
"teardown": ["bun dw teardown"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"autumn-js": "workspace:*",
|
"autumn-js": "workspace:*",
|
||||||
"better-auth": "catalog:",
|
"better-auth": "catalog:",
|
||||||
"kysely": "^0.28.11",
|
"kysely": "^0.28.11",
|
||||||
"next": "16.2.6",
|
"next": "16.1.6",
|
||||||
"pg": "^8.18.0",
|
"pg": "^8.18.0",
|
||||||
"radix-ui": "^1.4.3",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "19.2.3",
|
"react": "19.2.3",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"lottie-react": "^2.4.1",
|
"lottie-react": "^2.4.1",
|
||||||
"lottie-web": "^5.13.0",
|
"lottie-web": "^5.13.0",
|
||||||
"motion": "^12.38.0",
|
"motion": "^12.38.0",
|
||||||
"next": "16.2.6",
|
"next": "16.2.4",
|
||||||
"ngrok": "^5.0.0-beta.2",
|
"ngrok": "^5.0.0-beta.2",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
|
|||||||
@@ -25,10 +25,10 @@
|
|||||||
"packages/stripe-sync"
|
"packages/stripe-sync"
|
||||||
],
|
],
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"stripe": "22.1.0",
|
"stripe": "19.3.0-beta.1",
|
||||||
"drizzle-orm": "0.43.1",
|
"drizzle-orm": "0.43.1",
|
||||||
"drizzle-kit": "^0.31.1",
|
"drizzle-kit": "^0.31.1",
|
||||||
"@typescript/native-preview": "7.0.0-dev.20260527.2",
|
"@typescript/native-preview": "7.0.0-dev.20260220.1",
|
||||||
"@tailwindcss/vite": "4.2.1",
|
"@tailwindcss/vite": "4.2.1",
|
||||||
"@vitejs/plugin-react": "5.2.0",
|
"@vitejs/plugin-react": "5.2.0",
|
||||||
"@sentry/bun": "10.38.0",
|
"@sentry/bun": "10.38.0",
|
||||||
|
|||||||
@@ -108,7 +108,7 @@
|
|||||||
"@useautumn/sdk": "workspace:*",
|
"@useautumn/sdk": "workspace:*",
|
||||||
"esbuild-plugin-path-alias": "^1.0.7",
|
"esbuild-plugin-path-alias": "^1.0.7",
|
||||||
"hono": "4.12.7",
|
"hono": "4.12.7",
|
||||||
"next": "^15.5.18",
|
"next": "^15.2.3",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"tsup": "^8.4.0",
|
"tsup": "^8.4.0",
|
||||||
"typescript": "^5.8.3"
|
"typescript": "^5.8.3"
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
"@trigger.dev/sdk": "4.4.6",
|
"@trigger.dev/sdk": "4.4.6",
|
||||||
"@types/qs": "^6.14.0",
|
"@types/qs": "^6.14.0",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"@typescript/native-preview": "^7.0.0-dev.20260527.2",
|
"@typescript/native-preview": "^7.0.0-dev.20251114.1",
|
||||||
"@upstash/ratelimit": "^2.0.7",
|
"@upstash/ratelimit": "^2.0.7",
|
||||||
"@upstash/redis": "^1.35.6",
|
"@upstash/redis": "^1.35.6",
|
||||||
"@vercel/sdk": "^1.17.0",
|
"@vercel/sdk": "^1.17.0",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const calculateTieredAmount = ({
|
|||||||
let quantityCursor = quantity;
|
let quantityCursor = quantity;
|
||||||
for (const tier of tiers) {
|
for (const tier of tiers) {
|
||||||
const unitAmount = new Decimal(
|
const unitAmount = new Decimal(
|
||||||
tier.unit_amount_decimal?.toString() ?? tier.unit_amount ?? 0,
|
tier.unit_amount_decimal || tier.unit_amount!,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (notNullish(tier.up_to)) {
|
if (notNullish(tier.up_to)) {
|
||||||
@@ -54,9 +54,7 @@ export const getSubItemAmount = ({
|
|||||||
|
|
||||||
if (price.billing_scheme === "per_unit") {
|
if (price.billing_scheme === "per_unit") {
|
||||||
if (price.unit_amount_decimal) {
|
if (price.unit_amount_decimal) {
|
||||||
return new Decimal(price.unit_amount_decimal.toString())
|
return new Decimal(price.unit_amount_decimal).mul(quantity).toNumber();
|
||||||
.mul(quantity)
|
|
||||||
.toNumber();
|
|
||||||
} else {
|
} else {
|
||||||
return new Decimal(price.unit_amount || 0).mul(quantity).toNumber();
|
return new Decimal(price.unit_amount || 0).mul(quantity).toNumber();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ export const handleErrorSkip = (err: Error, c: Context<HonoEnv>) => {
|
|||||||
// 4. Check Stripe-specific rules
|
// 4. Check Stripe-specific rules
|
||||||
for (const rule of STRIPE_RULES) {
|
for (const rule of STRIPE_RULES) {
|
||||||
if (rule.match(err, c)) {
|
if (rule.match(err, c)) {
|
||||||
const stripeErr = err as Stripe.ErrorType.StripeError;
|
const stripeErr = err as Stripe.errors.StripeError;
|
||||||
logger.warn(`${rule.name}, org: ${ctx.org?.slug || "unknown"}`);
|
logger.warn(`${rule.name}, org: ${ctx.org?.slug || "unknown"}`);
|
||||||
return createErrorResponse({
|
return createErrorResponse({
|
||||||
c,
|
c,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Scopes } from "@autumn/shared";
|
|
||||||
import { initMasterStripe } from "@/external/connect/initStripeCli";
|
import { initMasterStripe } from "@/external/connect/initStripeCli";
|
||||||
|
import { Scopes } from "@autumn/shared";
|
||||||
import { createRoute } from "../../honoMiddlewares/routeHandler";
|
import { createRoute } from "../../honoMiddlewares/routeHandler";
|
||||||
|
|
||||||
export const handleGetMasterStripeAccount = createRoute({
|
export const handleGetMasterStripeAccount = createRoute({
|
||||||
@@ -10,7 +10,7 @@ export const handleGetMasterStripeAccount = createRoute({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const masterStripe = initMasterStripe({ env });
|
const masterStripe = initMasterStripe({ env });
|
||||||
const account = await masterStripe.accounts.retrieve(null);
|
const account = await masterStripe.accounts.retrieve();
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
id: account.id,
|
id: account.id,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
|||||||
import { buildStripeCheckoutSessionItems } from "@/internal/billing/v2/providers/stripe/utils/checkoutSessions/buildStripeCheckoutSessionItems";
|
import { buildStripeCheckoutSessionItems } from "@/internal/billing/v2/providers/stripe/utils/checkoutSessions/buildStripeCheckoutSessionItems";
|
||||||
import { buildAutumnSubscriptionMetadata } from "@/internal/billing/v2/providers/stripe/utils/common/autumnStripeMetadata";
|
import { buildAutumnSubscriptionMetadata } from "@/internal/billing/v2/providers/stripe/utils/common/autumnStripeMetadata";
|
||||||
import { stripeDiscountsToCheckoutParams } from "@/internal/billing/v2/providers/stripe/utils/discounts/stripeDiscountsToParams";
|
import { stripeDiscountsToCheckoutParams } from "@/internal/billing/v2/providers/stripe/utils/discounts/stripeDiscountsToParams";
|
||||||
import type { Checkout as CheckoutSessions } from "stripe/resources/Checkout/Sessions.js";
|
|
||||||
|
|
||||||
export const buildStripeCheckoutSessionAction = ({
|
export const buildStripeCheckoutSessionAction = ({
|
||||||
ctx,
|
ctx,
|
||||||
@@ -46,11 +45,11 @@ export const buildStripeCheckoutSessionAction = ({
|
|||||||
// Payment-mode checkout has no top-level default_tax_rates, so one-off items take per-line tax_rates.
|
// Payment-mode checkout has no top-level default_tax_rates, so one-off items take per-line tax_rates.
|
||||||
const taxRateId = billingContext.taxRateId;
|
const taxRateId = billingContext.taxRateId;
|
||||||
const applyTaxRateToLineItem = (
|
const applyTaxRateToLineItem = (
|
||||||
item: CheckoutSessions.SessionCreateParams.LineItem,
|
item: Stripe.Checkout.SessionCreateParams.LineItem,
|
||||||
): CheckoutSessions.SessionCreateParams.LineItem =>
|
): Stripe.Checkout.SessionCreateParams.LineItem =>
|
||||||
taxRateId ? { ...item, tax_rates: [taxRateId] } : item;
|
taxRateId ? { ...item, tax_rates: [taxRateId] } : item;
|
||||||
|
|
||||||
const lineItems: CheckoutSessions.SessionCreateParams.LineItem[] = [
|
const lineItems: Stripe.Checkout.SessionCreateParams.LineItem[] = [
|
||||||
...recurringLineItems.filter((item) => item.quantity !== 0),
|
...recurringLineItems.filter((item) => item.quantity !== 0),
|
||||||
...oneOffLineItems
|
...oneOffLineItems
|
||||||
.filter((item) => item.quantity !== 0)
|
.filter((item) => item.quantity !== 0)
|
||||||
@@ -65,7 +64,7 @@ export const buildStripeCheckoutSessionAction = ({
|
|||||||
|
|
||||||
// 5. Build subscription_data (only for subscription mode)
|
// 5. Build subscription_data (only for subscription mode)
|
||||||
const subscriptionData:
|
const subscriptionData:
|
||||||
| CheckoutSessions.SessionCreateParams.SubscriptionData
|
| Stripe.Checkout.SessionCreateParams.SubscriptionData
|
||||||
| undefined =
|
| undefined =
|
||||||
mode === "subscription"
|
mode === "subscription"
|
||||||
? {
|
? {
|
||||||
@@ -89,7 +88,7 @@ export const buildStripeCheckoutSessionAction = ({
|
|||||||
|
|
||||||
// 7. Build params. Tax policy is baked in here (not at execute time) so
|
// 7. Build params. Tax policy is baked in here (not at execute time) so
|
||||||
// the action object is self-describing in logs/EXTRA_LOGS.
|
// the action object is self-describing in logs/EXTRA_LOGS.
|
||||||
const autumnAutoTax: Partial<CheckoutSessions.SessionCreateParams> = org.config
|
const autumnAutoTax: Partial<Stripe.Checkout.SessionCreateParams> = org.config
|
||||||
.automatic_tax
|
.automatic_tax
|
||||||
? {
|
? {
|
||||||
automatic_tax: { enabled: true },
|
automatic_tax: { enabled: true },
|
||||||
@@ -99,7 +98,7 @@ export const buildStripeCheckoutSessionAction = ({
|
|||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
const params: CheckoutSessions.SessionCreateParams = {
|
const params: Stripe.Checkout.SessionCreateParams = {
|
||||||
customer: stripeCustomer?.id ?? "none",
|
customer: stripeCustomer?.id ?? "none",
|
||||||
mode,
|
mode,
|
||||||
line_items: lineItems,
|
line_items: lineItems,
|
||||||
@@ -113,6 +112,6 @@ export const buildStripeCheckoutSessionAction = ({
|
|||||||
type: "create",
|
type: "create",
|
||||||
params,
|
params,
|
||||||
checkoutSessionParams:
|
checkoutSessionParams:
|
||||||
checkoutSessionParams as Partial<CheckoutSessions.SessionCreateParams>,
|
checkoutSessionParams as Partial<Stripe.Checkout.SessionCreateParams>,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
import { mergeStripeMetadata } from "@/internal/billing/v2/providers/stripe/utils/common/mergeStripeMetadata";
|
import { mergeStripeMetadata } from "@/internal/billing/v2/providers/stripe/utils/common/mergeStripeMetadata";
|
||||||
import { buildCheckoutSessionMetadata } from "./buildCheckoutSessionMetadata";
|
import { buildCheckoutSessionMetadata } from "./buildCheckoutSessionMetadata";
|
||||||
import type { Checkout as CheckoutSessions } from "stripe/resources/Checkout/Sessions.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deep-merges subscription_data so user-provided fields (e.g. metadata)
|
* Deep-merges subscription_data so user-provided fields (e.g. metadata)
|
||||||
@@ -13,9 +12,9 @@ const mergeSubscriptionData = ({
|
|||||||
userSubscriptionData,
|
userSubscriptionData,
|
||||||
}: {
|
}: {
|
||||||
userMetadata?: Record<string, string>;
|
userMetadata?: Record<string, string>;
|
||||||
paramsSubscriptionData?: CheckoutSessions.SessionCreateParams.SubscriptionData;
|
paramsSubscriptionData?: Stripe.Checkout.SessionCreateParams.SubscriptionData;
|
||||||
userSubscriptionData?: CheckoutSessions.SessionCreateParams.SubscriptionData;
|
userSubscriptionData?: Stripe.Checkout.SessionCreateParams.SubscriptionData;
|
||||||
}): CheckoutSessions.SessionCreateParams.SubscriptionData | undefined => {
|
}): Stripe.Checkout.SessionCreateParams.SubscriptionData | undefined => {
|
||||||
if (!paramsSubscriptionData && !userSubscriptionData && !userMetadata) {
|
if (!paramsSubscriptionData && !userSubscriptionData && !userMetadata) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -42,16 +41,16 @@ export const buildCheckoutSessionParams = ({
|
|||||||
autumnMetadataId,
|
autumnMetadataId,
|
||||||
userMetadata,
|
userMetadata,
|
||||||
}: {
|
}: {
|
||||||
params: CheckoutSessions.SessionCreateParams;
|
params: Stripe.Checkout.SessionCreateParams;
|
||||||
checkoutSessionParams?: Partial<CheckoutSessions.SessionCreateParams>;
|
checkoutSessionParams?: Partial<Stripe.Checkout.SessionCreateParams>;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
defaultAllowPromotionCodes?: boolean;
|
defaultAllowPromotionCodes?: boolean;
|
||||||
defaultInvoiceCreation?: CheckoutSessions.SessionCreateParams.InvoiceCreation;
|
defaultInvoiceCreation?: Stripe.Checkout.SessionCreateParams.InvoiceCreation;
|
||||||
defaultSavedPaymentMethodOptions?: CheckoutSessions.SessionCreateParams.SavedPaymentMethodOptions;
|
defaultSavedPaymentMethodOptions?: Stripe.Checkout.SessionCreateParams.SavedPaymentMethodOptions;
|
||||||
autumnMetadataId?: string;
|
autumnMetadataId?: string;
|
||||||
userMetadata?: Record<string, string>;
|
userMetadata?: Record<string, string>;
|
||||||
}): CheckoutSessions.SessionCreateParams => {
|
}): Stripe.Checkout.SessionCreateParams => {
|
||||||
const mergedParams: CheckoutSessions.SessionCreateParams = {
|
const mergedParams: Stripe.Checkout.SessionCreateParams = {
|
||||||
...(checkoutSessionParams ?? {}),
|
...(checkoutSessionParams ?? {}),
|
||||||
...params,
|
...params,
|
||||||
};
|
};
|
||||||
@@ -84,11 +83,11 @@ export const buildCheckoutSessionParams = ({
|
|||||||
: mergeSubscriptionData({
|
: mergeSubscriptionData({
|
||||||
userMetadata,
|
userMetadata,
|
||||||
paramsSubscriptionData: params.subscription_data as
|
paramsSubscriptionData: params.subscription_data as
|
||||||
| CheckoutSessions.SessionCreateParams.SubscriptionData
|
| Stripe.Checkout.SessionCreateParams.SubscriptionData
|
||||||
| undefined,
|
| undefined,
|
||||||
userSubscriptionData:
|
userSubscriptionData:
|
||||||
checkoutSessionParams?.subscription_data as
|
checkoutSessionParams?.subscription_data as
|
||||||
| CheckoutSessions.SessionCreateParams.SubscriptionData
|
| Stripe.Checkout.SessionCreateParams.SubscriptionData
|
||||||
| undefined,
|
| undefined,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
priceUtils,
|
priceUtils,
|
||||||
type StripeItemSpec,
|
type StripeItemSpec,
|
||||||
} from "@autumn/shared";
|
} from "@autumn/shared";
|
||||||
import type { Checkout as CheckoutSessions } from "stripe/resources/Checkout/Sessions.js";
|
import type Stripe from "stripe";
|
||||||
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
||||||
import { customerProductsToOneOffStripeItemSpecs } from "@/internal/billing/v2/providers/stripe/utils/stripeItemSpec/customerProductsToOneOffStripeItemSpecs";
|
import { customerProductsToOneOffStripeItemSpecs } from "@/internal/billing/v2/providers/stripe/utils/stripeItemSpec/customerProductsToOneOffStripeItemSpecs";
|
||||||
import { customerProductsToRecurringStripeItemSpecs } from "@/internal/billing/v2/providers/stripe/utils/stripeItemSpec/customerProductsToRecurringStripeItemSpecs";
|
import { customerProductsToRecurringStripeItemSpecs } from "@/internal/billing/v2/providers/stripe/utils/stripeItemSpec/customerProductsToRecurringStripeItemSpecs";
|
||||||
@@ -17,13 +17,13 @@ import { updateOneOffTieredItems } from "./updateOneOffTieredItems";
|
|||||||
const isZeroAmountInlineLineItem = ({
|
const isZeroAmountInlineLineItem = ({
|
||||||
lineItem,
|
lineItem,
|
||||||
}: {
|
}: {
|
||||||
lineItem: CheckoutSessions.SessionCreateParams.LineItem;
|
lineItem: Stripe.Checkout.SessionCreateParams.LineItem;
|
||||||
}) => {
|
}) => {
|
||||||
if (!("price_data" in lineItem) || !lineItem.price_data) return false;
|
if (!("price_data" in lineItem) || !lineItem.price_data) return false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
lineItem.price_data.unit_amount === 0 ||
|
lineItem.price_data.unit_amount === 0 ||
|
||||||
lineItem.price_data.unit_amount_decimal?.toNumber() === 0
|
lineItem.price_data.unit_amount_decimal === "0"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ const isZeroAmountInlineRecurringStripeItemSpec = ({
|
|||||||
}) => {
|
}) => {
|
||||||
if (!stripeItemSpec.stripeInlinePrice?.recurring) return false;
|
if (!stripeItemSpec.stripeInlinePrice?.recurring) return false;
|
||||||
|
|
||||||
return stripeItemSpec.stripeInlinePrice.unit_amount_decimal.toNumber() === 0;
|
return stripeItemSpec.stripeInlinePrice.unit_amount_decimal === "0";
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRecurringCadenceKey = ({
|
const getRecurringCadenceKey = ({
|
||||||
@@ -94,10 +94,10 @@ const applyAdjustableQuantityToPrepaidLineItem = ({
|
|||||||
spec,
|
spec,
|
||||||
billingContext,
|
billingContext,
|
||||||
}: {
|
}: {
|
||||||
lineItem: CheckoutSessions.SessionCreateParams.LineItem;
|
lineItem: Stripe.Checkout.SessionCreateParams.LineItem;
|
||||||
spec: StripeItemSpec;
|
spec: StripeItemSpec;
|
||||||
billingContext: BillingContext;
|
billingContext: BillingContext;
|
||||||
}): CheckoutSessions.SessionCreateParams.LineItem => {
|
}): Stripe.Checkout.SessionCreateParams.LineItem => {
|
||||||
const { autumnPrice, autumnEntitlement, autumnProduct } = spec;
|
const { autumnPrice, autumnEntitlement, autumnProduct } = spec;
|
||||||
|
|
||||||
if (!autumnPrice || !autumnEntitlement || !isPrepaidPrice(autumnPrice)) {
|
if (!autumnPrice || !autumnEntitlement || !isPrepaidPrice(autumnPrice)) {
|
||||||
@@ -132,7 +132,7 @@ const applyAdjustableQuantityToPrepaidLineItem = ({
|
|||||||
}),
|
}),
|
||||||
maximum: 999999,
|
maximum: 999999,
|
||||||
},
|
},
|
||||||
} as CheckoutSessions.SessionCreateParams.LineItem;
|
} as Stripe.Checkout.SessionCreateParams.LineItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildStripeCheckoutSessionItems = ({
|
export const buildStripeCheckoutSessionItems = ({
|
||||||
@@ -144,8 +144,8 @@ export const buildStripeCheckoutSessionItems = ({
|
|||||||
billingContext: BillingContext;
|
billingContext: BillingContext;
|
||||||
newCustomerProducts: FullCusProduct[];
|
newCustomerProducts: FullCusProduct[];
|
||||||
}): {
|
}): {
|
||||||
recurringLineItems: CheckoutSessions.SessionCreateParams.LineItem[];
|
recurringLineItems: Stripe.Checkout.SessionCreateParams.LineItem[];
|
||||||
oneOffLineItems: CheckoutSessions.SessionCreateParams.LineItem[];
|
oneOffLineItems: Stripe.Checkout.SessionCreateParams.LineItem[];
|
||||||
} => {
|
} => {
|
||||||
// 1. Filter customer products by active statuses
|
// 1. Filter customer products by active statuses
|
||||||
const activeCustomerProducts = filterCustomerProductsByActiveStatuses({
|
const activeCustomerProducts = filterCustomerProductsByActiveStatuses({
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
} from "@autumn/shared";
|
} from "@autumn/shared";
|
||||||
import type Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
import { stripeItemSpecToCheckoutLineItem } from "../stripeItemSpec/stripeItemSpecToStripeParam";
|
import { stripeItemSpecToCheckoutLineItem } from "../stripeItemSpec/stripeItemSpecToStripeParam";
|
||||||
import type { Checkout as CheckoutSessions } from "stripe/resources/Checkout/Sessions.js";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update one-off items to use inline price_data if they're tiered.
|
* Update one-off items to use inline price_data if they're tiered.
|
||||||
@@ -23,7 +22,7 @@ export const updateOneOffTieredItems = ({
|
|||||||
}: {
|
}: {
|
||||||
oneOffItemSpecs: StripeItemSpec[];
|
oneOffItemSpecs: StripeItemSpec[];
|
||||||
org: Organization;
|
org: Organization;
|
||||||
}): CheckoutSessions.SessionCreateParams.LineItem[] => {
|
}): Stripe.Checkout.SessionCreateParams.LineItem[] => {
|
||||||
const currency = orgToCurrency({ org });
|
const currency = orgToCurrency({ org });
|
||||||
|
|
||||||
return oneOffItemSpecs.map((item) => {
|
return oneOffItemSpecs.map((item) => {
|
||||||
|
|||||||
@@ -256,10 +256,7 @@ const mergeStripeAndBillingLineItems = ({
|
|||||||
stripeLineItem.parent?.invoice_item_details?.invoice_item ?? null,
|
stripeLineItem.parent?.invoice_item_details?.invoice_item ?? null,
|
||||||
stripe_subscription_item_id: stripeSubscriptionItemId,
|
stripe_subscription_item_id: stripeSubscriptionItemId,
|
||||||
stripe_product_id: (priceDetails?.product as string) ?? null,
|
stripe_product_id: (priceDetails?.product as string) ?? null,
|
||||||
stripe_price_id:
|
stripe_price_id: priceDetails?.price ?? null,
|
||||||
typeof priceDetails?.price === "string"
|
|
||||||
? priceDetails?.price
|
|
||||||
: (priceDetails?.price?.id ?? null),
|
|
||||||
stripe_discountable: stripeLineItem.discountable,
|
stripe_discountable: stripeLineItem.discountable,
|
||||||
|
|
||||||
// Amounts (from Stripe or Autumn depending on discountable flag)
|
// Amounts (from Stripe or Autumn depending on discountable flag)
|
||||||
@@ -343,10 +340,7 @@ const createDbLineItemFromStripeOnly = ({
|
|||||||
stripeLineItem.parent?.invoice_item_details?.invoice_item ?? null,
|
stripeLineItem.parent?.invoice_item_details?.invoice_item ?? null,
|
||||||
stripe_subscription_item_id: stripeSubscriptionItemId,
|
stripe_subscription_item_id: stripeSubscriptionItemId,
|
||||||
stripe_product_id: (priceDetails?.product as string) ?? null,
|
stripe_product_id: (priceDetails?.product as string) ?? null,
|
||||||
stripe_price_id:
|
stripe_price_id: priceDetails?.price ?? null,
|
||||||
typeof priceDetails?.price === "string"
|
|
||||||
? priceDetails?.price
|
|
||||||
: (priceDetails?.price?.id ?? null),
|
|
||||||
stripe_discountable: stripeLineItem.discountable,
|
stripe_discountable: stripeLineItem.discountable,
|
||||||
|
|
||||||
amount,
|
amount,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type { PayInvoiceResult } from "./payStripeInvoice";
|
|||||||
const getFailureCodeFromStripeError = ({
|
const getFailureCodeFromStripeError = ({
|
||||||
stripeError,
|
stripeError,
|
||||||
}: {
|
}: {
|
||||||
stripeError: Stripe.ErrorType.StripeError;
|
stripeError: Stripe.errors.StripeError;
|
||||||
}): "3ds_required" | "payment_failed" => {
|
}): "3ds_required" | "payment_failed" => {
|
||||||
const authCodes = ["authentication_required", "authentication_not_handled"];
|
const authCodes = ["authentication_required", "authentication_not_handled"];
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ export const handleInvoicePaymentFailure = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Check if it's a Stripe error
|
// 2. Check if it's a Stripe error
|
||||||
const stripeError = error as Stripe.ErrorType.StripeError;
|
const stripeError = error as Stripe.errors.StripeError;
|
||||||
const isStripeError = stripeError.type !== undefined;
|
const isStripeError = stripeError.type !== undefined;
|
||||||
|
|
||||||
if (!isStripeError) throw error;
|
if (!isStripeError) throw error;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type PayInvoiceResult = {
|
|||||||
code: PaymentFailureCode;
|
code: PaymentFailureCode;
|
||||||
reason: string;
|
reason: string;
|
||||||
};
|
};
|
||||||
stripeError?: Stripe.ErrorType.StripeError;
|
stripeError?: Stripe.errors.StripeError;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PayStripeInvoiceParams = {
|
type PayStripeInvoiceParams = {
|
||||||
|
|||||||
@@ -13,11 +13,7 @@ type InlinePriceLike = {
|
|||||||
divide_by?: number;
|
divide_by?: number;
|
||||||
round?: string;
|
round?: string;
|
||||||
} | null;
|
} | null;
|
||||||
unit_amount_decimal?:
|
unit_amount_decimal?: string | number | null;
|
||||||
| string
|
|
||||||
| number
|
|
||||||
| ReturnType<typeof Stripe.Decimal.from>
|
|
||||||
| null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StripePriceShape = {
|
export type StripePriceShape = {
|
||||||
@@ -69,7 +65,7 @@ export const stripePriceToShape = ({
|
|||||||
intervalCount: price.recurring?.interval_count,
|
intervalCount: price.recurring?.interval_count,
|
||||||
tiersMode: price.tiers_mode ?? undefined,
|
tiersMode: price.tiers_mode ?? undefined,
|
||||||
transformQuantity: transformQuantityKey(price.transform_quantity),
|
transformQuantity: transformQuantityKey(price.transform_quantity),
|
||||||
unitAmountDecimal: decimalAmount(price.unit_amount_decimal?.toString()),
|
unitAmountDecimal: decimalAmount(price.unit_amount_decimal),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const inlinePriceToShape = ({
|
export const inlinePriceToShape = ({
|
||||||
@@ -84,7 +80,7 @@ export const inlinePriceToShape = ({
|
|||||||
interval: price.recurring?.interval,
|
interval: price.recurring?.interval,
|
||||||
intervalCount: price.recurring?.interval_count,
|
intervalCount: price.recurring?.interval_count,
|
||||||
transformQuantity: transformQuantityKey(price.transform_quantity),
|
transformQuantity: transformQuantityKey(price.transform_quantity),
|
||||||
unitAmountDecimal: decimalAmount(price.unit_amount_decimal?.toString()),
|
unitAmountDecimal: decimalAmount(price.unit_amount_decimal),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const stripePriceShapesEqual = (
|
export const stripePriceShapesEqual = (
|
||||||
|
|||||||
@@ -3,21 +3,13 @@ import {
|
|||||||
type StripeInlinePrice,
|
type StripeInlinePrice,
|
||||||
type StripeItemSpec,
|
type StripeItemSpec,
|
||||||
} from "@autumn/shared";
|
} from "@autumn/shared";
|
||||||
import Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
import type { Checkout as CheckoutSessions } from "stripe/resources/Checkout/Sessions.js";
|
|
||||||
|
|
||||||
type StoredPriceParam = { price: string };
|
type StoredPriceParam = { price: string };
|
||||||
type RecurringInlinePriceParam = {
|
type RecurringInlinePriceParam = {
|
||||||
price_data: Stripe.SubscriptionCreateParams.Item["price_data"];
|
price_data: Stripe.SubscriptionCreateParams.Item["price_data"];
|
||||||
};
|
};
|
||||||
|
|
||||||
const toStripeInlinePriceData = (stripeInlinePrice: StripeInlinePrice) => ({
|
|
||||||
...stripeInlinePrice,
|
|
||||||
unit_amount_decimal: Stripe.Decimal.from(
|
|
||||||
stripeInlinePrice.unit_amount_decimal.toString(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the price param for a StripeItemSpec — either a stored price ID or inline price_data.
|
* Returns the price param for a StripeItemSpec — either a stored price ID or inline price_data.
|
||||||
* For inline prices, asserts that `recurring` is present (one-off items should not reach this path).
|
* For inline prices, asserts that `recurring` is present (one-off items should not reach this path).
|
||||||
@@ -37,7 +29,7 @@ const toRecurringPriceParam = ({
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
price_data: {
|
price_data: {
|
||||||
...toStripeInlinePriceData(spec.stripeInlinePrice),
|
...spec.stripeInlinePrice,
|
||||||
recurring: spec.stripeInlinePrice.recurring,
|
recurring: spec.stripeInlinePrice.recurring,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -64,11 +56,9 @@ const toPriceParam = ({
|
|||||||
spec,
|
spec,
|
||||||
}: {
|
}: {
|
||||||
spec: StripeItemSpec;
|
spec: StripeItemSpec;
|
||||||
}):
|
}): StoredPriceParam | { price_data: StripeInlinePrice } => {
|
||||||
| StoredPriceParam
|
|
||||||
| { price_data: ReturnType<typeof toStripeInlinePriceData> } => {
|
|
||||||
if (spec.stripeInlinePrice) {
|
if (spec.stripeInlinePrice) {
|
||||||
return { price_data: toStripeInlinePriceData(spec.stripeInlinePrice) };
|
return { price_data: spec.stripeInlinePrice };
|
||||||
}
|
}
|
||||||
return { price: spec.stripePriceId! };
|
return { price: spec.stripePriceId! };
|
||||||
};
|
};
|
||||||
@@ -78,7 +68,7 @@ export const stripeItemSpecToCheckoutLineItem = ({
|
|||||||
spec,
|
spec,
|
||||||
}: {
|
}: {
|
||||||
spec: StripeItemSpec;
|
spec: StripeItemSpec;
|
||||||
}): CheckoutSessions.SessionCreateParams.LineItem => {
|
}): Stripe.Checkout.SessionCreateParams.LineItem => {
|
||||||
return {
|
return {
|
||||||
...toPriceParam({ spec }),
|
...toPriceParam({ spec }),
|
||||||
quantity: spec.quantity,
|
quantity: spec.quantity,
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export const storeInvoiceLineItems = async ({
|
|||||||
if (info?.isMetered) return false;
|
if (info?.isMetered) return false;
|
||||||
|
|
||||||
// Filter $0 empty price placeholders (e.g. stripe_empty_price_id)
|
// Filter $0 empty price placeholders (e.g. stripe_empty_price_id)
|
||||||
if (li.pricing?.unit_amount_decimal?.toNumber() === 0) return false;
|
if (li.pricing?.unit_amount_decimal === "0") return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { notNullish } from "@/utils/genUtils.js";
|
|||||||
import type { AutumnContext } from "../../../honoUtils/HonoEnv.js";
|
import type { AutumnContext } from "../../../honoUtils/HonoEnv.js";
|
||||||
import { attachParamsToMetadata } from "../../billing/attach/utils/attachParamsToMetadata.js";
|
import { attachParamsToMetadata } from "../../billing/attach/utils/attachParamsToMetadata.js";
|
||||||
import type { AttachParams } from "../cusProducts/AttachParams.js";
|
import type { AttachParams } from "../cusProducts/AttachParams.js";
|
||||||
import type { Checkout as CheckoutSessions } from "stripe/resources/Checkout/Sessions.js";
|
|
||||||
|
|
||||||
export const handleCreateCheckout = async ({
|
export const handleCreateCheckout = async ({
|
||||||
ctx,
|
ctx,
|
||||||
@@ -86,10 +85,10 @@ export const handleCreateCheckout = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checkoutParams = attachParams.checkoutSessionParams as
|
const checkoutParams = attachParams.checkoutSessionParams as
|
||||||
| Partial<CheckoutSessions.SessionCreateParams>
|
| Partial<Stripe.Checkout.SessionCreateParams>
|
||||||
| undefined;
|
| undefined;
|
||||||
const checkoutSubscriptionData = checkoutParams?.subscription_data as
|
const checkoutSubscriptionData = checkoutParams?.subscription_data as
|
||||||
| CheckoutSessions.SessionCreateParams.SubscriptionData
|
| Stripe.Checkout.SessionCreateParams.SubscriptionData
|
||||||
| undefined;
|
| undefined;
|
||||||
const trialEnd =
|
const trialEnd =
|
||||||
freeTrial && !attachParams.disableFreeTrial
|
freeTrial && !attachParams.disableFreeTrial
|
||||||
@@ -105,7 +104,7 @@ export const handleCreateCheckout = async ({
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const subscriptionData:
|
const subscriptionData:
|
||||||
| CheckoutSessions.SessionCreateParams.SubscriptionData
|
| Stripe.Checkout.SessionCreateParams.SubscriptionData
|
||||||
| undefined = isRecurring
|
| undefined = isRecurring
|
||||||
? {
|
? {
|
||||||
...(checkoutSubscriptionData ?? {}),
|
...(checkoutSubscriptionData ?? {}),
|
||||||
@@ -140,7 +139,7 @@ export const handleCreateCheckout = async ({
|
|||||||
notNullish(checkoutParams?.payment_method_types) ||
|
notNullish(checkoutParams?.payment_method_types) ||
|
||||||
notNullish(checkoutParams?.payment_method_configuration);
|
notNullish(checkoutParams?.payment_method_configuration);
|
||||||
|
|
||||||
let sessionParams: CheckoutSessions.SessionCreateParams = {
|
let sessionParams: Stripe.Checkout.SessionCreateParams = {
|
||||||
customer: customer.processor.id,
|
customer: customer.processor.id,
|
||||||
line_items: items,
|
line_items: items,
|
||||||
mode: isRecurring ? "subscription" : "payment",
|
mode: isRecurring ? "subscription" : "payment",
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
AppEnv,
|
AppEnv,
|
||||||
customerEntitlements,
|
|
||||||
customerPrices,
|
|
||||||
customerProducts,
|
|
||||||
customers,
|
customers,
|
||||||
ErrCode,
|
ErrCode,
|
||||||
member,
|
member,
|
||||||
organizations,
|
organizations,
|
||||||
type Organization,
|
type Organization,
|
||||||
products,
|
|
||||||
RecaseError,
|
RecaseError,
|
||||||
} from "@autumn/shared";
|
} from "@autumn/shared";
|
||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import type { DrizzleCli } from "@server/db/initDrizzle.js";
|
import type { DrizzleCli } from "@server/db/initDrizzle.js";
|
||||||
import type { Logger } from "@/external/logtail/logtailUtils.js";
|
import type { Logger } from "@/external/logtail/logtailUtils.js";
|
||||||
import {
|
import {
|
||||||
@@ -70,40 +66,6 @@ export const deletePlatformSubOrg = async ({
|
|||||||
logger.info("5. Deleting org memberships");
|
logger.info("5. Deleting org memberships");
|
||||||
await db.delete(member).where(eq(member.organizationId, org.id));
|
await db.delete(member).where(eq(member.organizationId, org.id));
|
||||||
|
|
||||||
// products.org_id cascades on org delete, but customer_products RESTRICTs
|
logger.info("6. Deleting organization");
|
||||||
// the product delete — so tear down the customer-side rows first, deepest
|
|
||||||
// FK child to shallowest: cusEnts/cusPrices → customer_products → products.
|
|
||||||
logger.info("6. Deleting customer products and products");
|
|
||||||
const orgProductIds = (
|
|
||||||
await db
|
|
||||||
.select({ internalId: products.internal_id })
|
|
||||||
.from(products)
|
|
||||||
.where(eq(products.org_id, org.id))
|
|
||||||
).map((p) => p.internalId);
|
|
||||||
|
|
||||||
if (orgProductIds.length > 0) {
|
|
||||||
const cusProductIds = (
|
|
||||||
await db
|
|
||||||
.select({ id: customerProducts.id })
|
|
||||||
.from(customerProducts)
|
|
||||||
.where(inArray(customerProducts.internal_product_id, orgProductIds))
|
|
||||||
).map((cp) => cp.id);
|
|
||||||
|
|
||||||
if (cusProductIds.length > 0) {
|
|
||||||
await db
|
|
||||||
.delete(customerEntitlements)
|
|
||||||
.where(inArray(customerEntitlements.customer_product_id, cusProductIds));
|
|
||||||
await db
|
|
||||||
.delete(customerPrices)
|
|
||||||
.where(inArray(customerPrices.customer_product_id, cusProductIds));
|
|
||||||
await db
|
|
||||||
.delete(customerProducts)
|
|
||||||
.where(inArray(customerProducts.id, cusProductIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.delete(products).where(eq(products.org_id, org.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("7. Deleting organization");
|
|
||||||
await db.delete(organizations).where(eq(organizations.id, org.id));
|
await db.delete(organizations).where(eq(organizations.id, org.id));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Scopes } from "@autumn/shared";
|
|
||||||
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
import { createStripeCli } from "@/external/connect/createStripeCli.js";
|
||||||
|
import { Scopes } from "@autumn/shared";
|
||||||
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
|
||||||
import { isStripeConnected } from "../../orgUtils.js";
|
import { isStripeConnected } from "../../orgUtils.js";
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ export const handleGetStripeAccount = createRoute({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const stripeCli = createStripeCli({ org, env });
|
const stripeCli = createStripeCli({ org, env });
|
||||||
const accountDetails = await stripeCli.accounts.retrieve(null);
|
const accountDetails = await stripeCli.accounts.retrieve();
|
||||||
return c.json(accountDetails);
|
return c.json(accountDetails);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ export const shouldReconnectStripe = async ({
|
|||||||
const stripeCli = createStripeCli({ org, env });
|
const stripeCli = createStripeCli({ org, env });
|
||||||
const newKey = new Stripe(stripeKey);
|
const newKey = new Stripe(stripeKey);
|
||||||
|
|
||||||
const oldAccount = await stripeCli.accounts.retrieve(null);
|
const oldAccount = await stripeCli.accounts.retrieve();
|
||||||
const newAccount = await newKey.accounts.retrieve(null);
|
const newAccount = await newKey.accounts.retrieve();
|
||||||
|
|
||||||
return oldAccount.id !== newAccount.id;
|
return oldAccount.id !== newAccount.id;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const handleStripeSecretKey = async ({
|
|||||||
// 1. Check if key is valid
|
// 1. Check if key is valid
|
||||||
await checkKeyValid(secretKey);
|
await checkKeyValid(secretKey);
|
||||||
const stripe = new Stripe(secretKey);
|
const stripe = new Stripe(secretKey);
|
||||||
const account = await stripe.accounts.retrieve(null);
|
const account = await stripe.accounts.retrieve();
|
||||||
|
|
||||||
// 2. Disconnect existing webhook endpoints
|
// 2. Disconnect existing webhook endpoints
|
||||||
const curWebhooks = await stripe.webhookEndpoints.list();
|
const curWebhooks = await stripe.webhookEndpoints.list();
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ import {
|
|||||||
trace,
|
trace,
|
||||||
} from "@opentelemetry/api";
|
} from "@opentelemetry/api";
|
||||||
import type Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
import type { StripeConfig } from "stripe/lib.js";
|
|
||||||
import { otelConfig } from "./otelConfig.js";
|
import { otelConfig } from "./otelConfig.js";
|
||||||
|
|
||||||
const TRACER_NAME = "autumn.stripe";
|
const TRACER_NAME = "autumn.stripe";
|
||||||
const SPAN_NAME = "stripe.api";
|
const SPAN_NAME = "stripe.api";
|
||||||
const INSTRUMENTED = new WeakSet<object>();
|
const INSTRUMENTED = new WeakSet<object>();
|
||||||
|
|
||||||
type StripeHttpClient = NonNullable<StripeConfig["httpClient"]>;
|
type StripeHttpClient = NonNullable<Stripe.StripeConfig["httpClient"]>;
|
||||||
|
|
||||||
type StripeApiLike = {
|
type StripeApiLike = {
|
||||||
httpClient?: StripeHttpClient;
|
httpClient?: StripeHttpClient;
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export const triggerSubscriptionCreated = async ({
|
|||||||
created: getUnixTime(subscriptionCreatedAtMs ?? Date.now()),
|
created: getUnixTime(subscriptionCreatedAtMs ?? Date.now()),
|
||||||
schedule: scheduleId ?? null,
|
schedule: scheduleId ?? null,
|
||||||
} as Stripe.Subscription;
|
} as Stripe.Subscription;
|
||||||
const retrieveSubscription: Stripe.SubscriptionResource["retrieve"] =
|
const retrieveSubscription: Stripe.SubscriptionsResource["retrieve"] =
|
||||||
async () =>
|
async () =>
|
||||||
stripeResponse({ object: subscription, requestId: `req_${stripeSubId}` });
|
stripeResponse({ object: subscription, requestId: `req_${stripeSubId}` });
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const normalizeActualSubItem = ({
|
|||||||
quantity: item.quantity ?? 0,
|
quantity: item.quantity ?? 0,
|
||||||
isInline: !!autumnCusPriceId,
|
isInline: !!autumnCusPriceId,
|
||||||
unitAmountDecimal: autumnCusPriceId
|
unitAmountDecimal: autumnCusPriceId
|
||||||
? (item.price.unit_amount_decimal?.toString() ?? undefined)
|
? (item.price.unit_amount_decimal ?? undefined)
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -42,7 +42,7 @@ export const normalizeActualPhaseItem = ({
|
|||||||
autumnCustomerPriceId: autumnCusPriceId || undefined,
|
autumnCustomerPriceId: autumnCusPriceId || undefined,
|
||||||
quantity: item.quantity ?? 0,
|
quantity: item.quantity ?? 0,
|
||||||
isInline: !!autumnCusPriceId,
|
isInline: !!autumnCusPriceId,
|
||||||
unitAmountDecimal: unitAmountDecimal?.toString(),
|
unitAmountDecimal,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export const normalizeExpectedPhaseItem = ({
|
|||||||
let unitAmountDecimal: string | undefined;
|
let unitAmountDecimal: string | undefined;
|
||||||
if (hasInlinePrice) {
|
if (hasInlinePrice) {
|
||||||
const priceData = (item as { price_data: StripeInlinePrice }).price_data;
|
const priceData = (item as { price_data: StripeInlinePrice }).price_data;
|
||||||
unitAmountDecimal = priceData.unit_amount_decimal.toString();
|
unitAmountDecimal = priceData.unit_amount_decimal;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -58,10 +58,7 @@ export const expectStripeInvoiceLineItemPeriodCorrect = async ({
|
|||||||
Boolean(
|
Boolean(
|
||||||
findPriceFromStripeId({
|
findPriceFromStripeId({
|
||||||
prices: usagePrices,
|
prices: usagePrices,
|
||||||
stripePriceId:
|
stripePriceId: line.pricing?.price_details?.price ?? "",
|
||||||
typeof line.pricing?.price_details?.price === "string"
|
|
||||||
? line.pricing?.price_details?.price
|
|
||||||
: (line.pricing?.price_details?.price?.id ?? ""),
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import { expireAllCusEntsForReset } from "@tests/utils/cusProductUtils/resetTest
|
|||||||
import { products } from "@tests/utils/fixtures/products.js";
|
import { products } from "@tests/utils/fixtures/products.js";
|
||||||
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
|
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { sql } from "drizzle-orm";
|
|
||||||
import { syncItemV4 } from "@/internal/balances/utils/sync/syncItemV4.js";
|
|
||||||
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js";
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────
|
||||||
@@ -51,40 +49,6 @@ test.concurrent(`${chalk.yellowBright("customer aggregated balance: rollover bal
|
|||||||
actions: [s.attach({ productId: base.id })],
|
actions: [s.attach({ productId: base.id })],
|
||||||
});
|
});
|
||||||
|
|
||||||
const syncMessagesBalance = async () => {
|
|
||||||
const cusEntRows = await ctx.db.execute(
|
|
||||||
sql`SELECT ce.id
|
|
||||||
FROM customer_entitlements ce
|
|
||||||
JOIN customers c ON c.internal_id = ce.internal_customer_id
|
|
||||||
WHERE c.id = ${customerId}
|
|
||||||
AND ce.feature_id = ${TestFeature.Messages}`,
|
|
||||||
);
|
|
||||||
const cusEntIds = cusEntRows.map((row) => row.id as string);
|
|
||||||
|
|
||||||
const rolloverRows = cusEntIds.length
|
|
||||||
? await ctx.db.execute(
|
|
||||||
sql`SELECT id FROM rollovers WHERE cus_ent_id IN (${sql.join(
|
|
||||||
cusEntIds.map((id) => sql`${id}`),
|
|
||||||
sql`, `,
|
|
||||||
)})`,
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
await syncItemV4({
|
|
||||||
ctx,
|
|
||||||
payload: {
|
|
||||||
customerId,
|
|
||||||
orgId: ctx.org.id,
|
|
||||||
env: ctx.env,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
rolloverIds: rolloverRows.map((row) => row.id as string),
|
|
||||||
modifiedCusEntIdsByFeatureId: {
|
|
||||||
[TestFeature.Messages]: cusEntIds,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── Step 1: Track 60 on ent-0, 30 on ent-1 ──
|
// ── Step 1: Track 60 on ent-0, 30 on ent-1 ──
|
||||||
// Expected per-entity balance: ent-0 = 40, ent-1 = 70.
|
// Expected per-entity balance: ent-0 = 40, ent-1 = 70.
|
||||||
// Customer aggregated: remaining = 110, usage = 90.
|
// Customer aggregated: remaining = 110, usage = 90.
|
||||||
@@ -94,14 +58,12 @@ test.concurrent(`${chalk.yellowBright("customer aggregated balance: rollover bal
|
|||||||
feature_id: TestFeature.Messages,
|
feature_id: TestFeature.Messages,
|
||||||
value: 60,
|
value: 60,
|
||||||
});
|
});
|
||||||
await syncMessagesBalance();
|
|
||||||
await autumnV1.track({
|
await autumnV1.track({
|
||||||
customer_id: customerId,
|
customer_id: customerId,
|
||||||
entity_id: entities[1].id,
|
entity_id: entities[1].id,
|
||||||
feature_id: TestFeature.Messages,
|
feature_id: TestFeature.Messages,
|
||||||
value: 30,
|
value: 30,
|
||||||
});
|
});
|
||||||
await syncMessagesBalance();
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
@@ -170,7 +132,6 @@ test.concurrent(`${chalk.yellowBright("customer aggregated balance: rollover bal
|
|||||||
feature_id: TestFeature.Messages,
|
feature_id: TestFeature.Messages,
|
||||||
value: 50,
|
value: 50,
|
||||||
});
|
});
|
||||||
await syncMessagesBalance();
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
||||||
import type { BillingContext } from "@autumn/shared";
|
import type { BillingContext } from "@autumn/shared";
|
||||||
import Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
||||||
|
|
||||||
const mockState = {
|
const mockState = {
|
||||||
@@ -112,7 +112,7 @@ describe("executeStripeSubscriptionScheduleAction", () => {
|
|||||||
product: "stripe_prod_inline",
|
product: "stripe_prod_inline",
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
recurring: { interval: "month" as const, interval_count: 1 },
|
recurring: { interval: "month" as const, interval_count: 1 },
|
||||||
unit_amount_decimal: Stripe.Decimal.from("1000"),
|
unit_amount_decimal: "1000",
|
||||||
};
|
};
|
||||||
const params = {
|
const params = {
|
||||||
phases: [
|
phases: [
|
||||||
@@ -152,7 +152,7 @@ describe("executeStripeSubscriptionScheduleAction", () => {
|
|||||||
product: "stripe_prod_inline",
|
product: "stripe_prod_inline",
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
recurring: { interval: "month", interval_count: 1 },
|
recurring: { interval: "month", interval_count: 1 },
|
||||||
unit_amount_decimal: Stripe.Decimal.from("1000"),
|
unit_amount_decimal: "1000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -189,7 +189,7 @@ describe("executeStripeSubscriptionScheduleAction", () => {
|
|||||||
product: "stripe_prod_inline",
|
product: "stripe_prod_inline",
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
recurring: { interval: "month" as const, interval_count: 1 },
|
recurring: { interval: "month" as const, interval_count: 1 },
|
||||||
unit_amount_decimal: Stripe.Decimal.from("1000"),
|
unit_amount_decimal: "1000",
|
||||||
};
|
};
|
||||||
const params = {
|
const params = {
|
||||||
phases: [
|
phases: [
|
||||||
@@ -242,7 +242,7 @@ describe("executeStripeSubscriptionScheduleAction", () => {
|
|||||||
product: "stripe_prod_inline",
|
product: "stripe_prod_inline",
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
recurring: { interval: "month", interval_count: 1 },
|
recurring: { interval: "month", interval_count: 1 },
|
||||||
unit_amount_decimal: Stripe.Decimal.from("1000"),
|
unit_amount_decimal: "1000",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
import {
|
import {
|
||||||
findMatchingInlinePriceIdForPhaseItem,
|
findMatchingInlinePriceIdForPhaseItem,
|
||||||
findMatchingInlineSubscriptionItem,
|
findMatchingInlineSubscriptionItem,
|
||||||
@@ -10,7 +10,7 @@ const inlinePrice = {
|
|||||||
product: "stripe_prod_inline",
|
product: "stripe_prod_inline",
|
||||||
currency: "usd",
|
currency: "usd",
|
||||||
recurring: { interval: "month" as const, interval_count: 1 },
|
recurring: { interval: "month" as const, interval_count: 1 },
|
||||||
unit_amount_decimal: Stripe.Decimal.from("1000"),
|
unit_amount_decimal: "1000",
|
||||||
};
|
};
|
||||||
|
|
||||||
const subscriptionItem = ({
|
const subscriptionItem = ({
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { customerProducts } from "@tests/utils/fixtures/db/customerProducts";
|
|||||||
import { prices } from "@tests/utils/fixtures/db/prices";
|
import { prices } from "@tests/utils/fixtures/db/prices";
|
||||||
import { stripeSubscriptions } from "@tests/utils/fixtures/stripe/subscriptions";
|
import { stripeSubscriptions } from "@tests/utils/fixtures/stripe/subscriptions";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
import { buildStripeSubscriptionItemsUpdate } from "@/internal/billing/v2/providers/stripe/utils/subscriptionItems/buildStripeSubscriptionItemsUpdate";
|
import { buildStripeSubscriptionItemsUpdate } from "@/internal/billing/v2/providers/stripe/utils/subscriptionItems/buildStripeSubscriptionItemsUpdate";
|
||||||
import {
|
import {
|
||||||
createCustomerPricesForProduct,
|
createCustomerPricesForProduct,
|
||||||
@@ -968,7 +968,7 @@ describe(
|
|||||||
const changedItem = currentItems.find(
|
const changedItem = currentItems.find(
|
||||||
(item) => item.metadata.inline_price === "true",
|
(item) => item.metadata.inline_price === "true",
|
||||||
)!;
|
)!;
|
||||||
changedItem.price.unit_amount_decimal = Stripe.Decimal.from("999999");
|
changedItem.price.unit_amount_decimal = "999999";
|
||||||
|
|
||||||
const result = buildUpdateWithItems({ currentItems });
|
const result = buildUpdateWithItems({ currentItems });
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
} from "@autumn/shared";
|
} from "@autumn/shared";
|
||||||
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
import type { DrizzleCli } from "@/db/initDrizzle.js";
|
||||||
import type { AutumnInt } from "@/external/autumn/autumnCli.js";
|
import type { AutumnInt } from "@/external/autumn/autumnCli.js";
|
||||||
import { CusProductService } from "@/internal/customers/cusProducts/CusProductService.js";
|
|
||||||
import { ProductService } from "@/internal/products/ProductService.js";
|
import { ProductService } from "@/internal/products/ProductService.js";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
@@ -48,20 +47,12 @@ export const createProduct = async ({
|
|||||||
const batchDelete = [];
|
const batchDelete = [];
|
||||||
for (const prod of products) {
|
for (const prod of products) {
|
||||||
batchDelete.push(
|
batchDelete.push(
|
||||||
(async () => {
|
ProductService.deleteByInternalId({
|
||||||
await CusProductService.deleteByProduct({
|
|
||||||
db,
|
|
||||||
internalProductId: prod.internal_id,
|
|
||||||
orgId,
|
|
||||||
env,
|
|
||||||
});
|
|
||||||
await ProductService.deleteByInternalId({
|
|
||||||
db,
|
db,
|
||||||
internalId: prod.internal_id,
|
internalId: prod.internal_id,
|
||||||
orgId,
|
orgId,
|
||||||
env,
|
env,
|
||||||
});
|
}),
|
||||||
})(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,15 +119,7 @@ export const createProducts = async ({
|
|||||||
const batchCreate = [];
|
const batchCreate = [];
|
||||||
for (const product of products) {
|
for (const product of products) {
|
||||||
batchCreate.push(
|
batchCreate.push(
|
||||||
createProduct({
|
createProduct({ db, orgId, env, autumn, product, prefix, createInStripe }),
|
||||||
db,
|
|
||||||
orgId,
|
|
||||||
env,
|
|
||||||
autumn,
|
|
||||||
product,
|
|
||||||
prefix,
|
|
||||||
createInStripe,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,12 @@
|
|||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"module": "Preserve",
|
"module": "Preserve",
|
||||||
"rootDirs": [".", ".."],
|
|
||||||
"typeRoots": ["./node_modules/@types", "../node_modules/@types"],
|
"typeRoots": ["./node_modules/@types", "../node_modules/@types"],
|
||||||
"types": ["bun", "node"],
|
"types": ["bun", "node"],
|
||||||
// "declaration": true,
|
// "declaration": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
"outDir": "./dist",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
@@ -40,9 +41,7 @@
|
|||||||
|
|
||||||
// Explicitly set drizzle-orm path to avoid monorepo issues
|
// Explicitly set drizzle-orm path to avoid monorepo issues
|
||||||
"drizzle-orm": ["../node_modules/drizzle-orm"],
|
"drizzle-orm": ["../node_modules/drizzle-orm"],
|
||||||
"drizzle-orm/*": ["../node_modules/drizzle-orm/*"],
|
"drizzle-orm/*": ["../node_modules/drizzle-orm/*"]
|
||||||
"stripe": ["../node_modules/.bun/node_modules/stripe/esm/stripe.esm.node"],
|
|
||||||
"stripe/*": ["../node_modules/.bun/node_modules/stripe/esm/*"]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src", "tests", "scripts", "experiments", "perf"],
|
"include": ["src", "tests", "scripts", "experiments", "perf"],
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type StripeInlinePrice = {
|
|||||||
product: string;
|
product: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
recurring?: Stripe.PriceCreateParams.Recurring;
|
recurring?: Stripe.PriceCreateParams.Recurring;
|
||||||
unit_amount_decimal: ReturnType<typeof Stripe.Decimal.from>;
|
unit_amount_decimal: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,9 +15,7 @@
|
|||||||
"@api/*": ["./api/*"],
|
"@api/*": ["./api/*"],
|
||||||
"@models/*": ["./models/*"],
|
"@models/*": ["./models/*"],
|
||||||
"@utils/*": ["./utils/*"],
|
"@utils/*": ["./utils/*"],
|
||||||
"@autumn/ksuid": ["../packages/ksuid/src/index.ts"],
|
"@autumn/ksuid": ["../packages/ksuid/src/index.ts"]
|
||||||
"stripe": ["../node_modules/.bun/node_modules/stripe/esm/stripe.esm.node"],
|
|
||||||
"stripe/*": ["../node_modules/.bun/node_modules/stripe/esm/*"]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["./**/*"],
|
"include": ["./**/*"],
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Decimal as DecimalJS } from "decimal.js";
|
import { Decimal } from "decimal.js";
|
||||||
import type Stripe from "stripe";
|
|
||||||
|
|
||||||
type DecimalLike = { toString(): string };
|
|
||||||
type StripeDecimal = string & ReturnType<typeof Stripe.Decimal.from>;
|
|
||||||
/**
|
/**
|
||||||
* Zero-decimal currencies that Stripe handles without decimal places.
|
* Zero-decimal currencies that Stripe handles without decimal places.
|
||||||
* These currencies don't require multiplying/dividing by 100.
|
* These currencies don't require multiplying/dividing by 100.
|
||||||
@@ -41,11 +38,11 @@ export const atmnToStripeAmount = ({
|
|||||||
if (ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase())) {
|
if (ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase())) {
|
||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
return new DecimalJS(amount).mul(100).round().toNumber();
|
return new Decimal(amount).mul(100).round().toNumber();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an Autumn amount to a Stripe Decimal class.
|
* Converts an Autumn amount to a Stripe decimal string.
|
||||||
* For most currencies, multiplies by 100 and returns as string with decimal places.
|
* For most currencies, multiplies by 100 and returns as string with decimal places.
|
||||||
* For zero-decimal currencies like JPY, returns the amount as-is with decimal places.
|
* For zero-decimal currencies like JPY, returns the amount as-is with decimal places.
|
||||||
* Used for Stripe API calls that require unit_amount_decimal as a string.
|
* Used for Stripe API calls that require unit_amount_decimal as a string.
|
||||||
@@ -55,19 +52,16 @@ export const atmnToStripeAmountDecimal = ({
|
|||||||
currency = "USD",
|
currency = "USD",
|
||||||
decimalPlaces = 10,
|
decimalPlaces = 10,
|
||||||
}: {
|
}: {
|
||||||
amount: number | DecimalJS;
|
amount: number | Decimal;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
decimalPlaces?: number;
|
decimalPlaces?: number;
|
||||||
}): StripeDecimal => {
|
}): string => {
|
||||||
const decimal = amount instanceof DecimalJS ? amount : new DecimalJS(amount);
|
const decimal = amount instanceof Decimal ? amount : new Decimal(amount);
|
||||||
|
|
||||||
if (ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase())) {
|
if (ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase())) {
|
||||||
return decimal.toDecimalPlaces(decimalPlaces).toString() as StripeDecimal;
|
return decimal.toDecimalPlaces(decimalPlaces).toString();
|
||||||
}
|
}
|
||||||
return decimal
|
return decimal.mul(100).toDecimalPlaces(decimalPlaces).toString();
|
||||||
.mul(100)
|
|
||||||
.toDecimalPlaces(decimalPlaces)
|
|
||||||
.toString() as StripeDecimal;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,26 +75,24 @@ export const stripeToAtmnAmount = ({
|
|||||||
decimalPlaces = 10,
|
decimalPlaces = 10,
|
||||||
round = true,
|
round = true,
|
||||||
}: {
|
}: {
|
||||||
amount: number | string | DecimalLike;
|
amount: number;
|
||||||
currency?: string;
|
currency?: string;
|
||||||
decimalPlaces?: number;
|
decimalPlaces?: number;
|
||||||
round?: boolean;
|
round?: boolean;
|
||||||
}): number => {
|
}): number => {
|
||||||
let finalAmount = new DecimalJS(
|
let finalAmount = amount;
|
||||||
typeof amount === "number" ? amount : amount.toString(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase())) {
|
if (!ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase())) {
|
||||||
finalAmount = finalAmount.div(100);
|
finalAmount = new Decimal(amount).div(100).toNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (round) {
|
if (round) {
|
||||||
return finalAmount.toDecimalPlaces(decimalPlaces).toNumber();
|
return new Decimal(finalAmount).toDecimalPlaces(decimalPlaces).toNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decimalPlaces) {
|
if (decimalPlaces) {
|
||||||
return finalAmount.toDecimalPlaces(decimalPlaces).toNumber();
|
return new Decimal(finalAmount).toDecimalPlaces(decimalPlaces).toNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
return finalAmount.toNumber();
|
return finalAmount;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import { atmnToStripeAmountDecimal } from "@utils/productUtils/priceUtils/conver
|
|||||||
import { Decimal } from "decimal.js";
|
import { Decimal } from "decimal.js";
|
||||||
import type Stripe from "stripe";
|
import type Stripe from "stripe";
|
||||||
|
|
||||||
type StripeDecimal = string & ReturnType<typeof Stripe.Decimal.from>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the Stripe tier array for a V2 prepaid price.
|
* Builds the Stripe tier array for a V2 prepaid price.
|
||||||
*
|
*
|
||||||
@@ -35,7 +33,7 @@ export const priceToStripePrepaidV2Tiers = ({
|
|||||||
price: Price;
|
price: Price;
|
||||||
entitlement: Entitlement;
|
entitlement: Entitlement;
|
||||||
org: Organization;
|
org: Organization;
|
||||||
}): Stripe.PriceCreateParams.Tier[] => {
|
}) => {
|
||||||
const config = price.config as UsagePriceConfig;
|
const config = price.config as UsagePriceConfig;
|
||||||
|
|
||||||
const tiers: Stripe.PriceCreateParams.Tier[] = [];
|
const tiers: Stripe.PriceCreateParams.Tier[] = [];
|
||||||
@@ -44,7 +42,7 @@ export const priceToStripePrepaidV2Tiers = ({
|
|||||||
// allowance. Applies to both graduated and volume pricing.
|
// allowance. Applies to both graduated and volume pricing.
|
||||||
if (entitlement.allowance) {
|
if (entitlement.allowance) {
|
||||||
tiers.push({
|
tiers.push({
|
||||||
unit_amount_decimal: "0" as StripeDecimal,
|
unit_amount_decimal: "0",
|
||||||
up_to: entitlement.allowance,
|
up_to: entitlement.allowance,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -86,15 +84,15 @@ export const priceToStripePrepaidV2Tiers = ({
|
|||||||
|
|
||||||
up_to:
|
up_to:
|
||||||
index === tiers.length - 1
|
index === tiers.length - 1
|
||||||
? ("inf" as const)
|
? "inf"
|
||||||
: new Decimal(tier.up_to ?? 0)
|
: new Decimal(tier.up_to ?? 0)
|
||||||
.div(config.billing_units ?? 1)
|
.div(config.billing_units ?? 1)
|
||||||
.ceil()
|
.ceil()
|
||||||
.toNumber(),
|
.toNumber(),
|
||||||
|
|
||||||
unit_amount_decimal: new Decimal(tier.unit_amount_decimal?.toString() ?? "0")
|
unit_amount_decimal: new Decimal(tier.unit_amount_decimal ?? 0)
|
||||||
.mul(config.billing_units ?? 1)
|
.mul(config.billing_units ?? 1)
|
||||||
.toString() as StripeDecimal,
|
.toString(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return dividedTiers;
|
return dividedTiers;
|
||||||
|
|||||||
Reference in New Issue
Block a user