Loading src/OmiseUtil.php +0 −48 Original line number Diff line number Diff line Loading @@ -2,8 +2,6 @@ namespace Drupal\commerce_omise; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_payment\Entity\PaymentGatewayInterface; use Drupal\commerce_payment\Exception\AuthenticationException; use Drupal\commerce_payment\Exception\DeclineException; use Drupal\commerce_payment\Exception\HardDeclineException; Loading Loading @@ -100,50 +98,4 @@ class OmiseUtil implements OmiseUtilInterface { } } /** * {@inheritdoc} */ public function getOrderPayment(OrderInterface $order) { $payment_ids = $this ->paymentStorage ->getQuery() ->condition('order_id', $order->id()) ->condition('payment_gateway', $this->getOmisePaymentGatewaysIds(), 'IN') ->execute(); if (empty($payment_ids)) { // Throw error early if the query returned empty result. throw new \InvalidArgumentException('No Omise payments found for order ID ' . $order->id()); } $payments = $this ->paymentStorage ->loadMultiple($payment_ids); if (count($payments) > 1) { throw new \InvalidArgumentException('More than one Omise payment found for order ID ' . $order->id()); } $payment = reset($payments); if (!$payment) { throw new \InvalidArgumentException('No Omise payments found for order ID ' . $order->id()); } return $payment; } /** * Returns list of IDs of all enabled Omise payment gateways. */ protected function getOmisePaymentGatewaysIds() { $paymentGateways = array_filter( $this->paymentGatewayStorage->loadMultiple(), function (PaymentGatewayInterface $gateway) { return $gateway->getPluginId() === 'omise' && $gateway->status(); }); return array_map(function (PaymentGatewayInterface $gateway) { return $gateway->id(); }, $paymentGateways); } } src/OmiseUtilInterface.php +0 −15 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ namespace Drupal\commerce_omise; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_price\Price; /** Loading Loading @@ -43,18 +42,4 @@ interface OmiseUtilInterface { */ public function handleException(\OmiseException $exception); /** * Retrieves Omise payment from the order. * * @param \Drupal\commerce_order\Entity\OrderInterface $order * The order entity. * * @return \Drupal\commerce_payment\Entity\PaymentInterface * The Omise payment entity corresponding given order. * * @throws \InvalidArgumentException * Thrown if there are no or more than one Omise payments for the order. */ public function getOrderPayment(OrderInterface $order); } src/Plugin/Commerce/PaymentGateway/Omise.php +163 −17 Original line number Diff line number Diff line Loading @@ -2,7 +2,9 @@ namespace Drupal\commerce_omise\Plugin\Commerce\PaymentGateway; use Drupal\commerce\Response\NeedsRedirectException; use Drupal\commerce_omise\OmiseUtilInterface; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_payment\CreditCard; use Drupal\commerce_payment\Entity\PaymentInterface; use Drupal\commerce_payment\Entity\PaymentMethodInterface; Loading @@ -14,7 +16,9 @@ use Drupal\commerce_price\Price; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; /** * Provides the Omise payment gateway. Loading Loading @@ -111,6 +115,7 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { return [ 'secret_key' => '', 'public_key' => '', '3d_secure' => FALSE, ] + parent::defaultConfiguration(); } Loading @@ -134,6 +139,13 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { '#required' => TRUE, ]; $form['3d_secure'] = [ '#type' => 'checkbox', '#title' => $this->t('Enable 3D Secure support'), '#default_value' => !empty($this->configuration['3d_secure']), '#required' => FALSE, ]; return $form; } Loading @@ -145,8 +157,9 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { if (!$form_state->getErrors()) { $values = $form_state->getValue($form['#parents']); $this->configuration['secret_key'] = $values['secret_key']; $this->configuration['public_key'] = $values['public_key']; $this->configuration['secret_key'] = $values['secret_key']; $this->configuration['3d_secure'] = $values['3d_secure']; } } Loading @@ -157,39 +170,69 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { $this->assertPaymentState($payment, ['new']); $payment_method = $payment->getPaymentMethod(); $this->assertPaymentMethod($payment_method); $amount = $payment->getAmount(); $currency_code = $payment->getAmount()->getCurrencyCode(); if ($payment->isNew()) { // Save the payment entity to get its ID in case if it's not available // yet. $payment->save(); } $transaction_data = [ 'currency' => $currency_code, 'amount' => $this->util->formatNumber($amount), 'currency' => $payment->getAmount()->getCurrencyCode(), 'amount' => $this->util->formatNumber($payment->getAmount()), 'capture' => $capture, // Always passing in a card since the user have selected or entered the // specific card in Drupal user interface. // Passing the 'customer' param without a card would always force using // users default card stored at Omise regardless of what card has been // selected in Drupal. 'card' => $payment_method->getRemoteId(), ]; $owner = $payment_method->getOwner(); if ($owner && $owner->isAuthenticated()) { // Add remote customer ID if it's available; this is required for // stored cards. // @see doCreatePaymentMethod() $transaction_data['customer'] = $this->getRemoteCustomerId($owner); } else { $transaction_data['card'] = $payment_method->getRemoteId(); if (!empty($this->configuration['3d_secure'])) { $transaction_data['return_uri'] = Url::fromRoute('commerce_payment.checkout.return', [ 'commerce_order' => $payment->getOrderId(), 'step' => 'payment', ], [ 'absolute' => TRUE, 'query' => [ 'payment_id' => $payment->id(), ], ])->toString(); } try { $result = \OmiseCharge::create($transaction_data, $this->configuration['public_key'], $this->configuration['secret_key']); if (!empty($result['status'] && $result['status'] === 'failed')) { throw \OmiseException::getInstance([ 'code' => $result['failure_code'], 'message' => $result['failure_message'], ]); } $result = \OmiseCharge::create( $transaction_data, $this->configuration['public_key'], $this->configuration['secret_key'] ); $this->validateChargeStatus($result); } catch (\OmiseException $e) { $this->util->handleException($e); } $payment->setRemoteId($result['id']); if (!empty($result['authorize_uri'])) { // Payment gateway requested redirecting user to their payment // authorization page (e.g. for 3D Secure check). // Store the payment (so the payment remote ID gets stored) and redirect // user. $payment->save(); throw new NeedsRedirectException($result['authorize_uri']); } $next_state = $capture ? 'completed' : 'authorization'; $payment->setState($next_state); $payment->setRemoteId($result['id']); $payment->save(); } Loading Loading @@ -337,6 +380,56 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { $payment_method->delete(); } /** * {@inheritdoc} */ public function updateRemotePaymentStatus(PaymentInterface $payment) { $this->assertPaymentState($payment, ['new']); try { $charge = \OmiseCharge::retrieve( $payment->getRemoteId(), $this->configuration['public_key'], $this->configuration['secret_key'] ); $this->validateChargeStatus($charge); } catch (\OmiseException $e) { // Remove payment method from the order. // Note we are NOT removing the payment method; it's still valid (since // it passed the initial card number check), it's just not usable for this // order (most likely due to invalid 3D Secure response provided by user). // Removing the order payment method would force user re-entering (or // selecting) it again. $order = $payment->getOrder(); $order->get('payment_method')->setValue(NULL); $order->save(); $this->util->handleException($e); } $payment->setState('completed'); $payment->save(); } /** * Validates Omise charge status. * * @param \OmiseCharge $charge * The Omise charge object. * * @throws \OmiseException * Omise errors are thrown if charge failed. */ protected function validateChargeStatus(\OmiseCharge $charge) { if (!empty($charge['status'] && $charge['status'] === 'failed')) { throw \OmiseException::getInstance([ 'code' => $charge['failure_code'], 'message' => $charge['failure_message'], ]); } } /** * Creates the payment method on the gateway. * Loading Loading @@ -406,9 +499,12 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { $this->setRemoteCustomerId($owner, $customer['id']); $owner->save(); foreach ($cards['data'] as $card) { return $card; if (count($cards['data']) !== 1) { throw new \RuntimeException(sprintf('Expected customer to have only 1 card, %d found.', count($cards['data']))); } return reset($cards['data']); } catch (\OmiseException $e) { $this->util->handleException($e); Loading @@ -428,4 +524,54 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { return []; } /** * {@inheritdoc} */ public function getNotifyUrl() { // This is a copy of OffsitePaymentGatewayBase::getNotifyUrl(). // Omise doesn't support usual Notify (i.e. when notify URL is passed in as // part of the API call), however, there's a similar thing called Webhooks // so keeping this code here for future reference. return Url::fromRoute('commerce_payment.notify', [ 'commerce_payment_gateway' => $this->parentEntity->id(), ], ['absolute' => TRUE]); } /** * {@inheritdoc} */ public function onReturn(OrderInterface $order, Request $request) { $payment_id = $request->query->get('payment_id'); if ($payment_id === NULL || !is_numeric($payment_id)) { throw new \InvalidArgumentException('Missing/invalid payment ID query parameter.'); } $payment = $this ->entityTypeManager ->getStorage('commerce_payment') ->load($payment_id); if (!$payment) { throw new \RuntimeException('The payment with given ID does not exist.'); } $this->updateRemotePaymentStatus($payment); } /** * {@inheritdoc} */ public function onCancel(OrderInterface $order, Request $request) { $this->messenger()->addMessage($this->t('You have canceled checkout at @gateway but may resume the checkout process here when you are ready.', [ '@gateway' => $this->getDisplayLabel(), ])); } /** * {@inheritdoc} */ public function onNotify(Request $request) { // This may change later if we implement Webhooks support. throw new \LogicException('Omise payment gateway does not support Notify method'); } } src/Plugin/Commerce/PaymentGateway/OmiseInterface.php +18 −1 Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ namespace Drupal\commerce_omise\Plugin\Commerce\PaymentGateway; use Drupal\commerce_payment\Entity\PaymentInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OnsitePaymentGatewayInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsAuthorizationsInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsRefundsInterface; Loading @@ -9,7 +11,11 @@ use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsRefundsInterf /** * Provides the interface for the Omise payment gateway. */ interface OmiseInterface extends OnsitePaymentGatewayInterface, SupportsAuthorizationsInterface, SupportsRefundsInterface { interface OmiseInterface extends OnsitePaymentGatewayInterface, SupportsAuthorizationsInterface, SupportsRefundsInterface, OffsitePaymentGatewayInterface { /** * Get the Omise API Public key set for the payment gateway. Loading @@ -19,4 +25,15 @@ interface OmiseInterface extends OnsitePaymentGatewayInterface, SupportsAuthoriz */ public function getPublicKey(); /** * Retrieve and update payment status for an existing charge. * * @param \Drupal\commerce_payment\Entity\PaymentInterface $payment * The payment. * * @throws \Drupal\commerce_payment\Exception\PaymentGatewayException * The Commerce exception. */ public function updateRemotePaymentStatus(PaymentInterface $payment); } Loading
src/OmiseUtil.php +0 −48 Original line number Diff line number Diff line Loading @@ -2,8 +2,6 @@ namespace Drupal\commerce_omise; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_payment\Entity\PaymentGatewayInterface; use Drupal\commerce_payment\Exception\AuthenticationException; use Drupal\commerce_payment\Exception\DeclineException; use Drupal\commerce_payment\Exception\HardDeclineException; Loading Loading @@ -100,50 +98,4 @@ class OmiseUtil implements OmiseUtilInterface { } } /** * {@inheritdoc} */ public function getOrderPayment(OrderInterface $order) { $payment_ids = $this ->paymentStorage ->getQuery() ->condition('order_id', $order->id()) ->condition('payment_gateway', $this->getOmisePaymentGatewaysIds(), 'IN') ->execute(); if (empty($payment_ids)) { // Throw error early if the query returned empty result. throw new \InvalidArgumentException('No Omise payments found for order ID ' . $order->id()); } $payments = $this ->paymentStorage ->loadMultiple($payment_ids); if (count($payments) > 1) { throw new \InvalidArgumentException('More than one Omise payment found for order ID ' . $order->id()); } $payment = reset($payments); if (!$payment) { throw new \InvalidArgumentException('No Omise payments found for order ID ' . $order->id()); } return $payment; } /** * Returns list of IDs of all enabled Omise payment gateways. */ protected function getOmisePaymentGatewaysIds() { $paymentGateways = array_filter( $this->paymentGatewayStorage->loadMultiple(), function (PaymentGatewayInterface $gateway) { return $gateway->getPluginId() === 'omise' && $gateway->status(); }); return array_map(function (PaymentGatewayInterface $gateway) { return $gateway->id(); }, $paymentGateways); } }
src/OmiseUtilInterface.php +0 −15 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ namespace Drupal\commerce_omise; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_price\Price; /** Loading Loading @@ -43,18 +42,4 @@ interface OmiseUtilInterface { */ public function handleException(\OmiseException $exception); /** * Retrieves Omise payment from the order. * * @param \Drupal\commerce_order\Entity\OrderInterface $order * The order entity. * * @return \Drupal\commerce_payment\Entity\PaymentInterface * The Omise payment entity corresponding given order. * * @throws \InvalidArgumentException * Thrown if there are no or more than one Omise payments for the order. */ public function getOrderPayment(OrderInterface $order); }
src/Plugin/Commerce/PaymentGateway/Omise.php +163 −17 Original line number Diff line number Diff line Loading @@ -2,7 +2,9 @@ namespace Drupal\commerce_omise\Plugin\Commerce\PaymentGateway; use Drupal\commerce\Response\NeedsRedirectException; use Drupal\commerce_omise\OmiseUtilInterface; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_payment\CreditCard; use Drupal\commerce_payment\Entity\PaymentInterface; use Drupal\commerce_payment\Entity\PaymentMethodInterface; Loading @@ -14,7 +16,9 @@ use Drupal\commerce_price\Price; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; /** * Provides the Omise payment gateway. Loading Loading @@ -111,6 +115,7 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { return [ 'secret_key' => '', 'public_key' => '', '3d_secure' => FALSE, ] + parent::defaultConfiguration(); } Loading @@ -134,6 +139,13 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { '#required' => TRUE, ]; $form['3d_secure'] = [ '#type' => 'checkbox', '#title' => $this->t('Enable 3D Secure support'), '#default_value' => !empty($this->configuration['3d_secure']), '#required' => FALSE, ]; return $form; } Loading @@ -145,8 +157,9 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { if (!$form_state->getErrors()) { $values = $form_state->getValue($form['#parents']); $this->configuration['secret_key'] = $values['secret_key']; $this->configuration['public_key'] = $values['public_key']; $this->configuration['secret_key'] = $values['secret_key']; $this->configuration['3d_secure'] = $values['3d_secure']; } } Loading @@ -157,39 +170,69 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { $this->assertPaymentState($payment, ['new']); $payment_method = $payment->getPaymentMethod(); $this->assertPaymentMethod($payment_method); $amount = $payment->getAmount(); $currency_code = $payment->getAmount()->getCurrencyCode(); if ($payment->isNew()) { // Save the payment entity to get its ID in case if it's not available // yet. $payment->save(); } $transaction_data = [ 'currency' => $currency_code, 'amount' => $this->util->formatNumber($amount), 'currency' => $payment->getAmount()->getCurrencyCode(), 'amount' => $this->util->formatNumber($payment->getAmount()), 'capture' => $capture, // Always passing in a card since the user have selected or entered the // specific card in Drupal user interface. // Passing the 'customer' param without a card would always force using // users default card stored at Omise regardless of what card has been // selected in Drupal. 'card' => $payment_method->getRemoteId(), ]; $owner = $payment_method->getOwner(); if ($owner && $owner->isAuthenticated()) { // Add remote customer ID if it's available; this is required for // stored cards. // @see doCreatePaymentMethod() $transaction_data['customer'] = $this->getRemoteCustomerId($owner); } else { $transaction_data['card'] = $payment_method->getRemoteId(); if (!empty($this->configuration['3d_secure'])) { $transaction_data['return_uri'] = Url::fromRoute('commerce_payment.checkout.return', [ 'commerce_order' => $payment->getOrderId(), 'step' => 'payment', ], [ 'absolute' => TRUE, 'query' => [ 'payment_id' => $payment->id(), ], ])->toString(); } try { $result = \OmiseCharge::create($transaction_data, $this->configuration['public_key'], $this->configuration['secret_key']); if (!empty($result['status'] && $result['status'] === 'failed')) { throw \OmiseException::getInstance([ 'code' => $result['failure_code'], 'message' => $result['failure_message'], ]); } $result = \OmiseCharge::create( $transaction_data, $this->configuration['public_key'], $this->configuration['secret_key'] ); $this->validateChargeStatus($result); } catch (\OmiseException $e) { $this->util->handleException($e); } $payment->setRemoteId($result['id']); if (!empty($result['authorize_uri'])) { // Payment gateway requested redirecting user to their payment // authorization page (e.g. for 3D Secure check). // Store the payment (so the payment remote ID gets stored) and redirect // user. $payment->save(); throw new NeedsRedirectException($result['authorize_uri']); } $next_state = $capture ? 'completed' : 'authorization'; $payment->setState($next_state); $payment->setRemoteId($result['id']); $payment->save(); } Loading Loading @@ -337,6 +380,56 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { $payment_method->delete(); } /** * {@inheritdoc} */ public function updateRemotePaymentStatus(PaymentInterface $payment) { $this->assertPaymentState($payment, ['new']); try { $charge = \OmiseCharge::retrieve( $payment->getRemoteId(), $this->configuration['public_key'], $this->configuration['secret_key'] ); $this->validateChargeStatus($charge); } catch (\OmiseException $e) { // Remove payment method from the order. // Note we are NOT removing the payment method; it's still valid (since // it passed the initial card number check), it's just not usable for this // order (most likely due to invalid 3D Secure response provided by user). // Removing the order payment method would force user re-entering (or // selecting) it again. $order = $payment->getOrder(); $order->get('payment_method')->setValue(NULL); $order->save(); $this->util->handleException($e); } $payment->setState('completed'); $payment->save(); } /** * Validates Omise charge status. * * @param \OmiseCharge $charge * The Omise charge object. * * @throws \OmiseException * Omise errors are thrown if charge failed. */ protected function validateChargeStatus(\OmiseCharge $charge) { if (!empty($charge['status'] && $charge['status'] === 'failed')) { throw \OmiseException::getInstance([ 'code' => $charge['failure_code'], 'message' => $charge['failure_message'], ]); } } /** * Creates the payment method on the gateway. * Loading Loading @@ -406,9 +499,12 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { $this->setRemoteCustomerId($owner, $customer['id']); $owner->save(); foreach ($cards['data'] as $card) { return $card; if (count($cards['data']) !== 1) { throw new \RuntimeException(sprintf('Expected customer to have only 1 card, %d found.', count($cards['data']))); } return reset($cards['data']); } catch (\OmiseException $e) { $this->util->handleException($e); Loading @@ -428,4 +524,54 @@ class Omise extends OnsitePaymentGatewayBase implements OmiseInterface { return []; } /** * {@inheritdoc} */ public function getNotifyUrl() { // This is a copy of OffsitePaymentGatewayBase::getNotifyUrl(). // Omise doesn't support usual Notify (i.e. when notify URL is passed in as // part of the API call), however, there's a similar thing called Webhooks // so keeping this code here for future reference. return Url::fromRoute('commerce_payment.notify', [ 'commerce_payment_gateway' => $this->parentEntity->id(), ], ['absolute' => TRUE]); } /** * {@inheritdoc} */ public function onReturn(OrderInterface $order, Request $request) { $payment_id = $request->query->get('payment_id'); if ($payment_id === NULL || !is_numeric($payment_id)) { throw new \InvalidArgumentException('Missing/invalid payment ID query parameter.'); } $payment = $this ->entityTypeManager ->getStorage('commerce_payment') ->load($payment_id); if (!$payment) { throw new \RuntimeException('The payment with given ID does not exist.'); } $this->updateRemotePaymentStatus($payment); } /** * {@inheritdoc} */ public function onCancel(OrderInterface $order, Request $request) { $this->messenger()->addMessage($this->t('You have canceled checkout at @gateway but may resume the checkout process here when you are ready.', [ '@gateway' => $this->getDisplayLabel(), ])); } /** * {@inheritdoc} */ public function onNotify(Request $request) { // This may change later if we implement Webhooks support. throw new \LogicException('Omise payment gateway does not support Notify method'); } }
src/Plugin/Commerce/PaymentGateway/OmiseInterface.php +18 −1 Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ namespace Drupal\commerce_omise\Plugin\Commerce\PaymentGateway; use Drupal\commerce_payment\Entity\PaymentInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OnsitePaymentGatewayInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsAuthorizationsInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsRefundsInterface; Loading @@ -9,7 +11,11 @@ use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsRefundsInterf /** * Provides the interface for the Omise payment gateway. */ interface OmiseInterface extends OnsitePaymentGatewayInterface, SupportsAuthorizationsInterface, SupportsRefundsInterface { interface OmiseInterface extends OnsitePaymentGatewayInterface, SupportsAuthorizationsInterface, SupportsRefundsInterface, OffsitePaymentGatewayInterface { /** * Get the Omise API Public key set for the payment gateway. Loading @@ -19,4 +25,15 @@ interface OmiseInterface extends OnsitePaymentGatewayInterface, SupportsAuthoriz */ public function getPublicKey(); /** * Retrieve and update payment status for an existing charge. * * @param \Drupal\commerce_payment\Entity\PaymentInterface $payment * The payment. * * @throws \Drupal\commerce_payment\Exception\PaymentGatewayException * The Commerce exception. */ public function updateRemotePaymentStatus(PaymentInterface $payment); }