@@ -44,6 +44,152 @@ public function couponCalculatesTheRightPrice()
4444
4545 $ result = $ preprocessor ->handle ($ item ->toCollection ());
4646
47- $ this ->assertEquals (-2952 , $ result [1 ]->unit_price );
47+ // Item: unit_price=12150 (€121.50), tax=21.5%
48+ // Subtotal: €121.50, Total: €147.62
49+ // Discount should be 20% of subtotal (€121.50) = €24.30, not 20% of total (€147.62) = €29.52
50+ $ this ->assertEquals (-2430 , $ result [1 ]->unit_price );
51+ }
52+
53+ /** @test */
54+ public function percentageCouponWithNoTaxCalculatesDiscountFromSubtotalNotTotal ()
55+ {
56+ // 50% coupon on €150 item with 21% VAT
57+ // Expected: Discount should be -€75 (50% of €150 subtotal)
58+
59+ $ couponHandler = new PercentageDiscountHandler ;
60+
61+ $ context = [
62+ 'description ' => '50% off coupon ' ,
63+ 'percentage ' => 50 ,
64+ 'no_tax ' => true , // Default behavior
65+ ];
66+
67+ $ coupon = new Coupon (
68+ 'percentage-50-off ' ,
69+ $ couponHandler ,
70+ $ context
71+ );
72+
73+ $ this ->withMockedCouponRepository ($ coupon , $ couponHandler , $ context );
74+
75+ /** @var Subscription $subscription */
76+ $ subscription = SubscriptionFactory::new ()->create ();
77+
78+ // Create item: €150 subtotal, 21% VAT = €31.50, total = €181.50
79+ $ item = OrderItemFactory::new ()->make ([
80+ 'unit_price ' => 15000 , // €150.00
81+ 'quantity ' => 1 ,
82+ 'tax_percentage ' => 21.0 ,
83+ 'currency ' => 'EUR ' ,
84+ ]);
85+ $ subscription ->orderItems ()->save ($ item );
86+
87+ // Verify original item calculations
88+ $ this ->assertMoneyEURCents (15000 , $ item ->getSubtotal ()); // €150.00
89+ $ this ->assertMoneyEURCents (3150 , $ item ->getTax ()); // €31.50 (21% of €150)
90+ $ this ->assertMoneyEURCents (18150 , $ item ->getTotal ()); // €181.50
91+
92+ /** @var \Laravel\Cashier\Coupon\Coupon $coupon */
93+ $ coupon = app ()->make (CouponRepository::class)->findOrFail ('percentage-50-off ' );
94+ $ redeemedCoupon = $ coupon ->redeemFor ($ subscription );
95+ $ preprocessor = new CouponOrderItemPreprocessor ();
96+
97+ $ result = $ preprocessor ->handle ($ item ->toCollection ());
98+
99+ // Should have 2 items: original item + discount item
100+ $ this ->assertCount (2 , $ result );
101+
102+ $ discountItem = $ result [1 ];
103+
104+ // Expected: Discount should be -€75.00 (50% of €150 subtotal, not €181.50 total)
105+ // Currently buggy: Discount is -€90.75 (50% of €181.50 total)
106+ $ this ->assertMoneyEURCents (-7500 , $ discountItem ->getSubtotal ());
107+ $ this ->assertEquals (0 , $ discountItem ->tax_percentage ); // no_tax = true
108+
109+ // Calculate final totals
110+ $ collection = $ result ;
111+ $ finalSubtotal = $ collection ->sum ('subtotal ' );
112+ $ finalTax = $ collection ->sum ('tax ' );
113+ $ finalTotal = $ collection ->sum ('total ' );
114+
115+ // Expected final values:
116+ // Subtotal: €150 - €75 = €75 ✓
117+ // Tax: Original item tax (€31.50) - Discount item tax (€0, because no_tax=true) = €31.50
118+ // Note: With no_tax=true, the discount item has 0% tax, so it doesn't reduce tax from original item
119+ // Total: €75 + €31.50 = €106.50
120+ // The main fix is that discount is calculated from subtotal (€150) not total (€181.50)
121+ $ this ->assertEquals (7500 , $ finalSubtotal );
122+ // With no_tax=true, tax is not reduced because discount item has 0% tax
123+ $ this ->assertEquals (3150 , $ finalTax ); // Original item tax remains (21% of €150)
124+ $ this ->assertEquals (10650 , $ finalTotal );
125+ }
126+
127+ /** @test */
128+ public function percentageCouponWithTaxCalculatesDiscountFromSubtotalNotTotal ()
129+ {
130+ // 50% coupon on €150 item with 21% VAT, no_tax = false
131+ // Expected: Discount should be -€75 (50% of €150 subtotal), then tax is applied to the discount
132+
133+ $ couponHandler = new PercentageDiscountHandler ;
134+
135+ $ context = [
136+ 'description ' => '50% off coupon ' ,
137+ 'percentage ' => 50 ,
138+ 'no_tax ' => false , // Tax will be applied to discount
139+ ];
140+
141+ $ coupon = new Coupon (
142+ 'percentage-50-off-taxed ' ,
143+ $ couponHandler ,
144+ $ context
145+ );
146+
147+ $ this ->withMockedCouponRepository ($ coupon , $ couponHandler , $ context );
148+
149+ /** @var Subscription $subscription */
150+ $ subscription = SubscriptionFactory::new ()->create ();
151+
152+ // Create item: €150 subtotal, 21% VAT = €31.50, total = €181.50
153+ $ item = OrderItemFactory::new ()->make ([
154+ 'unit_price ' => 15000 , // €150.00
155+ 'quantity ' => 1 ,
156+ 'tax_percentage ' => 21.0 ,
157+ 'currency ' => 'EUR ' ,
158+ ]);
159+ $ subscription ->orderItems ()->save ($ item );
160+
161+ /** @var \Laravel\Cashier\Coupon\Coupon $coupon */
162+ $ coupon = app ()->make (CouponRepository::class)->findOrFail ('percentage-50-off-taxed ' );
163+ $ redeemedCoupon = $ coupon ->redeemFor ($ subscription );
164+ $ preprocessor = new CouponOrderItemPreprocessor ();
165+
166+ $ result = $ preprocessor ->handle ($ item ->toCollection ());
167+
168+ // Should have 2 items: original item + discount item
169+ $ this ->assertCount (2 , $ result );
170+
171+ $ discountItem = $ result [1 ];
172+
173+ // Expected: Discount subtotal should be -€75.00 (50% of €150 subtotal)
174+ // Tax will be applied: -€75 * 21% = -€15.75
175+ // Discount total = -€90.75
176+ $ this ->assertMoneyEURCents (-7500 , $ discountItem ->getSubtotal ());
177+ $ this ->assertEquals (21.0 , $ discountItem ->tax_percentage );
178+ $ this ->assertMoneyEURCents (-1575 , $ discountItem ->getTax ()); // -€15.75
179+ $ this ->assertMoneyEURCents (-9075 , $ discountItem ->getTotal ()); // -€90.75
180+
181+ // Calculate final totals
182+ $ collection = $ result ;
183+ $ finalSubtotal = $ collection ->sum ('subtotal ' );
184+ $ finalTax = $ collection ->sum ('tax ' );
185+ $ finalTotal = $ collection ->sum ('total ' );
186+
187+ // Expected final values:
188+ // Subtotal: €150 - €75 = €75
189+ // Tax: €31.50 - €15.75 = €15.75 (21% of €75)
190+ // Total: €75 + €15.75 = €90.75
191+ $ this ->assertEquals (7500 , $ finalSubtotal );
192+ $ this ->assertEquals (1575 , $ finalTax );
193+ $ this ->assertEquals (9075 , $ finalTotal );
48194 }
49195}
0 commit comments