Set up Stripe Payment Gateway in ProCourse Memberships

Hi,

Thanks for this how-t,o, particularly the webhooks part, really helpful to check step by step.

Sadly, I have an error when I test with a recurring payment, in test or live mode.

On the client side, I can’t pass the payment page “Sorry, an error occured”.

If it helps, here some additionnal informations

On the discourse logs, I have this :

NoMethodError (undefined method `pry' for #<Binding:0x00007f3f624b3838> Did you mean? try) /var/www/discourse/plugins/procourse-memberships/lib/procourse_memberships/gateways/stripe.rb:62:in `rescue in subscribe'


/var/www/discourse/plugins/procourse-memberships/lib/procourse_memberships/gateways/stripe.rb:62:in `rescue in subscribe'
/var/www/discourse/plugins/procourse-memberships/lib/procourse_memberships/gateways/stripe.rb:38:in `subscribe'
/var/www/discourse/plugins/procourse-memberships/app/controllers/procourse_memberships/checkout_controller.rb:65:in `submit_payment'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/abstract_controller/base.rb:194:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/rendering.rb:30:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/callbacks.rb:132:in `run_callbacks'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/abstract_controller/callbacks.rb:41:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/rescue.rb:22:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/notifications.rb:168:in `block in instrument'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/notifications.rb:168:in `instrument'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.0/lib/active_record/railties/controller_runtime.rb:24:in `process_action'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/abstract_controller/base.rb:134:in `process'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionview-5.2.0/lib/action_view/rendering.rb:32:in `process'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-mini-profiler-1.0.0/lib/mini_profiler/profiling_methods.rb:104:in `block in profile_method'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal.rb:191:in `dispatch'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal.rb:252:in `dispatch'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:34:in `serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/mapper.rb:18:in `block in <class:Constraints>'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/mapper.rb:48:in `serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:52:in `block in serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:35:in `each'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:35:in `serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:840:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:524:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/railtie.rb:190:in `public_send'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/railtie.rb:190:in `method_missing'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/mapper.rb:19:in `block in <class:Constraints>'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/mapper.rb:48:in `serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:52:in `block in serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:35:in `each'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:35:in `serve'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:840:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-protection-2.0.3/lib/rack/protection/frame_options.rb:31:in `call'
/var/www/discourse/lib/middleware/omniauth_bypass_middleware.rb:24:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/tempfile_reaper.rb:15:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/conditional_get.rb:38:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/head.rb:12:in `call'
/var/www/discourse/lib/content_security_policy.rb:14:in `call'
/var/www/discourse/lib/middleware/anonymous_cache.rb:216:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:232:in `context'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:226:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/cookies.rb:670:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/callbacks.rb:98:in `run_callbacks'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/logster-1.3.1/lib/logster/middleware/reporter.rb:31:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/rack/logger.rb:38:in `call_app'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/rack/logger.rb:28:in `call'
/var/www/discourse/config/initializers/100-quiet_logger.rb:16:in `call'
/var/www/discourse/config/initializers/100-silence_logger.rb:29:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/request_id.rb:27:in `call'
/var/www/discourse/lib/middleware/enforce_hostname.rb:17:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/method_override.rb:22:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/executor.rb:14:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/sendfile.rb:111:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-mini-profiler-1.0.0/lib/mini_profiler/profiler.rb:285:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/message_bus-2.1.6/lib/message_bus/rack/middleware.rb:63:in `call'
/var/www/discourse/lib/middleware/request_tracker.rb:180:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:524:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/railtie.rb:190:in `public_send'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/railtie.rb:190:in `method_missing'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:68:in `block in call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `each'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `call'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.0/lib/unicorn/http_server.rb:606:in `process_client'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.0/lib/unicorn/http_server.rb:701:in `worker_loop'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.0/lib/unicorn/http_server.rb:549:in `spawn_missing_workers'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.0/lib/unicorn/http_server.rb:142:in `start'
/var/www/discourse/vendor/bundle/ruby/2.5.0/gems/unicorn-5.4.0/bin/unicorn:126:in `<top (required)>'
/var/www/discourse/vendor/bundle/ruby/2.5.0/bin/unicorn:23:in `load'
/var/www/discourse/vendor/bundle/ruby/2.5.0/bin/unicorn:23:in `<main>'
hostname vps575005-app
process_id 458
application_version ed400a90fe1f5e1622e7f28aaddfa5a1aa1afdf4
HTTP_HOST hub.arborersens.fr
REQUEST_URI /memberships/checkout/submit-payment
REQUEST_METHOD POST
HTTP_USER_AGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
HTTP_ACCEPT application/json, text/javascript, /; q=0.01
HTTP_REFERER https://hub.arborersens.fr/memberships/l/8239
HTTP_X_FORWARDED_FOR 86.000.00.00
HTTP_X_REAL_IP 86.000.00.00
username Steven

And the logs on Stripe :

Summary
ID : req_75xTdXXX
Time : 2018/11/27 17:26:51
Method : POST
URL : /v1/subscriptions
Status : 400
IP address : 51.68.000.000
Version : [2017-08-15](https://stripe.com/docs/upgrades#2017-08-15)
Source : Stripe/v1 RubyBindings/3.28.0

Good catch, @Steven. That’s a debugging line that should have been removed. I just pulled that out so you’ll need to update the plugin.

That said, the location of that debugger is in an error catch. Which means there’s a likely error with the Stripe configuration. But with the debugger gone, it should display an error message that actually means something.

Would you be willing to give it another go and let us know what you see?

Thanks for the reply

An error occured: the server responded with status 400

I double checked the webhook on Stripe, I think I did it well.

I wonder if it isn’t because the currency is different on the initial payment (euros) and the reccuring one (dollars) :

I thought it was just a text that didn’t have any incidence.

On Stripe, I have this lines in the logs

It’s missing a parameter but I can’t see which one

I checked the customer.subscription.deleted and these two

:thinking:

This may be the source of the issue. Stripe is pretty picky with currencies. I’d try doing some configuration changes to make sure currencies are the same the entirety of the process around with that and give it a shot again.

Keep us posted with your findings!

2 Likes

Little update : I did a test with the USD currency, thinking it would be ok. I have the same issue with the recurring membership. So I think it’s not really the currency that was the issue

Is it possible to mess something up in the stripe configuration (a setting I may have forgotten on the sripe dashboard), when I’m sure these are ok?

The webhook url

I put this : https://hub.arborersens.fr/memberships/webhook/stripe, and it should be ok. Unless the url changed since then?

  • Check the following event types to send:
    • customer.subscription.deleted
    • invoice.payment_failed
    • invoice.payment_succeeded

I checked and double check, the webhook is correctly configured

  • memberships gateway — Set to Stripe
  • memberships stripe publishable key — Enter the sandbox/live Publishable key here.
  • memberships stripe secret key — Enter the sandbox/live Secret key here.

I deleted, re-created the keys and they are correctly configured in the settings. For an immediat payment, the tests are all ok, it’s just the webhook. I still have the error 400

The logs on Stripe show this:

image

In the err400

Request POST body

{
"customer": "cus_E8d***********",
"trial_from_plan": "false"
}

Response body

{
  "error": {
    "code": "parameter_missing",
    "doc_url": "https://stripe.com/docs/error-codes/parameter-missing",
    "message": "Missing required param: items.",
    "param": "items",
    "type": "invalid_request_error"
  }
}

I think I should have shown the message before. Missing required param: items. But I don’t see something useful on the stripe website

There’s nothing urgent at all by the way, I’m just sharing this little updates, hoping we can find a solution. I will contact the client if we can try another gateway if it’s only a bug on my part

Hey @Steven, I tried to replicate on our demo server and can’t get the error to throw. Do you have all fields filled out in the new level when you’re trying to create it? Those need to be completed before saving, as all the info is sent to the Stripe API then.

1 Like

Ah this is it, I recreated it entirely, fill every option before saving it and it passed, even in euros. I think I did it well the first time, but maybe something was changed at the time.

I’m a little dumb with this plugin, it’s pretty rare usually :sweat:

Sorry about that, and thanks for the solution

You can clean the topic if you want, my posts won’t be useful for others :grin:

Oh no worries! Glad it’s working. :hugs:

This is very helpful! It’d probably be good if the README in the repo pointed here.

I’ve gotten stuff configured for Stripe and created a membership level, but the public membership page gives me a 404.

Dumb question first – Did you enable the level after saving it?

1 Like

No, that’s the smart question. That solved it.

If I ever work with someone and they don’t first ask “Is it plugged in?” I know I’m in trouble.

I’m not sure if there’s something other than 404 that should happen there, or . . . oh, yeah, here’s what. If the plan isn’t active, it should say “activate to get the link to the membership page” or something like that.

Thanks.

1 Like

OK, so here’s what a potential client wants:

  • Expired users are placed in the /MEMBERSHIP Expired Group/.

Also, they want a dashboard. . .

  • Perhaps it’s just a single click on an alphabetized list of users to add 365 days to the user’s current expire date, where the treasurer scrolls to the name, clicks a button next to the user, and the user is extended for another year. Manual or API editing of the expire date should also be possible. (Our Treasurer has a separate Admin account he can use for this task.)
  • We should have some way to see users expiring in the next month and some simple way to set up automated messages to expiring users with renewal instructions.
  • The “Renewal Date” (In the TBD Dashboard) needs to be easy for our Treasurer to update when the payment of dues are received via Stripe, Zelle, PayPal, Venmo, etc.

and another thing!

Also, I don’t have “go live” checked but I got

Your card was declined. Your request was in live mode, but used a known test card.

I bet this is because I used my real Stripe ID rather than the sandbox one, but the name of the “go live” setting implies that I could do this. (I’m guessing that this is a difference in how Braintree does things?). Well, I changed it to the test API keys, and now I’m getting:

An error occurred: the server responded with status 400

@joebuhlig-- any interest in doing any of this stuff? These people have some money, but not much.

Try recreating your level after putting the new test keys in place. The plugin communicates with Stripe on level creation via the API.

2 Likes

Doh! That’s another obvious one. Sorry.

I think that did it!

But en.admin.groups.bulk_select is undefined.

2 Likes

Hit me up: hello@procourse.co. But just a heads up, working with payment gateways and such isn’t simple and cheap.

@joebuhlig - I am hoping you can point me in the right direction with this issue:

Users are able to sign-up as members & payments are coming through Stripe.

However my Stripe webhooks log is filling up with the “Failed” invoice.payment_succeeded webhooks with my server returning:

<?xml version="1.0" encoding="UTF-8"?>
<hash>
  <status type="integer">500</status>
  <error>Internal Server Error</error>
</hash>

I have double checked all my settings & they match your instructions here.

The server is Digital Ocean running Discourse only - could there be any server side configuration I need to do?

We have the Stripe integration up and running live. Looks like really nice work.

A question about the level settings. We set a level like so, thinking that’s $20 today, $80 next year:

But the charge came thru in Stripe as $80 today:

Can you help us understand how that works?

Thanks for this amazing work!

Kim @ Momentum

Charge came thru in Stripe as $80 today…

Or are these two choices meant to be mutually exclusive: An Initial Payment OR Recurring? Now reflecting on this question, that must be it. If so, a simple “yes” might close my question. Thanks.

BTW: Is it safe to assume you are open to PR’s on this project? We have a number of tweaks we’d be willing to offer back to the repo and would of course prefer there only be a single code base.

Here’s a tip that took me a week to figure out. You need make sure Discourse can load a script in order to have the plugin talk to Stripe.

js.stripe.com/v3

In my case, I needed to whitelist the script so it would load. I believe I have a default security settings, using the Digital Ocean auto install.

Without the script loading, the credit card fields on the payment page just spins and spins and spins.

Adding this here for the next person who has this issue. :slight_smile: