125 lines
4.0 KiB
TypeScript
125 lines
4.0 KiB
TypeScript
import { CusProductStatus } from "@models/cusProductModels/cusProductEnums";
|
|
import { nullish } from "@utils/utils";
|
|
import { z } from "zod/v4";
|
|
import { AttachDiscountSchema } from "../attachV2/attachDiscount";
|
|
import { BillingBehaviorSchema } from "../common/billingBehavior";
|
|
import { BillingCycleAnchorSchema } from "../common/billingCycleAnchor";
|
|
import { BillingParamsBaseV0Schema } from "../common/billingParamsBase/billingParamsBaseV0";
|
|
import { CancelActionSchema } from "../common/cancelAction";
|
|
import { RedirectModeSchema } from "../common/redirectMode";
|
|
import { RefundLastPaymentSchema } from "../common/refundLastPayment";
|
|
|
|
export const ExtUpdateSubscriptionV0ParamsSchema =
|
|
BillingParamsBaseV0Schema.extend({
|
|
// Product identification (optional for update subscription - can target by customer_product_id)
|
|
product_id: z.string().nullish(),
|
|
|
|
invoice: z.boolean().optional(),
|
|
enable_product_immediately: z.boolean().optional(),
|
|
finalize_invoice: z.boolean().optional(),
|
|
invoice_template_id: z.string().optional(),
|
|
net_terms_days: z.number().int().positive().optional(),
|
|
|
|
// New
|
|
|
|
// Cancel action: 'cancel_immediately' | 'cancel_end_of_cycle' | 'uncancel'
|
|
cancel_action: CancelActionSchema.optional(),
|
|
|
|
// Billing behavior for subscription updates:
|
|
// - 'prorate_immediately' (default): Invoice line items are charged immediately
|
|
// - 'next_cycle_only': Do NOT create any charges due to the update
|
|
billing_behavior: BillingBehaviorSchema.optional(),
|
|
refund_last_payment: RefundLastPaymentSchema.optional(),
|
|
billing_cycle_anchor: BillingCycleAnchorSchema.optional(),
|
|
|
|
processor_subscription_id: z.string().nullable().optional(),
|
|
no_billing_changes: z.boolean().optional(),
|
|
discounts: z.array(AttachDiscountSchema).optional(),
|
|
recalculate_balances: z
|
|
.object({
|
|
enabled: z.boolean(),
|
|
})
|
|
.optional(),
|
|
status: z
|
|
.enum([
|
|
CusProductStatus.Active,
|
|
CusProductStatus.PastDue,
|
|
CusProductStatus.Expired,
|
|
])
|
|
.optional(),
|
|
});
|
|
|
|
export const UpdateSubscriptionV0ParamsSchema =
|
|
ExtUpdateSubscriptionV0ParamsSchema.extend({
|
|
customer_product_id: z.string().optional(),
|
|
redirect_mode: RedirectModeSchema.optional(),
|
|
})
|
|
|
|
.check((ctx) => {
|
|
if (ctx.value.options && ctx.value.options.length > 0) {
|
|
const invalidFeatures = ctx.value.options
|
|
.filter((opt) => nullish(opt.quantity) || opt.quantity < 0)
|
|
.map((opt) => opt.feature_id);
|
|
|
|
if (invalidFeatures.length > 0) {
|
|
ctx.issues.push({
|
|
code: "custom",
|
|
message: `Options quantity must be >= 0 for features: ${invalidFeatures.join(", ")}`,
|
|
input: ctx.value,
|
|
});
|
|
}
|
|
}
|
|
})
|
|
.refine(
|
|
(data) => {
|
|
if (data.cancel_action !== "cancel_immediately") return true;
|
|
|
|
const forbiddenFields = [
|
|
"options",
|
|
"items",
|
|
"version",
|
|
"free_trial",
|
|
] as const;
|
|
return !forbiddenFields.some((field) => data[field] !== undefined);
|
|
},
|
|
{
|
|
message:
|
|
"Cannot pass options, items, version, or free_trial when cancel_action is 'cancel_immediately'. Immediate cancellation only processes a prorated refund.",
|
|
},
|
|
)
|
|
.refine(
|
|
(data) => {
|
|
if (data.cancel_action !== "cancel_end_of_cycle") return true;
|
|
|
|
// Cannot pass free_trial when cancel_action is 'cancel_end_of_cycle'
|
|
return data.free_trial === undefined;
|
|
},
|
|
{
|
|
message:
|
|
"Cannot pass free_trial when cancel_action is 'cancel_end_of_cycle'.",
|
|
},
|
|
)
|
|
.refine((data) => !(data.refund_last_payment && data.billing_behavior), {
|
|
message:
|
|
"Cannot pass both billing_behavior and refund_last_payment. Use billing_behavior for invoice credits/proration, or refund_last_payment for direct refunds.",
|
|
})
|
|
.refine(
|
|
(data) =>
|
|
!(
|
|
data.refund_last_payment &&
|
|
data.cancel_action !== "cancel_immediately"
|
|
),
|
|
{
|
|
message:
|
|
"refund_last_payment requires cancel_action to be 'cancel_immediately'.",
|
|
},
|
|
);
|
|
|
|
export type ExtUpdateSubscriptionV0Params = z.infer<
|
|
typeof ExtUpdateSubscriptionV0ParamsSchema
|
|
>;
|
|
|
|
export type UpdateSubscriptionV0Params = z.infer<
|
|
typeof UpdateSubscriptionV0ParamsSchema
|
|
>;
|