Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/fadendaten/solidus_six_saferpay
Six Saferpay payment integration into solidus using PaymentPage and Transaction interface
https://github.com/fadendaten/solidus_six_saferpay
Last synced: about 1 month ago
JSON representation
Six Saferpay payment integration into solidus using PaymentPage and Transaction interface
- Host: GitHub
- URL: https://github.com/fadendaten/solidus_six_saferpay
- Owner: fadendaten
- License: bsd-3-clause
- Created: 2019-07-31T14:41:27.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2021-09-16T11:27:05.000Z (over 3 years ago)
- Last Synced: 2024-11-21T17:54:58.088Z (about 1 month ago)
- Language: Ruby
- Size: 792 KB
- Stars: 0
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Solidus Six Saferpay
[![CircleCI](https://circleci.com/gh/fadendaten/solidus_six_saferpay.svg?style=shield)](https://circleci.com/gh/fadendaten/solidus_six_saferpay)
[![codecov](https://codecov.io/gh/fadendaten/solidus_six_saferpay/branch/master/graph/badge.svg)](https://codecov.io/gh/fadendaten/solidus_six_saferpay)The `solidus_six_saferpay` engine adds checkout options for the Saferpay Payment Page ([Integration Guide](https://saferpay.github.io/sndbx/Integration_PP.html), [JSON API documentation](http://saferpay.github.io/jsonapi/#ChapterPaymentPage)) and the Saferpay Transaction ([Integration Guide](https://saferpay.github.io/sndbx/Integration_trx.html), [JSON API documentation](https://saferpay.github.io/sndbx/Integration_trx.html)).
### Disclaimer
This gem is built to be a general-purpose integration of the Six Saferpay payment interface. However due to lack of resources and because we are (as far as we know) the only users of this gem, we are only testing our use cases (PaymentPage). Therefore we can not guarantee that this will work in any other solidus shop. If you consider using this gem, please test everything thoroughly.## Installation
Add solidus_six_saferpay to your Gemfile:
```ruby
gem 'solidus_six_saferpay'
```Bundle your dependencies and run the installation generator:
```shell
bin/rails generate solidus_six_saferpay:install
```Add the following javascript to your `application.js` manifest file below the `//= require spree` line:
```javascript
//= require solidus_six_saferpay/saferpay_payment
```## Configuration and Usage
Configure the credentials for the Saferpay API. These credentials must be set as ENV variables.
You can find the required information in the Saferpay interface under https://test.saferpay.com/BO/Settings/Terminal```bash
SIX_SAFERPAY_CUSTOMER_ID='XXXXXX'
SIX_SAFERPAY_TERMINAL_ID='XXXXXXXX'
SIX_SAFERPAY_USERNAME='your api basic auth username'
SIX_SAFERPAY_PASSWORD='your api basic auth password'
SIX_SAFERPAY_BASE_URL='https://test.saferpay.com/api/'
SIX_SAFERPAY_CSS_URL='' # currently not tested
```Configure the host for your application so that we can give Saferpay an absolute URL to redirect on success or failure
```
# in development.rb
Spree::Core::Engine.routes.default_url_options { 'http://localhost:3000' }
``````
# in production.rb
Spree::Core::Engine.routes.default_url_options { 'https://url-to-your-solidus-shop.tld' }
```After adding the `solidus_six_saferpay` gem to your Solidus Rails app, you can create new payment methods `Saferpay Payment Page` and `Saferpay Transaction` in the admin backend under "Settings" > "Payment". When adding a new Saferpay payment method, you can configure the payment method with the information you have received from SIX when creating a new test account.
### Configuration Options available in Solidus Admin
Notable configuration options are:
* `as_iframe`: If checked, the payment form is displayed on the "Payment" checkout page. If unchecked, the user needs to select a payment method and then proceed with the checkout to be redirected to the Saferpay payment interface.
* `require_liability_shift`: If checked, payments are only accepted if Saferpay grants liability shift for the payment. If a payment has no liability shift, then the checkout process fails and the customer needs to use other means of payment.All other configuration options are restrictions for available payment methods. If you don't check any payment methods, then the interface will make all payment methods available. If you restrict the available payment methods for the user, the interface will reflect your choice. If you select only a single payment method, the user is directly forwarded to the input form for the selected payment method without having to choose themselves.
### Customizing the Confirm Page
If you want to display the masked number on the confirm page, you must override the default `_payment.html.erb` partial of spree so that the provided partial can be rendered (instead of just displaying the name of your payment method).```erb
<% source = payment.source %>
<% if source.is_a?(Spree::SixSaferpayPayment) %>
<%= render source, payment: payment %>
<% elsif source.is_a?(Spree::CreditCard) %>
<% unless (cc_type = source.cc_type).blank? %>
<%= image_tag "credit_cards/icons/#{cc_type}.png" %>
<% end %>
<% if source.last_digits %>
<%= t('spree.ending_in') %> <%= source.last_digits %>
<% end %>
<%= source.name %>
<% elsif source.is_a?(Spree::StoreCredit) %>
<%= content_tag(:span, payment.payment_method.name) %>:
<%= content_tag(:span, payment.display_amount) %>
<% else %>
<%= content_tag(:span, payment.payment_method.name) %>
<% end %>
```### Extending/Overriding Functionality
There are several options for extending or overriding default behaviour such as the params used for initializing a payment or the type of items provided.
To configure this functionality, take a look at `SolidusSixSaferpay::Configuration` and start from there.## Technical Details: How it works
### Overview
This section should provide a birds-eye view of the implementation to help you not get lost when you dive into the details below.
The basic flow for a Saferpay Payment goes like this:
1. User chooses Saferpay payment method on "Payment" checkout step
2. Controller receives AJAX request to initialize Saferpay payment
3. The `InitializePayment` service requests a `token` from the Saferpay API and stores this token in a `SixSaferpayPayment`
4. User enters payment information and submits Saferpay form
5. Controller receives the `success` request from Saferpay
6. Controller asserts/authorizes payment via `AuthorizePayment` service with help of the previously stored `token`
7. If assert/authorize are successful, Controller validates and processes the payment via `ProcessPayment` service which results in a `Spree::Payment`
8. Controller redirects to the "Confirm" checkout step
9. User confirms the purchase
10. During completing the order, `Spree::Payment` initiates the `capture!` of the paymentAs you can see, most interactions with the Saferpay API are encapsulated in service objects, which then call the appropriate gateway methods to perform requests.
A note about __error handling__:
If the user aborts the checkout at any point or the payment fails for some other reason, the user is redirected to the "Payment" step of the checkout process and shown an error message.
Additionally, already authorized payments are voided so that no money stays allocated for longer than necessary.### Technical Implementation Details
In this section, we provide detailed information about the checkout flow and its implementation. Note that the flow is almost identical for both the PaymentPage and the Transaction interface.
Because of this, there is usually a base service class that contains the logic, and then there are subclass services for the PaymentPage and Transaction interface that configure the base service class.The same pattern also exists for the gateway: The `SolidusSixSaferpay::Gateway` implements the common logic, and the `SolidusSixSaferpay::PaymentPageGateway` and `SolidusSixSaferpay::TransactionGateway` only implement gateway actions that are unique for this interface.
#### Checkout: Payment Initialize
During the "Payment" step of the checkout process, solidus renders a partial for all active and available payment methods. Our partial is called `_saferpay_payment`.
When the partial is loaded, an AJAX request goes to the `CheckoutController#initialize_payment` action.
From there, we make a request to the Saferpay server to initialize the Payment. This request happens via the SixSaferpay Gateway and is abstracted away in the `InitializePayment` service.If this request is successful, a new `SixSaferpayPayment` object is created. This object contains the Saferpay `Token` for the current payment and links it with the current `Spree::Order` and the used `Spree::PaymentMethod`. It also stores the response of the `PaymentInitialize` request in hash form.
If the initialize request is not successful, then the user is shown an error message.
##### Success
If Saferpay can successfully process the user-submitted information, then Saferpay redirects the user to a `SuccessUrl`, which is configured to be handled by `CheckoutController#success`.
In this `#success` action, we find the `SixSaferpayPayment` record with the correct token that was created in the `PaymentInitialize` request. If the `SixSaferpayPayment` is found, a `PaymentAuthorize` request is performed (abstracted away the `AuthorizePayment` service).##### Fail
If Saferpay can not successfully process the submitted information or the payment fails for some other reason, Saferpay redirects to a `FailUrl`, which is configured to be handled by `SaferpayPaymentPageController#fail`.
In this `#fail` action, we try to find the `SixSaferpayPayment` record based on the token that was created in the `PaymentPageInitialize` request. If the `SixSaferpayPayment` is found, a `PaymentPageInquire` request is performed to gather information about the failure, and the user is redirected to the "Payment" step of the checkout process and shown an error with information about the failure. If the record can not be found, then a generic error is displayed.#### Checkout: Payment Authorize
If the user has entered the payment information successfully, we can perform an authorize request. Because this request is different depending on the payment interface, it is explained for each interface below.
When the authorize request is successful, we update the `SixSaferpayPayment` record with the received data. This data most importantly includes:* `TransactionId`
* `TransactionStatus`
* `TransactionDate`
* `SixTransactionReference`
* `DisplayText`And, if a credit card was used:
* `MaskedNumber`
* `ExpirationYear`
* `ExpirationMonth`##### PaymentPage Interface
If the PaymentPage interface is used, then the payment is authorized directly when the user submits the Saferpay form. In this case, we can not perform an authorize request and instead perform an assert request to gather information about the payment.
After performing the assert request, we update the `SixSaferpayPayment` record based on the data from the assert request.##### Transaction Interface
If the Transaction interface is used, then the payment must be authorized after it has been initialized. Therefore, we perform an authorize request to reserve the requested amount.
If the authorize request is successful, we update the `SixSaferpayPayment` based on the data from the authorize request.#### Checkout: Payment Validation and Processing
If the authorize request is successful, the received information is validated and processed in the `ProcessPaymentPagePayment` service.
At the moment, the following validations are performed:* Liability Shift: We check if the liability shift has been granted for the payment
* Payment Status: We check if the payment status of the Saferpay payment is `AUTHORIZED`
* Order Reference: We check if the order referenced by Saferpay matches the order that is being processed
* Matching Amount and Currency: We check if the Saferpay amount and currency match the total and currency of the processed orderIf any of these checks fail, then the payment process is aborted and the user must restart the payment flow.
If the payment validation is successful, all previously existing payments for this order that are still valid are cancelled.
After cancelling old payments, a new `Spree::Payment` is created based on the data stored in the `SixSaferpayPayment` record.
This ensures that only one valid payment exists from this point onward.If the payment processing fails, then the user is redirected to the "Payment" step of the checkout process and shown an error message.
#### Checkout: Confirm
When the user confirms the purchase in the checkout process, the saferpay payment is automatically captured. This action is triggered in the following way:1. When the user confirms the order, `Spree::CheckoutController#update` triggers `@order.complete` (through `#transition_forward`)
2. `Spree::Order::Checkout` defines the state transition `before_transition_to :complete, do: :process_payments_before_complete`
3. `Spree::Order` defines `#process_payments_before_complete` and calls `#process_payments!` if any valid payments exist
4. `Spree::Order::Payments` defines `#process_payments!` which calls `process!` on each unprocessed payment
5. `Spree::Payment::Processing` defines `#process!` and calls `#purchase!`
6. `Spree::Payment::Processing` defines `#purchase` and calls `#purchase` on the `PaymentMethod` associated with the payment
7. Since this payment method is a `Spree::PaymentMethod::SaferpayPaymentPage` that inherits from `Spree::PaymentMethod` (through `SaferpayPaymentMethod` and `CreditCard`), the `#purchase` method is delegated to the `#gateway`
8. `Spree::PaymentMethod::SaferpayPaymentPage#gateway_class` defines the gateway to be the `SolidusSixSaferpay::PaymentPageGateway`
9. Therefore, the `PaymentPageGateway#purchase` action is called#### Checkout: Payment Cancel
When a user cancels a payment, the `CheckoutController` receives a `fail` request and handles this request in the `#fail` action. The result is that the user is shown an error message stating that the payment was aborted.## Development
### Testing the extension
First bundle your dependencies, then run `bin/rake`. `bin/rake` will default to building the dummy
app if it does not exist, then it will run specs. The dummy app can be regenerated by using
`bin/rake extension:test_app`.```shell
bin/rake
```To run [Rubocop](https://github.com/bbatsov/rubocop) static code analysis run
```shell
bundle exec rubocop
```When testing your application's integration with this extension you may use its factories.
Simply add this require statement to your `spec/spec_helper.rb`:```ruby
require 'solidus_six_saferpay/testing_support/factories'
```Or, if you are using `FactoryBot.definition_file_paths`, you can load Solidus core
factories along with this extension's factories using this statement:```ruby
SolidusDevSupport::TestingSupport::Factories.load_for(SolidusSixSaferpay::Engine)
```### Running the sandbox
To run this extension in a sandboxed Solidus application, you can run `bin/sandbox`. The path for
the sandbox app is `./sandbox` and `bin/rails` will forward any Rails commands to
`sandbox/bin/rails`.Here's an example:
```
$ bin/rails server
=> Booting Puma
=> Rails 6.0.2.1 application starting in development
* Listening on tcp://127.0.0.1:3000
Use Ctrl-C to stop
```### Updating the changelog
Before and after releases the changelog should be updated to reflect the up-to-date status of
the project:```shell
bin/rake changelog
git add CHANGELOG.md
git commit -m "Update the changelog"
```### Releasing new versions
Please refer to the dedicated [page](https://github.com/solidusio/solidus/wiki/How-to-release-extensions) on Solidus wiki.
## License
Copyright (c) 2021 [name of extension author], released under the New BSD License.