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

feat: display tax in regular checkout #2773

Merged
merged 11 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 2 additions & 1 deletion ecommerce/mail_api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,8 @@ def test_send_ecommerce_order_receipt(mocker, receipt_data):
{
"quantity": 1,
"total_paid": "100.00",
"discount": "0.0",
"tax_paid": 0,
"discount": "0.00",
"price": "100.00",
"readable_id": get_readable_id(
line.product_version.product.content_object
Expand Down
28 changes: 16 additions & 12 deletions ecommerce/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
determine_visitor_country,
get_or_create_data_consent_users,
get_product_from_querystring_id,
get_product_version_price_with_discount,
get_product_version_price_with_discount_tax,
get_valid_coupon_versions,
latest_coupon_version,
latest_product_version,
Expand Down Expand Up @@ -963,26 +963,29 @@ def get_receipt(self, instance):

def get_lines(self, instance):
"""Get product information along with applied discounts"""
# pylint: disable=too-many-locals
coupon_redemption = instance.couponredemption_set.first()
lines = []
for line in instance.lines.all():
total_paid = line.product_version.price * line.quantity
discount = 0.0
coupon_version = (
coupon_redemption.coupon_version if coupon_redemption else None
)
product_price_and_tax = get_product_version_price_with_discount_tax(
coupon_version=coupon_version,
product_version=line.product_version,
tax_rate=instance.tax_rate,
)
tax_paid = product_price_and_tax["tax_assessed"] * line.quantity
total_price = product_price_and_tax["price"] * line.quantity
total_paid = total_price + tax_paid
discount = (line.product_version.price * line.quantity) - total_price

dates = CourseRunEnrollment.objects.filter(
order_id=instance.id, change_status__isnull=True
).aggregate(
start_date=dj_models.Min("run__start_date"),
end_date=dj_models.Max("run__end_date"),
)
if coupon_redemption:
total_paid = (
get_product_version_price_with_discount(
coupon_version=coupon_redemption.coupon_version,
product_version=line.product_version,
)
* line.quantity
)
discount = line.product_version.price - total_paid

content_object = line.product_version.product.content_object
(course, program, certificate_page, CEUs) = (None, None, None, None)
Expand Down Expand Up @@ -1013,6 +1016,7 @@ def get_lines(self, instance):
dict(
quantity=line.quantity,
total_paid=str(total_paid),
tax_paid=tax_paid,
discount=str(discount),
CEUs=str(CEUs) if CEUs else None,
**BaseProductVersionSerializer(line.product_version).data,
Expand Down
3 changes: 2 additions & 1 deletion ecommerce/serializers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,11 +454,12 @@ def test_serialize_order_receipt(receipt_data):
{
"readable_id": get_readable_id(product_version.product.content_object),
"content_title": product_version.product.content_object.title,
"discount": "0.0",
"discount": "0.00",
"start_date": None,
"end_date": None,
"price": str(product_version.price),
"total_paid": str(line.quantity * product_version.price),
"tax_paid": 0,
"quantity": line.quantity,
"CEUs": product_version.product.content_object.course.page.certificate_page.CEUs,
}
Expand Down
1 change: 1 addition & 0 deletions mail/templates/product_order_receipt/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ <h3 style="color: #000000; font-size: 16px; font-weight: 700; line-height: 18px;
<strong style="font-weight: 700;">Quantity:</strong> {{ line.quantity }}<br>
<strong style="font-weight: 700;">Unit Price:</strong> ${{ line.price }}<br>
<strong style="font-weight: 700;">Discount:</strong> ${{ line.discount }}<br>
<strong style="font-weight: 700;">Tax:</strong> ${{ line.tax_paid }}<br>
<strong style="font-weight: 700;">Total Paid:</strong> ${{ line.total_paid }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These need to be quantized - in testing I got a Tax line that was $21.675000 (which is right, but too many decimals).

FWIW - I added a couple of template tags in the mail app to handle this too; they do work differently than the route you've taken though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed in 426b61e

</p>
{% endfor %}
Expand Down
13 changes: 12 additions & 1 deletion static/js/components/forms/CheckoutForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { formatErrors, formatSuccessMessage } from "../../lib/form"
import {
calculateDiscount,
calculatePrice,
calculateTax,
calculateTotalAfterTax,
formatPrice,
formatRunTitle
} from "../../lib/ecommerce"
Expand Down Expand Up @@ -344,9 +346,18 @@ export class InnerCheckoutForm extends React.Component<InnerProps, InnerState> {
</div>
) : null}
<div className="bar" />
<div className="flex-row total-before-tax-row">
<span>Total before tax:</span>
<span>{formatPrice(calculatePrice(item, coupon))}</span>
</div>
<div className="flex-row tax-row">
<span>Tax:</span>
<span>{formatPrice(calculateTax(item, coupon, basket.tax_info.tax_rate))}</span>
</div>
<div className="bar" />
<div className="flex-row total-row">
<span>Total:</span>
<span>{formatPrice(calculatePrice(item, coupon))}</span>
<span>{formatPrice(calculateTotalAfterTax(item, coupon, basket.tax_info.tax_rate))}</span>
</div>
</div>
<div>
Expand Down
4 changes: 4 additions & 0 deletions static/js/containers/pages/ReceiptPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ export class ReceiptPage extends React.Component<Props> {
<th>Quantity</th>
<th>Unit Price</th>
<th>Discount</th>
<th>Tax</th>
<th>Total Paid</th>
</tr>
</thead>
Expand Down Expand Up @@ -285,6 +286,9 @@ export class ReceiptPage extends React.Component<Props> {
<td>
<div>${line.discount}</div>
</td>
<td>
<div>${line.tax_paid}</div>
</td>
<td>
<div>${line.total_paid}</div>
</td>
Expand Down
10 changes: 9 additions & 1 deletion static/js/factories/ecommerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { incrementer } from "./util"
import type {
BasketItem,
BasketResponse,
TaxInfo,
CouponSelection,
CouponPayment,
CouponPaymentVersion,
Expand Down Expand Up @@ -105,10 +106,17 @@ export const makeBasketResponse = (itemType: ?string): BasketResponse => {
return {
items: [item],
coupons: [makeCouponSelection(item)],
data_consents: [makeDataConsent()]
data_consents: [makeDataConsent()],
tax_info: makeTaxInfo(),
}
}

export const makeTaxInfo = (): TaxInfo => ({
country_code: "",
tax_rate: 0,
tax_rate_name: ""
})

export const makeProductVersion = (
productType: string = PRODUCT_TYPE_COURSERUN,
readableId: string = casual.text
Expand Down
13 changes: 11 additions & 2 deletions static/js/flow/ecommerceTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,24 +97,33 @@ export type B2BCouponStatusResponse = {
export type BasketResponse = {
items: Array<BasketItem>,
coupons: Array<CouponSelection>,
data_consents: Array<DataConsentUser>
data_consents: Array<DataConsentUser>,
tax_info: TaxInfo,
}

type BasketItemPayload = {
product_id: number | string,
run_ids?: Array<number>
}

export type TaxInfo = {
country_code: string,
tax_rate: number,
tax_rate_name: string,
}

export type BasketPayload = {
items?: Array<BasketItemPayload>,
coupons?: Array<{ code: string }>,
data_consents?: Array<number>
data_consents?: Array<number>,
tax_info?: TaxInfo,
}

export type OrderLine = {
price: string,
quantity: number,
total_paid: string,
tax_paid: string,
discount: string,
content_title: string,
readable_id: string,
Expand Down
19 changes: 19 additions & 0 deletions static/js/lib/ecommerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ export const calculatePrice = (
}
}

export const calculateTax = (
item: BasketItem,
coupon: ?CouponSelection,
taxRate: number,
): Decimal => {
const priceAfterDiscount = calculatePrice(item, coupon)
return taxRate ? priceAfterDiscount * (taxRate / 100) : 0
}

export const calculateTotalAfterTax = (
item: BasketItem,
coupon: ?CouponSelection,
taxRate: number,
): Decimal => {
const priceAfterDiscount = calculatePrice(item, coupon)
const taxAmount = calculateTax(item, coupon, taxRate)
return priceAfterDiscount.add(taxAmount)
}

const determinePreselectRunTag = (
item: BasketItem,
preselectId: number = 0
Expand Down
14 changes: 10 additions & 4 deletions static/scss/checkout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,19 @@
}

.price-row,
.discount-row {
font-size: 24px;
.discount-row,
.total-before-tax-row,
.tax-row {
font-size: 22px;
}

.discount-row {
.discount-row,
.tax-row {
margin-top: 10px;
color: $primary;
}

.total-before-tax-row {
margin-top: 25px;
}

.total-row {
Expand Down
Loading