The Payment Information pane lists all payment methods, regardless if they have expired. They should either

  1. Not be listed
  2. List but be disabled, mark as expired

Also: the ::loadReusable method for payment method storage is returning payment methods which are not valid for the current store.

Comments

mglaman created an issue. See original summary.

bojanz’s picture

We need to filter them out, as well as those whose billing country isn't in the list of store's supported billing countries.

arosboro’s picture

Here's a quick patch that accomplishes the filtering on REQUEST_TIME and the store's billing countries. It does not alter the address' country_code options in commerce_payment_gateway_form.

mglaman’s picture

Status: Active » Needs review

Marking for review

mglaman’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests
+++ b/modules/payment/src/PaymentMethodStorage.php
@@ -25,6 +25,7 @@ class PaymentMethodStorage extends CommerceContentEntityStorage implements Payme
+      ->condition('expires', REQUEST_TIME, '>')

We have a time service (8.3.x backport) to use instead. Constant is to be deprecated.

Also we need a test.

mglaman’s picture

StatusFileSize
new7.74 KB

Here is patch using time service and a test https://github.com/drupalcommerce/commerce/pull/605

+++ b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
@@ -130,6 +130,11 @@ class PaymentInformation extends CheckoutPaneBase implements ContainerFactoryPlu
+        $country_code = $payment_method->getBillingProfile()->address->first()->getCountryCode();
+        $billing_countries = $this->order->getStore()->getBillingCountries();
+        if (!in_array($country_code, $billing_countries)) {
+          continue;
+        }

Shoot. I forgot about this in the patch. I think this should probably go in its own follow-up? Or we should change issue title to reflect "reusable should account for expired and methods with proper billing countries"

mglaman’s picture

Title: Do not allow selecting expired payment methods on checkout » Checkout shows payment methods which cannot be reused
Issue summary: View changes

Updated title.

mglaman’s picture

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

Here's an adjustment which tests countries, too. Moved the logic to the storage. Ran into a blocker for using entity_reference_revisions for the entity query. Blocked on #2822359: Support "entity" property definition in entity queries using Entity Reference Revisions fields.

borisson_’s picture

Status: Needs review » Needs work

Mostly docs things that can be improved, tests looks good. I think the tag can be removed?

  1. +++ b/modules/payment/src/PaymentMethodStorage.php
    @@ -25,13 +79,29 @@ class PaymentMethodStorage extends CommerceContentEntityStorage implements Payme
    +    // @todo Review and refactor for proper entity query condition as billing_profile.entity.address.country_code
    +    // @see https://www.drupal.org/node/2822359
    

    80 cols.

  2. +++ b/modules/payment/src/PaymentMethodStorage.php
    @@ -25,13 +79,29 @@ class PaymentMethodStorage extends CommerceContentEntityStorage implements Payme
    +      foreach ($payment_methods as $key => $payment_method) {
    +        $country_code = $payment_method->getBillingProfile()->address->first()->getCountryCode();
    +        if (!in_array($country_code, $store_billing_countries)) {
    +          unset($payment_methods[$key]);
    +        }
    +      }
    

    This probably reads easier as an array_filter($payment_methods, function() { ... }).

    That doesn't really matter though. It's the same result.

  3. +++ b/modules/payment/src/PaymentMethodStorageInterface.php
    @@ -18,10 +19,12 @@ interface PaymentMethodStorageInterface extends ContentEntityStorageInterface {
    +   * @param \Drupal\commerce_store\Entity\StoreInterface $store
    +   *   An optional store to filter by, for available billing countries.
    

    I think we can improve the comment here.

    An optional store, when passed the returned payment methods will only be those that are allowed for the the store's billing countries.

    Not sure if that makes sense?

  4. +++ b/modules/payment/tests/src/Kernel/PaymentMethodReusableTest.php
    @@ -0,0 +1,172 @@
    +  /**
    +   * Modules to enable.
    +   *
    +   * @var array
    +   */
    +  public static $modules = [
    

    This docblock can be replaced by {@inheritdoc}

  5. +++ b/modules/payment/tests/src/Kernel/PaymentMethodReusableTest.php
    @@ -0,0 +1,172 @@
    +    // An order item type that doesn't need a purchasable entity, for simplicity.
    

    80 cols.

  6. +++ b/modules/payment/tests/src/Kernel/PaymentMethodReusableTest.php
    @@ -0,0 +1,172 @@
    +
    +
    +
    

    Too many blank lines. 1 should suffice.

  7. +++ b/modules/payment/tests/src/Kernel/PaymentMethodReusableTest.php
    @@ -0,0 +1,172 @@
    +  public function testBillingCountryPaymentMethods() {
    

    Needs a docblock.

  8. +++ b/modules/payment/tests/src/Kernel/PaymentMethodReusableTest.php
    @@ -0,0 +1,172 @@
    +  }
    +}
    

    Needs a blank link in between.

vasike’s picture

Status: Needs work » Needs review
Related issues: +#2848754: Completing checkout as anonymous user stores payment method
StatusFileSize
new14.29 KB
new3.2 KB

here is a new patch that add a solution for #2848754: Completing checkout as anonymous user stores payment method.
I included in the patch here as i think it completes the work done here (tests PaymentMethodReusableTest).
Extra: extra check for billing country restrictions added

if ($billing_profile = $payment_method->getBillingProfile()) {

For this patch, i used the PR patch:
https://github.com/drupalcommerce/commerce/pull/605
as the #8 patch throws an error:
PHPUnit_Framework_Exception: PHP Fatal error: Call to a member function getCountryCode() on a non-object in /d8path/modules/contrib/commerce/modules/payment/src/PaymentMethodStorage.php on line 97
Probably, somthing is missing from that patch.
Interdiff available.

bojanz’s picture

Priority: Normal » Critical

We don't want to tag a new beta without this.

luksak’s picture

The patch in #10 fixed the issue of all anonymous user's payment methods being stored and showing up for other users.

bojanz’s picture

Status: Needs review » Needs work
Issue tags: -Needs tests

I don't like the passing of $store to loadReusable(), it doesn't make sense from an API standpoint. We should be passing $billing_countries instead.

Making the change.

  • bojanz committed 5d03652 on 8.x-2.x authored by vasike
    Issue #2832493 by mglaman, vasike, arosboro, bojanz: Checkout shows...
bojanz’s picture

Status: Needs work » Fixed

Status: Fixed » Closed (fixed)

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

rgpublic’s picture

Um, I thought Expires=0 means "Never expires". But when I use it, the payment method is filtered anyway...?

rgpublic’s picture

PS: I've changed PaymentMethodStorage.php like so:

    $query = $this->getQuery();

    $expires = $query->orConditionGroup()
    ->condition('expires', 0)
    ->condition('expires', $this->time->getRequestTime(), '>');

    $query->condition('uid', $account->id())
      ->condition('payment_gateway', $payment_gateway->id())
      ->condition('reusable', TRUE)
      ->condition($expires)
      ->sort('created', 'DESC');

This makes the never-expiring payment methods appear again. Don't know at all whether this naive approach is correct. Put perhaps this quick band-aid fix is useful for anyone.