View source
<?php
namespace Drupal\commerce_worldpay\Plugin\Commerce\PaymentGateway;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_payment\Entity\PaymentInterface;
use Drupal\commerce_payment\Exception\PaymentGatewayException;
use Drupal\commerce_payment\PaymentMethodTypeManager;
use Drupal\commerce_payment\PaymentTypeManager;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayBase;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Html;
use Drupal\Core\Routing\TrustedRedirectResponse;
use Drupal\Core\TypedData\Exception\MissingDataException;
use Drupal\Core\Url;
use Drupal\Core\Utility\LinkGeneratorInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
class WorldpayRedirect extends OffsitePaymentGatewayBase implements WorldpayRedirectInterface {
use WorldpayPaymentTrait;
private $requestStack;
private $logger;
protected $time;
private $moduleHandler;
private $linkGenerator;
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PaymentTypeManager $payment_type_manager, PaymentMethodTypeManager $payment_method_type_manager, RequestStack $requestStack, LoggerInterface $logger, TimeInterface $time, ModuleHandlerInterface $moduleHandler, LinkGeneratorInterface $linkGenerator) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $payment_type_manager, $payment_method_type_manager, $time);
$this->requestStack = $requestStack;
$this->logger = $logger;
$this->time = $time;
$this->moduleHandler = $moduleHandler;
$this->linkGenerator = $linkGenerator;
}
public function defaultConfiguration() {
return [
'installation_id' => '',
'txn_mode' => C_WORLDPAY_BG_DEF_SERVER_TEST,
'txn_type' => '',
'debug' => 'log',
'confirmed_setup' => FALSE,
'site_id' => '',
'payment_parameters' => [
'test_mode' => '',
'test_result' => 'AUTHORISED',
],
'payment_security' => [
'use_password' => FALSE,
'password' => '',
'md5_salt' => '',
],
'payment_urls' => [
'live' => C_WORLDPAY_BG_DEF_SERVER_LIVE,
'test' => C_WORLDPAY_BG_DEF_SERVER_TEST,
'use_ssl' => FALSE,
'force_non_ssl_links' => FALSE,
],
] + parent::defaultConfiguration();
}
public static function md5signatureFields() {
return [
'instId',
'amount',
'currency',
'cartId',
'MC_orderId',
C_WORLDPAY_BG_RESPONSE_URL_TOKEN,
];
}
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$payment_url = \Drupal::request()
->getSchemeAndHttpHost() . '/payment/notify/MACHINE_NAME_OF_PAYMENT_GATEWAY';
$help_url = Url::fromUri('http://www.worldpay.com/support/kb/bg/paymentresponse/pr5502.html');
$form['help_text'] = [
'#type' => 'fieldset',
'#title' => t('Installation instructions'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$form['help_text']['worldpay_settings'] = [
'#markup' => $this
->t('<h4>Installation instructions</h4>
<p>For this module to work properly you must configure a few specific options in your RBS WorldPay account under <em>Installation Administration</em> settings:</p>
<ul>
<li><strong>Payment Response URL</strong> must be set to: <em>@response_url</em></li>
<li><strong>Payment Response enabled?</strong> must be <em>enabled</em></li>
<li><strong>Enable the Shopper Response</strong> should be <em>enabled</em> to get the Commerce response page.</li>
<li><strong>Shopper Redirect URL</strong> and set the value to be <em>@shopper_link</em>. @link.</li>
<li><strong>SignatureFields must be set to</strong>: <em>@sig</em></li>
<li><strong>MD5 secret for transactions</strong>: The secret must be between 20 and 30 characters long with no white space and contain at least one upper case letter, one lower case letter, one number and one symbol.</li>
</ul>', [
'@response_url' => '<wpdisplay item=' . C_WORLDPAY_BG_RESPONSE_URL_TOKEN . '-ppe empty="' . $payment_url . '">',
'@sig' => implode(':', static::md5signatureFields()),
'@link' => $this->linkGenerator
->generate($this
->t('Worldpay help document'), $help_url),
'@shopper_link' => '<wpdisplay item=MC_callback>',
]),
];
$form['help_text']['confirmed_setup'] = [
'#type' => 'checkbox',
'#title' => t('I have completed the WorldPay installation setup (above).'),
'#default_value' => $this->configuration['confirmed_setup'],
'#required' => TRUE,
'#tree' => TRUE,
];
$form['installation_id'] = [
'#type' => 'textfield',
'#title' => t('Installation ID'),
'#size' => 16,
'#default_value' => $this->configuration['installation_id'],
'#required' => TRUE,
];
$form['debug'] = [
'#type' => 'select',
'#title' => t('Debug mode'),
'#multiple' => FALSE,
'#options' => [
'log' => t('Log'),
'screen' => t('Screen'),
'both' => t('Both'),
'none' => t('None'),
],
'#default_value' => $this->configuration['debug'],
];
$form['site_id'] = [
'#type' => 'textfield',
'#title' => t('Site ID'),
'#description' => t('A custom identifier that will be passed to WorldPay. This is useful for using one WorldPay account for multiple web sites.'),
'#size' => 10,
'#default_value' => $this->configuration['site_id'],
'#required' => FALSE,
];
$form['payment_parameters'] = [
'#type' => 'fieldset',
'#title' => t('Payment parameters'),
'#description' => t('These options control what parameters are sent to RBS WorldPay when the customer submits the order.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$form['payment_parameters']['test_mode'] = [
'#type' => 'checkbox',
'#title' => t('Enable test mode'),
'#default_value' => $this->configuration['payment_parameters']['test_mode'],
];
$form['payment_parameters']['test_result'] = [
'#type' => 'select',
'#title' => t('Test mode result'),
'#description' => t('Specify the required transaction result when working in test mode.'),
'#default_value' => $this->configuration['payment_parameters']['test_result'],
'#options' => [
'AUTHORISED' => 'Authorised',
'REFUSED' => 'Refused',
'ERROR' => 'Error',
'CAPTURED' => 'Captured',
],
'#disabled' => !$this->configuration['payment_parameters']['test_mode'] ? TRUE : FALSE,
];
$form['payment_security'] = [
'#type' => 'fieldset',
'#title' => t('Security'),
'#description' => t('These options are for insuring a secure transaction to RBS WorldPay when the customer submits the order.'),
'#collapsible' => FALSE,
'#collapsed' => FALSE,
];
$form['payment_security']['use_password'] = [
'#type' => 'checkbox',
'#title' => t('Use WorldPay installation password?'),
'#description' => t('It is recomended that you set a password in your Worldpay Merchant Interface > Installation. Once done check this and enter the password.'),
'#default_value' => $this->configuration['payment_security']['use_password'],
];
$form['payment_security']['password'] = [
'#type' => 'textfield',
'#title' => t('Installation password'),
'#description' => t('This will only be used if you have checked "Use WorldPay installation password?".'),
'#size' => 20,
'#maxlength' => 30,
'#default_value' => $this->configuration['payment_security']['password'],
];
$form['payment_security']['md5_salt'] = [
'#type' => 'textfield',
'#title' => t('MD5 secret for transactions'),
'#description' => t('The secret is used to hash some of the content for verification between Worldpay and this site.'),
'#size' => 20,
'#maxlength' => 30,
'#default_value' => $this->configuration['payment_security']['md5_salt'],
'#required' => TRUE,
];
$form['payment_urls'] = [
'#type' => 'details',
'#title' => t('Payment URLs'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
];
$form['payment_urls']['test'] = [
'#type' => 'textfield',
'#title' => t('Test URL'),
'#description' => t('The WorldPay test environment URL.'),
'#default_value' => $this->configuration['payment_urls']['test'],
'#required' => TRUE,
];
$form['payment_urls']['live'] = [
'#type' => 'textfield',
'#title' => t('Live URL'),
'#description' => t('The WorldPay live environment URL.'),
'#default_value' => $this->configuration['payment_urls']['live'],
'#required' => TRUE,
];
$form['payment_urls']['use_ssl'] = [
'#type' => 'checkbox',
'#title' => t('Use SSL for payment notifications'),
'#description' => t('If checked, when WorldPay passes information, it will be done over SSL for greater security. Use in combination with callback password to prevent spoofing.'),
'#default_value' => $this->configuration['payment_urls']['use_ssl'],
];
$form['payment_urls']['force_non_ssl_links'] = [
'#type' => 'checkbox',
'#title' => t('Force http (non-ssl) return links'),
'#description' => t('This is needed if "Use SSL" is checked and you want your buyers to return to the non-ssl site.'),
'#default_value' => $this->configuration['payment_urls']['force_non_ssl_links'],
];
return $form;
}
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
if (!UrlHelper::isValid($form_state
->getValue($form['#parents'])['payment_urls']['live'])) {
$form_state
->setErrorByName('live', $this
->t('The URL @url for @title is invalid. Enter a fully-qualified URL, such as https://secure.worldpay.com/example.', [
'@url' => $form['#value'],
'@title' => $form['#title'],
]));
}
}
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
if (!$form_state
->getErrors()) {
$values = $form_state
->getValue($form['#parents']);
$this->configuration['installation_id'] = $values['installation_id'];
$this->configuration['debug'] = $values['debug'];
$this->configuration['confirmed_setup'] = $values['help_text']['confirmed_setup'];
$this->configuration['site_id'] = $values['site_id'];
$this->configuration['payment_parameters']['test_mode'] = $values['payment_parameters']['test_mode'];
$this->configuration['payment_parameters']['test_result'] = $values['payment_parameters']['test_result'];
$this->configuration['payment_security']['password'] = $values['payment_security']['password'];
$this->configuration['payment_security']['md5_salt'] = $values['payment_security']['md5_salt'];
$this->configuration['payment_urls']['live'] = $values['payment_urls']['live'];
$this->configuration['payment_urls']['test'] = $values['payment_urls']['test'];
$this->configuration['payment_urls']['use_ssl'] = $values['payment_urls']['use_ssl'];
$this->configuration['payment_urls']['force_non_ssl_links'] = $values['payment_urls']['force_non_ssl_links'];
}
}
public function buildFormData(PaymentInterface $payment) {
$order = $payment
->getOrder();
$worldPayFormApi = $this
->getWorldPayApi($order);
try {
$worldPayFormApi
->addAddress($this
->getBillingAddress($order));
} catch (MissingDataException $exception) {
$this->logger
->error($exception
->getMessage());
return FALSE;
}
if ($this->moduleHandler
->moduleExists('commerce_shipping') && $order
->hasField('shipments')) {
$shipments = $order
->get('shipments')
->referencedEntities();
if (!empty($shipments) && ($shippingAddress = $this
->getShippingAddress(reset($shipments)))) {
$worldPayFormApi
->addShipmentAddress($shippingAddress);
}
}
$data = $worldPayFormApi
->createData();
$order
->setData('worldpay_form', [
'request' => $data,
'return_url' => $this
->getNotifyUrl()
->toString(),
]);
$order
->save();
return $data;
}
public function getUrl() {
$url = C_WORLDPAY_BG_DEF_SERVER_TEST;
if ($this
->getMode() == WORLDPAY_BG_SERVER_LIVE) {
$url = C_WORLDPAY_BG_DEF_SERVER_LIVE;
}
return $url;
}
protected function getWorldPayApi($order) {
return new WorldPayHelper($order, $this
->getConfiguration());
}
protected function buildReturnUrl(OrderInterface $order) {
return Url::fromRoute('commerce_payment.checkout.return', [
'commerce_order' => $order
->id(),
'step' => 'payment',
], [
'absolute' => TRUE,
])
->toString();
}
protected function buildCancelUrl(OrderInterface $order) {
return Url::fromRoute('commerce_payment.checkout.cancel', [
'commerce_order' => $order
->id(),
'step' => 'payment',
], [
'absolute' => TRUE,
])
->toString();
}
public function createPayment(array $responseData, OrderInterface $order) {
$paymentStorage = $this->entityTypeManager
->getStorage('commerce_payment');
$payment = $paymentStorage
->create([
'state' => 'authorization',
'amount' => $order
->getTotalPrice(),
'payment_gateway' => $this->parentEntity
->id(),
'order_id' => $order
->id(),
'test' => $this
->getMode() == 'test',
'remote_id' => $responseData['transId'],
'remote_state' => $responseData['transStatus'],
'authorized' => $this->time
->getRequestTime(),
]);
$payment
->save();
return $payment;
}
public function onNotify(Request $request) {
$content = $request
->getMethod() === 'POST' ? $request
->getContent() : FALSE;
if (!$content) {
$this->logger
->error('There is no response was received');
throw new PaymentGatewayException();
}
$this->moduleHandler
->invokeAll('commerce_worldpay_payment_response', [
$request,
]);
if ($this->configuration['debug'] === 'log') {
$this->logger
->debug('<pre>' . $content . '</pre>');
$this->logger
->debug('Transaction ID %transID Order ID %orderID', [
'%transID' => $request->request
->get('transId'),
'%orderID' => $request->request
->get('MC_orderId'),
]);
}
$txCode = $request->request
->get('transId') !== NULL ? $request->request
->get('transId') : FALSE;
if (empty($txCode) || empty($request->request
->get('MC_orderId'))) {
$this->logger
->error('No Transaction code have been returned.');
throw new PaymentGatewayException();
}
$order = $this->entityTypeManager
->getStorage('commerce_order')
->load($request->request
->get('MC_orderId'));
$build = [];
if ($order instanceof OrderInterface && $request->request
->get('transStatus') === 'Y') {
$payment = $this
->createPayment($request->request
->all(), $order);
$payment->state = 'capture_completed';
$payment
->save();
$logLevel = 'info';
$logMessage = 'OK Payment callback received from WorldPay for order %order_id with status code %transID';
$logContext = [
'%order_id' => $order
->id(),
'%transID' => $request->request
->get('transId'),
];
$this->logger
->log($logLevel, $logMessage, $logContext);
$build += [
'#theme' => 'commerce_worldpay_success',
'#transaction_id' => $request->request
->get('transId'),
'#order_id' => $order
->id(),
'#return_url' => $this
->buildReturnUrl($order),
'#cache' => [
'max-age' => 0,
],
];
}
if ($order instanceof OrderInterface && $request->request
->get('transStatus') === 'C') {
$logLevel = 'info';
$logMessage = 'Cancel Payment callback received from WorldPay for order %order_id with status code %transID';
$logContext = [
'%order_id' => $order
->id(),
'%transID' => $request->request
->get('transId'),
];
$this->logger
->log($logLevel, $logMessage, $logContext);
$build += [
'#theme' => 'commerce_worldpay_cancel',
'#transaction_id' => $request->request
->get('transId'),
'#order_id' => $order
->id(),
'#return_url' => $this
->buildCancelUrl($order),
'#cache' => [
'max-age' => 0,
],
];
}
$output = \Drupal::service('renderer')
->renderRoot($build);
$response = new Response();
$response
->setStatusCode(200);
$response
->setContent($output);
$response->headers
->set('Content-Type', 'text/html');
return $response;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition, $container
->get('entity_type.manager'), $container
->get('plugin.manager.commerce_payment_type'), $container
->get('plugin.manager.commerce_payment_method_type'), $container
->get('request_stack'), $container
->get('logger.channel.commerce_worldpay'), $container
->get('datetime.time'), $container
->get('module_handler'), $container
->get('link_generator'));
}
}