About the Author
Emile Swarts is a developer at Made. Made is a Spree Commerce Certified Partner comprised of software experts who are passionate about delivering well-crafted, mission-critical software. The team works with organizations across many sectors who share their drive to produce standout, commercially succssful work.
Custom gateways in the Spree Commerce platform 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 use. 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.
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’ve 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:
A complete list of options can be seen here.
Register the gateway
We can register Foopay by adding the following code to the spree initializer:
Persisting the gateway
We need to persist our gateway, and also add it to our Spree store:
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.
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 customize 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.
h4.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:
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.
The state machine hooks are triggered.
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:
It’s 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:
action in this case will be ‘purchase’.
In Foopay, our purchase method accepts the following arguments:
- payment source
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.
To view this post in its original format, visit the blog of Made.