Skip to content
Snippets Groups Projects
Commit 59fe35da authored by Primoz Hmeljak's avatar Primoz Hmeljak
Browse files

by Primsi: add PluginForm and other improvements.

parent be6b80cd
Branches
Tags
No related merge requests found
commerce_datatrans.dummy_redirect_post:
path: 'commerce_datatrans/dummy_redirect_post'
commerce_datatrans.gateway_post:
path: '/datatrans/post'
defaults:
_controller: '\Drupal\commerce_datatrans\Controller\DummyRedirectController::post'
options:
no_cache: TRUE
_controller: '\Drupal\commerce_datatrans\Controller\DatatransGatewayController::post'
requirements:
_access: 'TRUE'
commerce_datatrans.dummy_redirect_302:
path: 'commerce_datatrans/dummy_redirect_302'
commerce_datatrans.gateway_success:
path: '/datatrans/success/{payment}'
defaults:
_controller: '\Drupal\commerce_datatrans\Controller\DummyRedirectController::on302'
options:
no_cache: TRUE
_controller: '\Drupal\commerce_datatrans\Controller\DatatransGatewayController::success'
requirements:
_access: 'TRUE'
commerce_datatrans.gateway_error:
path: '/datatrans/error/{payment}'
defaults:
_controller: '\Drupal\commerce_datatrans\Controller\DatatransGatewayController::error'
requirements:
_access: 'TRUE'
commerce_datatrans.gateway_cancel:
path: '/datatrans/cancel/{payment}'
defaults:
_controller: '\Drupal\commerce_datatrans\Controller\DatatransGatewayController::cancel'
requirements:
_access: 'TRUE'
<?php
namespace Drupal\commerce_datatrans\Controller;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\payment\Entity\PaymentInterface;
use Drupal\payment_datatrans\DatatransHelper;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* This is a dummy controller for mocking an off-site gateway.
*/
class DatatransGatewayController implements ContainerInjectionInterface {
/**
* The current request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $currentRequest;
/**
* Constructs a new DummyRedirectController object.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
*/
public function __construct(RequestStack $request_stack) {
$this->currentRequest = $request_stack->getCurrentRequest();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('request_stack')
);
}
/**
* Page callback for processing post data sent from Datatrans.
*/
public function post() {
// $cancel = $this->currentRequest->request->get('cancel');
// $return = $this->currentRequest->request->get('return');
// $total = $this->currentRequest->request->get('total');
return [
'#markup' => 'ok'
];
}
/**
* Page callback for processing successful Datatrans response.
*
* @param Request $request
* Request
* @param PaymentInterface $payment
* The Payment entity type.
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function success(Request $request, PaymentInterface $payment) {
try {
// This needs to be checked to match the payment method settings
// ND being valid with its keys and data.
$post_data = $request->request->all();
// Check if payment is pending.
if ($post_data['datatrans_key'] != DatatransHelper::generateDatatransKey($payment)) {
throw new \Exception('Invalid datatrans key.');
}
// If Datatrans returns error status.
if ($post_data['status'] == 'error') {
throw new \Exception(DatatransHelper::mapErrorCode($post_data['errorCode']));
}
// This is the internal guaranteed configuration that is to be considered
// authoritative.
$plugin_definition = $payment->getPaymentMethod()->getPluginDefinition();
// Check for invalid security level.
if (empty($post_data) || $post_data['security_level'] != $plugin_definition['security']['security_level']) {
throw new \Exception('Invalid security level.');
}
// If security level 2 is configured then generate and use a sign.
if ($plugin_definition['security']['security_level'] == 2) {
// Generates the sign.
$sign2 = $this->generateSign2($plugin_definition, $post_data);
// Check for correct sign.
if (empty($post_data['sign2']) || $sign2 != $post_data['sign2']) {
throw new \Exception('Invalid sign.');
}
}
// At that point the transaction is treated to be valid.
if ($post_data['status'] == 'success') {
// Store data in the payment configuration.
$this->setPaymentConfiguration($payment, $post_data);
/** @var \Drupal\payment_datatrans\Plugin\Payment\Method\DatatransMethod $payment_method */
$payment_method = $payment->getPaymentMethod();
// Some providers do not enforce an alias. If the option is enabled and
// none was provided, the action taken depends on the req_type setting.
if ($plugin_definition['use_alias'] && empty($post_data['aliasCC'])) {
if ($plugin_definition['req_type'] == 'conditional') {
$payment_method->cancelPayment();
if ($payment->getPaymentStatus()->getPluginId() == $plugin_definition['cancel_status_id']) {
// The payment was authorized but has now been cancelled. Let the
// user take action.
drupal_set_message(t('No alias was provided with the payment. Ensure that the necessary option is selected or use a different payment provider.'), 'warning');
return $payment->getPaymentType()->getResumeContextResponse()->getResponse();
}
else {
// The payment is still authorized with the service. Let the user
// complete; a payment with missing recurrence is preferred over
// no payment.
\Drupal::logger('datatrans')->warning('Alias is missing but authorization cancelling failed for Payment @id.', ['@id' => $payment->id()]);
}
}
else {
$payment_method->refundPayment();
// If refund was successful, redirect the user back, with a message.
// If refund failed, then there is nothing that can be done, log an
// error but let the user complete.
if ($payment->getPaymentStatus()->getPluginId() == $plugin_definition['refund_status_id']) {
drupal_set_message(t('No alias was provided with the payment. Ensure that the necessary option is selected or use a different payment provider.'), 'warning');
return $payment->getPaymentType()->getResumeContextResponse()->getResponse();
}
else {
\Drupal::logger('datatrans')->warning('Alias is missing but payment refund failed for Payment @id.', ['@id' => $payment->id()]);
}
}
}
// To complete the payment when using the conditional request type, make
// a new request for the settlement.
if ($plugin_definition['req_type'] == 'conditional') {
$payment_method->capturePayment();
if ($payment->getPaymentStatus()->getPluginId() != $plugin_definition['execute_status_id']) {
throw new \Exception('Authorization succeeded but settlement failed');
}
}
// Save the successful payment.
return $this->savePayment($payment, isset($plugin_definition['execute_status_id']) ? $plugin_definition['execute_status_id'] : 'payment_success');
}
else {
throw new \Exception('Datatrans communication failure. Invalid data received from Datatrans.');
}
}
catch (\Exception $e) {
\Drupal::logger('datatrans')->error('Processing failed with exception @e.', array('@e' => $e->getMessage()));
drupal_set_message(t('Payment processing failed.'), 'error');
return $this->savePayment($payment);
}
}
/**
* Page callback for processing error Datatrans response.
*
* @param PaymentInterface $payment
* The Payment entity type.
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
*/
public function error(PaymentInterface $payment) {
$message = 'Datatrans communication failure. Invalid data received from Datatrans.';
\Drupal::logger('datatrans')->error('Processing failed with exception @e.', array('@e' => $message));
drupal_set_message(t('Payment processing failed.'), 'error');
return $this->savePayment($payment);
}
/**
* Page callback for processing cancellation Datatrans response.
*
* @param PaymentInterface $payment
* The Payment entity type.
*
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
*/
public function cancel(PaymentInterface $payment) {
drupal_set_message(t('Payment cancelled.'), 'error');
return $this->savePayment($payment, 'payment_cancelled');
}
/**
* Generates the sign2 to compare with the datatrans post data.
*
* @param array $plugin_definition
* Plugin Definition.
* @param array $post_data
* Datatrans Post Data.
*
* @return string
* The generated sign.
* @throws \Exception
*/
public function generateSign2($plugin_definition, $post_data) {
if ($plugin_definition['security']['hmac_key'] || $plugin_definition['security']['hmac_key_2']) {
if ($plugin_definition['security']['use_hmac_2']) {
$key = $plugin_definition['security']['hmac_key_2'];
}
else {
$key = $plugin_definition['security']['hmac_key'];
}
return DatatransHelper::generateSign($key, $plugin_definition['merchant_id'], $post_data['uppTransactionId'], $post_data['amount'], $post_data['currency']);
}
throw new \Exception('Problem generating sign.');
}
/**
* Saves success/cancelled/failed payment.
*
* @param \Drupal\payment\Entity\PaymentInterface $payment
* Payment entity.
* @param string $status
* Payment status to set
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function savePayment(PaymentInterface $payment, $status = 'payment_failed') {
$payment->setPaymentStatus(\Drupal::service('plugin.manager.payment.status')
->createInstance($status));
$payment->save();
return $payment->getPaymentType()->getResumeContextResponse()->getResponse();
}
/**
* Sets payments configuration data if present.
*
* No validation.
*
* @param PaymentInterface $payment
* Payment Interface.
* @param $post_data
* Datatrans Post Data.
*/
public function setPaymentConfiguration(PaymentInterface $payment, $post_data) {
/** @var \Drupal\payment_datatrans\Plugin\Payment\Method\DatatransMethod $payment_method */
$payment_method = $payment->getPaymentMethod();
$customer_details = array(
'uppCustomerTitle',
'uppCustomerName',
'uppCustomerFirstName',
'uppCustomerLastName',
'uppCustomerStreet',
'uppCustomerStreet2',
'uppCustomerCity',
'uppCustomerCountry',
'uppCustomerZipCode',
'uppCustomerPhone',
'uppCustomerFax',
'uppCustomerEmail',
'uppCustomerGender',
'uppCustomerBirthDate',
'uppCustomerLanguage',
'refno',
'aliasCC',
'maskedCC',
'expy',
'expm',
'pmethod',
'testOnly',
'authorizationCode',
'responseCode',
'uppTransactionId'
);
foreach ($customer_details as $key) {
if (!empty($post_data[$key])) {
$payment_method->setConfigField($key, $post_data[$key]);
}
}
}
}
<?php
namespace Drupal\commerce_datatrans\Controller;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* This is a dummy controller for mocking an off-site gateway.
*/
class DummyRedirectController implements ContainerInjectionInterface {
/**
* The current request.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $currentRequest;
/**
* Constructs a new DummyRedirectController object.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
*/
public function __construct(RequestStack $request_stack) {
$this->currentRequest = $request_stack->getCurrentRequest();
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('request_stack')
);
}
/**
* Callback method which accepts POST.
*
* @throws \Drupal\commerce\Response\NeedsRedirectException
*/
public function post() {
$cancel = $this->currentRequest->request->get('cancel');
$return = $this->currentRequest->request->get('return');
$total = $this->currentRequest->request->get('total');
if ($total > 20) {
return new TrustedRedirectResponse($return);
}
return new TrustedRedirectResponse($cancel);
}
/**
* Callback method which reacts to GET from a 302 redirect.
*
* @throws \Drupal\commerce\Response\NeedsRedirectException
*/
public function on302() {
$cancel = $this->currentRequest->query->get('cancel');
$return = $this->currentRequest->query->get('return');
$total = $this->currentRequest->query->get('total');
if ($total > 20) {
return new TrustedRedirectResponse($return);
}
return new TrustedRedirectResponse($cancel);
}
}
......@@ -2,9 +2,9 @@
namespace Drupal\commerce_datatrans;
use Drupal\commerce_payment\Entity\PaymentInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Site\Settings;
use Drupal\payment\Entity\PaymentInterface;
/**
* Helper methods for various Datatrans related tasks.
......@@ -21,9 +21,14 @@ class DatatransHelper {
* The generated key.
*/
public static function generateDatatransKey(PaymentInterface $payment) {
return Crypt::hashBase64($payment->id() . $payment->getPaymentStatus()
->getPluginId() . Settings::getHashSalt() . \Drupal::service('private_key')
->get());
$hash_parts = [
$payment->id(),
$payment->getPaymentGatewayId(),
Settings::getHashSalt(),
\Drupal::service('private_key')->get(),
];
return Crypt::hashBase64(implode('', $hash_parts));
}
/**
......
......@@ -2,9 +2,11 @@
namespace Drupal\commerce_datatrans\PluginForm;
use Drupal\commerce_datatrans\DatatransHelper;
use Drupal\commerce_payment\PluginForm\PaymentOffsiteForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\currency\Entity\Currency;
/**
* Provides a checkout form for the Datatrans gateway.
......@@ -19,29 +21,30 @@ class DatatransForm extends PaymentOffsiteForm {
/** @var \Drupal\commerce_payment\Entity\PaymentInterface $payment */
$payment = $this->entity;
/** @var \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface $payment_gateway_plugin */
$payment_gateway_plugin = $payment->getPaymentGateway()->getPlugin();
$redirect_method = $payment_gateway_plugin->getConfiguration()['redirect_method'];
if ($redirect_method == 'post') {
$redirect_url = Url::fromRoute('commerce_datatrans.dummy_redirect_post')->toString();
}
else {
$order = $payment->getOrder();
// Gateways that use the GET redirect method usually perform an API call
// that prepares the remote payment and provides the actual url to
// redirect to. Any params received from that API call that need to be
// persisted until later payment creation can be saved in $order->data.
// Example: $order->setData('my_gateway', ['test' => '123']), followed
// by an $order->save().
$redirect_url = Url::fromRoute('commerce_datatrans.dummy_redirect_302', [], ['absolute' => TRUE])->toString();
// We need the payment_id.
if ($payment->isNew()) {
$payment->save();
}
/** @var \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface $gateway */
$gateway = $payment->getPaymentGateway()->getPlugin();
$gateway_config = $gateway->getConfiguration();
$data = [
'return' => $form['#return_url'],
'cancel' => $form['#cancel_url'],
'total' => $payment->getAmount()->getNumber(),
'merchantId' => $gateway_config['merchant_id'],
'amount' => $payment->getAmount()->getNumber(),
'currency' => $payment->getAmount()->getCurrencyCode(),
'refno' => $payment->id(),
'sign' => NULL,
'successUrl' => Url::fromRoute('commerce_datatrans.gateway_success', ['payment' => $payment->id()], ['absolute' => TRUE])->toString(),
'errorUrl' => Url::fromRoute('commerce_datatrans.gateway_error', ['payment' => $payment->id()], ['absolute' => TRUE])->toString(),
'cancelUrl' => Url::fromRoute('commerce_datatrans.gateway_cancel', ['payment' => $payment->id()], ['absolute' => TRUE])->toString(),
'security_level' => $gateway_config['security_level'],
'datatrans_key' => DatatransHelper::generateDatatransKey($payment),
];
return $this->buildRedirectForm($form, $form_state, $redirect_url, $data, $redirect_method);
return $this->buildRedirectForm($form, $form_state, $gateway_config['service_url'], $data, static::REDIRECT_POST);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment