This entry is part 5 of 9 in the Integrating Stripe.com with WordPress Series
- Stripe Integration Part 1 – Building the Settings and a Simple Payment Form
- Stripe Integration Part 2 – Recurring Payments
- Stripe Integration Part 3 – Variable Prices and Enhanced Plan Handling
- Stripe Integration Part 4 – Multiple Recurring Payment Options
- Stripe Integration Part 5 – Accepting Discount Codes
- Stripe Integration Part 6 – Payment Receipts
- Stripe Integration Part 7 – Creating and Storing Customers
- Stripe Integration Part 8 – Working with Invoices
- Stripe Integration Part 9 – The Stripe Button
In this part of our WordPress + Stripe Integration tutorial series, we will be expanding our payment form to accept discount codes. The coupon system in Stripe is pretty robust, and I’m going to show you how to take advantage of it by walking you through the process of adding it to our plugin.
There will be several required updates to our plugin that will have to happen in order for us to accept discount codes. The changes that must be made are:
- Add a discount code field to the payment form
- Check the code’s validity upon submit
- Add the coupon to the customer object for recurring payments
- Add the coupon to the charge object, and calculate the discounted price, for one time payments
We will start by adding the discount field to the payment form.
Updating the Payment Form
Our payment form is defined in includes/shortcodes.php, so open that file. We are going to place the new field at the bottom, just above the submit button. If you prefer to place it else where, you can. The placement only matters for aesthetic reasons.
Our field code looks like this:
1 2 3 4 | <div class="form-row"> <label><?php _e('Discount Code', 'pippin_stripe'); ?></label> <input type="text" size="20" class="discount" name="discount"/> </div> |
When combined with the rest of our payment form, the complete short code looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | <?php function pippin_stripe_payment_form($atts, $content = null) { extract( shortcode_atts( array( 'amount' => '' ), $atts ) ); global $stripe_options; if(isset($_GET['payment']) && $_GET['payment'] == 'paid') { echo '<p class="success">' . __('Thank you for your payment.', 'pippin_stripe') . '</p>'; } else { ?> <form action="" method="POST" id="stripe-payment-form"> <div class="form-row"> <label><?php _e('Email', 'pippin_stripe'); ?></label> <input type="text" size="20" class="email" name="email"/> </div> <div class="form-row"> <label><?php _e('Card Number', 'pippin_stripe'); ?></label> <input type="text" size="20" autocomplete="off" class="card-number"/> </div> <div class="form-row"> <label><?php _e('CVC', 'pippin_stripe'); ?></label> <input type="text" size="4" autocomplete="off" class="card-cvc"/> </div> <div class="form-row"> <label><?php _e('Expiration (MM/YYYY)', 'pippin_stripe'); ?></label> <input type="text" size="2" class="card-expiry-month"/> <span> / </span> <input type="text" size="4" class="card-expiry-year"/> </div> <?php if(isset($stripe_options['recurring'])) { ?> <div class="form-row"> <label><?php _e('Payment Type:', 'pippin_stripe'); ?></label> <input type="radio" name="recurring" class="stripe-recurring" value="no" checked="checked"/><span><?php _e('One time payment', 'pippin_stripe'); ?></span> <input type="radio" name="recurring" class="stripe-recurring" value="yes"/><span><?php _e('Recurring monthly payment', 'pippin_stripe'); ?></span> </div> <div class="form-row" id="stripe-plans" style="display:none;"> <label><?php _e('Choose Your Plan', 'pippin_stripe'); ?></label> <select name="plan_id" id="stripe_plan_id"> <?php $plans = pippin_get_stripe_plans(); if($plans) { foreach($plans as $id => $plan) { echo '<option value="' . $id . '">' . $plan . '</option>'; } } ?> </select> </div> <div class="form-row"> <label><?php _e('Discount Code', 'pippin_stripe'); ?></label> <input type="text" size="20" class="discount" name="discount"/> </div> <?php } ?> <input type="hidden" name="action" value="stripe"/> <input type="hidden" name="redirect" value="<?php echo get_permalink(); ?>"/> <input type="hidden" name="amount" value="<?php echo base64_encode($amount); ?>"/> <input type="hidden" name="stripe_nonce" value="<?php echo wp_create_nonce('stripe-nonce'); ?>"/> <button type="submit" id="stripe-submit"><?php _e('Submit Payment', 'pippin_stripe'); ?></button> </form> <div class="payment-errors"></div> <?php } } add_shortcode('payment_form', 'pippin_stripe_payment_form'); |
Now we need to process our discount code form.
Verifying Discount Codes
For the discount verification, we must first check to see if our discount field contains a code (is not empty). We will do this in the includes/process-payment.php file.
Just after we setup the Stripe::setApiKey($secret_key); function, add this:
1 2 3 4 5 | if(isset($_POST['discount']) && strlen(trim($_POST['discount'])) > 0) { $using_discount = true; } |
This checks to see if the discount field was posted, and that it wasn’t blank. If the field contains a code, we set the $using_discount variable to true.
All we’ve done is check to see if a code was used, now we need to check and see if the code is valid. To do that, we will use the Stripe_Coupon::retrieve() function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // check for a discount code and make sure it is valid if present if(isset($_POST['discount']) && strlen(trim($_POST['discount'])) > 0) { $using_discount = true; // we have a discount code, now check that it is valid try { $coupon = Stripe_Coupon::retrieve( trim( $_POST['discount'] ) ); // if we got here, the coupon is valid } catch (Exception $e) { // an exception was caught, so the code is invalid wp_die(__('The coupon code you entered is invalid. Please click back and enter a valid code, or leave it blank for no discount.', 'pippin'), 'Error'); } } |
The Stripe_Coupon::retrieve() will return an object containing the information about the coupon (which we will use later) if it is valid. If the code is invalid, an exception will be caught. If the code isn’t valid, then we kill WordPress with wp_die() and show an error message. This method of showing errors isn’t super fancy, but it works well for this example, since we’re focusing on how to integrate with Stripe, not how to show pretty error messages.
Our discount code checking is complete, so we can now add the coupon to the functions that create recurring payment profiles and single charges.
Adding the Discount to Recurring Payments
Applying a discount to a recurring payment is almost stupidly simple. All we have to do is pass a “coupon” variable that contains the coupon code. Remember, the recurring payments are setup with the Stripe_Customer::create() function, and when this is updated to include the coupon code, it looks like this:
1 2 3 4 5 6 7 | $customer = Stripe_Customer::create(array( 'card' => $token, 'plan' => $plan_id, 'email' => strip_tags(trim($_POST['email'])), 'coupon' => trim($_POST['discount']) ) ); |
Pretty simple right? Well almost. This will work perfectly fine anytime a discount code is entered, but if the discount field is left blank, having the “coupon” parameter present will actually cause the payment profile creation to fail. To get past this, we setup a simple conditional:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | if($using_discount !== false) { $customer = Stripe_Customer::create(array( 'card' => $token, 'plan' => $plan_id, 'email' => strip_tags(trim($_POST['email'])), 'coupon' => trim($_POST['discount']) ) ); } else { $customer = Stripe_Customer::create(array( 'card' => $token, 'plan' => $plan_id, 'email' => strip_tags(trim($_POST['email'])) ) ); } |
The $using_discount variable was defined when we checked for the presence of a coupon code earlier.
Adding the Coupon to One Time Payments
The one time payments work slightly different than the recurring profiles, primarily in that there is no “coupon” parameter that we can pass to Stripe_Charge::create(). This is a limitation of the Stripe API that I really don’t quite understand, but after clarifying with Stripe support, you definitely cannot add coupons to one time charges. In order to get past this, we have to calculate the charge amount before we pass it to Stripe. Luckily, this is pretty easy.
When we checked the validity of the discount code, we stored the code returned from Stripe in a variable called $coupon. This is an object and contains all of the details of the coupon code. All we have to do is take the discount amount (it’s a percentage), do a little math, and then pass the updated $amount to our Stripe_Charge:create() function.
Here’s how we calculate the discounted amount:
1 2 3 4 | if($using_discount !== false) { // calculate the discounted price $amount = $amount - ( $amount * ( $coupon->percent_off / 100 ) ); } |
Combined with our Stripe_Charge::create() function, we have this:
1 2 3 4 5 6 7 8 9 10 11 | if($using_discount !== false) { // calculate the discounted price $amount = $amount - ( $amount * ( $coupon->percent_off / 100 ) ); } $charge = Stripe_Charge::create(array( 'amount' => $amount, // amount in cents 'currency' => 'usd', 'card' => $token ) ); |
You can see that if no discount is provided, we create a charge for the amount specified by our short code. If a discount is present (and is valid), we overwrite the amount from the short code with a discounted amount.
That’s it! Our payment form now accepts discount codes.
All Together
The complete process-payment.php file looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | <?php function pippin_stripe_process_payment() { if(isset($_POST['action']) && $_POST['action'] == 'stripe' && wp_verify_nonce($_POST['stripe_nonce'], 'stripe-nonce')) { global $stripe_options; // load the stripe libraries require_once(STRIPE_BASE_DIR . '/lib/Stripe.php'); $amount = base64_decode($_POST['amount']) * 100; // retrieve the token generated by stripe.js $token = $_POST['stripeToken']; // check if we are using test mode if(isset($stripe_options['test_mode']) && $stripe_options['test_mode']) { $secret_key = $stripe_options['test_secret_key']; } else { $secret_key = $stripe_options['live_secret_key']; } Stripe::setApiKey($secret_key); // check for a discount code and make sure it is valid if present if(isset($_POST['discount']) && strlen(trim($_POST['discount'])) > 0) { $using_discount = true; // we have a discount code, now check that it is valid try { $coupon = Stripe_Coupon::retrieve( trim( $_POST['discount'] ) ); // if we got here, the coupon is valid } catch (Exception $e) { // an exception was caught, so the code is invalid wp_die(__('The coupon code you entered is invalid. Please click back and enter a valid code, or leave it blank for no discount.', 'pippin'), 'Error'); } } if(isset($_POST['recurring']) && $_POST['recurring'] == 'yes') { // process a recurring payment $plan_id = strip_tags(trim($_POST['plan_id'])); try { if($using_discount !== false) { $customer = Stripe_Customer::create(array( 'card' => $token, 'plan' => $plan_id, 'email' => strip_tags(trim($_POST['email'])), 'coupon' => trim($_POST['discount']) ) ); } else { $customer = Stripe_Customer::create(array( 'card' => $token, 'plan' => $plan_id, 'email' => strip_tags(trim($_POST['email'])) ) ); } // redirect on successful recurring payment setup $redirect = add_query_arg('payment', 'paid', $_POST['redirect']); } catch (Exception $e) { // redirect on failure $redirect = add_query_arg('payment', 'failed', $_POST['redirect']); } } else { // process a one-tiome payment // attempt to charge the customer's card try { if($using_discount !== false) { // calculate the discounted price $amount = $amount - ( $amount * ( $coupon->percent_off / 100 ) ); } $charge = Stripe_Charge::create(array( 'amount' => $amount, // amount in cents 'currency' => 'usd', 'card' => $token ) ); // redirect on successful payment $redirect = add_query_arg('payment', 'paid', $_POST['redirect']); } catch (Exception $e) { wp_die($e); // redirect on failed payment $redirect = add_query_arg('payment', 'failed', $_POST['redirect']); } } // redirect back to our previous page with the added query variable wp_redirect($redirect); exit; } } add_action('init', 'pippin_stripe_process_payment'); |
Note! You must create the discount codes in your Stripe Dashboard under the “Coupons” section.
You can download the complete (work in progress) plugin below.
[download id=”44″ format=”1″]
this is awesome, thanks so much.
i’m curious how do you come about creating a receipt for the client after the payment?
Since Stripe does not automatically send a payment receipt (except to the recipient) as a lot of merchants do, you have to manually create the system for that. You have to use Stripe’s Webhook system. It’s pretty simple to use and the documentation on it is pretty thorough.
I’m planning to write another part (sometime next week) on creating the payment receipts.
That would be rockin!
Steve 😉
Adding an else statement to line 45 and adding $using_discount = false; was the only way I was able to make recurring work without a coupon code entered
Hello,
Maybe you could take into account coupons expressed in fixed amount instead of percentage?
if (isset($coupon->percent_off)) {
$amount = $amount - ( $amount * ( $coupon->percent_off / 100 ) );
} else if (isset($coupon->amount_off)){
$amount = $amount - $coupon->amount_off;
}
percent_off and amount_off will never be set at the same time (Stripe doesn’t allow it) so this code should work.
Good idea.
That worked??
Please can we hire you to help us install stripe on our WP site?
Hey,
When I try to enter an expired coupon code, I get a blank error page with a bunch of text? Do you get the same when you enter an expired code? Thanks in advance.
Can you show me a screenshot?
This error comes when i try to purchase anything with float price(e.g. $10999.98) with discount code, which is in percentage(e.g 12%).
exception ‘Stripe_InvalidRequestError’ with message ‘Invalid integer: 967998.24
Does your final amount have 3 or more decimals by chance?
Yes,4 decimals
i edited the code
if ( $using_discount !== false ) {
// calculate the discounted price
if (isset($coupon->percent_off)) {
$price = $amount – ( $amount * ( $coupon->percent_off / 100 ) );
$amount = number_format($price, 0, ‘.’, ”);
} else if (isset($coupon->amount_off)){
$amount = $amount – $coupon->amount_off;
}
}
That edit should work fine.
Hi! For One Time Payments, how can I control max_redemptions property? Are there any way to increase the times_redeemed property? Thanks!!!
Since coupon codes do not work with one time payments, you won’t be able to use that. Stripe doesn’t permit manually updating the stats of discounts.