Problem/motivation

In order for order payments to be fully manageable from the Payment UI, administrative users need to be able to create payments on anonymous as well as user-orders, either a.) with a payment method on file, or b.) with a new payment method. Currently, the Payment UI only allows payments to be created on user-orders (not anonymous orders) and from stored payment methods (a.). There is no ability to create payments for: 1.) anonymous orders with stored payment methods; 2.) anonymous orders with a new payment method; 3.) user-orders with a new payment method.

Proposed resolution

Add the following abilities for those with permissions:

  1. Create and use new payment methods on user-orders
  2. Create and use new payment methods on anonymous orders
  3. Use stored payment methods on anonymous orders (NOTE: this does not mean allowing anonymous users to have stored payment methods -- this obviously isn't possible)

Completed tasks

  1. Create and use new payment methods on user-orders
  2. Create and use new payment methods on anonymous orders

Remaining tasks

  1. Use stored payment methods on anonymous orders
  2. Tests!

Comments

bradjones1 created an issue. See original summary.

bradjones1’s picture

steveoliver’s picture

Looks good to me functionally -- PR works as expected on a local manual test.

ericchew’s picture

Looks like it works to me. Since the functionality is pretty much there, why not allow admin to create new payment method for anonymous or existing user? The site i'm building for my company will rely heavily on the Admin UI, customers may call to place an order in which case my employees will need to be able to add everything via Admin UI.

See patch.

Status: Needs review » Needs work

The last submitted patch, 4: support_admin_payments_on_orders-2912996.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

ericchew’s picture

Added support for manual payments.

ericchew’s picture

Status: Needs work » Needs review
ericchew’s picture

StatusFileSize
new4.58 KB

There are some unnecessary spaces in the patch for comment #6, I removed them so that they would not show on the interdiff. I can post the updated patch if needed. Sorry for the confusion...this is my first time posting a patch and interdiff.

ericchew’s picture

Here is the updated patch. I also noticed I named my other patches incorrectly based on comment numbers. Hopefully I got it right this time!

steveoliver’s picture

Title: Support admin payments on anonymous orders » Support admin order payments

@ericchew, Nice work getting up to speed on git, d.o issue reviews and interdiffs. Your approach was almost there, but there were several changes that were hard to review, so I branched off of bradjones1's 809 PR, and made a few (smaller) changes to achieve the same functionality (diff here: https://github.com/bradjones1/commerce/compare/anonymous-order-payments....).

I could create another PR against commerce, but would rather use @bradjones1's branch to wrap up all changes we need to make for support admin payment operations, which I confirmed with @bojanz makes sense in this issue (so I renamed the issue).

To test Brad's branch with my commit added: https://github.com/drupalcommerce/commerce/compare/8.x-2.x...steveoliver... (complete diff against 8.x-2.x).

steveoliver’s picture

steveoliver’s picture

Issue summary: View changes

Updated issue summary.

steveoliver’s picture

Issue summary: View changes
bradjones1’s picture

@steveoliver I opened a PR on my branch and did a review https://github.com/bradjones1/commerce/pull/1#pullrequestreview-78003108 but it looks like the crux is that the profile re-use stuff for anonymous users still needs to be ironed out. Should we try to chat on Slack to plot a path forward and get this moved forward?

steveoliver’s picture

StatusFileSize
new10.76 KB
new9.82 KB

@bradjones1, I was finishing up the rest of the to-dos while you were reviewing. I'm happy to make changes more atomic, but the current code at https://github.com/drupalcommerce/commerce/compare/8.x-2.x...steveoliver... attempts to "finish the admin ui for order payments". I've commented on https://github.com/bradjones1/commerce/pull/1#pullrequestreview-78003108.

Current patch and interdiff from #2 here for what it's worth. (Eric, I'm not ignoring your patches, I just found it easier to work from the older commits in Git).

Status: Needs review » Needs work

The last submitted patch, 15: 2912996-15.diff, failed testing. View results

steveoliver’s picture

Status: Needs work » Needs review
StatusFileSize
new10.35 KB

Should fix test failures. No interdiff, because I rebased since #15.

bojanz’s picture

Status: Needs review » Needs work

#2858599: Move a portion of the PaymentInformation pane logic to a PaymentOptionsBuilder has landed, we can now reroll this patch to make use of the new service, simplifying it in the process.

Sut3kh’s picture

StatusFileSize
new10.7 KB

Rerolled patch 17 with the following manual tweak to apply the change in #2995325: Order::getCustomer() should guard against deleted users that caused the patch to stop applying:

diff --git a/modules/payment/src/Form/PaymentAddForm.php b/modules/payment/src/Form/PaymentAddForm.php
index 8077b33..77c5bf4 100644
--- a/modules/payment/src/Form/PaymentAddForm.php
+++ b/modules/payment/src/Form/PaymentAddForm.php
@@ -143,7 +143,7 @@ class PaymentAddForm extends FormBase implements ContainerInjectionInterface {
       $payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method');
       $billing_countries = $this->order->getStore()->getBillingCountries();
       $payment_methods = [];
-      if ($this->order->getCustomerId()) {
+      if (!$this->order->getCustomer()->isAnonymous()) {
         $payment_methods += $payment_method_storage->loadReusable($this->order->getCustomer(), $selected_payment_gateway, $billing_countries);
       }
       if ($this->order->getBillingProfile()) {

I don't have time to figure out/do the work @bojanz suggests unfortunately, this is just sharing the patch I rolled to allow us to continue having working 'admin payments' for now.

czigor’s picture

StatusFileSize
new67.88 KB

I'm not really sure what else to do here apart from tests.

- The "Use stored payment methods on anonymous orders" remaining task seems to be implemented: an admin user is able to add payments of stored payment methods to an anonymous order.
- Using PaymentOptionsBuilder on admin payments would change the current UI, wouldn't it? The current UI can be seen on the attached screenshot. AFAIK PaymentOptionsBuilder is useful when building one set of radios including both payment gateways and stored payment methods as options.

Sut3kh’s picture

StatusFileSize
new10.5 KB

Just a simple re-roll of #19.

embeau’s picture

Tested patch from #21 on my site (Drupal 8.7.3) and can confirm it works!

embeau’s picture

Status: Needs work » Reviewed & tested by the community
bradjones1’s picture

Status: Reviewed & tested by the community » Needs work

This still needs tests and also an answer to whether we should use PaymentOptionsBuilder, or keep the current logic per the question re: UI interface.

bojanz’s picture

I would say yes re: PaymentOptionsBuilder usage.

ericchew’s picture

On the latest 2.x-dev that includes the profile reuse functionality (using commerce_authnet), it appears the Add Payment Method functionality in this patch is broken. When submitting the form during the step that you add the CC / billing info, the following error occurs:

The website encountered an unexpected error. Please try again later.Drupal\Core\Entity\EntityStorageException: Missing bundle for entity type commerce_payment_method in Drupal\Core\Entity\ContentEntityStorageBase->doCreate() (line 108 of core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php).

Drupal\Core\Entity\EntityStorageBase->create(Array) (Line: 268)
Drupal\commerce_payment\Form\PaymentAddForm->buildPaymentMethodForm(Array, Object) (Line: 97)
Drupal\commerce_payment\Form\PaymentAddForm->buildForm(Array, Object)
call_user_func_array(Array, Array) (Line: 519)
Drupal\Core\Form\FormBuilder->retrieveForm('commerce_payment_add_form', Object) (Line: 369)
Drupal\Core\Form\FormBuilder->rebuildForm('commerce_payment_add_form', Object, Array) (Line: 626)
Drupal\Core\Form\FormBuilder->processForm('commerce_payment_add_form', Array, Object) (Line: 319)
Drupal\Core\Form\FormBuilder->buildForm('commerce_payment_add_form', Object) (Line: 93)
Drupal\Core\Controller\FormController->getContentResult(Object, Object)
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 582)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 151)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 693)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
bradjones1’s picture

Version: 8.x-2.x-dev » 8.x-2.13
Status: Needs work » Needs review
StatusFileSize
new13.34 KB
new12.88 KB

Updated to use the PaymentOptionsBuilder. The upcoming 2.14 release does indeed affect this file in terms of the UI for the address book, however I'm not in a position right now to develop against a Commerce site running -dev, so this is against 2.13 for now. I think once 2.14 lands, and site owners like myself have the opportunity to do broader integration with the address book, we can re-roll this and target its inclusion in 2.15.

Much of this is inspired by the PaymentInformation checkout pane, with the appropriate edits where necessary. I've spot-checked it against an order with an authenticated user having existing stored payment methods, as well as an anonymous order (which was the motivation for this to begin with.)

Status: Needs review » Needs work

The last submitted patch, 27: 2912996-27-admin-order-payments.patch, failed testing. View results

bradjones1’s picture

It appears the test failures have to do with the change in the UI as a result of using PaymentOptionsBuilder.

bradjones1’s picture

Version: 8.x-2.13 » 8.x-2.x-dev
ericchew’s picture

StatusFileSize
new2.15 KB
new14.01 KB

On Commerce_Authnet, if #button_type isn't primary, it breaks the profile reuse functionality. The bug is the same as described in this issue: https://www.drupal.org/project/commerce_authnet/issues/3069640

@@ -182,6 +182,7 @@ class PaymentAddForm extends FormBase implements ContainerInjectionInterface {
     $form['actions']['submit'] = [
       '#type' => 'submit',
       '#value' => $this->t('Continue'),
+      '#button_type' => 'primary',
     ];

On #27 if you select an existing payment method, you receive the following error:

Error: Call to a member function getEntity() on null in Drupal\commerce_payment\Form\PaymentAddForm->buildPaymentForm() (line 262 of modules/contrib/commerce/modules/payment/src/Form/PaymentAddForm.php).

This is because the #inline_form is not built when selecting an existing payment method.

   public function submitForm(array &$form, FormStateInterface $form_state) {
     $step = $form_state->get('step');
     if ($step == 'payment_gateway') {
+      $payment_method = NULL;
+      if (isset($form['add_payment_method']['#inline_form'])) {
+        /** @var \Drupal\commerce\Plugin\Commerce\InlineForm\EntityInlineFormInterface $inline_form */
+        $inline_form = $form['add_payment_method']['#inline_form'];
+        $payment_method = $inline_form->getEntity();
+      }
+      elseif ($payment_method_id = $form_state->getValue('payment_method')) {
+        $payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method');
+        $payment_method = $payment_method_storage->load($payment_method_id);
+      }
+      $form_state->set('payment_method', $payment_method);
       $form_state->set('step', 'payment');

protected function buildPaymentForm(array $form, FormStateInterface $form_state) {
-    /** @var \Drupal\commerce\Plugin\Commerce\InlineForm\EntityInlineFormInterface $payment_method_inline_form */
-    $payment_method_inline_form = $form_state
-      ->getCompleteForm()['add_payment_method']['#inline_form'];
-    $payment_method = $payment_method_inline_form->getEntity();
+    $payment_method = $form_state->get('payment_method');
ericchew’s picture

Status: Needs work » Needs review

The last submitted patch, 31: 2912996-31-admin-order-payments.patch, failed testing. View results

ericchew’s picture

StatusFileSize
new15.66 KB
new3.41 KB

The patch in #31 does not work for manual payment gateway. I updated the code to support manual payment.

I looked into the test failure on #31. There are two reasons:

As @bradjones1 pointed out, changes in PaymentOptionsBuilder is one reason. The label for the radios is no longer set to the payment gateway label, it is set to the plugin display_label.

    $this->paymentGateway = $this->createEntity('commerce_payment_gateway', [
      'id' => 'manual',
      'label' => 'Manual example',
      'plugin' => 'manual',
    ]);
    $this->paymentGateway->setPluginConfiguration([
      'display_label' => 'Cash on delivery',
      'instructions' => [
        'value' => 'Test instructions.',
        'format' => 'plain_text',
      ],
    ]);

Which means we have to change
$this->assertSession()->pageTextContains('Manual example'); to $this->assertSession()->pageTextContains('Cash on delivery');

The second reason this test still fails is because of how #access is set. This causes the radio button to disappear, so the assertion fails.

    $form['payment_method'] = [
      '#type' => 'radios',
      '#title' => $this->t('Payment method'),
      '#options' => $option_labels,
      '#default_value' => $default_option->getId(),
      '#ajax' => [
        'callback' => [get_class($this), 'ajaxRefresh'],
        'wrapper' => $form['#wrapper_id'],
      ],
      '#access' => count($options) > 1,
    ];

The fix:

'#access' => count($options) > 1 || $default_option->getId() == 'manual'

jastraat’s picture

So I am attempting to use this patch with commerce_purchase_order. A common scenario is that a customer would email a purchase order and a site admin would enter the order along with a payment.

The purchase order payment method appears as an option, but when I enter a purchase order number and submit the add payment method, I receive the following error:

Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException: The "purchase_order_gateway" plugin did not specify a "add-payment" form class in Drupal\Core\Plugin\PluginFormFactory->createInstance() (line 41 of /core/lib/Drupal/Core/Plugin/PluginFormFactory.php).

Could someone more familiar with this patch explain what changes commerce payment modules will need to make in order to take advantage of this patch?

jastraat’s picture

Unfortunately the most recent patch does not work for us. There may also be a disconnect on what a typical use case might be.

Example case (Commerce 2.16):

  • Using two payment gateways: commerce_paypal and manual
  • Expectation: admin can create an order associated with a user and add a manual payment to that order (e.g. a customer paid by check in person)
  • Reality: after clicking the "add payment" button on an order (admin/commerce/orders/[orderid]/payments/add - no options display.

I also tried adding a payment method to an existing user in the hopes that then I could add a payment to one of their orders. (user/[uid]/payment-methods/add) This failed with access denied despite being user 1.

Based on this issue summary, "Create and use new payment methods on user-orders" is supposed to already be implemented but I don't believe it is.

cola’s picture

Patch #34 is not working

Checking patch payment/tests/src/Functional/ManualPaymentAdminTest.php...

error: payment/tests/src/Functional/ManualPaymentAdminTest.php: No such file or directory

Testing ability to patch with git apply. This command may produce errors that can be safely ignored.
git -C 'modules/contrib/commerce' apply --check -v '-p4' '/tmp//5e712c04a91ee.patch'
Executing command (CWD): git -C 'modules/contrib/commerce' apply --check -v '-p4' '/tmp//5e712c04a91ee.patch'
Checking patch Form/PaymentAddForm.php...
error: Form/PaymentAddForm.php: No such file or directory
Checking patch PaymentMethodStorage.php...

error: PaymentMethodStorage.php: No such file or directory
Checking patch PaymentMethodStorageInterface.php...

error: PaymentMethodStorageInterface.php: No such file or directory
Checking patch src/Functional/ManualPaymentAdminTest.php...

error: src/Functional/ManualPaymentAdminTest.php: No such file or directory

patch '-p1' --no-backup-if-mismatch -d 'modules/contrib/commerce' < '/tmp//5e712c04a91ee.patch'
Executing command (CWD): patch '-p1' --no-backup-if-mismatch -d 'modules/contrib/commerce' < '/tmp//5e712c04a91ee.patch'
patch: unrecognized option `--no-backup-if-mismatch'
usage: patch [-bCcEeflNnRstuv] [-B backup-prefix] [-D symbol] [-d directory]
             [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]
             [-r rej-name] [-V t | nil | never | none] [-x number]
             [-z backup-ext] [--posix] [origfile [patchfile]]
       patch <patchfile

patch '-p0' --no-backup-if-mismatch -d 'modules/contrib/commerce' < '/tmp//5e712c04a91ee.patch'
Executing command (CWD): patch '-p0' --no-backup-if-mismatch -d 'modules/contrib/commerce' < '/tmp//5e712c04a91ee.patch'
patch: unrecognized option `--no-backup-if-mismatch'
usage: patch [-bCcEeflNnRstuv] [-B backup-prefix] [-D symbol] [-d directory]
             [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]
             [-r rej-name] [-V t | nil | never | none] [-x number]
             [-z backup-ext] [--posix] [origfile [patchfile]]
       patch <patchfile

patch '-p2' --no-backup-if-mismatch -d 'modules/contrib/commerce' < '/tmp//5e712c04a91ee.patch'
Executing command (CWD): patch '-p2' --no-backup-if-mismatch -d 'modules/contrib/commerce' < '/tmp//5e712c04a91ee.patch'
patch: unrecognized option `--no-backup-if-mismatch'
usage: patch [-bCcEeflNnRstuv] [-B backup-prefix] [-D symbol] [-d directory]
             [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]
             [-r rej-name] [-V t | nil | never | none] [-x number]
             [-z backup-ext] [--posix] [origfile [patchfile]]
       patch <patchfile

patch '-p4' --no-backup-if-mismatch -d 'modules/contrib/commerce' < '/tmp//5e712c04a91ee.patch'
Executing command (CWD): patch '-p4' --no-backup-if-mismatch -d 'modules/contrib/commerce' < '/tmp//5e712c04a91ee.patch'
patch: unrecognized option `--no-backup-if-mismatch'
usage: patch [-bCcEeflNnRstuv] [-B backup-prefix] [-D symbol] [-d directory]
             [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]
             [-r rej-name] [-V t | nil | never | none] [-x number]
             [-z backup-ext] [--posix] [origfile [patchfile]]
       patch <patchfile

   Could not apply patch! Skipping. The error was: Cannot apply patch https://www.drupal.org/files/issues/2019-10-09/2912996-34-admin-order-payments.patch
shaunole’s picture

StatusFileSize
new15.85 KB

There were a couple issues I found with patch #34. The method to return a value from the FormStateInterface should be $form_state->getValue('payment_method');. Also, the patch only allows for the initial 'manual' payment method. There was no logic to handle other manual payment methods(ie. Purchase Orders, Checks, etc).

This is my first patch submitted back to the community, please let me know if anything is missing.

Status: Needs review » Needs work

The last submitted patch, 38: admin-order-payments-2912996-38.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

ericchew’s picture

Status: Needs work » Needs review
StatusFileSize
new15.98 KB
new2.85 KB

This patch should now work with the manual payment gateway.

@Jastraat #35

The purchase order payment method appears as an option, but when I enter a purchase order number and submit the add payment method, I receive the following error:

Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException: The "purchase_order_gateway" plugin did not specify a "add-payment" form class in Drupal\Core\Plugin\PluginFormFactory->createInstance() (line 41 of /core/lib/Drupal/Core/Plugin/PluginFormFactory.php).

Could someone more familiar with this patch explain what changes commerce payment modules will need to make in order to take advantage of this patch?

That is an issue with commerce_purchase_order, not payment. The module should probably provide a PaymentAddForm and specify it in the "add-payment" form class in the plugins annotation (as the error suggests). Maybe it could just use ManualPaymentAddForm implementation? I have no experience with the module so hard for me to say.

#36

Example case (Commerce 2.16):

  • Using two payment gateways: commerce_paypal and manual
  • Expectation: admin can create an order associated with a user and add a manual payment to that order (e.g. a customer paid by check in person)
  • Reality: after clicking the "add payment" button on an order (admin/commerce/orders/[orderid]/payments/add - no options display.

I have no experience with commerce_paypal, but I imagine it is an offsite payment gateway, which the current patch does not support.

I also tried adding a payment method to an existing user in the hopes that then I could add a payment to one of their orders. (user/[uid]/payment-methods/add) This failed with access denied despite being user 1.

This is also because commerce_paypal probably doesn't support storing payment methods https://git.drupalcode.org/project/commerce/-/blob/8.x-2.x/modules/payme.... It should probably provide a message rather than a 403 access denied.

@shaunole #38

The method to return a value from the FormStateInterface should be $form_state->getValue('payment_method');

If you look at the submit handler, we store the value of the payment method using FormState storage $form_state->set('payment_method', $payment_method);. The correct way to retrieve that value is $form_state->get('payment_method');. Those methods are used to get/set arbitrary data, rather than getting/setting actual form values.

ericchew’s picture

StatusFileSize
new16.18 KB
new6.01 KB

Cleaning up some of the variable names that did not make sense anymore after integrating PaymentOptionsBuilder. Also simplified the submit handler a little bit.

Status: Needs review » Needs work

The last submitted patch, 41: admin-order-payments-2912996-41.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

ericchew’s picture

Forgot to update tests since I changed the form element key. Have to head out for the day...will update it next week!

jastraat’s picture

So - this patch also does not support manual payments?

ericchew’s picture

@jastraat it should support the manual payment gateway provided by commerce_payment. If you are referring to supporting commerce_purchase_order, I'm not sure. That module provides it's own PaymentGateway plugin which may not define everything it needs to be compatible with this feature. I do not utilise that module so I have not tested it.

Have you tried the patch? Please post any errors you are running into.

jastraat’s picture

I'll try the most recent patch and post back. The earlier version of the patch was not allowing me as an admin to add a manual payment (provided in base Commerce).

sah62’s picture

The patch in #41 seems to work fine for me. I was just able to successfully apply a manual payment to an order without error.

ericchew’s picture

StatusFileSize
new17.03 KB
new877 bytes

@sah62 good to hear.

This patch should pass tests.

ericchew’s picture

Status: Needs work » Needs review
jastraat’s picture

Status: Needs review » Reviewed & tested by the community

The most recent patch in 48 also worked for us in entering a manual payment. Thanks!

ericchew’s picture

@jastraat thanks for the feedback.

One thing I did not change in the patch is the CSS class that @bradjones1 added in #27

+    // Add a class to each individual radio, to help themers.
+    foreach ($this->paymentOptions as $option) {
+      $class_name = $option->getPaymentMethodId() ? 'stored' : 'new';
+      $form['payment_option'][$option->getId()]['#attributes']['class'][] = "payment-method--$class_name";
+    }

payment-method should be changed to payment-option to be consistent. I don't plan on submitting a patch for this because it all depends on whether Centarro agrees with changing the form element key in the first place.

sah62’s picture

For folks who want this capability with Commerce PayPal: #3121537: Add Support for Admin Order Payments

shaunole’s picture

After running tests, the only failure was on the Functional Test PaymentGatewayTest::testEdit() method where field "id" is unreachable.

@ericchew Thanks for the explanation. I was able to install the latest patch without issue.

liquidcms’s picture

Status: Reviewed & tested by the community » Needs work

I think still issues with this.. possibly as noted by @jastraat in #44.

when i first added Manual payment, it does show on Add Payments page for my client's order; but it also shows as an option when my client is doing standard checkout through his Cart. If i limit the GW to only admin roles; then the payment option is no longer allowed for Admins.

Possibly the payment GW is only available to admins if also available to the order user?

liquidcms’s picture

i had posted this a couple weeks back: #3121414: Admin can not Add Payment for Gateway unless client has used that GW. and then marked it as a duplicate of this issue (today). Set it back to Active as not really a duplicate of this issue.. but probably related.

For now, as my client definitely needs to allow "pay by cheque" for support staff.. we will likely remove role access limitation to clients and then just alter the checkout for to remove it (until this issue get fixed completely).

liquidcms’s picture

sadly though, not so easy to alter that form, so will need to look deeper as to why admin's adding payments is limited to methods allowed to users.

shaunole’s picture

Can we incorporate a checkbox allowing the user to choose whether the newly created payment method is reusable? It should really only be needed in the case that the plugin implements SupportsStoredPaymentMethodsInterface. A similar issue was recently introduced for checkout: 2871483 - Add checkout settings for payment method behavior.

The default behavior is currently to store all payment methods that implement SupportsStoredPaymentMethodsInterface, but this should probably be configurable.

liquidcms’s picture

From my comment in #54 and looking now at the code, i think the issue is in paymentOptionsBuilder method which returns a list of payment methods for that Order; but based on the Order's user; which is incorrect for this as the admin most likely has access to payment methods not allowed to the order's owner.

I'll dig into the code for this and figure out the fix as it is likely easier than trying to figure out how to just alter the cart payment form (and its the right way to fix this).

liquidcms’s picture

it would be great if someone could comment on this as i would think this would be a release block for Commerce (yes, i realize it was already released years ago). I have gone through the code a bit and although i can see where this is getting broken, i am not sure of the best approach to fix it.

To start with, PaymentAddForm::buildPaymentGatewayForm gets $payment_gateways from loadMultipleForOrder() which incorrectly returns GWs based on conditions set for that order, but role based restrictions for this are based on the Order's owner; not the current user as it should be. I simply swapped out the $order->id for the currentUser's id and i now have access to the correct payment gateways allowed for the user processing the order (an admin in this case).

Back in the Form builder function though, i then get included all the existing CC options for my currentUser as opposed to now what should be for the Order's owner. So swapping back the original uid and that is now fixed.

Last part is when the form builder removes the other GWs allowed (even for order's owner) which in my test include paypal and a test gw). The comments there state: "// Allow on-site and manual payment gateways." but this filter is removing those other payment types. I am not sure what that code is doing:

    $payment_gateways = array_filter($payment_gateways, function ($payment_gateway) {
      /** @var \Drupal\commerce_payment\Entity\PaymentGateway $payment_gateway */
      return !($payment_gateway->getPlugin() instanceof OffsitePaymentGatewayInterface);
    });

but i don't think it does what was intended here.

So my couple fixes:
- set order->uid as currentUser for the purposes of evaluating GW conditions
- return the order id back to its original value so as to not pull in existing CC methods for the currentUser
- completely remove the code which removes other GW's such as PayPal and Test GW

but can't help wonder if there isn't a better, higher level, way to be handling this.

liquidcms’s picture

Status: Needs work » Needs review
StatusFileSize
new12.59 KB

patch attached for my suggestions in #59

shaunole’s picture

@liquidcms I'm not sure changing the gateway condition evaluation from Customer to User is the right approach. Payment gateways, by default, have a Conditions Group for Customer conditions. This is where the Order Owner (aka Customer) condition would be set. Many stores might want to have conditions based on the current order owner. Additionally, if changed to the Current User, a User with appropriate permissions (aka staff) would not be able to see the Payment Methods that the Customer had stored.

We needed to address a similar issue in our application. To address this we added a new CommerceCondition that extends ConditionBase. In our application, we created a new ConditionGroup Category called 'User' that allowed us to limit payment gateways by user role.

For more information on building a new Condition Group, check this article out: https://docs.drupalcommerce.org/commerce2/developer-guide/core/conditions

liquidcms’s picture

@shaunole, yes, good points:

Many stores might want to have conditions based on the current order owner.

- i am not sure what those could be other than Role; and that specifically doesn't apply here as the staff should be allowed other GWs than what the customer is allowed

if changed to the Current User, a User with appropriate permissions (aka staff) would not be able to see the Payment Methods that the Customer had stored.

- good point, I'll need to see how i can accomplish that.

We needed to address a similar issue in our application. To address this we added a new CommerceCondition that extends ConditionBase. In our application, we created a new ConditionGroup Category called 'User' that allowed us to limit payment gateways by user role.

- ahh, your new group is separate from the current one and limits by role depending on role. that sounds like a good appraoch.. will check it out.

thanks.

liquidcms’s picture

@shaunole, but you also can't use the patch in #48, correct? as it will limit other GWs from being listed?

liquidcms’s picture

@shaunole, I added a new Condition to override the existing Customer Role one and simply copied the existing condition but added this line to the top of the evaluate method:

if (isStaff()) return true;

Works like a charm. I did, as i mentioned above, still have to remove the array_filter part from the original patch. Not sure what its purpose is but pretty sure it doesn't work.

Thanks again.

ericchew’s picture

@liquidcms regarding the array_filter

  $payment_gateways = array_filter($payment_gateways, function ($payment_gateway) {
      /** @var \Drupal\commerce_payment\Entity\PaymentGateway $payment_gateway */
      return !($payment_gateway->getPlugin() instanceof OffsitePaymentGatewayInterface);
    });

I don't use an OffSite gateway, so I have not tested. With the array_filter removed, you're saying that the form properly redirects you offsite to PayPal, allows you to pay, and redirects you back to the admin form? I would think the logic to make all of that happens still needs to be added to this patch...which is the reason for filtering those gateways out.

liquidcms’s picture

@ericchew, ah, no, haven't tested but even the Test gw is blocked by that code and guessing that will work.. but maybe it doesn't.. will test now. Didn't think of the downstream issues with those GWs as i am always thinking things should work for admin ui as they do for cart ui; but very little does, so this makes sense.

the comment above that code: "// Allow on-site and manual payment gateways." was a bit confusing as it suggests adding GWs into the form; not removing them.

just tested and nope, even the test gw throws an error from the admin ui.

thanks for the reply.

ericchew’s picture

Can you elaborate on what you mean by the Test gateway? Is it one of the Gateways provided in payment_example module included with commerce? I haven't tested those, but I'd think the onsite one would work. The offsite test gateway would fail just like any other offsite gateway with this patch.

liquidcms’s picture

Yes, it is: Example (Off-site redirect) GW. It fails. Haven't tried the onsite example gw (but i believe you that it likely works).

Still confused by the comment: "Allow on-site and manual payment gateways." perhaps just logic.. the code doesn't actually add anything (which "allow" suggests); but comment is maybe better as "Remove other non-offsite gateways"?

ericchew’s picture

I agree that the comment could be more clear. I haven't dug into how offsite gateways work to even know if it would be possible to support them in the admin UI.

shaunole’s picture

@liquidcms
I was able to successfully install the patch from 48 without issue and we have it functioning on a production website. We do not have any offsite payment gateways as we only use Paypal Payflow, which is an Onsite gateway. We have multiple gateways:

  • Manually applied payments,
  • Payment through Payflow,
  • Check Payments,
  • And one for processing balance transfers between orders.

Of those, the Customer should only be able to see the Payflow gateway (with their stored payment methods or a form to enter a new payment method). The other gateways we limit to authorized roles through the new User ConditionBase.

@erricchew
I believe my comment on adding support for reusable payments (#57) might have been inadvertently buried. Any chance we can incorporate that into this patch?

ericchew’s picture

@shaunole

Im not sure if that would be in the scope of this issue or not, might be best to ping one of the Centarro guys about that. Maybe ask during one of the office hours on slack.

shaunole’s picture

@erricchew

I can certainly ask them today during office hours. Although, I'd like to better understand the question I'd be asking. It seems that:
a) either the option to support opting out of reusable payments on admin order management needs to be incorporated into this patch
- OR -
b) this patch needs to be committed first as that change would require changes to the code in this patch.

Specifically starting on line 234 of Drupal\commerce_payment\Form\PaymentAddForm.php (this is where the payment method is created):

$payment_method = $payment_method_storage->create([
      'type' => $payment_option->getPaymentMethodTypeId(),
      'payment_gateway' => $payment_option->getPaymentGatewayId(),
      'uid' => $this->order->getCustomerId(),
      'billing_profile' => $this->order->getBillingProfile(),
      'reusable' => false, // <== I added this line via patch to disable reusable payments
    ]);

For our purposes, we need to disable reusable payment methods, so I've flagged the line we added via patch (which prevents storing the payment method as reusable). This is really just a bandaid as most applications should be able to choose how they want this configured. That being said, I do not see any mechanism to inject additional values to the created payment method (ie, whether it is reusable). Perhaps allowing additional data to be added to the array passed to $payment_method_storage->create() is another way to tackle this?

If I'm missing something here, please point me in the right direction. Thanks again for your thoughts on this.

ericchew’s picture

From taking a look at issue 2871483, to make it work here we'd need to incorporate the changes to PaymentOptionsBuilder and PaymentMethodAddForm from there. After that, we'd take the updates to PaymentInformation checkout pane from 2871483 and try to replicate the functionality in PaymentAddForm. I'm just not sure if it would be too messy to try to keep both of these issues in sync whenever one or the other is updated.

Also, the PaymentInformation pane exposes configuration that allows you to decide what the behavior should be for how the "allow reuse" stuff works. I imagine some decisions would need to be made on how it should work for admins.

shaunole’s picture

@erricchew

You make a good point. I suppose the best solution (although, it would cause quite a delay until the next release) would be to create a new issue once your patch has been adopted. Otherwise, I would have to create an issue for a feature that is not yet implemented which also seems pretty messy. Thanks for the input.

sah62’s picture

The patch in #48 fails to apply with Commerce 8.x-2.19. I'll have to see what's changed.

sah62’s picture

StatusFileSize
new16.31 KB

Re-roll attempt...

dragonwize’s picture

StatusFileSize
new17.03 KB

The payment_gateway and payment_method entity references were not being set. These are normally set in the PaymentInformation pane which is not running here so adding the lines to the PaymentAddForm submit after the payment is created.

mglaman’s picture

Assigned: bradjones1 » Unassigned

While doing a quick review, I noticed there's a new method for the payment method storage that does not seem to be used.

+++ b/modules/payment/src/PaymentMethodStorage.php
@@ -138,4 +138,31 @@ class PaymentMethodStorage extends CommerceContentEntityStorage implements Payme
+  /**
+   * {@inheritdoc}
+   */
+  public function loadForProfile(ProfileInterface $profile, PaymentGatewayInterface $payment_gateway) {
+    if (!($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface)) {
+      return [];
+    }
+
+    $query = $this->getQuery();
+    $query
+      ->condition('billing_profile.target_id', $profile->id())
+      ->condition('payment_gateway', $payment_gateway->id())
+      ->condition($query->orConditionGroup()
+        ->condition('expires', $this->time->getRequestTime(), '>')
+        ->condition('expires', 0))
+      ->sort('created', 'DESC');
+    $result = $query->execute();
+    if (empty($result)) {
+      return [];
+    }
+
+    /** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface[] $payment_methods */
+    $payment_methods = $this->loadMultiple($result);
+
+    return $payment_methods;
+  }
+

+++ b/modules/payment/src/PaymentMethodStorageInterface.php
@@ -48,4 +48,17 @@ interface PaymentMethodStorageInterface extends ContentEntityStorageInterface {
+  /**
+   * Loads the payment methods for a billing profile and payment gateway.
+   *
+   * @param \Drupal\profile\Entity\ProfileInterface $profile
+   *   The billing profile.
+   * @param \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway
+   *   The payment gateway.
+   *
+   * @return \Drupal\commerce_payment\Entity\PaymentMethodInterface[]
+   *   The payment methods for the given billing profile and payment gateway.
+   */
+  public function loadForProfile(ProfileInterface $profile, PaymentGatewayInterface $payment_gateway);

This method is provided but not actually used.

jrochate’s picture

In our use case, anonymous users is mandatory, and when there are problems with payments, the back office operator can't "fix" the order using manual payments, because it is "denied" by commerce.

It would be good if operator could edit payments on a anonymous user, so he/she could correct the problematic / pending orders.

Let's say I order on the site, but my payment method don't work.
I go to shop, pay by cash, and my order is complete. If I'm anonymous, and only have order ID, this can't be done.

Thanks. Long waiting feature :)

robbmlewis’s picture

I am in the same place as jrochate. There are times when I need to add a payment manually to an anonymous user's order. It's always a manual payment, not paypal or similar. If I change the order user to someone else, I can add a payment, but if it's anonymous, I can't. Going through and changing ownership, adding payment, and changing ownership back is not really a solution but currently the only thing that seems to work.

khiminrm’s picture

StatusFileSize
new14.75 KB
new1.86 KB

Removed unused method mentioned by @mglaman in #78

dpolant’s picture

StatusFileSize
new15.42 KB

This patch is working great for me. I've added something to fix the following use case:

  1. Customer checks out with non-reusable payment method (PO for instance)
  2. Admin wants to add another payment to the order for some reason
  3. Admin will see the non-reusable payment method as an option, which doesn't make sense.

This new patch filters out the current order's payment method if it is non-reusable.

lukasss’s picture

#82 Works good for me. It is possible to make payments for anonymous orders.

lighttree’s picture

Confirmed #82 works well

  • jsacksick committed 9938c1b on 8.x-2.x authored by dpolant
    Issue #2912996 by ericchew, steveoliver, Sut3kh, bradjones1, khiminrm,...
jsacksick’s picture

Status: Needs review » Fixed

Fixed minor coding standard issues and phpcs violations and committed the patch from #82. Thanks everyone!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.