Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checkout: Change plan upsell calculation to take volume and discounts into account #90893

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 46 additions & 4 deletions client/my-sites/checkout/cart/cart-free-user-plan-upsell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ const isRegistrationOrTransfer = ( item: ResponseCartProduct ) => {
return isDomainRegistration( item ) || isDomainTransfer( item );
};

function applyPercentageDiscount( percentageDiscount: number, basePrice: number ): number {
return basePrice - basePrice * ( percentageDiscount / 100 );
}

function UpgradeText( {
planPrice,
planName,
Expand All @@ -46,9 +50,43 @@ function UpgradeText( {
firstDomain: ResponseCartProduct;
} ) {
const translate = useTranslate();
// Use the subtotal with discounts for the current cart item price if the
// item's discounts are only for the first year, but use the subtotal
// without discounts to calculate the price after the upsell because the
// upsell will remove other first year discounts.
//
// For example, if the domain has a sale coupon then it will be removed by
// the "first year free" bundle discount when a plan is added to the cart
// because the bundle discount of 100% is higher than the sale coupon
// discount. Since we are trying to compare the current cart price with the
// predicted cart price with the plan, we must consider the current cart
// with the sale and the predicted cart without the sale.
//
// This will not be accurate for all types of discounts and predicting what
// discounts will be applied is difficult, but this makes an attempt based
// on what discounts are currently applied.
const isDomainDiscountFromFirstYearOverride = firstDomain.cost_overrides.every(
( override ) => override.first_unit_only
);
// Coupon code discounts will apply before and after the upsell, so we must
// re-apply them to the price difference also.
const domainCouponCodeDiscount = firstDomain.cost_overrides.find(
( override ) => override.override_code === 'coupon-discount'
);
const domainCouponPercentageDiscount = domainCouponCodeDiscount?.percentage ?? 0;
const domainInCartPrice = isDomainDiscountFromFirstYearOverride
? firstDomain.item_subtotal_integer
: firstDomain.item_original_subtotal_integer;
const domainInCartPricePerYear = firstDomain.item_original_subtotal_integer / firstDomain.volume;
const cartPriceWithDomainAndPlan =
domainInCartPricePerYear * ( firstDomain.volume - 1 ) + planPrice;

if ( planPrice > firstDomain.item_subtotal_integer ) {
const extraToPay = planPrice - firstDomain.item_subtotal_integer;
if ( cartPriceWithDomainAndPlan > domainInCartPrice ) {
// Re-apply any coupon code discount.
const extraToPay = applyPercentageDiscount(
domainCouponPercentageDiscount,
cartPriceWithDomainAndPlan - domainInCartPrice
);
return translate(
'Pay an {{strong}}extra %(extraToPay)s{{/strong}} for our %(planName)s plan, and get access to all its features, plus the first year of your domain for free.',
{
Expand All @@ -66,8 +104,12 @@ function UpgradeText( {
);
}

if ( planPrice < firstDomain.item_subtotal_integer ) {
const savings = firstDomain.item_subtotal_integer - planPrice;
if ( cartPriceWithDomainAndPlan < domainInCartPrice ) {
// Re-apply any coupon code discount.
const savings = applyPercentageDiscount(
domainCouponPercentageDiscount,
domainInCartPrice - cartPriceWithDomainAndPlan
);
return translate(
'{{strong}}Save %(savings)s{{/strong}} when you purchase a WordPress.com %(planName)s plan instead — your domain comes free for a year.',
{
Expand Down
1 change: 1 addition & 0 deletions packages/shopping-cart/src/empty-carts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@ export function getEmptyResponseCartProduct(): ResponseCartProduct {
product_type: 'test',
included_domain_purchase_amount: 0,
product_variants: [],
cost_overrides: [],
};
}
4 changes: 3 additions & 1 deletion packages/shopping-cart/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ export interface ResponseCartProduct {
* The override_code is a string that identifies the reason for the override.
* When displaying the reason to the customer, use the human_readable_reason.
*/
cost_overrides?: ResponseCartCostOverride[];
cost_overrides: ResponseCartCostOverride[];

/**
* If set, is used to transform the usage/quantity of units used to derive the number of units
Expand Down Expand Up @@ -525,6 +525,8 @@ export interface ResponseCartCostOverride {
old_subtotal_integer: number;
override_code: string;
does_override_original_cost: boolean;
percentage: number;
first_unit_only: boolean;
}

export type IntroductoryOfferUnit = 'day' | 'week' | 'month' | 'year' | 'indefinite';
Expand Down
Loading