Spree makes it easy to take payments from any Payment Service Provider, and in this post I will briefly walk you through the process of creating and using your own custom gateway.
Custom gateways in Spree encapsulate the operations required to take payments online. This functionality is abstracted out of the order logic, which makes it easy to think about in isolation. Ultimately what we want is to substitute a small subset of functionality in Spree for our own. The way Spree knows whether a transaction was successful or not is that we tell it exactly what happened. We return a Response object (ActiveMerchant::Billing::Response), which holds a state of successful or failed. If we return a successful response object, everything went well and the user has now completed the checkout. If the transaction was not successful, the user will be redirected back to the payment step with an error message where they can try again.
Foopay gateway
#app/models/spree/gateway/foopay.rb
class Spree::Gateway::Foopay < Spree::Gateway
def provider_class
Spree::Gateway::Foopay
end
def payment_source_class
Spree::CreditCard
end
def method_type
'foopay'
end
def purchase(amount, transaction_details, options = {})
ActiveMerchant::Billing::Response.new(true, 'success', {}, {})
end
end
In this case we have stubbed out the response to always be successful. This happens because the first argument is set to true. If it were set to false, it would always indicate to Spree that the payment has failed.
Typically you would look for something like ‘success’ in your xml, json or post data after you have completed communication with the external gateway. Note that the payment_source_class is Spree::CreditCard. We could specify a custom model here if we wanted to add extra attributes to it. The ‘options’ argument passed in contains only a set list of predefined options including:
:email, :customer, :customer_id, :ip, :order_id, :shipping, :tax, :subtotal
A complete list of these options can be seen here.
Register the gateway
We can register Foopay by adding the following code to the spree initializer:
#config/initializers/spree.rb
Rails.application.config.spree.payment_methods << Spree::Gateway::Foopay
Persisting the gateway
We need to persist our gateway, and also add it to our Spree store:
Spree::Gateway::Foopay.create(
name: 'Foopay',
description: 'My fancy new Foopay!',
active: true,
environment: 'development')
Spree::Store.first.payment_methods << payment_method
Frontend
In order to get the data we need from the user, we need to render a form for them to fill in. Looking back at our custom gateway, we specified a ‘method_type’ method. This will be used to look up the partial to be rendered in the payment step. In Spree, we can see this happening in frontend/app/views/spree/checkout/_payment.html.erb.
render partial: "spree/checkout/payment/#{method.method_type}"
Our gateway specified ‘foopay’ as the method_type, so it will try and render the the _foopay partial. Let’s create it at app/views/spree/checkout/payment/_foopay.html.erb
This file is used to customise what is sent through to the checkout controller.
Note: Naming of the form inputs is important. Custom payment values may also need to be added to the permitted attributes list.
Behind the scenes
I decided to take a few notes on the interesting things that happen when the request goes into Spree.
We can see that the payment form points to: /checkout/update/payment
The request hits the .update method on the Spree::Checkout controller. The first code it hits is:
@order.update_from_params(params, permitted_checkout_attributes, request.headers.env)
This line will update the order with the new data submitted from the form, any associated models will also be updated or created. If this completes successfully, the order tries to advance to the ‘complete’ state.
@order.next
The state machine hooks are triggered.
before_transition to: :complete do |order|
...
order.process_payments!
end
process_payments! is defined in core/app/models/spree/order/payments.rb
Both the payments and checkout modules are mixed into the order object, so all the methods they provide are added directly to it. Next, it finds each of the unprocessed_payments on the order, and runs process! on them.
.process! checks whether the gateway has auto_capture? enabled. If it does, the purchase method is called directly, otherwise authorize is called.
Following the call into the Payment model, we find the following:
after_initialize :build_source
It is within the .build_source method that Foopay is instantiated.
Time to shine
The actual line of code that hands over responsibility from Spree to Foopay looks like this:
response = payment_method.send(action, money.money.cents, source, gateway_options)
action in this case will be ‘purchase’.
In Foopay, our purchase method accepts the following arguments:
- amount
- payment source
- options
The second payment_source argument will be an instance of Spree::CreditCard. The third argument, ‘options’, is a pre-defined list of gateway options, this argument was discussed above. We use the data in these arguments to finish the payment. Our method returns the appropriate response object, and the transaction is complete.
ActiveMerchant::Billing::Response.new(true, 'success', {}, {})
Resources:
https://guides.spreecommerce.com/developer/payments.html
https://github.com/shopify/active_merchant
https://github.com/spree/spree/tree/2-4-stable
http://api.rubyonrails.org/classes/ActionController/Parameters.html