Appearance
Payment Gateways
We use the following off-site gateways. The following gateways can be enabled and configured in the control panel and after that you can use the following tag (inside your checkout tag) to loop over your enabled gateways.
{exp:reinos_store:checkout}
{!-- This is a select list, but your can convert it to anything you like. As long we have an input[gateway] field in the checkout tag --}
<select name="gateway">
<option value="">Choose your gateway</option>
{exp:reinos_store:gateway_list}
<option value="{gateway:name}">{gateway:label}</option>
{/exp:reinos_store:gateway_list}
</select>
{/exp:reinos_store:checkout}
Manual
By using this Gateway you can order any product without doing a real payment. Mostly this is used to send the customer a custom invoice. But it can also be used when developing your site where you cannot use the real provider.
Mollie
This is a Dutch payment provider that support a large amount of payment methods.
Usage
Go to your dashboard --> Developers --> API Keys. There you see an API key for either testing or for production. Both API keys are needed, so you can copy both.
Multisafepay
This is another Dutch payment provider that support a large amount of payment methods.
Usage
Go to your dashboard --> Settings --> Website --> API Keys. By default you have a production account, but you can also create a developer account via https://testmerchant.multisafepay.com to obtain a test API key
Paypal
For Paypal we use the REST client to give the best Paypal experience.
Usage
- Go to developer.paypal.com and login with your own credentials.
- Navigate to "My Apps and Credentials". By default, there is an app created for the sandbox (test) version but for the Live version you have to create a new app.
- When your app has been created, you can copy the
Client ID
and theSecret
to the Store Paypal fields.
Stripe (Checkout)
We support Stripe checkout. See the official documentation https://stripe.com/docs/payments/checkout
Usage
- Go to developer --> API Keys
- Copy the Publishable and the Secret key and paste them in the payment overview for Stripe
Not listed
If you'd like to use a different Gateway, please let me know, so I can add this for you (not free).
Custom Gateways
Reinos Store Module Gateways are vital for your store, as they facilitate the actual payment process between your customers and you. We currently support a few native gateways. However, since there are many payment providers worldwide, we cannot support them all out of the box.
To address this, we have implemented a clean gateway system built on top of the Omnipay library, making it easy for you to add your own payment provider.
TL;DR
Below we will explain a bit how the file should look like, but if you don't have time to read you can just look at the file example_gateway/system/user/addons/reinos_store_gateway_test/Gateway.php
that is shipped with the Reinos Store module
Folder structure
The folder and file structure is essential for the Reinos Store module to recognize and support your new gateway.
To add a new gateway, you need to use the following folder structure:
└── system
│ └── user
│ │ └── addons
│ │ │ └── reinos_store_gateway_<your_gateway_name>
| │ │ │ └── Gateway.php
So if your gatway is called instapay
you structure looks like
└── system
│ └── user
│ │ └── addons
│ │ │ └── reinos_store_gateway_instapay
| │ │ │ └── Gateway.php
Gateway.php
The Gateway.php
file is the file that connect your payment to the Reinos Store module. It should contain a couple of functions that will be called by the module.
$info varibale
In the $info
variable we define the names and fields of this Gateway.
name
This is the short_name of the gateway. It should be lowercase snake_case.
public static $info = [
'name' => 'instapay',
];
label
The label is displayed both in the Control Panel (CP) and in the payment selector. It represents the clean, user-friendly name of the gateway.
public static $info = [
'label' => 'InstaPay',
];
desc
The description (desc) provides details about the payment gateway, offering a clean and concise explanation. It may also include information on how to obtain the required keys.
public static $info = [
'desc' => 'You can get your KEYS from the InstaPay dashboard (Developer --> Api Keys)',
];
showNotifyUrl
For some gateways, the notify URL needs to be visible to the public so it can be copied into the payment provider's dashboard. This flag enables that functionality. When set to true
, the notify URL will be displayed publicly in the Control Panel (CP).
public static $info = [
'showNotifyUrl' => true,
];
offsite
Some payment providers offer an offsite payment selection, while others handle payments directly during checkout. Here, you can define whether this gateway is an offsite or onsite gateway.
public static $info = [
'offsite' => true,
];
fields
Most gateways require an API key or similar credentials. With the fields section, you can define these or other necessary fields, allowing you to fill them directly within your Control Panel (CP) when enabling this gateway.
public static $info = [
'fields' => [
'debug' => [
'type' => 'yes_no',
'label' => 'Debug mode',
'desc' => 'Enable debug mode',
],
'test_api_key' => [
'type' => 'text',
'label' => 'Test API KEY',
],
'live_api_key' => [
'type' => 'text',
'label' => 'Live API KEY',
],
]
];
constructor()
In the constructor, we create the actual payment instance using the Omnipay library. This is also where we check if an API key is set.
Depending on your gateway, there may be additional steps required, but that is up to you to implement.
Below, you'll find the bare minimum example.
public function __construct($gatewayData)
{
// set the gateway data
// this hold all data that was set in CP for the dynamic fields
$this->gatewayData = $gatewayData;
// init the gateway from the omnipay library
$this->gateway = \Omnipay\Omnipay::create('YourGatewayName');
// set the api key based on the debug setting
// this is up to you how you want to handle this
$apiKey = $this->gatewayData['debug'] === 'y' ? $this->gatewayData['test_api_key'] : $this->gatewayData['live_api_key'];
// pass the api key to the gateway
$this->gateway->setApiKey($apiKey);
}
create()
The create()
function is responsible for initiating the payment. It uses the Omnipay library's purchase()
function to call the actual payment provider.
Depending on your gateway, you can extend the purchase data as needed.
public function create($order, $currency, $gatewayData = [])
{
// create a purchase request
$response = $this->gateway->purchase(
[
'currency' => $currency,
'amount' => $order->total_plus_tax,
'description' => 'Order #' . $order->order_id,
// generate the return and notify url
// you can use the helper functions here
'returnUrl' => $this->generateReturnUrl($order),
'notifyUrl' => $this->generateNotifyUrl(self::$info['name']),
// generate the metadata, such as the order data
'metadata' => $this->generateMetaData($order),
]
)->send();
// format the response so the Store module can handle it
return $this->formatResponse($response, $this);
}
saveStatus
The saveStatus()
call is needed to define which status should be set. By default, the Omnipay library provides several methods to determine the status. You can refer to the documentation here: Omnipay Response Statuses.
Depending on your gateway implementation, you can choose to use these methods or implement your own. Below, you'll find a complete example.
protected function saveStatus($order, $transaction)
{
// this one is a bit up to you
// not every payment provider has the same status or has implemented the same status methods
// below an example of how you can handle this
if ($order && $transaction) {
if ($transaction->isPaid() && !$transaction->isRefunded() && !$transaction->hasChargebacks()) {
$newStatus = REINOS_STORE_STATUS_PAID;
} elseif ($transaction->isOpen()) {
$newStatus = REINOS_STORE_STATUS_OPEN;
} elseif ($transaction->isPending()) {
$newStatus = REINOS_STORE_STATUS_PENDING;
} elseif ($transaction->isExpired()) {
$newStatus = REINOS_STORE_STATUS_EXPIRED;
} elseif ($transaction->isCancelled()) {
$newStatus = REINOS_STORE_STATUS_CANCELLED;
} elseif ($transaction->hasRefunds()) {
$newStatus = REINOS_STORE_STATUS_REFUNDED;
} elseif ($transaction->hasChargebacks()) {
$newStatus = REINOS_STORE_STATUS_PAID_CHARGEBACK;
} else {
$newStatus = REINOS_STORE_STATUS_FAILED;
}
// this is the most important part
// this will update the order, inventory and sync the entry if needed
return $this->updateVitals($order, $newStatus);
}
return false;
}
completeOrder
completeOrder()
is called when we can complete an order. Here we fetch the transaction and call in the saveStatus()
public function completeOrder($order)
{
// complete an order by fetching the transaction
// and update the status based on the transaction
$transactionResponse = $this->gateway->fetchTransaction(
array('transactionReference' => $order->transaction_id)
)->send();
return $this->saveStatus($order, $transactionResponse);
}
getTransactionIdOnNotifyCall
When a payment provider calls the notify URL to inform you about a status update, it will usually send the transaction ID along with the request.
The getTransactionIdOnNotifyCall()
function is called to fetch that ID. It's up to you and your payment provider to determine how and where to retrieve it. However, in most cases, it is sent as a POST parameter, often labeled as id.
public function getTransactionIdOnNotifyCall()
{
return ee()->input->get_post('id');
}