Skip to content

Commit

Permalink
Checkout: Change plan upsell calculation to take volume and discounts…
Browse files Browse the repository at this point in the history
… into account (#90893)

* Change savings calculation to take volume discounts into account

* Add percentage and first_unit_only to cart item cost_overrides

Also make it clear that `cost_overrides` is always set.

* Only use undiscounted domain price in calc for first year discounts

* Re-apply coupon code discount to price difference

* Add cost_overrides to empty response cart
  • Loading branch information
sirbrillig committed May 22, 2024
1 parent b1ecd16 commit 599fe0a
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 5 deletions.
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

0 comments on commit 599fe0a

Please sign in to comment.