Coupons! Adding the Form

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

Let's talk about something fun: coupon codes. When a user checks out, I want them to be able to add a coupon code. Fortunately Stripe totally supports this, and that makes our job easier.

In fact, in the Stripe dashboard, there's a Coupon section. Create a new coupon: you can choose either a percent off or an amount off. To make things simple, I'm only going to support coupons that are for a specific amount off.

Create one that saves us $50. Oh, and in case this is used on an order with a subscription, you can set the duration to once, multi-month or forever.

Then, give the code a creative, unique code: like CHEAP_SHEEP. Oh, and the coupon codes are case sensitive.

Tip

I'm choosing to create my coupons through Stripe's admin interface. If you want an admin section that does this on your site, that's totally possible! You can create new Coupons through Stripe's API.

Adding a Coupon During Checkout

Back on our site, before we get into any Stripe integration, we need to add a spot for adding coupons during checkout.

Open the template: order/checkout.html.twig. Below the cart table, add a button, give it some styling classes and a js-show-code-form class. Say, "I have a coupon code":

... lines 1 - 19
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
... lines 24 - 26
<div class="col-xs-12 col-sm-6">
... lines 28 - 57
<button class="btn btn-xs btn-link pull-right js-show-code-form">
I have a coupon code
</button>
... lines 61 - 75
</div>
... lines 77 - 79
</div>
</div>
</div>
{% endblock %}

Instead of adding this form by hand, open your tutorial/ directory: this is included in the code download. Open coupon-form.twig, copy its code, then paste it below the button:

... lines 1 - 19
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
... lines 24 - 26
<div class="col-xs-12 col-sm-6">
... lines 28 - 57
<button class="btn btn-xs btn-link pull-right js-show-code-form">
I have a coupon code
</button>
<div class="js-code-form" style="display: none;">
<form action="" method="POST" class="form-inline">
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">
<i class="fa fa-terminal"></i>
</span>
<input type="text" name="code" autocomplete="off" class="form-control" placeholder="Coupon Code"/>
</div>
<button type="submit" class="btn btn-primary">Add</button>
</div>
</form>
</div>
</div>
... lines 77 - 79
</div>
</div>
</div>
{% endblock %}

This new div is hidden by default and has a js-code-form class that we'll use soon via JavaScript. And, it has just one field named code.

Copy the js-show-code-form class and scroll up to the javascripts block. Add a new document.ready() function:

... lines 1 - 3
{% block javascripts %}
... lines 5 - 8
<script>
jQuery(document).ready(function() {
... lines 11 - 15
});
</script>
{% endblock %}
... lines 19 - 84

Inside, find the .js-show-code-form element and on click, add a callback. Start with our favorite e.preventDefault():

... lines 1 - 3
{% block javascripts %}
... lines 5 - 8
<script>
jQuery(document).ready(function() {
$('.js-show-code-form').on('click', function(e) {
e.preventDefault();
... lines 13 - 14
})
});
</script>
{% endblock %}
... lines 19 - 84

Then, scroll down to the form, copy the js-code-form class, use jQuery to select this, and... drumroll... show it!

... lines 1 - 3
{% block javascripts %}
... lines 5 - 8
<script>
jQuery(document).ready(function() {
$('.js-show-code-form').on('click', function(e) {
e.preventDefault();
$('.js-code-form').show();
})
});
</script>
{% endblock %}
... lines 19 - 84

Cool! Now when you refresh, we have a new link that shows the form.

Submitting the Coupon Form

So let's move to phase two: when we hit "Add", this should submit to a new endpoint that validates the code in Stripe and attaches it to our user's cart.

To create the new endpoint, open OrderController. Near the bottom add a new public function addCouponAction() with @Route("/checkout/coupon"). Name it order_add_coupon. And to be extra-hipster, add @Method("POST") to guarantee that you can only POST to this:

... lines 1 - 9
use Symfony\Component\HttpFoundation\Request;
class OrderController extends BaseController
{
... lines 14 - 79
/**
* @Route("/checkout/coupon", name="order_add_coupon")
* @Method("POST")
*/
public function addCouponAction(Request $request)
{
... lines 86 - 97
}
... lines 99 - 144
}
... lines 146 - 147

Cool! Copy the route name, then find the coupon form in the checkout template. Update the form's action: add path() and then paste the route name:

... lines 1 - 19
{% block body %}
<div class="nav-space-checkout">
<div class="container">
<div class="row">
... lines 24 - 26
<div class="col-xs-12 col-sm-6">
... lines 28 - 61
<div class="js-code-form" style="display: none;">
<form action="{{ path('order_add_coupon') }}" method="POST" class="form-inline">
... lines 64 - 73
</form>
</div>
</div>
... lines 77 - 79
</div>
</div>
</div>
{% endblock %}

Next, we'll read the submitted code and check with Stripe to make sure it's real, and not just someone trying to guess clever coupon codes. Come on, we've all tried it before.

Leave a comment!

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.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9, <7.4",
        "symfony/symfony": "3.1.*", // v3.1.10
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.8
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0.1", // v2.0.1
        "stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
        "stripe/stripe-php": "^3.15", // v3.23.0
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.2.1
        "phpunit/phpunit": "^5.5", // 5.7.20
        "twig/twig": "^1.24.1" // v1.35.2
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.4
        "symfony/phpunit-bridge": "^3.0", // v3.3.0
        "hautelook/alice-bundle": "^1.3", // v1.4.1
        "doctrine/data-fixtures": "^1.2" // v1.2.2
    }
}