Skip to content
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
177 changes: 177 additions & 0 deletions src/__tests__/fixtures/price.samples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3552,3 +3552,180 @@ export const compositePriceCashbackCombinedWithComponentCashbacks: CompositePric
],
},
};

export const unorderedPriceItemsOneTimeRecurrencesWithDiscount = [
{
description: 'Price 1 One time - no discount',
pricing_model: 'per_unit',
unit_amount: 10000000,
unit_amount_currency: 'EUR',
unit_amount_decimal: '100000',
is_tax_inclusive: true,
price_display_in_journeys: 'show_price',
active: true,
type: 'one_time',
billing_duration_unit: 'months',
notice_time_unit: 'months',
termination_time_unit: 'months',
renewal_duration_unit: 'months',
_tags: [],
_schema: 'price',
_id: '2f922147-f3eb-46d6-8ef0-ce35a8542466',
_org: '739224',
_owners: [
{
org_id: '739224',
user_id: '10009151',
},
],
_created_at: '2025-11-26T13:37:10.419Z',
_updated_at: '2025-11-26T13:37:10.419Z',
internal_description: 'Price 1 One time - no discount',
billing_period: 'weekly',
_title: 'Price 1 One time - no discount',
_viewers: {},
_messages: {},
_coupons: [],
blockMappingData: {},
quantity: 1,
},
{
description: 'Price 2 One time - with discount',
pricing_model: 'per_unit',
unit_amount: 1000000,
unit_amount_currency: 'EUR',
unit_amount_decimal: '10000',
is_tax_inclusive: true,
price_display_in_journeys: 'show_price',
active: true,
type: 'one_time',
billing_duration_unit: 'months',
notice_time_unit: 'months',
termination_time_unit: 'months',
renewal_duration_unit: 'months',
_tags: [],
_schema: 'price',
_id: '4d5b79c9-a3b6-45d7-b078-1b78dd9f4b9e',
_org: '739224',
_created_at: '2025-11-26T13:38:22.953Z',
_updated_at: '2025-11-26T13:38:22.953Z',
internal_description: 'Price 2 One time - with discount',
billing_period: 'weekly',
_title: 'Price 2 One time - with discount',
_viewers: {},
_messages: {},
_coupons: [
{
name: 'PROMO 20',
type: 'percentage',
fixed_value: 0,
fixed_value_currency: 'EUR',
fixed_value_decimal: '0.00',
category: 'discount',
active: true,
prices: [
{
description: 'Price Issue 2 (PROMO)',
pricing_model: 'per_unit',
unit_amount: 1000000,
unit_amount_currency: 'EUR',
unit_amount_decimal: '10000',
is_tax_inclusive: true,
price_display_in_journeys: 'show_price',
active: true,
type: 'one_time',
billing_duration_unit: 'months',
notice_time_unit: 'months',
termination_time_unit: 'months',
renewal_duration_unit: 'months',
_tags: [],
_schema: 'price',
_id: '4d5b79c9-a3b6-45d7-b078-1b78dd9f4b9e',
_org: '739224',
_owners: [
{
org_id: '739224',
user_id: '10009151',
},
],
_created_at: '2025-11-26T13:38:22.953Z',
_updated_at: '2025-11-26T13:38:22.953Z',
internal_description: 'Price Issue 2 (PROMO)',
billing_period: 'weekly',
_title: 'Price Issue 2 (PROMO)',
_acl: {
view: ['org_739224'],
edit: ['org_739224'],
delete: ['org_739224'],
},
$relation: {
entity_id: '4d5b79c9-a3b6-45d7-b078-1b78dd9f4b9e',
_schema: 'price',
_tags: [],
},
},
],
_schema: 'coupon',
percentage_value: '20',
_id: 'bd801823-e4d2-47cb-bdfa-58e40392055f',
_org: '739224',
_owners: [
{
org_id: '739224',
user_id: '10009151',
},
],
_created_at: '2025-11-26T13:39:37.146Z',
_updated_at: '2025-11-26T13:39:37.146Z',
cashback_period: '0',
_title: 'PROMO 20',
_acl: {
view: ['org_739224'],
edit: ['org_739224'],
delete: ['org_739224'],
},
_relations: [
{
entity_id: '4d5b79c9-a3b6-45d7-b078-1b78dd9f4b9e',
_acl: {
view: ['org_739224'],
edit: ['org_739224'],
delete: ['org_739224'],
},
},
],
_viewers: {},
_messages: {},
},
],
blockMappingData: {},
quantity: 1,
},
{
description: 'Price 3 One time - no discount',
pricing_model: 'per_unit',
unit_amount: 100000,
unit_amount_currency: 'EUR',
unit_amount_decimal: '1000',
is_tax_inclusive: true,
price_display_in_journeys: 'show_price',
active: true,
type: 'one_time',
billing_duration_unit: 'months',
notice_time_unit: 'months',
termination_time_unit: 'months',
renewal_duration_unit: 'months',
_tags: [],
_schema: 'price',
_id: '5d3f3968-6e94-4934-b282-517d990d62e2',
_org: '739224',
_created_at: '2025-11-26T13:39:11.980Z',
_updated_at: '2025-11-26T13:39:11.980Z',
internal_description: 'Price 3 One time - no discount',
billing_period: 'weekly',
_title: 'Price 3 One time - no discount',
_coupons: [],
blockMappingData: {},
quantity: 1,
},
] as PriceItemDto[];
11 changes: 11 additions & 0 deletions src/computations/compute-totals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,17 @@ describe('computeAggregatedAndPriceTotals', () => {
);
expect(result).toEqual(results.computedCompositePriceWithComponentsWithPromoCodeRequiredCoupon);
});
it('should compute the pricing total breakdown details with discounts correctly independently of the order of the items', () => {
const result = computeAggregatedAndPriceTotals(samples.unorderedPriceItemsOneTimeRecurrencesWithDiscount, {
redeemedPromos: [],
});
expect(result.total_details?.breakdown?.recurrences?.[0]?.amount_total_decimal).toEqual('109000');
expect(result.total_details?.breakdown?.recurrences?.[0]?.amount_total).toEqual(10900000);
expect(result.total_details?.breakdown?.recurrences?.[0]?.before_discount_amount_total_decimal).toEqual('111000');
expect(result.total_details?.breakdown?.recurrences?.[0]?.before_discount_amount_total).toEqual(11100000);
expect(result.total_details?.breakdown?.recurrences?.[0]?.discount_amount_decimal).toEqual('2000');
expect(result.total_details?.breakdown?.recurrences?.[0]?.discount_amount).toEqual(200000);
});
});

it('computes the pricing details for a simple price', () => {
Expand Down
8 changes: 6 additions & 2 deletions src/computations/compute-totals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,13 @@ const recomputeDetailTotals = (
typeof recurrence.discount_amount !== 'undefined' ? toDineroFromInteger(recurrence.discount_amount) : undefined;

if (priceBeforeDiscountAmountTotal || existingRecurrenceBeforeDiscountAmountTotal) {
// If recurrence doesn't have before_discount_amount_total yet, initialize it with current total (before adding this item)
const initializedExistingTotal =
existingRecurrenceBeforeDiscountAmountTotal ??
toDineroFromInteger(recurrence.amount_total).subtract(priceTotal);

const baseAmount = priceBeforeDiscountAmountTotal || priceTotal;
const recurrenceBeforeDiscountAmountTotal =
existingRecurrenceBeforeDiscountAmountTotal?.add(baseAmount) ?? baseAmount;
const recurrenceBeforeDiscountAmountTotal = initializedExistingTotal.add(baseAmount);
recurrence.before_discount_amount_total = recurrenceBeforeDiscountAmountTotal.getAmount();
recurrence.before_discount_amount_total_decimal = recurrenceBeforeDiscountAmountTotal.toUnit().toString();
}
Expand Down