Skip to main content

Coupons, Discounts & Fees

This tutorial extends the mock implementation started in the Basic Flow and extended in the Shipping Options tutorials.

Visit these pages before continuing as we will be omitting steps and code that has been covered already from our examples for brevity.

Handling coupons added in the Simpler form

When the shopper is presented with the Simpler Checkout form, they will be able to supply a coupon to their cart before proceeding with the payment.

Coupons that the shopper has added will be included in the Quote requests and we can handle them in our QuoteController in order to return the updated total payable amount as well as the discount.

Once again we will be delegating the coupon validation logic to our core platform and using the updated cart to serialize our quote response.

src/Controller/Simpler/QuoteController.php
class SimplerQuoteController extends AbstractController {
public function postData(Request $request): Response {
$requestData = json_decode($request->getContent(), true);

$response = [
'request_id' => $requestData['request_id'],
'quotes' => []
];

$cart = CartService::createCart();
try {
$this->addQuotationItemsToCart($request['quotation']['items'], $cart);

try {
$cart->applyCoupon($request['quotation']['coupon']);
} catch (InvalidCouponException $e) {
// return the INVALID_COUPON error code to inform the shopper that the coupon is invalid
return new JSONResponse([
'code' => 'INVALID_COUPON'
]);
}

$options = ShippingService::getShippingOptions($cart, $shippingAddress);
foreach ($options as $option) {
$response['quotes'][] = $this->buildQuote($cart, $option);
}

return $this->json($response);
} finally {
$cart->delete();
}
}

private function buildQuote($cart, $option) {
$quote = [];

foreach ($cart->getLineItems() as $item) {
$quote['items'][] = [
'id' => $item->getProduct()->getSKU(),
'quantity' => $item->getQuantity(),
'subtotal_cents' => $item->getTotalCost(),
]
}

$quote['shipping_option'] = [
'id' => $option->getId(),
'name' => $option->getLabelText(),
'total_cents' => $option->getCost(),
'type' => 'DELIVERY'
]

// include the total discount amount in the quote
$quote['discount_cents'] = $cart->getDiscountTotal();


// the total_cents value should include the discount
$quote['total_cents'] = $cart->getGrandTotal() + $option->getCost() - $cart->getDiscountTotal();
}
}

Applying a discount automatically

In the case where you want to apply a discount to the cart regardless of the user's coupon selection (for example in the case of a limited time promotion), you can use the fees option of the quote interface.

src/Controller/Simpler/QuoteController.php
private function buildQuote($cart, $option) {
$quote = [];

foreach ($cart->getLineItems() as $item) {
$quote['items'][] = [
'id' => $item->getProduct()->getSKU(),
'quantity' => $item->getQuantity(),
'subtotal_cents' => $item->getTotalCost(),
]
}

$quote['shipping_option'] = [
'id' => $option->getId(),
'name' => $option->getLabelText(),
'total_cents' => $option->getCost(),
'type' => 'DELIVERY'
]

// note that we don't include automatic fees in the discount_cents as it's being used for coupons only
$quote['discount_cents'] = $cart->getCouponDiscountTotal();

foreach ($cart->getDiscounts() as $discount) {
$quote['fees'][] = [
'title' => $discount->getTitle(),
'cost_cents' => $discount->getTotal()
]
}

// the total_cents value should include the discount
$quote['total_cents'] = $cart->getGrandTotal() + $option->getCost() - $cart->getDiscountTotal();
}

Discounts that apply for specific shipping options

In the advanced case where a discount might apply for some shipping options only, we have to adjust our approach a bit.

src/Controller/Simpler/QuoteController.php
class SimplerQuoteController extends AbstractController {
public function postData(Request $request): Response {
$requestData = json_decode($request->getContent(), true);

$response = [
'request_id' => $requestData['request_id'],
'quotes' => []
];

$cart = CartService::createCart();
try {
$this->addQuotationItemsToCart($request['quotation']['items'], $cart);
if ($coupon = $request['quotation']['coupon']) {
$this->applyCoupon($coupon, $cart);
}

$shippingAddress = $request['quotation']['address'];
$options = ShippingService::getShippingOptions($cart, $shippingAddress);

foreach ($options as $option) {
// set the shipping option on the cart, so our discount gets
$cart->setSelectedShippingOption($option);
// re-trigger a calculation (if needed by your platform) so that the discounts are included
$cart->calculateTotals();
$response['quotes'][] = $this->buildQuote($cart, $option);
}

return $this->json($response);
} finally {
$cart->delete();
}
}
}

Applying discounts for returning customers

As we'll see in the Identifying Customers tutorial, the quote request includes the email that a shopper has added to the form. We can use this to our advantage in order to calculate discounts for this specific customer :

src/Controller/Simpler/QuoteController.php
class SimplerQuoteController extends AbstractController {
public function postData(Request $request): Response {
$requestData = json_decode($request->getContent(), true);

$response = [
'request_id' => $requestData['request_id'],
'quotes' => []
];

$cart = CartService::createCart();

if ($email = $request['quotation']['email']) {
$customer = CustomerService::getByEmail($email);
if ($customer) {
$cart->setCustomerId($customer->id);
}
}

try {
$this->addQuotationItemsToCart($request['quotation']['items'], $cart);
if ($coupon = $request['quotation']['coupon']) {
$this->applyCoupon($coupon, $cart);
}

$shippingAddress = $request['quotation']['address'];
$options = ShippingService::getShippingOptions($cart, $shippingAddress);

foreach ($options as $option) {
$cart->setSelectedShippingOption($option);
// your calculateTotals method should take into account the customer and apply the discount
// if this is not the case you can use the `customer` record to calculate the total discount in a separate function
$cart->calculateTotals();
$response['quotes'][] = $this->buildQuote($cart, $option);
}

return $this->json($response);
} finally {
$cart->delete();
}
}
}

Adding gifts to the cart

When Simpler sends you a cart, the items property will contain only the list of items that the customer has added to their cart. In the case where you have rules that automatically add items to the cart based on the cart contents (such as free gifts), you can include the gift items in your items response and they will appear on the cart contents.

Do note that the gift items will not be updated to the cart in the final order submission, so you will have to make sure that the gift items are added to the final cart before converting to an order on your order submission controller

src/Controller/Simpler/QuoteController.php
private function buildQuote($cart, $option) {
$quote = [];

foreach ($cart->getLineItems() as $item) {
$quote['items'][] = [
'id' => $item->getProduct()->getSKU(),
'quantity' => $item->getQuantity(),
'subtotal_cents' => $item->getTotalCost()
]
}

foreach ($cart->getGiftItems() as $item) {
$quote['items'][] = [
'id' => $item->getProduct()->getSKU(),
'quantity' => $item->getQuantity(),
'subtotal_cents' => 0,
]
}

$quote['shipping_option'] = [
'id' => $option->getId(),
'name' => $option->getLabelText(),
'total_cents' => $option->getCost(),
'type' => 'DELIVERY'
]

$quote['discount_cents'] = $cart->getCouponDiscountTotal();

foreach ($cart->getDiscounts() as $discount) {
$quote['fees'][] = [
'title' => $discount->getTitle(),
'cost_cents' => $discount->getTotal()
]
}

// the total_cents value should include the discount
$quote['total_cents'] = $cart->getGrandTotal() + $option->getCost() - $cart->getDiscountTotal();
}