diff --git a/modules/payment/commerce_payment.routing.yml b/modules/payment/commerce_payment.routing.yml index 74a92e43..f6961460 100644 --- a/modules/payment/commerce_payment.routing.yml +++ b/modules/payment/commerce_payment.routing.yml @@ -101,3 +101,29 @@ commerce_payment.notify: parameters: commerce_payment_gateway: type: entity:commerce_payment_gateway + +commerce_payment.offsite_payment_method.return: + path: '/user/{user}/payment-methods/{payment_gateway}/return' + defaults: + _controller: '\Drupal\commerce_payment\Controller\OffsitePaymentMethodUserPageController::returnPage' + requirements: + _custom_access: '\Drupal\commerce_payment\Access\PaymentMethodAccessCheck::checkAccess' + options: + parameters: + user: + type: entity:user + payment_gateway: + type: entity:commerce_payment_gateway + +commerce_payment.offsite_payment_method.cancel: + path: '/user/{user}/payment-methods/{payment_gateway}/cancel' + defaults: + _controller: '\Drupal\commerce_payment\Controller\OffsitePaymentMethodUserPageController::cancelPage' + requirements: + _custom_access: '\Drupal\commerce_payment\Access\PaymentMethodAccessCheck::checkAccess' + options: + parameters: + user: + type: entity:user + payment_gateway: + type: entity:commerce_payment_gateway diff --git a/modules/payment/src/Controller/OffsitePaymentMethodUserPageController.php b/modules/payment/src/Controller/OffsitePaymentMethodUserPageController.php new file mode 100644 index 00000000..7a6e3c94 --- /dev/null +++ b/modules/payment/src/Controller/OffsitePaymentMethodUserPageController.php @@ -0,0 +1,158 @@ +logger = $logger; + $this->entityTypeManager = $entity_type_manager; + $this->stringTranslation = $translation; + $this->currentUser = $current_user; + $this->messenger = $messenger; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('logger.channel.commerce_payment'), + $container->get('entity_type.manager'), + $container->get('string_translation'), + $container->get('current_user'), + $container->get('messenger') + ); + } + + /** + * Provides the "return" offsite payment method page. + * + * @param \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway + * The payment gateway entity. + * @param \Drupal\Core\Session\AccountInterface $user + * The user entity. + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function returnPage(PaymentGatewayInterface $payment_gateway, AccountInterface $user, Request $request) { + /** @var \Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsCreatingOffsitePaymentMethodsOnUserPageInterface $payment_gateway_plugin */ + $payment_gateway_plugin = $payment_gateway->getPlugin(); + if (!$payment_gateway_plugin instanceof SupportsCreatingOffsitePaymentMethodsOnUserPageInterface) { + throw new AccessException('The payment gateway for the order does not implement ' . SupportsCreatingOffsitePaymentMethodsOnUserPageInterface::class); + } + try { + $payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method'); + /** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */ + $payment_method = $payment_method_storage->create([ + // Payment method type may depend on the request payload. We create + // payment method stub based on default payment method type available + // for payment gateway plugin but module developers can swap it + // with custom payment method. Keep in mind that $payment_method is + // passed by reference and code below relies on that reference to add + // payment method to the order. + 'type' => $payment_gateway_plugin->getDefaultPaymentMethodType()->getPluginId(), + 'payment_gateway' => $payment_gateway, + 'uid' => $user->id(), + 'payment_gateway_mode' => $payment_gateway_plugin->getMode(), + ]); + // In case payment gateway supports payment method. + $payment_gateway_plugin->onReturnOffsitePaymentMethodOnUserPage($payment_method, $request); + return new RedirectResponse(Url::fromRoute('entity.commerce_payment_method.collection', ['user' => $user->id()])->toString()); + } + catch (PaymentGatewayException $e) { + $this->logger->error($e->getMessage()); + $this->messenger->addError($this->t('Payment method creation has failed.')); + return new RedirectResponse(Url::fromRoute('entity.commerce_payment_method.collection', ['user' => $user->id()])->toString()); + } + } + + /** + * Provides the "cancel" offsite payment method page. + * + * @param \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway + * The payment gateway entity. + * @param \Drupal\Core\Session\AccountInterface $user + * The user entity. + * @param \Symfony\Component\HttpFoundation\Request $request + * The request. + * + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + public function cancelPage(PaymentGatewayInterface $payment_gateway, AccountInterface $user, Request $request) { + $payment_gateway_plugin = $payment_gateway->getPlugin(); + if (!$payment_gateway_plugin instanceof SupportsCreatingOffsitePaymentMethodsOnUserPageInterface) { + throw new AccessException('The payment gateway for the order does not implement ' . SupportsCreatingOffsitePaymentMethodsOnUserPageInterface::class); + } + $payment_gateway_plugin->onCancelOffsitePaymentMethodOnUserPage($request); + return new RedirectResponse(Url::fromRoute('entity.commerce_payment_method.collection', ['user' => $user->id()])->toString()); + } + +} diff --git a/modules/payment/src/Controller/PaymentCheckoutController.php b/modules/payment/src/Controller/PaymentCheckoutController.php index 84159e11..169c249f 100644 --- a/modules/payment/src/Controller/PaymentCheckoutController.php +++ b/modules/payment/src/Controller/PaymentCheckoutController.php @@ -7,8 +7,10 @@ use Drupal\commerce_checkout\CheckoutOrderManagerInterface; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_payment\Exception\PaymentGatewayException; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface; +use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface; use Drupal\Core\Access\AccessException; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Url; @@ -42,6 +44,13 @@ class PaymentCheckoutController implements ContainerInjectionInterface { */ protected $logger; + /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + /** * Constructs a new PaymentCheckoutController object. * @@ -51,11 +60,14 @@ class PaymentCheckoutController implements ContainerInjectionInterface { * The messenger. * @param \Psr\Log\LoggerInterface $logger * The logger. + * @param \Drupal\Core\Entity\EntityTypeManagerInterface + * The entity type manager. */ - public function __construct(CheckoutOrderManagerInterface $checkout_order_manager, MessengerInterface $messenger, LoggerInterface $logger) { + public function __construct(CheckoutOrderManagerInterface $checkout_order_manager, MessengerInterface $messenger, LoggerInterface $logger, EntityTypeManagerInterface $entity_type_manager) { $this->checkoutOrderManager = $checkout_order_manager; $this->messenger = $messenger; $this->logger = $logger; + $this->entityTypeManager = $entity_type_manager; } /** @@ -65,7 +77,8 @@ class PaymentCheckoutController implements ContainerInjectionInterface { return new static( $container->get('commerce_checkout.checkout_order_manager'), $container->get('messenger'), - $container->get('logger.channel.commerce_payment') + $container->get('logger.channel.commerce_payment'), + $container->get('entity_type.manager') ); } @@ -94,6 +107,38 @@ class PaymentCheckoutController implements ContainerInjectionInterface { $checkout_flow = $order->get('checkout_flow')->entity; $checkout_flow_plugin = $checkout_flow->getPlugin(); + if ($payment_gateway_plugin instanceof SupportsStoredPaymentMethodsInterface) { + try { + $payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method'); + /** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */ + $payment_method = $payment_method_storage->create([ + // Payment method type may depend on the request payload. We create + // payment method stub based on default payment method type available + // for payment gateway plugin but module developers can swap it + // with custom payment method. Keep in mind that $payment_method is + // passed by reference and code below relies on that reference to add + // payment method to the order. + 'type' => $payment_gateway_plugin->getDefaultPaymentMethodType()->getPluginId(), + 'payment_gateway' => $payment_gateway, + 'uid' => $order->getCustomerId(), + 'billing_profile' => $order->getBillingProfile(), + 'payment_gateway_mode' => $payment_gateway_plugin->getMode(), + ]); + // In case payment gateway supports payment method. + $payment_gateway_plugin->createPaymentMethod($payment_method, ['order' => $order, 'request' => $request]); + + // Add payment method to the order. + $order->set('payment_method', $payment_method); + $order->save(); + } + catch (PaymentGatewayException $e) { + // If creating payment method failed we allow onReturn to handle + // creating Payment, which is required to fulfill the payment. This + // exception is muted and logged. + $this->logger->error($e->getMessage()); + } + } + try { $payment_gateway_plugin->onReturn($order, $request); $redirect_step_id = $checkout_flow_plugin->getNextStepId($step_id); diff --git a/modules/payment/src/Form/PaymentMethodAddForm.php b/modules/payment/src/Form/PaymentMethodAddForm.php index f4dc0472..09c9d212 100644 --- a/modules/payment/src/Form/PaymentMethodAddForm.php +++ b/modules/payment/src/Form/PaymentMethodAddForm.php @@ -3,11 +3,13 @@ namespace Drupal\commerce_payment\Form; use Drupal\commerce\InlineFormManager; +use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsCreatingOffsitePaymentMethodsOnUserPageInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\user\UserInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; @@ -71,7 +73,7 @@ class PaymentMethodAddForm extends FormBase implements ContainerInjectionInterfa $payment_gateway_storage = $this->entityTypeManager->getStorage('commerce_payment_gateway'); $payment_gateway = $payment_gateway_storage->loadForUser($user); // @todo Move this check to the access handler. - if (!$payment_gateway || !($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface)) { + if (!$payment_gateway || (!($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface) && !($payment_gateway->getPlugin() instanceof SupportsCreatingOffsitePaymentMethodsOnUserPageInterface))) { throw new AccessDeniedHttpException(); } $form_state->set('payment_gateway', $payment_gateway); @@ -152,6 +154,8 @@ class PaymentMethodAddForm extends FormBase implements ContainerInjectionInterfa 'payment_gateway' => $form_state->get('payment_gateway'), 'uid' => $form_state->getBuildInfo()['args'][0]->id(), ]); + /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */ + $payment_gateway = $form_state->get('payment_gateway'); $inline_form = $this->inlineFormManager->createInstance('payment_gateway_form', [ 'operation' => 'add-payment-method', ], $payment_method); @@ -160,7 +164,12 @@ class PaymentMethodAddForm extends FormBase implements ContainerInjectionInterfa '#parents' => ['payment_method'], '#inline_form' => $inline_form, ]; + if ($payment_gateway && $payment_gateway->getPlugin() instanceof SupportsCreatingOffsitePaymentMethodsOnUserPageInterface) { + $form['payment_method']['#return_url'] = $this->buildReturnUrl($form_state->getBuildInfo()['args'][0]->id(), $payment_gateway->id())->toString(); + $form['payment_method']['#cancel_url'] = $this->buildCancelUrl($form_state->getBuildInfo()['args'][0]->id(), $payment_gateway->id())->toString(); + } $form['payment_method'] = $inline_form->buildInlineForm($form['payment_method'], $form_state); + $form['actions']['submit'] = [ '#type' => 'submit', '#value' => $this->t('Save'), @@ -170,6 +179,42 @@ class PaymentMethodAddForm extends FormBase implements ContainerInjectionInterfa return $form; } + /** + * Builds the URL to the "return" page. + * + * @param int $uid + * The user id the payment method is being added for. + * @param string $gateway_id + * The payment gateway id of the payment gateway + * + * @return \Drupal\Core\Url + * The "return" page URL. + */ + protected function buildReturnUrl(int $uid, string $gateway_id) { + return Url::fromRoute('commerce_payment.offsite_payment_method.return', [ + 'user' => $uid, + 'payment_gateway' => $gateway_id, + ], ['absolute' => TRUE]); + } + + /** + * Builds the URL to the "cancel" page. + * + * @param int $uid + * The user id the payment method is being added for. + * @param string $gateway_id + * The payment gateway id of the payment gateway. + * + * @return \Drupal\Core\Url + * The "cancel" page URL. + */ + protected function buildCancelUrl(int $uid, string $gateway_id) { + return Url::fromRoute('commerce_payment.offsite_payment_method.cancel', [ + 'user' => $uid, + 'payment_gateway' => $gateway_id, + ], ['absolute' => TRUE]); + } + /** * {@inheritdoc} */ diff --git a/modules/payment/src/PaymentGatewayStorage.php b/modules/payment/src/PaymentGatewayStorage.php index 55417710..2fe968a6 100644 --- a/modules/payment/src/PaymentGatewayStorage.php +++ b/modules/payment/src/PaymentGatewayStorage.php @@ -5,6 +5,7 @@ namespace Drupal\commerce_payment; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\commerce_payment\Event\FilterPaymentGatewaysEvent; use Drupal\commerce_payment\Event\PaymentEvents; +use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsCreatingOffsitePaymentMethodsOnUserPageInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface; use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface; @@ -70,7 +71,7 @@ class PaymentGatewayStorage extends ConfigEntityStorage implements PaymentGatewa public function loadForUser(UserInterface $account) { $payment_gateways = $this->loadByProperties(['status' => TRUE]); $payment_gateways = array_filter($payment_gateways, function ($payment_gateway) { - return $payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface; + return $payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface || $payment_gateway->getPlugin() instanceof SupportsCreatingOffsitePaymentMethodsOnUserPageInterface; }); // @todo Implement resolving logic. $payment_gateway = reset($payment_gateways); diff --git a/modules/payment/src/PaymentMethodStorage.php b/modules/payment/src/PaymentMethodStorage.php index 6b39df04..3521b7a7 100644 --- a/modules/payment/src/PaymentMethodStorage.php +++ b/modules/payment/src/PaymentMethodStorage.php @@ -4,7 +4,7 @@ namespace Drupal\commerce_payment; use Drupal\commerce\CommerceContentEntityStorage; use Drupal\commerce_payment\Entity\PaymentGatewayInterface; -use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface; +use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsCreatingPaymentInterface; use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\MemoryCache\MemoryCacheInterface; @@ -86,7 +86,7 @@ class PaymentMethodStorage extends CommerceContentEntityStorage implements Payme if ($account->isAnonymous()) { return []; } - if (!($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface)) { + if (!($payment_gateway->getPlugin() instanceof SupportsCreatingPaymentInterface)) { return []; } diff --git a/modules/payment/src/PaymentOptionsBuilder.php b/modules/payment/src/PaymentOptionsBuilder.php index d58864a2..cf61061d 100644 --- a/modules/payment/src/PaymentOptionsBuilder.php +++ b/modules/payment/src/PaymentOptionsBuilder.php @@ -4,6 +4,7 @@ namespace Drupal\commerce_payment; use Drupal\commerce\EntityHelper; use Drupal\commerce_order\Entity\OrderInterface; +use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsCreatingPaymentInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; @@ -44,6 +45,12 @@ class PaymentOptionsBuilder implements PaymentOptionsBuilderInterface { } /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface[] $payment_gateways_with_payment_methods */ $payment_gateways_with_payment_methods = array_filter($payment_gateways, function ($payment_gateway) { + /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */ + return $payment_gateway->getPlugin() instanceof SupportsCreatingPaymentInterface; + }); + + /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface[] $payment_gateways_with_payment_methods */ + $payment_gateways_supporting_creating_payment_methods = array_filter($payment_gateways, function ($payment_gateway) { /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */ return $payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface; }); @@ -92,7 +99,7 @@ class PaymentOptionsBuilder implements PaymentOptionsBuilderInterface { // 3) Add options to create new stored payment methods of supported types. $payment_method_type_counts = []; // Count how many new payment method options will be built per gateway. - foreach ($payment_gateways_with_payment_methods as $payment_gateway) { + foreach ($payment_gateways_supporting_creating_payment_methods as $payment_gateway) { $payment_method_types = $payment_gateway->getPlugin()->getPaymentMethodTypes(); foreach ($payment_method_types as $payment_method_type_id => $payment_method_type) { @@ -105,7 +112,7 @@ class PaymentOptionsBuilder implements PaymentOptionsBuilderInterface { } } - foreach ($payment_gateways_with_payment_methods as $payment_gateway) { + foreach ($payment_gateways_supporting_creating_payment_methods as $payment_gateway) { $payment_gateway_plugin = $payment_gateway->getPlugin(); $payment_method_types = $payment_gateway_plugin->getPaymentMethodTypes(); @@ -132,7 +139,7 @@ class PaymentOptionsBuilder implements PaymentOptionsBuilderInterface { // 4) Add options for the remaining gateways (off-site, manual, etc). /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface[] $other_payment_gateways */ - $other_payment_gateways = array_diff_key($payment_gateways, $payment_gateways_with_payment_methods); + $other_payment_gateways = array_diff_key($payment_gateways, $payment_gateways_supporting_creating_payment_methods); foreach ($other_payment_gateways as $payment_gateway) { $payment_gateway_id = $payment_gateway->id(); $options[$payment_gateway_id] = new PaymentOption([ diff --git a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php index e713d6b5..cd66750e 100644 --- a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php +++ b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php @@ -7,6 +7,8 @@ use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface; use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase; use Drupal\commerce_payment\PaymentOption; use Drupal\commerce_payment\PaymentOptionsBuilderInterface; +use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface; +use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsCreatingPaymentInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Entity\EntityTypeManagerInterface; @@ -206,10 +208,15 @@ class PaymentInformation extends CheckoutPaneBase { $default_payment_gateway_id = $default_option->getPaymentGatewayId(); $payment_gateway = $payment_gateways[$default_payment_gateway_id]; - if ($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface) { + $payment_gateway_plugin = $payment_gateway->getPlugin(); + // If this payment gateway plugin supports stored payment methods, we build + // the "add-payment-method" plugin form. However, we skip if this is an + // off-site payment gateway, since payment method creation is part of the + // payment process which occurs later. + if ($payment_gateway_plugin instanceof SupportsStoredPaymentMethodsInterface && !$payment_gateway_plugin instanceof OffsitePaymentGatewayInterface) { $pane_form = $this->buildPaymentMethodForm($pane_form, $form_state, $default_option); } - elseif ($payment_gateway->getPlugin()->collectsBillingInformation()) { + elseif ($payment_gateway_plugin->collectsBillingInformation()) { $pane_form = $this->buildBillingProfileForm($pane_form, $form_state); } @@ -366,7 +373,8 @@ class PaymentInformation extends CheckoutPaneBase { return; } - if ($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface) { + $payment_gateway_plugin = $payment_gateway->getPlugin(); + if ($payment_gateway_plugin instanceof SupportsStoredPaymentMethodsInterface && !$payment_gateway_plugin instanceof OffsitePaymentGatewayInterface) { if (!empty($selected_option->getPaymentMethodTypeId())) { /** @var \Drupal\commerce\Plugin\Commerce\InlineForm\EntityInlineFormInterface $inline_form */ $inline_form = $pane_form['add_payment_method']['#inline_form']; @@ -400,6 +408,15 @@ class PaymentInformation extends CheckoutPaneBase { $this->order->setBillingProfile($billing_profile); } } + elseif ($payment_gateway->getPlugin() instanceof SupportsCreatingPaymentInterface && $selected_option->getPaymentMethodId()) { + /** @var \Drupal\commerce_payment\PaymentMethodStorageInterface $payment_method_storage */ + $payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method'); + $payment_method = $payment_method_storage->load($selected_option->getPaymentMethodId()); + /** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */ + $this->order->set('payment_gateway', $payment_method->getPaymentGateway()); + $this->order->set('payment_method', $payment_method); + $this->order->setBillingProfile($payment_method->getBillingProfile()); + } else { $this->order->set('payment_gateway', $payment_gateway); $this->order->set('payment_method', NULL); diff --git a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentProcess.php b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentProcess.php index 4bff60ad..c5f0527b 100644 --- a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentProcess.php +++ b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentProcess.php @@ -10,7 +10,7 @@ use Drupal\commerce_payment\Exception\DeclineException; use Drupal\commerce_payment\Exception\PaymentGatewayException; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\ManualPaymentGatewayInterface; use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OffsitePaymentGatewayInterface; -use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\OnsitePaymentGatewayInterface; +use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsCreatingPaymentInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Link; @@ -173,9 +173,9 @@ class PaymentProcess extends CheckoutPaneBase { $payment = $this->createPayment($payment_gateway); $next_step_id = $this->checkoutFlow->getNextStepId($this->getStepId()); - if ($payment_gateway_plugin instanceof OnsitePaymentGatewayInterface) { + if ($payment_gateway_plugin instanceof SupportsCreatingPaymentInterface && $payment_method = $this->order->payment_method->entity) { try { - $payment->payment_method = $this->order->payment_method->entity; + $payment->payment_method = $payment_method; $payment_gateway_plugin->createPayment($payment, $this->configuration['capture']); $this->checkoutFlow->redirectToStep($next_step_id); } diff --git a/modules/payment/src/Plugin/Commerce/PaymentGateway/OnsitePaymentGatewayInterface.php b/modules/payment/src/Plugin/Commerce/PaymentGateway/OnsitePaymentGatewayInterface.php index 6108efe9..6ca5e919 100644 --- a/modules/payment/src/Plugin/Commerce/PaymentGateway/OnsitePaymentGatewayInterface.php +++ b/modules/payment/src/Plugin/Commerce/PaymentGateway/OnsitePaymentGatewayInterface.php @@ -2,8 +2,6 @@ namespace Drupal\commerce_payment\Plugin\Commerce\PaymentGateway; -use Drupal\commerce_payment\Entity\PaymentInterface; - /** * Defines the base interface for on-site payment gateways. * @@ -27,22 +25,6 @@ use Drupal\commerce_payment\Entity\PaymentInterface; * checkout step that contains the PaymentInformation checkout pane, to provide * a different payment method. */ -interface OnsitePaymentGatewayInterface extends PaymentGatewayInterface, SupportsStoredPaymentMethodsInterface { - - /** - * Creates a payment. - * - * @param \Drupal\commerce_payment\Entity\PaymentInterface $payment - * The payment. - * @param bool $capture - * Whether the created payment should be captured (VS authorized only). - * Allowed to be FALSE only if the plugin supports authorizations. - * - * @throws \InvalidArgumentException - * If $capture is FALSE but the plugin does not support authorizations. - * @throws \Drupal\commerce_payment\Exception\PaymentGatewayException - * Thrown when the transaction fails for any reason. - */ - public function createPayment(PaymentInterface $payment, $capture = TRUE); +interface OnsitePaymentGatewayInterface extends PaymentGatewayInterface, SupportsStoredPaymentMethodsInterface, SupportsCreatingPaymentInterface { } diff --git a/modules/payment/src/Plugin/Commerce/PaymentGateway/SupportsCreatingOffsitePaymentMethodsOnUserPageInterface.php b/modules/payment/src/Plugin/Commerce/PaymentGateway/SupportsCreatingOffsitePaymentMethodsOnUserPageInterface.php new file mode 100644 index 00000000..a2f32e8d --- /dev/null +++ b/modules/payment/src/Plugin/Commerce/PaymentGateway/SupportsCreatingOffsitePaymentMethodsOnUserPageInterface.php @@ -0,0 +1,37 @@ +createPaymentMethod($payment_method, $values['payment_details']); + if ($payment_gateway_plugin instanceof SupportsStoredPaymentMethodsInterface) { + $payment_gateway_plugin->createPaymentMethod($payment_method, $values['payment_details']); + } + elseif ($payment_gateway_plugin instanceof SupportsCreatingOffsitePaymentMethodsOnUserPageInterface) { + $payment_gateway_plugin->createPaymentMethodOnUserPage($payment_method, $values['payment_details']); + } } catch (DeclineException $e) { $this->logger->warning($e->getMessage()); diff --git a/modules/payment/src/PluginForm/PaymentMethodOffsiteAddForm.php b/modules/payment/src/PluginForm/PaymentMethodOffsiteAddForm.php new file mode 100644 index 00000000..633c520d --- /dev/null +++ b/modules/payment/src/PluginForm/PaymentMethodOffsiteAddForm.php @@ -0,0 +1,113 @@ + $value) { + $form[$key] = [ + '#type' => 'hidden', + '#value' => $value, + // Ensure the correct keys by sending values from the form root. + '#parents' => [$key], + ]; + } + // The key is prefixed with 'commerce_' to prevent conflicts with $data. + $form['commerce_message'] = [ + '#markup' => '