Discount codes for Stripe, pls

Hi there,

Ghost/Stripe checkout doesn’t currently support discount codes.

These would let folks have a month or two discounted, for free, so they could trial membership. This would lean to more signups.

This is also something that Substack supports, and is very handy.

1 Like

Ghost does support free trials, documented here:

https://ghost.org/docs/members/free-trial/

And you can already use/apply discount codes directly in Stripe, if you want to create special offers. It’s manual, but it works fine - I have quite a few setup myself

2 Likes

amazing!!! thanks @John

ps. being able to incorporate coupon codes (not free trials) into the checkout screen would be great.

Stripe are currently beta testing that - I’ve seen it and works well. I imagine they’ll release it relatively soon :slight_smile:

2 Likes

Hi John - I’m hoping to do this as well as I have a few thousand members on a different site I’d like to bring over and I have to offer them a discount. It’s a bit tedious to do it manually but if you had a solution for something broader, that would really be cool.

hey Rob, you could always open membership on a public link at a discount, but only tell your members about it and keep it open for, say, 28 days?

That way you can invite them over for the discounted price, and then just switch to the regular price.

Thanks @jsh4 - that was kind of what I was thinking. However… I’ve been playing around a bit with how coupons might work and am happy to do a PR but for the life of me I can’t see where the checkout session is created. I’m doing a full scale search within the project and I’m utterly stumped.

I then got to thinking “why don’t I just create my own checkout” using Stripe Elements. I was thinking that I could apply a coupon there (which I can) but there’s no way, that I can see, to add the member_id to subscription.

Finally - I see how the membership API is being used to create the session then redirect off to Stripe. I can intercept all of it and make my own thing easily - I just want to pass in a coupon ID through the session which the docs say is possible:

It would seem a simple thing to add to the API call… Happy to PR this if I could just get a pointer to where the Stripe Session is created.

sorry, can’t help you here… One for @DavidDarnes perhaps?

OK I think I figured this out. @John weigh in and let me know if I’m crazy :).

I dove into the members-api package and noticed that a metadata key was being passed in and, as far as I can tell, it’s not being used for anything currently. It seems like it’s there for theming reasons? Not sure - anyway I worked up a Vue/axios call to the API endpoint that looks like this:

const sessionRes = await axios.get("/members/api/session");

const payload ={
    plan: "Monthly",
    identity: sessionRes.data,
    successUrl: location.href,
    cancelUrl: location.href,
    metadata: {coupon: 'asdasdasd'}
}
try{
    const stripeCheckoutSession = await axios.post("/members/api/create-stripe-checkout-session/", payload);

    Stripe(stripeCheckoutSession.data.publicKey).redirectToCheckout({
        sessionId: stripeCheckoutSession.data.sessionId 
    });
}catch(err){
    //if we're here we have a bad coupon code
}

I made two small tweaks to the createCheckoutSession method, which were to pull the payload into a variable and then check for the presence of a coupon passed in through the metadata:

const payload = {
    payment_method_types: ['card'],
    success_url: options.successUrl || this._checkoutSuccessUrl,
    cancel_url: options.cancelUrl || this._checkoutCancelUrl,
    customer: customer ? customer.id : undefined,
    customer_email: customerEmail,
    metadata,
    subscription_data: {
        trial_from_plan: true,
        items: [{
            plan: plan.id
        }]
    }
}
if(metadata && metadata.coupon) payload.subscription_data.coupon = metadata.coupon;
const session = await this._stripe.checkout.sessions.create(payload);

As you can see, it works great when there’s a valid coupon code :).

shot_361

The one thing I’m not a super huge fan of is trapping the 400 here - ideally the API would return a wrapper for the checkout with a success/fail flag or the like and an error message the dev could do something with.

I’m happy to PR both things if you find this useful.

1 Like

Hi friends me again - this is interesting… I got this to work by sidestepping the Ghost checkout process entirely, which is fun and interesting. I created a Firebase function (serverless) that accepts a plan name (“Monthly” or “Yearly”) along with a customer email. It pings Stripe to see if the customer exists and uses that customer information (which is another thing I wanted). It then sets up the checkout session, passing in the discount code if one was provided.

I didn’t expect it to work - but it did. The webhook coming from Stripe is reconciled by Ghost on the user’s email thankfully. The one thing that’s not present is the discount code on the user’s screen but… oh well. I suppose I can tweak that later with another serverless call.

If you’re interested in the code let me know - it’s pretty straightforward stuff.

Stripe just announced coupon support for Checkout, shown in this tweet:

Maybe this fixes this issue?

edit: looks like it’s done with a allow_promotion_codes bool passed into the stripe checkout session call. i get that it might not be as clear cut as that (should it always default to true?) but looks like this is a good first step to this stuff landing. @robconery maybe if you’ve been looking at the source you know where this could fit in?

1 Like

ok well i made a PR for this :)

2 Likes

Yep, I mentioned above 10 days ago that this was in progress and coming shortly — we’ve been beta testing it for several months.

The implementation suggested by @signalnerve is correct, and the question on the PR is the right one: Does it make sense for this to be on by default for everyone? At the moment: probably not.

We would consider adding it behind a config flag, to start.

thanks for flagging, this is cool!

Yep - adding this to the session code triggered the coupon at Stripe. Works great! I’m able to create the session using my serverless function so all’s well.

I think most people are content just sending people off to Stripe to setup a quick paywall to get off the ground. If they’re successful, however, not having promos/discounts is a bit of a blocker. Discount codes are extremely common in online checkouts and people ask for them constantly. This is going to be even more the case as people turn to online resources rather than in-person.

I would urge you to consider adding a checkbox to the labs page next to the Stripe signup.

Oh - if anyone is curious about the Stripe solution that I got working I made a gist here. If you’re not familiar with Firebase this might look weird but you should be able to use the same code with Cloudflare workers or AWS Lambda.

So far it’s working well in testing so… fingers crossed.