How would you deal with having a bogus / dummy payment gateway but only in dev and test?

I’m implementing a checkout experience where the end user who is purchasing an item can choose to put in their credit card details and have the transaction handled by Stripe, or they can choose PayPal and use that instead.

So in my embedded_schema for checkout, I have an enum for the PaymentGateway like this:

  defenum(PaymentGateway, stripe: 1, paypal: 2)

And then in the changeset I make sure the value is included with:

    |> validate_inclusion(:payment_gateway, PaymentGateway.__valid_values__())

This is all working no problem.

But now for local testing I’d like to set up a new dummy payment gateway. I was thinking about going with something like this:

  defenum(PaymentGateway, dummy: 0, stripe: 1, paypal: 2)

But of course I don’t want that dummy gateway being a valid option in production, however in development it would be really nice if I could pick that dummy gateway from my checkout form.

For the form itself, imagine a radio button where you can pick 1 of the 3 gateways.

Would it be common practice to only add this radio option to the template based on the value of Mix.env not being production? And in a similar fashion, would I define the defenum twice based on Mix.env being production or not?

Something like:

  if Mix.env == :prod do
    defenum(PaymentGateway, stripe: 1, paypal: 2)
  else
    defenum(PaymentGateway, bogus: 0, stripe: 1, paypal: 2)
  end

Is there a better way to handle this? It feels dirty to me to check the mix env in a template because now suddenly it’s like I’m modifying the normal flow of my app just for dev and test, but I can’t think of a better way to do this.

PSPs usually have a test mode… https://stripe.com/docs/keys#test-live-modes https://developer.paypal.com/docs/classic/lifecycle/ug-sandbox/ I would definitely use those…

I have a different account setup for my PSP, but some/most even support testmode on the “real” production account - but I don’t want API keys available outside of the prod env - so would recommend using a different account…

btw: for receiving webhooks back to dev I use https://github.com/joshuafleck/ex_ngrok

for the question I would use a config, and set that in dev

if Application.get_env(:myapp, :show_dummy_psp, false) do
  ..
else
  ..
end
2 Likes

Define a different module, which is an implementation of a behavior, per environment (in dev.exs and the rest).

1 Like

Yes thanks. I’ve used Stripe’s test keys and am familiar with ngrok (it’s a great tool).

But I still want the concept of a bogus gateway so I can run tests without using Stripe’s API keys without having to resort to mocking a bunch of responses. I’d also like to implement the bogus gateway before I even think about adding Stripe, just so I can nail down the flow of everything.

A different module for what tho? The gateway is picked at runtime based on what the visitor wants to pay with. Are you saying to create 2 different Stripe / Paypal modules , each with their own “bogus” implementation? But also what if I want to test Stripe’s test keys in dev but also want to pick bogus sometimes? That’s a valid use case IMO.

1 Like

I don‘t think you should define the available gateways in your templates at all. List all available ones in your config or even put the list in the db if you need the flixibility to change gateways without e.g. redeploying.

3 Likes

Oh you mean loop over that config value in the template?

That’s a good idea, although it has potential to get pretty complicated.

The complication is that not all gateways are created equal in terms of presentation. For example when you pick Stripe, the label is really “Credit card” and it has a bunch of fields. There’s also floating credit card icons on the side. Where as with PayPal the label is really a PayPal logo (as an image) and it has no visible CC icons. The bogus gateway would likely have no decorations or elements at all.

So if I were to just loop over that config value, each gateway would need drastically different HTML. Maybe as a compromise I could have a template partial for each supported gateway and that partial gets rendered based on the config loop’s gateway name? That seems pretty reasonable IMO.

You could also have {StripePayment, "only-cc.html"} in your config if you otherwise would need to duplicate complete files. But I doubt payment gateway requirements being 100% the same (even in the forseeable future). I’d rather have those gateway templates be composed of properly reusable chunks of markup.

Edit: You could also make the template selection for a gateway a protocol if you’re passing them around as structs, so you’d make it clear that each gateway needs a protocol implementation to be used in the web based frontend.

Thanks. What I ended up doing was having a config data structure that looks like this:

config :hello, :payment_gateways,
  local: [id: 0, visible: true],
  stripe: [id: 1, visible: true],
  paypal: [id: 2, visible: true]

And then in the templates I loop over that (only showing the visible ones since I might want to temporarily disable a gateway in the future but still have it available for existing orders).

I was able to solve the template differences with a few view functions.

Just another hint. Reading from the config should probably be hidden inside a function in your business domain. Your web layer shouldn’t know any of the details.

1 Like

Yeah, that’s how I’m doing it.

The template loops over the result of @payment_gateways which is passed in from a controller, which is populated by a domain function. Seems solid because if I were to change everything to be backed by a DB in the future, nothing at the web level changes.