This course is archived!
This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.
Applying a Coupon at Checkout
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
There are two ways to use a coupon on checkout: either attach it to the subscription to say "This subscription should have this coupon code" - or - attach it to the customer. They're approximately the same, but we'll attach the coupon to the customer, in part, because the coupon should also work on individual products.
In OrderController
, scroll down to the chargeCustomer()
method:
// ... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
// ... lines 14 - 108 | |
private function chargeCustomer($token) | |
{ | |
$stripeClient = $this->get('stripe_client'); | |
/** @var User $user */ | |
$user = $this->getUser(); | |
if (!$user->getStripeCustomerId()) { | |
$stripeCustomer = $stripeClient->createCustomer($user, $token); | |
} else { | |
$stripeCustomer = $stripeClient->updateCustomerCard($user, $token); | |
} | |
// save card details | |
$this->get('subscription_helper') | |
->updateCardDetails($user, $stripeCustomer); | |
$cart = $this->get('shopping_cart'); | |
foreach ($cart->getProducts() as $product) { | |
$stripeClient->createInvoiceItem( | |
$product->getPrice() * 100, | |
$user, | |
$product->getName() | |
); | |
} | |
if ($cart->getSubscriptionPlan()) { | |
// a subscription creates an invoice | |
$stripeSubscription = $stripeClient->createSubscription( | |
$user, | |
$cart->getSubscriptionPlan() | |
); | |
$this->get('subscription_helper')->addSubscriptionToUser( | |
$stripeSubscription, | |
$user | |
); | |
} else { | |
// charge the invoice! | |
$stripeClient->createInvoice($user, true); | |
} | |
} | |
} | |
// ... lines 151 - 152 |
We know this method: we get or create the Stripe Customer, create InvoiceItems for any products, create the Subscription, and then create an invoice, if needed.
Before adding the invoice items, let's add the coupon to the Customer. So, if
$cart->getCouponCodeValue()
, then very simply,
$stripeCustomer->coupon = $cart->getCouponCode()
. Make it official with $customer->save()
:
// ... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
// ... lines 14 - 108 | |
private function chargeCustomer($token) | |
{ | |
// ... lines 111 - 123 | |
$cart = $this->get('shopping_cart'); | |
if ($cart->getCouponCodeValue()) { | |
$stripeCustomer->coupon = $cart->getCouponCode(); | |
$stripeCustomer->save(); | |
} | |
// ... lines 130 - 153 | |
} | |
} | |
// ... lines 156 - 157 |
The important thing is that you don't need to change how much you're charging the user: attach the coupon, charge them for the full amount, and let Stripe figure it all out.
I think we should try this out! Use our favorite fake credit card, and Checkout! So far so good!
Find the Customer in Stripe. Yep! There's the order: $49. The invoice tells the whole story: with the sub-total, the discount and the total.
Very, very, nice.
Handling Invalid Coupons
And very easy! So easy, that we have time to add code to handle invalid coupons. Add another item to your cart. Now, try a FAKE coupon code.
Ah! 500 error is no fun. The exception is a \Stripe\Error\InvalidRequest
because,
basically, the API responds with a 404 status code.
This all falls apart in OrderController
on line 95. Hunt that down!
Ah, findCoupon()
: surround this beast with a try-catch block for \Stripe\Error\InvalidRequest
:
// ... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
// ... lines 14 - 83 | |
public function addCouponAction(Request $request) | |
{ | |
// ... lines 86 - 93 | |
try { | |
$stripeCoupon = $this->get('stripe_client') | |
->findCoupon($code); | |
} catch (\Stripe\Error\InvalidRequest $e) { | |
// ... lines 98 - 100 | |
} | |
// ... lines 102 - 108 | |
} | |
// ... lines 110 - 160 | |
} | |
// ... lines 162 - 163 |
The easiest thing to do is add a flash error message: Invalid Coupon code
. Then,
redirect back to the checkout page:
// ... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
// ... lines 14 - 83 | |
public function addCouponAction(Request $request) | |
{ | |
// ... lines 86 - 93 | |
try { | |
$stripeCoupon = $this->get('stripe_client') | |
->findCoupon($code); | |
} catch (\Stripe\Error\InvalidRequest $e) { | |
$this->addFlash('error', 'Invalid coupon code!'); | |
return $this->redirectToRoute('order_checkout'); | |
} | |
// ... lines 102 - 108 | |
} | |
// ... lines 110 - 160 | |
} | |
// ... lines 162 - 163 |
Refresh that bad coupon! Ok! That's covered!
Expired Coupons
There's just one other situation to handle. In Stripe, find the Coupon section
and create a second code. Set the amount to $50, duration "once" and the code:
SINGLE_USE
. By here's the kicker: set Max redemptions to 1. So, only one customer
should be able to use this. There's also a time-sensitive "Redeem by" option.
Quickly, go use the SINGLE_USE
code and fill out the form to checkout. This will
be the first - and only - allowed "redemption" of this code. When you refresh the
Coupon page in Stripe, Redemptions are 1/1.
Now, add another subscription to your cart. If you tried to use the code a second time, our system would allow this. And that makes sense: all we're doing now is looking up the code in Stripe to make sure it exists.
But, if we tried to checkout, Stripe would be pissed: it would not allow us to use the code a second time. Stripe has our back.
But, we should definitely prevent the code from being attached to the cart in
the first place. Checkout the Coupon section of Stripe's API docs. Ah, this valid
field is the key. This field basically answers this question:
In this moment, can this coupon be used?
Brilliant! Back in OrderController::addCouponAction()
, add an if statement: if
!$stripeCoupon->valid
, then, just like in the catch, add an error flash - "Coupon expired" -
and redirect over to the checkout page:
// ... lines 1 - 11 | |
class OrderController extends BaseController | |
{ | |
// ... lines 14 - 83 | |
public function addCouponAction(Request $request) | |
{ | |
// ... lines 86 - 93 | |
try { | |
$stripeCoupon = $this->get('stripe_client') | |
->findCoupon($code); | |
} catch (\Stripe\Error\InvalidRequest $e) { | |
$this->addFlash('error', 'Invalid coupon code!'); | |
return $this->redirectToRoute('order_checkout'); | |
} | |
if (!$stripeCoupon->valid) { | |
$this->addFlash('error', 'Coupon expired'); | |
return $this->redirectToRoute('order_checkout'); | |
} | |
// ... lines 108 - 114 | |
} | |
// ... lines 116 - 166 | |
} | |
// ... lines 168 - 169 |
Try it again. Awesome, this time, we get blocked.
If you want to be extra careful, you could add some try-catch logic to your checkout code just to prevent the edge-case where the code becomes invalid between the time of adding it to your cart and checking out. But either way, Stripe will never allow an invalid coupon to be used.
Hello,
If we are using some coupons with percent reduction instead of amount, how are we supposed to do this?