Commit 0df1296a authored by Gareth Hall's avatar Gareth Hall

Initial Commit

parents
# Commerce PartPay
[Commerce PartPay](http://drupal.org/project/commerce_partpay) integrates PartPay with Drupal Commerce payment and checkout system.
## Features
Commerce PartPay supports gateway hosted billing methods.
## Available Payment Methods
### PartPay
[PartPay](https://partpay.co.nz) is a
payment solution hosted by PartPay; your site won't see credit card details so
you have less requirements for compliance. It's safer and simpler to implement.
## Installation & Configuration
1. Download from [Commerce PartPay project page](http://drupal.org/project/commerce_partpay)
2. Enable the payment processor(s) you wish to use.
3. Visit the Commerce Payment Methods page at admin/commerce/config/payment-methods
4. Enable and edit the payment methods you require.
Pretty much all the details required in the configuration interface are obtained from
Payment Express. If the inline documentation isn't clear, please file an issue and help
improve the interface!
## Troubleshooting
For any questions, the first point of reference should be the issue queue on Drupal.org.
Please use the search interface provided to see if your problem has been fixed or is
currently being worked on, and if not create a new issue to discuss it.
Always check your site error logs (both webserver and Drupal logs) and give detail when
making a report.
Documentation fixes are welcome!
## Contributing
We prefer that you use the Drupal.org issue queue, but contributions are welcome via
Github also.
## Credits
* The Commerce PartPay maintainer is [Communica](https://communica.nz)
<?php
/**
* @file
* Developer documentation.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Allow other modules to alter partpay payment capture.
*
* @param array $data
* The payment data being save to commerce.
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* The order.
* @param \Omnipay\Common\Message\ResponseInterface $response
* The payment gateway response.
*/
function hook_commerce_partpay_capture_payment_alter(
array &$data,
\Drupal\commerce_order\Entity\OrderInterface $order,
\Omnipay\Common\Message\ResponseInterface $response
) {
$data['state'] = 'my_custom_state';
}
/**
* @} End of "addtogroup hooks".
*/
name: 'Commerce PartPay'
description: 'Integrates the PartPay gateway with Drupal Commerce payment module.'
core: 8.x
package: 'Commerce (contrib)'
dependencies:
- commerce
- commerce_payment
- commerce_order
type: module
<?php
/**
* @file
* Integrates PartPay gateway with Drupal Commerce.
*/
commerce_partpay.checkout.partpay:
path: '/checkout/{commerce_order}/partpay'
defaults:
_controller: '\Drupal\commerce_partpay\Controller\OffSitePaymentController::notifyPage'
requirements:
_custom_access: '\Drupal\commerce_partpay\Controller\OffSitePaymentController::checkAccess'
_module_dependencies: commerce_checkout
options:
parameters:
commerce_order:
type: entity:commerce_order
\ No newline at end of file
services:
commerce_partpay.logger:
class: Drupal\Core\Logger\LoggerChannel
factory: logger.factory:get
arguments: ['commerce_partpay']
commerce_partpay.partpay_service:
class: Drupal\commerce_partpay\PartPay\PartPayService
arguments: ['@commerce_partpay.logger', '@module_handler']
commerce_partpay.partpay:
class: Drupal\commerce_partpay\PartPay\PartPay
arguments: ['@commerce_partpay.logger', '@module_handler']
commerce_payment.commerce_payment_gateway.plugin.partpay:
type: commerce_payment_gateway_configuration
mapping:
partpayClientId:
type: string
label: 'Client Id'
partpaySecret:
type: string
label: 'Secret'
partpayRef:
type: string
label: 'Merchant Reference Prefix'
<?php
namespace Drupal\commerce_partpay\Controller;
use Drupal\commerce_checkout\CheckoutOrderManager;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Class OffSitePaymentController.
*
* @package Drupal\commerce_partpay\Controller
*/
class OffSitePaymentController implements ContainerInjectionInterface {
/**
* The checkout order manager.
*
* @var \Drupal\commerce_checkout\CheckoutOrderManagerInterface
*/
protected $checkoutOrderManager;
/**
* The checkout order manager.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $request;
/**
* Constructs a new CheckoutController object.
*/
public function __construct(CheckoutOrderManager $checkout_order_manager, RequestStack $request) {
$this->checkoutOrderManager = $checkout_order_manager;
$this->request = $request->getCurrentRequest();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('commerce_checkout.checkout_order_manager'),
$container->get('request_stack')
);
}
/**
* Provides the "notify" page.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*
* @return \Symfony\Component\HttpFoundation\Response
* The response.
*/
public function notifyPage(RouteMatchInterface $route_match) {
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $route_match->getParameter('commerce_order');
$payment_gateway_plugin = $order->payment_gateway->entity->getPlugin();
$response = $payment_gateway_plugin->onNotify($this->request);
if (!$response) {
$response = new Response('', 200);
}
return $response;
}
/**
* Checks access for the form page.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
*
* @return \Drupal\Core\Access\AccessResult
* The access result.
*/
public function checkAccess(RouteMatchInterface $route_match) {
/** @var \Drupal\commerce_order\Entity\OrderInterface $order */
$order = $route_match->getParameter('commerce_order');
if ($order->getState()->value == 'canceled') {
return AccessResult::forbidden()->addCacheableDependency($order);
}
if (is_null($this->request->get('result'))) {
return AccessResult::forbidden()->addCacheableDependency($order);
}
if ($order->payment_gateway->entity->id() !== 'partpay') {
return AccessResult::forbidden()->addCacheableDependency($order);
}
return AccessResult::allowed();
}
}
<?php
namespace Drupal\commerce_partpay\PartPay;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayBase;
use Omnipay\Common\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides Base DPS class.
*/
abstract class CommercePartPay extends OffsitePaymentGatewayBase implements CommercePartPayInterface {
/**
* {@inheritdoc}
*/
public function onNotify(Request $request) {
/** @var \Omnipay\Common\Message\AbstractResponse $response */
$response = $this->gateway->completePurchase([])->send();
if (!$response->isRedirect() && $response->isSuccessful()) {
$order = $request->attributes->all()['commerce_order'];
$this->capturePayment($order, $response);
$order->state = 'completed';
$order->save();
}
}
/**
* {@inheritdoc}
*/
public function onCancel(OrderInterface $order, Request $request) {
/** @var \Omnipay\Common\Message\AbstractResponse $response */
$response = $this->gateway->completePurchase([])->send();
if (!$response->isRedirect() && !$response->isSuccessful()) {
$this->capturePayment($order, $response);
}
$response_data = $response->getData();
if (!$response->isSuccessful() && !empty($response_data->ReCo) && $response_data->ReCo[0] != 'RC') {
$message = ucwords(strtolower($response->getMessage()));
drupal_set_message(
$this->t(
'Sorry @gateway failed with "@message". You may resume the checkout process on this page when you are ready.',
[
'@message' => $message,
'@gateway' => $this->getDisplayLabel(),
]
),
'error'
);
}
else {
parent::onCancel($order, $request);
}
}
/**
* {@inheritdoc}
*/
public function onReturn(OrderInterface $order, Request $request) {
/** @var \Omnipay\Common\Message\AbstractResponse $response */
$response = $this->gateway->completePurchase([])->send();
if (!$response->isRedirect() && $response->isSuccessful() && $order->state->value !== 'completed') {
$this->capturePayment($order, $response);
}
}
/**
* {@inheritdoc}
*/
public function capturePayment(OrderInterface $order, ResponseInterface $response) {
$payment_storage = $this->entityTypeManager->getStorage('commerce_payment');
$data = [
'state' => ucfirst(strtolower($response->getMessage())),
'amount' => $order->getTotalPrice(),
'payment_gateway' => $this->entityId,
'order_id' => $order->id(),
'remote_id' => $response->getTransactionId(),
'remote_state' => $response->getMessage(),
'authorized' => \Drupal::time()->getRequestTime(),
];
/** @var \Drupal\commerce_partpay\PartPay\PartPay $partPayService */
$partPayService = $this->PartPayService;
$module_handler = $partPayService->getModuleHandler();
$module_handler->alter('commerce_partpay_capture_payment', $data, $order, $response);
$payment = $payment_storage->create($data);
$payment->save();
}
}
<?php
namespace Drupal\commerce_partpay\PartPay;
use Drupal\commerce_order\Entity\OrderInterface;
use Omnipay\Common\Message\ResponseInterface;
/**
* Dps Interface.
*/
interface CommercePartPayInterface {
/**
* Capture the payment.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* Order entity.
* @param \Omnipay\Common\Message\ResponseInterface $response
* Omnipay response.
*/
public function capturePayment(OrderInterface $order, ResponseInterface $response);
}
<?php
namespace Drupal\commerce_partpay\PartPay;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Psr\Log\LoggerInterface;
/**
* Class Payment Express Service.
*
* @package Drupal\commerce_partpay
*/
class PartPay implements PartPayInterface {
/**
* The logger.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
public $logger;
/**
* Commerce gateway configuration.
*
* @var array
*/
public $configuration;
/**
* PartPay gateway.
*/
public $gateway;
/**
* Module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandle;
/**
* Constructs a new PaymentGatewayBase object.
*
* @param \Psr\Log\LoggerInterface $logger
* The logger channel.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(LoggerInterface $logger, ModuleHandlerInterface $module_handler) {
$this->logger = $logger;
$this->moduleHandle = $module_handler;
}
/**
* {@inheritdoc}
*/
public function getGateway() {
return $this->gateway;
}
/**
* {@inheritdoc}
*/
public function setCredentials() {
$this->gateway->setUsername($this->getClientId());
$this->gateway->setPassword($this->getSecret());
}
/**
* {@inheritdoc}
*/
public function getClientId() {
return $this->getConfiguration('partpayClientId');
}
/**
* {@inheritdoc}
*/
public function getSecret() {
return $this->getConfiguration('partpaySecret');
}
/**
* {@inheritdoc}
*/
public function getReference() {
return $this->getConfiguration('partpayRef');
}
/**
* {@inheritdoc}
*/
public function isValidateCurrency($code) {
$currencies = [
'CAD', 'CHF', 'DKK', 'EUR', 'FRF', 'GBP', 'HKD', 'JPY',
'NZD', 'SGD', 'THB', 'USD', 'ZAR', 'AUD', 'WST', 'VUV',
'TOP', 'SBD', 'PGK', 'MYR', 'KWD', 'FJD',
];
return in_array($code, $currencies);
}
/**
* {@inheritdoc}
*/
public function getConfiguration($key = NULL) {
if (array_key_exists($key, $this->configuration)) {
return $this->configuration[$key];
};
return $this->configuration;
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration) {
$this->configuration = $configuration;
}
/**
* {@inheritdoc}
*/
public function getModuleHandler() {
return $this->moduleHandle;
}
}
<?php
namespace Drupal\commerce_partpay\PartPay;
/**
* Provides a handler for IPN requests from PayPal.
*/
interface PartPayInterface {
/**
* Get PartPay gateway instance.
*/
public function getGateway();
/**
* Set PartPay credentials.
*/
public function setCredentials();
/**
* Set PartPay configuration property.
*/
public function setConfiguration(array $configuration);
/**
* Set PartPay configuration property.
*/
public function getConfiguration($key = NULL);
/**
* Get PartPay client id.
*/
public function getClientId();
/**
* Get PartPay key.
*/
public function getSecret();
/**
* Get merchant reference.
*/
public function getReference();
/**
* Is this a valid currency.
*/
public function isValidateCurrency($code);
/**
* Get the module handler.
*/
public function getModuleHandler();
}
<?php
namespace Drupal\commerce_partpay\PartPay;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\commerce_payment\Entity\PaymentInterface;
use Psr\Log\LoggerInterface;
/**
* Class PartPayService.
*
* @package Drupal\commerce_partpay
*/
class PartPayService extends PartPay implements PartPayServiceInterface {
/**
* Prepare xml request data to PartPay.
*/
public function preparePartPayTransaction(array $form, PaymentInterface $payment) {
$this->gateway->setCurrency($payment->getAmount()->getCurrencyCode());
$this->gateway->setParameter('returnUrl', $form['#return_url']);
$this->gateway->setParameter('cancelUrl', $form['#cancel_url']);
$this->gateway->setParameter('amount', $payment->getAmount()->getNumber());
$this->gateway->setParameter('description', $this->getReference() . ' #' . $payment->getOrderId());
}
}
<?php
namespace Drupal\commerce_partpay\PartPay;
use Drupal\commerce_payment\Entity\PaymentInterface;
/**
* Provides a handler for from PartPay.
*/
interface PartPayServiceInterface {
/**
* Prepare xml request data to PartPay.
*/
public function preparePartPayTransaction(array $form, PaymentInterface $payment);
}
<?php
namespace Drupal\commerce_partpay\Plugin\Commerce\PaymentGateway;
use Drupal\commerce_partpay\PartPay\CommercePartPay;
use Drupal\commerce_payment\PaymentMethodTypeManager;
use Drupal\commerce_payment\PaymentTypeManager;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides the PartPay payment gateway.
*
* @CommercePaymentGateway(
* id = "partpay",
* label = @Translation("PartPay"),
* display_label = @Translation("PartPay"),
* payment_method_types = {"credit_card"},
* forms = {
* "offsite-payment" = "Drupal\commerce_partpay\PluginForm\OffSiteRedirect\PartPayForm",
* },
* credit_card_types = {
* "amex", "discover", "mastercard", "visa",
* },
* )
*/
class PartPay extends CommercePartPay {
/**
* PartPay Service.
*
* @var \Drupal\commerce_partpay\PartPay\PartPayServiceInterface
*/
protected $partPayService;
/**
* PartPay gateway.
*/
protected $gateway;
/**
* Constructs a new PaymentGatewayBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\commerce_payment\PaymentTypeManager $payment_type_manager
* The payment type manager.
* @param \Drupal\commerce_payment\PaymentMethodTypeManager $payment_method_type_manager
* The payment method type manager.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
EntityTypeManagerInterface $entity_type_manager,
PaymentTypeManager $payment_type_manager,
PaymentMethodTypeManager $payment_method_type_manager,
TimeInterface $time
) {
parent::__construct(
$configuration,
$plugin_id,
$plugin_definition,
$entity_type_manager,
$payment_type_manager,
$payment_method_type_manager,
$time
);
$this->partPayService = \Drupal::service('commerce_partpay.partpay_service');
$this->gateway = $this->partPayService->getGateway();
$this->partPayService->setConfiguration($configuration);
$this->partPayService->setCredentials();
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array_merge([
'partpayClientId' => '',
'partpaySecret' => '',
'partpayRef' => 'Website Order',
], parent::defaultConfiguration());
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$key = 'partpayClientId';
$form[$key] = [
'#type' => 'textfield',
'#title' => $this->t('Client Id'),
'#default_value' => $this->configuration[$key],
'#required' => TRUE,
];
$key = 'partpaySecret';
$form[$key] = [
'#type' => 'textfield',
'#title' => $this->t('Secret'),
'#default_value' => $this->configuration[$key],
'#required' => TRUE,
];