esewa-1.1.2/src/Plugin/Commerce/PaymentGateway/EsewaCheckoutCheckout.php
src/Plugin/Commerce/PaymentGateway/EsewaCheckoutCheckout.php
<?php
namespace Drupal\esewa\Plugin\Commerce\PaymentGateway;
use Drupal\esewa\Event\CommerceEsewaCheckoutEvents;
use Drupal\esewa\Event\CommerceEsewaCheckoutPaymentEvent;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\Core\State\StateInterface;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_order\Entity\Order;
use Drupal\commerce_payment\Exception\PaymentGatewayException;
use Drupal\commerce_payment\Entity\PaymentInterface;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayBase;
use Drupal\commerce_payment\PaymentMethodTypeManager;
use Drupal\commerce_payment\PaymentTypeManager;
use Drupal\commerce_price\Calculator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Provides the eSewa Payment gateway plugin.
*
* @CommercePaymentGateway(
* id = "esewa",
* label = @Translation("eSewa Payment"),
* display_label = @Translation("eSewa Payment"),
* forms = {
* "offsite-payment" = "Drupal\esewa\PluginForm\EsewaCheckoutCheckoutForm",
* },
* payment_method_types = {"credit_card"},
* credit_card_types = {
* "mastercard", "visa", "maestro",
* },
* )
*/
class EsewaCheckoutCheckout extends OffsitePaymentGatewayBase implements EsewaCheckoutCheckoutInterface {
/**
* The event dispatcher service.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* 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.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
* The event dispatcher service.
* @param \Drupal\Core\State\StateInterface $state
* The state service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PaymentTypeManager $payment_type_manager, PaymentMethodTypeManager $payment_method_type_manager, TimeInterface $time, EventDispatcherInterface $eventDispatcher, StateInterface $state) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $payment_type_manager, $payment_method_type_manager, $time);
$this->eventDispatcher = $eventDispatcher;
$this->state = $state;
}
/**
* {@inheritdoc}
*/
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('datetime.time'),
$container->get('event_dispatcher'),
$container->get('state')
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'redirect_method' => 'post',
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['merchant_code'] = [
'#type' => 'textfield',
'#title' => $this->t('Merchant Code'),
'#description' => t('The merchant code retrieved from eSewa'),
'#default_value' => $this->state->get('esewa.merchant_code'),
];
$form['pass_customer_email'] = [
'#type' => 'checkbox',
'#title' => $this->t('Pass customer email to esewa'),
'#description' => t('Pass customer email to esewa during checkout.'),
'#default_value' => $this->state->get('esewa.pass_customer_email'),
'#access' => FALSE
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::submitConfigurationForm($form, $form_state);
if (!$form_state->getErrors()) {
$values = $form_state->getValue($form['#parents']);
$this->state->set('esewa.merchant_code', $values['merchant_code']);
$this->state->set('esewa.pass_customer_email', $values['pass_customer_email']);
$this->configuration['merchant_code'] = $values['merchant_code'];
$this->configuration['pass_customer_email'] = $values['pass_customer_email'];
}
}
/**
* {@inheritdoc}
*/
public function onReturn(OrderInterface $order, Request $request) {
$data = $this->getRequestData($request);
$configuration = $this->getConfiguration();
$data['fp_hash'] = strtoupper($this->hashData($data, $configuration['merchant_code']));
$fp_hash = addslashes(trim($request->request->get('fp_hash')));
if ($data['fp_hash'] !== $fp_hash) {
throw new PaymentGatewayException('Invalid signature');
}
$this->createPaymentStorage($order, $request);
if ($request->request->get('action') == "0") {
$order->setData('state', 'completed');
$event = new CommerceEsewaCheckoutPaymentEvent($order);
$this->eventDispatcher->dispatch(CommerceEsewaCheckoutEvents::PAYMENT_SUCCESS, $event);
$this->messenger->addMessage($this->t('The payment was made successfully.'));
}
else {
$event = new CommerceEsewaCheckoutPaymentEvent($order);
$this->eventDispatcher->dispatch(CommerceEsewaCheckoutEvents::PAYMENT_FAILURE, $event);
$this->messenger->addWarning($this->t('Transaction failed: @message'), [
'@message' => $request->request->get('message')
]);
}
}
/**
* {@inheritdoc}
*/
public function setEsewaCheckoutCheckoutData(PaymentInterface $payment) {
$order = $payment->getOrder();
$amount = $payment->getAmount();
$configuration = $this->getConfiguration();
// Order description.
$order_desc = 'Order #' . $order->id() . ': ';
foreach ($order->getItems() as $item) {
$product_sku = $item->getPurchasedEntity()->getSku();
$order_desc .= $item->getTitle() . ' [' . $product_sku . ']';
$order_desc .= ', ';
}
// Remove the last comma.
$order_desc = rtrim($order_desc, ', ');
// Curent timestamp.
$timestamp = gmdate('YmdHis');
$nonce = md5(microtime() . mt_rand());
// Build a name-value pair array for this transaction.
// The data which should be signed to be transported to CommerceEsewaCheckout.ro.
$data = [
'amount' => Calculator::round($amount->getNumber(), 2),
'currency' => $amount->getCurrencyCode(),
'invoice_id' => $order->id(),
'order_desc' => $order_desc,
'timestamp' => $timestamp,
'nonce' => $nonce,
];
$address = $order->getBillingProfile()->get('address')->first();
// The hidden data wich should be transported to CommerceEsewaCheckout.ro.
$nvp_data = [
'fname' => $address->getGivenName(),
'lname' => $address->getFamilyName(),
'country' => $address->getCountryCode(),
'city' => $address->getLocality(),
'email' => $order->getEmail(),
'amount' => Calculator::round($amount->getNumber(), 2),
'currency' => $amount->getCurrencyCode(),
'invoice_id' => $order->id(),
'order_desc' => $order_desc,
'timestamp' => $timestamp,
'nonce' => $nonce,
'order' => $order,
'fp_hash' => strtoupper($this->hashData($data, $configuration['merchant_code']??''))
];
return $nvp_data;
}
/**
* Get data from Request object.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*
* @return array
* The built request array.
*/
public function getRequestData(Request $request) {
return [
'amount' => addslashes(trim($request->request->get('amount'))),
'curr' => addslashes(trim($request->request->get('curr'))),
'invoice_id' => addslashes(trim($request->request->get('invoice_id'))),
// A unique id provided by CommerceEsewaCheckout.ro.
'ep_id' => addslashes(trim($request->request->get('ep_id'))),
'merch_id' => addslashes(trim($request->request->get('merch_id'))),
// For the transaction to be ok, the action should be 0.
'action' => addslashes(trim($request->request->get('action'))),
// The transaction response message.
'message' => addslashes(trim($request->request->get('message'))),
// If the transaction action is different 0, the approval value is empty.
'approval' => addslashes(trim($request->request->get('approval'))),
'timestamp' => addslashes(trim($request->request->get('timestamp'))),
'nonce' => addslashes(trim($request->request->get('nonce'))),
];
}
/**
* {@inheritdoc}
*/
public function onNotify(Request $request) {
$data = $request->request->all();
$configuration = $this->getConfiguration();
if (isset($data['fp_hash']) && isset($data['lang'])) {
unset($data['fp_hash']);
unset($data['lang']);
}
$data['fp_hash'] = strtoupper($this->hashData($data, $configuration['merchant_code']));
$fp_hash = $request->request->get('fp_hash');
if ($data['fp_hash'] !== $fp_hash) {
throw new PaymentGatewayException('Invalid signature');
}
$order = Order::load($data['invoice_id']);
$payment = $this->createPaymentStorage($order, $request, 'notify');
if ($request->request->get('action') == "0") {
$order->set('state', 'completed');
$this->messenger->addMessage($this->t('The payment was made successfully.'));
$url = Url::fromUri('internal:/checkout/' . $order->id() . '/complete');
}
else {
$this->messenger->addWarning($this->t('Transaction failed: @message.', [
'@message' => $request->request->get('message')
]));
$url = Url::fromUri('internal:/checkout/' . $order->id() . '/order_information');
}
$order->save();
return new RedirectResponse($url->toString());
}
/**
* Create a PaymentStorage object.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $order
* The commerce_order object.
* @param \Symfony\Component\HttpFoundation\Request $request
* The Request object.
*
* @return \Drupal\Core\Entity\EntityStorageInterface
* The PaymentStorage object.
*/
public function createPaymentStorage(OrderInterface $order, Request $request, $payment_state) {
$payment_storage = $this->entityTypeManager->getStorage('commerce_payment');
$request_time = $this->time->getRequestTime();
$payment_storage->create([
'amount' => $order->getTotalPrice(),
'payment_gateway' => $this->entityId,
'order_id' => $order->id(),
'test' => $this->getMode() == 'test',
'remote_id' => $request->request->get('ep_id'),
'remote_state' => $request->request->get('message'),
'authorized' => $request_time,
]);
if ($request->request->get('action') == "0") {
$payment_storage->state = isset($payment_state) ? 'completed' : 'authorization';
$this->messenger->addMessage($this->t('The payment was made successfully.'));
}
else {
$payment_storage->state = 'authorization_voided';
$this->messenger->addWarning($this->t('Transaction failed: @message'), [
'@message' => $request->request->get('message')
]);
}
$payment_storage->save();
return $payment_storage;
}
/**
* {@inheritdoc}
*/
public function onCancel(OrderInterface $order, Request $request) {
parent::onCancel($order, $request);
}
/**
* Custom function from CommerceEsewaCheckout documentation.
* Fore more details, please read the documentation from module.
*
* @param array $data
* Data that is passed through SHA1 function.
* @param string $key
* Merchant Code.
*
* @return string.
* Hash code that is sent to CommerceEsewaCheckout.
*/
public static function hashData($data, $key) {
$str = NULL;
foreach ($data as $d) {
if ($d === NULL || strlen($d) == 0) {
// The NULL values will be replaced with - .
$str .= '-';
}
else {
$str .= strlen($d) . $d;
}
}
// We convert the secret code into a binary string.
// $key = pack('H*', $key);
return self::hashSHA1($str, $key);
}
/**
* Custom function from CommerceEsewaCheckout documentation.
* Fore more details, please read the documentation from module.
*
* @param string $data
* Data regarding the order.
* @param string $key
* Merchant Code.
*
* @return string.
* The digest of the function.
*/
private static function hashSHA1($data, $key) {
$blocksize = 64;
$hashfunc = 'md5';
if (strlen($key) > $blocksize) {
$key = pack('H*', $hashfunc($key));
}
$key = str_pad($key, $blocksize, chr(0x00));
$ipad = str_repeat(chr(0x36), $blocksize);
$opad = str_repeat(chr(0x5c), $blocksize);
$hmac = pack('H*', $hashfunc(($key ^ $opad) . pack('H*', $hashfunc(($key ^ $ipad) . $data))));
return bin2hex($hmac);
}
}
