Commit 020e722a authored by bojanz's avatar bojanz Committed by GitHub

Merge pull request #581 from mglaman/offiste-payments

Add an API for offsite payments
parents e4d6636f c84a4206
......@@ -2,12 +2,14 @@
namespace Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow;
use Drupal\commerce\Response\NeedsRedirectException;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
......@@ -147,6 +149,22 @@ abstract class CheckoutFlowBase extends PluginBase implements CheckoutFlowInterf
return isset($step_ids[$current_index + 1]) ? $step_ids[$current_index + 1] : NULL;
}
/**
* {@inheritdoc}
*/
public function redirectToStep($step_id) {
$this->order->checkout_step = $step_id;
if ($step_id == 'complete') {
$transition = $this->order->getState()->getWorkflow()->getTransition('place');
$this->order->getState()->applyTransition($transition);
}
$this->order->save();
throw new NeedsRedirectException(Url::fromRoute('commerce_checkout.form', [
'commerce_order' => $this->order->id(),
'step' => $step_id,
])->toString());
}
/**
* {@inheritdoc}
*/
......@@ -373,11 +391,6 @@ abstract class CheckoutFlowBase extends PluginBase implements CheckoutFlowInterf
}
// Hide the actions element if it has no buttons.
$actions['#access'] = isset($actions['previous']) || isset($actions['next']);
// Once these two steps are reached, the user can't go back.
if (in_array($this->stepId, ['offsite_payment', 'complete'])) {
$actions['#access'] = FALSE;
}
return $actions;
}
......
......@@ -56,6 +56,16 @@ interface CheckoutFlowInterface extends FormInterface, ConfigurablePluginInterfa
*/
public function getNextStepId();
/**
* Redirects an order to a specific step in the checkout.
*
* @param string $step_id
* The step ID to redirect to.
*
* @throws \Drupal\commerce\Response\NeedsRedirectException
*/
public function redirectToStep($step_id);
/**
* Gets the defined steps.
*
......
......@@ -29,6 +29,7 @@ class MultistepDefault extends CheckoutFlowWithPanesBase {
'review' => [
'label' => $this->t('Review'),
'next_label' => $this->t('Continue to review'),
'previous_label' => $this->t('Cancel payment'),
'has_order_summary' => TRUE,
],
] + parent::getSteps();
......
......@@ -9,3 +9,12 @@ payment_method_icons:
css:
theme:
css/commerce_payment.payment_method_icons.css: {}
offsite_redirect:
version: VERSION
js:
js/offsite-redirect.js: {}
dependencies:
- core/jquery
- core/drupal
- core/drupalSettings
......@@ -62,3 +62,38 @@ entity.commerce_payment_method.collection:
parameters:
user:
type: entity:user
commerce_payment.checkout.return:
path: '/checkout/{commerce_order}/{step}/return'
defaults:
_controller: '\Drupal\commerce_payment\Controller\OffsitePaymentController::returnCheckoutPage'
requirements:
_custom_access: '\Drupal\commerce_checkout\Controller\CheckoutController::checkAccess'
_module_dependencies: commerce_checkout
options:
parameters:
commerce_order:
type: entity:commerce_order
commerce_payment.checkout.cancel:
path: '/checkout/{commerce_order}/{step}/cancel'
defaults:
_controller: '\Drupal\commerce_payment\Controller\OffsitePaymentController::cancelCheckoutPage'
requirements:
_custom_access: '\Drupal\commerce_checkout\Controller\CheckoutController::checkAccess'
_module_dependencies: commerce_checkout
options:
parameters:
commerce_order:
type: entity:commerce_order
commerce_payment.notify:
path: '/payment/notify/{commerce_payment_gateway}'
defaults:
_controller: '\Drupal\commerce_payment\Controller\OffsitePaymentController::notifyPage'
requirements:
_access: 'TRUE'
options:
parameters:
commerce_payment_gateway:
type: entity:commerce_payment_gateway
/**
* @file
* Defines behaviors for the payment redirect form.
*/
(function ($, Drupal, drupalSettings) {
'use strict';
/**
* Attaches the commercePaymentRedirect behavior.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches the commercePaymentRedirect behavior.
*/
Drupal.behaviors.commercePaymentRedirect = {
attach: function (context) {
$('.payment-redirect-form', context).find('input[type="submit"]').trigger('click');
}
};
})(jQuery, Drupal, drupalSettings);
<?php
namespace Drupal\commerce_payment\Controller;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\commerce_payment\Entity\PaymentGatewayInterface;
use Drupal\commerce_payment\Exception\PaymentGatewayException;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface;
use Drupal\Core\Access\AccessException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Provides endpoints for off-site payments.
*/
class OffsitePaymentController {
/**
* Provides the "return" checkout payment page.
*
* Redirects to the next checkout page, completing checkout.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $commerce_order
* The order.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*/
public function returnCheckoutPage(OrderInterface $commerce_order, Request $request) {
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = $commerce_order->payment_gateway->entity;
$payment_gateway_plugin = $payment_gateway->getPlugin();
if (!$payment_gateway_plugin instanceof OffsitePaymentGatewayInterface) {
throw new AccessException('The payment gateway for the order does not implement ' . OffsitePaymentGatewayInterface::class);
}
/** @var \Drupal\commerce_checkout\Entity\CheckoutFlowInterface $checkout_flow */
$checkout_flow = $commerce_order->checkout_flow->entity;
$checkout_flow_plugin = $checkout_flow->getPlugin();
try {
$payment_gateway_plugin->onReturn($commerce_order, $request);
$redirect_step = $checkout_flow_plugin->getNextStepId();
}
catch (PaymentGatewayException $e) {
\Drupal::logger('commerce_payment')->error($e->getMessage());
drupal_set_message(t('Payment failed at the payment server. Please review your information and try again.'), 'error');
$redirect_step = $checkout_flow_plugin->getPreviousStepId();
}
$checkout_flow_plugin->redirectToStep($redirect_step);
}
/**
* Provides the "cancel" checkout payment page.
*
* Redirects to the previous checkout page.
*
* @param \Drupal\commerce_order\Entity\OrderInterface $commerce_order
* The order.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*/
public function cancelCheckoutPage(OrderInterface $commerce_order, Request $request) {
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = $commerce_order->payment_gateway->entity;
$payment_gateway_plugin = $payment_gateway->getPlugin();
if (!$payment_gateway_plugin instanceof OffsitePaymentGatewayInterface) {
throw new AccessException('The payment gateway for the order does not implement ' . OffsitePaymentGatewayInterface::class);
}
$payment_gateway_plugin->onCancel($commerce_order, $request);
/** @var \Drupal\commerce_checkout\Entity\CheckoutFlowInterface $checkout_flow */
$checkout_flow = $commerce_order->checkout_flow->entity;
$checkout_flow_plugin = $checkout_flow->getPlugin();
$checkout_flow_plugin->redirectToStep($checkout_flow_plugin->getPreviousStepId());
}
/**
* Provides the "notify" page.
*
* Called the "IPN" or "status" page by some payment providers.
*
* @param \Drupal\commerce_payment\Entity\PaymentGatewayInterface $commerce_payment_gateway
* The payment gateway.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @return \Symfony\Component\HttpFoundation\Response
* The response.
*/
public function notifyPage(PaymentGatewayInterface $commerce_payment_gateway, Request $request) {
$payment_gateway_plugin = $commerce_payment_gateway->getPlugin();
if (!$payment_gateway_plugin instanceof OffsitePaymentGatewayInterface) {
throw new AccessException('Invalid payment gateway provided.');
}
$response = $payment_gateway_plugin->onNotify($request);
if (!$response) {
$response = new Response('', 200);
}
return $response;
}
}
......@@ -5,6 +5,7 @@ namespace Drupal\commerce_payment\Form;
use Drupal\commerce\Form\CommercePluginEntityFormBase;
use Drupal\commerce_payment\PaymentGatewayManager;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SortArray;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -46,6 +47,7 @@ class PaymentGatewayForm extends CommercePluginEntityFormBase {
$plugins = array_map(function ($definition) {
return $definition['label'];
}, $this->pluginManager->getDefinitions());
uasort($plugins, [SortArray::class, 'sortByTitleElement']);
// Use the first available plugin as the default value.
if (!$gateway->getPluginId()) {
......
......@@ -54,7 +54,9 @@ class PaymentGatewayPluginCollection extends DefaultSingleLazyPluginCollection {
throw new PluginException("The payment gateway '{$this->entityId}' did not specify a plugin.");
}
parent::initializePlugin($instance_id);
$configuration = $this->configuration + ['_entity_id' => $this->entityId];
$plugin = $this->manager->createInstance($instance_id, $configuration);
$this->set($instance_id, $plugin);
}
}
......@@ -4,13 +4,16 @@ namespace Drupal\commerce_payment\Plugin\Commerce\CheckoutPane;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
use Drupal\commerce_payment\Entity\PaymentGatewayInterface as EntityPaymentGatewayInterface;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\profile\Entity\Profile;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -80,10 +83,11 @@ class PaymentInformation extends CheckoutPaneBase implements ContainerFactoryPlu
* {@inheritdoc}
*/
public function buildPaneSummary() {
$summary = '';
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = $this->order->payment_gateway->entity;
if (!$payment_gateway) {
return '';
return $summary;
}
$payment_gateway_plugin = $payment_gateway->getPlugin();
......@@ -95,10 +99,12 @@ class PaymentInformation extends CheckoutPaneBase implements ContainerFactoryPlu
}
else {
$billing_profile = $this->order->getBillingProfile();
$profile_view_builder = $this->entityTypeManager->getViewBuilder('profile');
$profile_view = $profile_view_builder->view($billing_profile, 'default');
$summary = $payment_gateway->getPlugin()->getDisplayLabel();
$summary .= $this->renderer->render($profile_view);
if ($billing_profile) {
$profile_view_builder = $this->entityTypeManager->getViewBuilder('profile');
$profile_view = $profile_view_builder->view($billing_profile, 'default');
$summary = $payment_gateway->getPlugin()->getDisplayLabel();
$summary .= $this->renderer->render($profile_view);
}
}
return $summary;
......@@ -110,8 +116,6 @@ class PaymentInformation extends CheckoutPaneBase implements ContainerFactoryPlu
public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
/** @var \Drupal\commerce_payment\PaymentGatewayStorageInterface $payment_gateway_storage */
$payment_gateway_storage = $this->entityTypeManager->getStorage('commerce_payment_gateway');
/** @var \Drupal\commerce_payment\PaymentMethodStorageInterface $payment_method_storage */
$payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method');
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface[] $payment_gateways */
$payment_gateways = $payment_gateway_storage->loadMultipleForOrder($this->order);
// When no payment gateways are defined, throw an error and fail reliably.
......@@ -122,8 +126,57 @@ class PaymentInformation extends CheckoutPaneBase implements ContainerFactoryPlu
// @todo Support multiple gateways.
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = reset($payment_gateways);
$pane_form['payment_gateway'] = [
'#type' => 'value',
'#value' => $payment_gateway->id(),
];
$payment_gateway_plugin = $payment_gateway->getPlugin();
if ($payment_gateway_plugin instanceof SupportsStoredPaymentMethodsInterface) {
$this->attachPaymentMethodForm($payment_gateway, $pane_form, $form_state);
}
else {
/** @var \Drupal\profile\Entity\ProfileInterface $billing_profile */
$billing_profile = $this->order->getBillingProfile();
if (!$billing_profile) {
$billing_profile = Profile::create([
'uid' => $this->order->getCustomerId(),
'type' => 'customer',
]);
}
$form_display = EntityFormDisplay::collectRenderDisplay($billing_profile, 'default');
$form_display->buildForm($billing_profile, $pane_form, $form_state);
// Remove the details wrapper from the address field.
if (!empty($pane_form['address']['widget'][0])) {
$pane_form['address']['widget'][0]['#type'] = 'container';
}
// Store the billing profile for the validate/submit methods.
$pane_form['#entity'] = $billing_profile;
}
return $pane_form;
}
/**
* Creates the payment method selection form for supported gateways.
*
* @param \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway
* The payment gateway.
* @param array $pane_form
* The pane form, containing the following basic properties:
* - #parents: Identifies the position of the pane form in the overall
* parent form, and identifies the location where the field values are
* placed within $form_state->getValues().
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the parent form.
*/
protected function attachPaymentMethodForm(EntityPaymentGatewayInterface $payment_gateway, array &$pane_form, FormStateInterface $form_state) {
/** @var \Drupal\commerce_payment\PaymentMethodStorageInterface $payment_method_storage */
$payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method');
$payment_gateway_plugin = $payment_gateway->getPlugin();
$options = [];
$default_option = NULL;
$customer = $this->order->getCustomer();
......@@ -174,8 +227,6 @@ class PaymentInformation extends CheckoutPaneBase implements ContainerFactoryPlu
'#default_value' => $payment_method,
];
}
return $pane_form;
}
/**
......@@ -192,8 +243,23 @@ class PaymentInformation extends CheckoutPaneBase implements ContainerFactoryPlu
*/
public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
$values = $form_state->getValue($pane_form['#parents']);
if (!isset($values['payment_method'])) {
$form_state->setError($complete_form, $this->noPaymentGatewayErrorMessage());
/** @var \Drupal\commerce_payment\PaymentGatewayStorageInterface $payment_gateway_storage */
$payment_gateway_storage = $this->entityTypeManager->getStorage('commerce_payment_gateway');
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = $payment_gateway_storage->load($values['payment_gateway']);
if ($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface) {
if (!isset($values['payment_method'])) {
$form_state->setError($complete_form, $this->noPaymentGatewayErrorMessage());
}
}
else {
/** @var \Drupal\profile\Entity\ProfileInterface $billing_profile */
$billing_profile = $pane_form['#entity'];
$form_display = EntityFormDisplay::collectRenderDisplay($billing_profile, 'default');
$form_display->extractFormValues($billing_profile, $pane_form, $form_state);
$form_display->validateFormValues($billing_profile, $pane_form, $form_state);
}
}
......@@ -202,19 +268,37 @@ class PaymentInformation extends CheckoutPaneBase implements ContainerFactoryPlu
*/
public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
$values = $form_state->getValue($pane_form['#parents']);
if (is_numeric($values['payment_method'])) {
/** @var \Drupal\commerce_payment\PaymentMethodStorageInterface $payment_method_storage */
$payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method');
$payment_method = $payment_method_storage->load($values['payment_method']);
/** @var \Drupal\commerce_payment\PaymentGatewayStorageInterface $payment_gateway_storage */
$payment_gateway_storage = $this->entityTypeManager->getStorage('commerce_payment_gateway');
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = $payment_gateway_storage->load($values['payment_gateway']);
if ($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface) {
if (is_numeric($values['payment_method'])) {
/** @var \Drupal\commerce_payment\PaymentMethodStorageInterface $payment_method_storage */
$payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method');
$payment_method = $payment_method_storage->load($values['payment_method']);
}
else {
$payment_method = $values['add_payment_method'];
}
/** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
$this->order->payment_gateway = $payment_method->getPaymentGateway();
$this->order->payment_method = $payment_method;
$this->order->setBillingProfile($payment_method->getBillingProfile());
}
else {
$payment_method = $values['add_payment_method'];
/** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
$this->order->payment_gateway = $payment_gateway;
/** @var \Drupal\profile\Entity\ProfileInterface $billing_profile */
$billing_profile = $pane_form['#entity'];
$form_display = EntityFormDisplay::collectRenderDisplay($billing_profile, 'default');
$form_display->extractFormValues($billing_profile, $pane_form, $form_state);
$billing_profile->save();
$this->order->setBillingProfile($billing_profile);
}
/** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
$this->order->payment_gateway = $payment_method->getPaymentGateway();
$this->order->payment_method = $payment_method;
$this->order->setBillingProfile($payment_method->getBillingProfile());
}
/**
......
......@@ -2,15 +2,15 @@
namespace Drupal\commerce_payment\Plugin\Commerce\CheckoutPane;
use Drupal\commerce\Response\NeedsRedirectException;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
use Drupal\commerce_payment\Exception\DeclineException;
use Drupal\commerce_payment\Exception\PaymentGatewayException;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OnsitePaymentGatewayInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
......@@ -133,41 +133,44 @@ class PaymentProcess extends CheckoutPaneBase implements ContainerFactoryPluginI
$payment_gateway = $this->order->payment_gateway->entity;
$payment_gateway_plugin = $payment_gateway->getPlugin();
$payment_storage = $this->entityTypeManager->getStorage('commerce_payment');
$payment = $payment_storage->create([
'state' => 'new',
'amount' => $this->order->getTotalPrice(),
'payment_gateway' => $payment_gateway->id(),
'order_id' => $this->order->id(),
]);
if ($payment_gateway_plugin instanceof OnsitePaymentGatewayInterface) {
try {
$payment_storage = $this->entityTypeManager->getStorage('commerce_payment');
$payment = $payment_storage->create([
'state' => 'new',
'amount' => $this->order->getTotalPrice(),
'payment_gateway' => $payment_gateway->id(),
'payment_method' => $this->order->payment_method->entity,
'order_id' => $this->order->id(),
]);
$payment->payment_method = $this->order->payment_method->entity;
$payment_gateway_plugin->createPayment($payment, $this->configuration['capture']);
$next_step_id = $this->checkoutFlow->getNextStepId();
// @todo Add a checkout flow method for completing checkout.
if ($next_step_id == 'complete') {
$transition = $this->order->getState()->getWorkflow()->getTransition('place');
$this->order->getState()->applyTransition($transition);
}
$this->order->checkout_step = $next_step_id;
$this->order->save();
throw new NeedsRedirectException(Url::fromRoute('commerce_checkout.form', [
'commerce_order' => $this->order->id(),
'step' => $next_step_id,
])->toString());
$this->checkoutFlow->redirectToStep($this->checkoutFlow->getNextStepId());
}
catch (DeclineException $e) {
$message = $this->t('We encountered an error processing your payment method. Please verify your details and try again.');
drupal_set_message($message, 'error');
$this->redirectToPreviousStep();
}
catch (PaymentGatewayException $e) {
drupal_set_message($e->getMessage(), 'error');
\Drupal::logger('commerce_payment')->error($e->getMessage());
$message = $this->t('We encountered an unexpected error processing your payment method. Please try again later.');
drupal_set_message($message, 'error');
$this->redirectToPreviousStep();
}
}
else {
drupal_set_message($this->t('Sorry, we can currently only support on site payment gateways.'), 'error');
$this->redirectToPreviousStep();
elseif ($payment_gateway_plugin instanceof OffsitePaymentGatewayInterface) {
$pane_form['offsite_payment'] = [
'#type' => 'commerce_payment_gateway_form',
'#operation' => 'offsite-payment',
'#default_value' => $payment,
];
$complete_form['actions']['next']['#value'] = $this->t('Proceed to @gateway', [
'@gateway' => $payment_gateway_plugin->getDisplayLabel(),
]);
return $pane_form;
}
}
......@@ -183,13 +186,7 @@ class PaymentProcess extends CheckoutPaneBase implements ContainerFactoryPluginI
$previous_step_id = $pane->getStepId();
}
}
$this->order->checkout_step = $previous_step_id;
$this->order->save();
throw new NeedsRedirectException(Url::fromRoute('commerce_checkout.form', [
'commerce_order' => $this->order->id(),
'step' => $previous_step_id,
])->toString());
$this->checkoutFlow->redirectToStep($previous_step_id);
}
}
<?php
namespace Drupal\commerce_payment\Plugin\Commerce\PaymentGateway;
use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides the base class for off-site payment gateways.
*/
abstract class OffsitePaymentGatewayBase extends PaymentGatewayBase implements OffsitePaymentGatewayInterface {
/**
* {@inheritdoc}
*/
public function getReturnUrl(OrderInterface $order) {
return Url::fromRoute('commerce_payment.checkout.return', [
'commerce_order' => $order->id(),
'step' => 'payment',
], ['absolute' => TRUE]);
}
/**
* {@inheritdoc}
*/
public function getCancelUrl(OrderInterface $order) {
return Url::fromRoute('commerce_payment.checkout.cancel', [
'commerce_order' => $order->id(),
'step' => 'payment',
], ['absolute' => TRUE]);
}
/**
* {@inheritdoc}
*/
public function getNotifyUrl() {
return Url::fromRoute('commerce_payment.notify', [
'commerce_payment_gateway' => $this->entityId,
], ['absolute' => TRUE]);
}
/**
* {@inheritdoc}
*/
public function onReturn(OrderInterface $order, Request $request) {}
/**
* {@inheritdoc}
*/
public function onCancel(OrderInterface $order, Request $request) {
drupal_set_message($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) {}
}
<?php
namespace Drupal\commerce_payment\Plugin\Commerce\PaymentGateway;
use Drupal\commerce_order\Entity\OrderInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Defines the base interface for off-site payment gateways.
*
* Off-site payment flow:
* 1) Customer hits the "payment" checkout step.
* 2) The PaymentProcess checkout pane shows the "offsite-payment" plugin form.
* 3) The plugin form performs a redirect or shows an iFrame.
* 4) The customer provides their payment details to the payment provider.
* 5) The payment provider redirects the customer back to the return url.
* 6) A payment is created in either onReturn() or onNotify().
*
* If the payment provider supports asynchronous notifications (IPNs), then
* creating the payment in onNotify() is preferred, since it is guaranteed to
* be called even if the customer does not return to the site.
*
* If the customer declines to provide their payment details, and cancels
* the payment at the payment provider, they will be redirected back to the
* cancel url.
*/
interface OffsitePaymentGatewayInterface extends PaymentGatewayInterface {
/**