Splitting off from #1375216: Anonymous users see other anoynmous user's credit card details.

Problem/Motivation

Anonymous users don't have a chance to 'use my existing card' for obvious reasons. However it is a desired feature that anonymous users should be able to complete checkout and have the card details they entered associated with their newly created account (which is created by the commerce checkout rules).

Proposed resolution

Add an order id column to the card on file table, which stores the order id associated with the first checkout with those card details. Create new checkout rules that run after checkout and update the user id column of the card on file table to use the new user id for the matching order id of the completed checkout.

Remaining tasks

Review the attached patches

User interface changes

None

API changes

Modules implementing card on file storage (saving card details to the db) need to provide the order id as well.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Heine’s picture

IMO commerce_cardonfile_update_anonymous should also call the payment provider to enable it to update necessary data. An example of such data is the Customer ID kept by Authorize.Net's Customer Information Manager.

rickmanelius’s picture

@Heine

Unless I'm mistaken, I believe the card on file module only stores a reference to the credit card profile and/or communicates through the merchant API and doesn't actually store the raw credit card information (which would be a HUGE PCI compliance mess). What this patch would do is essentially create create a temporary mapping to the order_id until the checkout is completed and THEN associate it with the full uid.

Heine’s picture

I did not say anything about credit card data.

The authorize.net CIM also stores a "merchant user id". Card on file uses the uid of the drupal user for this. Atm this will be 0 for anon users, even with the patch. The payment modules should be informed of the association change and be able to update this ID (not sure if / how Auth.net supports this).

westwesterson’s picture

Same patch as in main comment, with new files included in patch.

westwesterson’s picture

Chadwick Wood’s picture

I just tried out the patch in #5, and it didn't quite work.

When I used patch < patch_commit_6e3913c95c94.patch to run the patch, it all succeeded except for the update to commerce_cardonfile.info.

patching file commerce_cardonfile.info
Hunk #1 FAILED at 9.

But I just went in and manually made that edit. However, applying the patch didn't successfully tie the card on file data to the newly created account. The patch DID add the order_id column, and it did create the associated Rule to tie the card data to the user account associated with the order.

Looking at my database, the cardonfile data entered when I completed checkout has an order_id value of 0, so I'm guessing the problem is there. I'm looking at the patch itself, and I don't see where the code is that stores the order_id with the cardonfile row in the first place. So maybe that's what's missing? Or am I misreading it?

Chadwick Wood’s picture

OH! Maybe that's in the hook_schema implementation? I'm not an expert on that stuff. HOWEVER, I do think I see that the patch adds the foreign key on INSTALL, but on UPDATE, it only adds the order_id, not the foreign key. I ran this patch as an update, so that might explain the problem?

Chadwick Wood’s picture

I tried uninstalling and re-installing the module, and this didn't make a difference. A second order still generated a row in commerce_card_data with an order_id of 0.

Looking more into this, wouldn't this feature require you to alter the way cardonfile data is getting saved in other modules? For instance, I'm using commerce_authnet, and that's the module it looks like that's actually saving the card on file data, so that's where the order_id would need to get written, right?

Chadwick Wood’s picture

Ok, I just modified my copy of commerce_authnet to write order_id to the saved card info table. After doing that, this patch works! But it seems there's no way to really test this patch WITHOUT modifying a module that works with it, so I don't know what that means for the review process.

Berdir’s picture

Status: Needs review » Needs work
+++ b/commerce_cardonfile.installundefined
@@ -27,6 +27,12 @@ function commerce_cardonfile_schema() {
+      'order_id' => array(
+        'description' => 'The {commerce_order}.order_id that originally supplied this card data.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,

The definition should contain unsigned => TRUE, to match the order id column of cmmerce_order.

+++ b/commerce_cardonfile.installundefined
@@ -111,8 +118,22 @@ function commerce_cardonfile_schema() {
+
+/**
+ * Implements hook_update_N
+ * Adds the order_id column to the commerce_card_data table.
+*/
+function commerce_cardonfile_update_7001() {
+  $schema = commerce_cardonfile_schema();
+  db_add_field('commerce_card_data', 'order_id', $schema['commerce_card_data']['fields']['order_id']);
+  return t('Added order_id column to commerce_card_data table.');

No need for the Implements docblock, update hooks should just have a short description of what they're doing. Also, tables are usually inside {} in comments.

The field schema needs to be hardcoded, or it will result in conflicts in case 7002 attempts to change the field definition.

And there is no need to add a return message unless it's something important for the user, something that he must do or so.

+++ b/commerce_cardonfile.rules.incundefined
@@ -0,0 +1,42 @@
+/*
+ * @file commerce_cardonfile.rules.inc
+ * Provides rules integration for assigning anonymous records against user
+ * @copyright Copyright(c) 2012 Lee Rowlands
+ * @license GPL v2 http://www.fsf.org/licensing/licenses/gpl.html
+ * @author Lee Rowlands contact at rowlandsgroup dot com
+ * ¶

No need for the file name after @file and the author/license tags are usually not added in contrib projects. You are credited in the commit message.

+++ b/commerce_cardonfile.rules.incundefined
@@ -0,0 +1,42 @@
+/**
+ * Action callback for rules
+ *  Associate anonymous card data with the newly created user

Suggestion: "Rules action callback; Associate...". On a single line if possible..

+++ b/commerce_cardonfile.rules.incundefined
@@ -0,0 +1,42 @@
+ * @param $order object
+ *   Commerce order object

The object after $order has no meaning.

dwkitchen’s picture

Version: 7.x-1.x-dev » 7.x-2.x-dev

Moving to 2.x branch, will review soon.

univate’s picture

Status: Needs work » Needs review
FileSize
7.24 KB

I have re-rolled this patch and tested on the current 2.x branch.

Obviously the method used here is a bit hacky (saving the order id an then fixing the uid when the account is finally created), but it works and I cant really think of a better to achieve saving credit cards for anonymous users.

univate’s picture

Sorry meant to upload this patch, ignore previous.

dwkitchen’s picture

Status: Needs review » Needs work

Thanks @univate sorry I missed this when you posted it and the patch no longer applies.

I'm just trying to work on some other issues and then I can come back to this.

nicksanta’s picture

Rerolled against latest 7.x-2.x
Please test patch in #16

nicksanta’s picture

Status: Needs work » Needs review
FileSize
6.79 KB

Ughh... missed a file. Ignore patch from #15 and test this one.

dwkitchen’s picture

Status: Needs review » Needs work

Thanks for the patch, I have had chance to have a look though it.

I see that the order_id being added to the card on file properties has NOT_NULL = TRUE, however a card on file does not have to be created through the checkout process and customer could use the add form if the payment gateway implements the callback.

In commerce_cardonfile_anonymous() use EntityFieldQuery rather than db select to get the card on file id.

It's better to define the default rule using the rule object rather than using the JSON import. See the default_rules in the recurring sub module.

stewart.adam’s picture

Patch attached with changes requested from #16. If this gets committed please credit nicksanta, I only changed a few lines :)

stewart.adam’s picture

Status: Needs work » Needs review

Oops, forgot to set to needs review.

stewart.adam’s picture

Hm, looks like I mixed up the latest dev and git. Here's a copy that doesn't reproduce the dwkitchen's commits from earlier today.

(sorry for the redundant emails, followers!)

bendikrb’s picture

@ #20; where did the default_rules hook implementation go..?

Anonymous’s picture

@ #3, Is there a solution to this problem?

deggertsen’s picture

Marking this as needs work since it seems there are some pending questions that need responses, particularly #21. I have not reviewed this patch, but have been using a rule to force people to log in instead. However, this patch obviously appears to be a better solution so I am anxious to see it get applied and am willing to test once the above questions are answered.

Just in case anybody else would like to force login for a recurring payment order here is my rule:

{ "rules_make_sure_user_is_logged_in_for_recurring_payment" : {
    "LABEL" : "Make sure user is logged in for recurring payment",
    "PLUGIN" : "reaction rule",
    "TAGS" : [ "Support" ],
    "REQUIRES" : [ "rules", "commerce_recurring", "entity" ],
    "ON" : [ "commerce_order_update" ],
    "IF" : [
      { "data_is" : {
          "data" : [ "commerce-order:status" ],
          "op" : "IN",
          "value" : { "value" : {
              "checkout_checkout" : "checkout_checkout",
              "checkout_shipping" : "checkout_shipping",
              "checkout_review" : "checkout_review",
              "checkout_payment" : "checkout_payment"
            }
          }
        }
      },
      { "commerce_recurring_order_contains_recurring_product" : { "commerce_order" : [ "commerce_order" ] } },
      { "user_has_role" : {
          "account" : [ "site:current-user" ],
          "roles" : { "value" : { "1" : "1" } }
        }
      }
    ],
    "DO" : [
      { "redirect" : { "url" : "user\/login?destination=checkout" } },
      { "drupal_message" : {
          "message" : "You must log in before purchasing a recurring product. Please login or create an account before proceeding. Thank you!",
          "type" : "warning"
        }
      }
    ]
  }
}

I'm also cleaning up the issue files so it is clear to newcomers which patch needs review.

mglaman’s picture

Reroll of patch #20 to include rules_default.inc from #18 against current dev.

mglaman’s picture

Just realized function in rules.inc did not match that in rules_default.inc. Fixed in this patch, tested successfully for anonymous users.

mglaman’s picture

Status: Needs work » Needs review

Forgot to mark as needs review.

andyg5000’s picture

Added missing rules_default file required for this to work. Thanks Matt!

andyg5000’s picture

caspervoogt’s picture

Just tested https://drupal.org/files/issues/commerce_cardonfile-save-profiles-anonym... from #28 and is working beautifully.

j2r’s picture

Tested the patch #28 and it is working fine.
Thanks @andyg5000

mglaman’s picture

Status: Needs review » Reviewed & tested by the community

I know this is being used on a production site, @andyg5000 revised and is using, @plethoradesign & @j2r say it works, so marking RBTC!

torgosPizza’s picture

Not sure if it matters, but according to the Drupal docs, if this is being rolled against the 2.x version, shouldn't the update hook be 7200?

Current patch has commerce_cardonfile_update_7105() as the update_n hook.

andyg5000’s picture

Status: Reviewed & tested by the community » Needs work

Good find @torgospizza. We welcome an updated patch if you have time.

mglaman’s picture

Error when applying against latest dev

Hunk #1 succeeded at 439 (offset 336 lines).
Hunk #2 succeeded at 457 (offset 311 lines).
error: while searching for:
  $handler->display->display_options['filters']['status']['field'] = 'status';
  $handler->display->display_options['filters']['status']['value'] = array(
    1 => '1',
    3 => '3',
  );

  /* Display: Page */

error: patch failed: includes/views/commerce_cardonfile.views_default.inc:415
error: includes/views/commerce_cardonfile.views_default.inc: patch does not apply

Reroll in process..

mglaman’s picture

Here is reroll of patch.

  • Rerolled to fix update hook discussed in #32.
  • Rerolled against latest dev, updates to views_default broke patch.
  • Hiding previous patches so it looks cleaner here :)
bojanz’s picture

Status: Needs review » Closed (won't fix)

If you're using cardonfile (and / or have a recurring use case), you shouldn't have anonymous checkout at all.
Even with this patch, if the user is not logged in, he won't be able to see his existing cards, and he might create a duplicate card, which is bad UX.

Also, Heine is right, cardonfile isn't mapping the user id to a remote customer id at the moment (#2259529: Map the uid to a remote customer_id), but it will have to, in order to have correct
address behavior. However, many payment methods (Stripe, Braintree) require you to create a customer before creating the card,
which means that you can't create the card unless you know the user.
Because of this limitation, I am marking the issue as "won't fix".

mindaugasd’s picture

Status: Closed (won't fix) » Active

I think, users should be able to register on his first checkout. And login to his account if he wants to change his plan later on. I think this is very important.

My site is build this way, so I guess, I cannot use beta3 :(

mindaugasd’s picture

Possibly I could create an account programmically before user makes his first checkout :) So if this is really non-fix, even for the first checkout, It is possible to hack around this :)

stefank’s picture

What I have done is to create a new user before completing order(Prepare the transaction for sending to payment gateway). This means that when the checkout is completed then the card is saved to existing user. Also for saving duplicate card I have a rule which checks the user cards before completing checkout based on order email.

mglaman’s picture

After reading bojanz's comment it makes sense. It's up to the site builder to discover a way to create user before checkout finishes so that Card on File can properly save cards. Otherwise the module would be stepping out of its scope. Not to mention it would be providing methods of saving cards which do not work with some payment providers.

bojanz’s picture

Status: Active » Closed (won't fix)

Exactly.
There is nothing stopping you from creating a user account as soon as the user enters his email and hits submit.
Commerce anonymous checkout is a bit broken and in the recurring use case there is no point in working around it (you are creating a relationship
with the customer, he's not a one time visitor. Make him register).
(Kickstart itself doesn't allow anonymous checkout, uses commerce_checkout_redirect-like functionality to force login/register before checkout).

stewart.adam’s picture

Please consider updating the documentation / project page with this fact just to avoid repeat bugs or RFE submissions.

bojanz’s picture

I've added this to the list of documentation that needs writing (currently we have 0 docs)

thermalmusic’s picture

Hi,

I tried running the patch from #35 on the current commerce cardonfile dev version Aug 28, 2014 and get 1 Hunk fail:

patching file commerce_cardonfile.module
Hunk #1 succeeded at 514 (offset -41 lines).
Hunk #2 FAILED at 943.

Then its asking to recreate rules that exist, so not sure if I should proceed:

The next patch would create the file commerce_cardonfile.rules_defaults.inc,
which already exists! Assume -R? [n]

Is there an update for this patch?

I manage a site where anonymous users can join as paid members. They should be able to store a card on their initial purchase, before logging in for the first time. Otherwise, Recurring and Cardonfile seems to work fine barring this one hurdle.

mglaman’s picture

Status: Closed (won't fix) » Active

Discussed with bojanz. This is up for discussion again.

andyg5000’s picture

I've worked through this in the past by creating a custom rules action to create the COF entity after the fact. The action can then be executed after the rule to create a new user account from anonymous order. The action uses the transaction remote id to re-query the payment gateway for transaction info and payment token. This may not always be an option, so we could store the info in the payload/data.

mglaman’s picture

Status: Active » Needs review
drupov’s picture

Well the patch from #35 applies cleanly against commit 075dc82 on 7.x-2.x (from April 16, 2014 17:00) - see the date of the patch. There are 36 commits after that one, so a lot has changed since.

I re-rolled the patch against the latest commit in dev (commit 1579916 on 7.x-2.x from July 23, 2015 16:08) and now it works.

Please review!

MichaelSt’s picture

I need this functionality on my site but I'm not comfortable with coding. If I apply the patch in #48 should I be good to go? Or do I need all the ones before that one.

drupov’s picture

MichaelSt you need only the patch from #48, applied against the latest dev of the module

Cheers

AsadKamil’s picture

Status: Needs review » Reviewed & tested by the community

Patch applied successfully.
Thanks

MichaelSt’s picture

I just made the changes and tested on our live site - it seems to work as intended.

gravit’s picture

Category: Feature request » Bug report
Status: Reviewed & tested by the community » Needs review

After implementing patch #48 -

I have a build (with commerce_stripe) that allows anonymous users to see old/stale card radio buttons in checkout and use "existing cards on file" from previous orders that obviously were not theirs. This is obviously a very bad bug.

I am just starting to debug it - but one issue I see is that all uses of "commerce_cardonfile_load_multiple_by_uid" don't have a behavior set for ignoring UID == 0. This may be a good fail safe to implement.

companyguy’s picture

gravit , I'm seeing that as well in my build. Did you figure out a patch to fix that? This is a pretty major problem if it's doing what I think it's doing...

gravit’s picture

Hey Jason -

My current solution is an edit to the function responsible for loading card data for a user. I've modified it to load no data if you are not logged in. This assumes the proper workflow that if you are not logged in, no existing card data should be available.

Note that I believe I "uncovered" the issue because I was having some instances of checkout failing to correctly assign orders to users after the order completed. The use cases where checkout would "succeed" but rules would fail to finish the process of assinging the order to a new user ID, then the card would be saved in the database with an anonymous UID.

Module Devs: An equally important fix would probably be to make sure that the function that saves the card data removes that entry from the database if it can't find a uid to assign it to. I manually cleared cards out of my db that had a UID of 0.

// dont allow anonymous users to access any card data on file
function commerce_cardonfile_load_multiple_by_uid($uid, $instance_id = NULL, $default_only = FALSE) {
  if ($uid == 0) {
    return;
  }
mglaman’s picture

Status: Needs review » Needs work

Based on #53 this needs work.

mglaman’s picture

Category: Bug report » Feature request

Also, this isn't a bug, it's a feature request :)

torgosPizza’s picture

@gravit: IMO you should submit that patch to the module as a bug report. That seems rather major to me!

Kingdutch’s picture

Status: Needs work » Needs review
FileSize
5.89 KB
641 bytes

- Applied the patch to the latest version of dev
- Implemented fix as suggested by gravit in #55

Anas_maw’s picture

+1 for #59

deggertsen’s picture

Yes please to #59.

torgosPizza’s picture

If the patch is working for people, you might consider setting the status to RTBC :)

Anas_maw’s picture

Status: Needs review » Reviewed & tested by the community

This should be set to RTCB

mirom’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
6.91 KB
1.07 KB

We are experiencing problem, that if user was created by anonymous user and then assigned to existing account, user had multiple default cards. Attached patch is solving this problem.

deggertsen’s picture

Status: Needs review » Reviewed & tested by the community

Good catch mirom. I think your change is good so I'm setting back to RTBC.

  • mglaman committed 5c06260 on 7.x-2.x
    Issue #1553468 by mglaman, stewart.adam, andyg5000, univate, nicksanta,...
mglaman’s picture

Status: Reviewed & tested by the community » Fixed

Thank you everyone! I went and credited all who participated, because I'm sure there was lots of testing on your own ends when integrating. WOOT!

attisan’s picture

is it only me, or did this property implementation not make it into the entity information, hence commerce_cardonfile_entity_property_info nor the entity class CommerceCardOnFile?

would be great in order to be able to use entity_metadata_wrapper.

mglaman’s picture

attisan, not sure which property. Can you open a follow up issue.

attisan’s picture

Status: Fixed » Needs review
FileSize
1.26 KB

I have attached a patch reflecting (fixing) the missing order_id.

hth
attisan

ibuildit’s picture

The latest dev does not work with the allow anonymous user patch, which appears to be needed.

The checkout crashes.... how ever I can highly recommend to use:

ajax_register
email_registration
Then you get a very smooth and slick registration form, just add email, you get logged in, redirected to where you want (i.e. checkout with a product already added via rules, or product offer page, and from there start a subscription. Then card on file will work just fine! :)

I'm running beta5 with patch in #23. Works GREAT. (only combo that appears to work with Recurring, Card on File and Braintree, at the moment)

deardagny’s picture

The patch in #59 combined with #70 worked for me on latest dev.

@ibuildit – Checkout crashed for me at first. Then I realized I needed to run update.php in order to create the new order_id column included in the patch.

Seems to be working fine for me with one minor exception. Using Stripe, the purchaser's email doesn't make it to the merchant correctly on order creation. See this screenshot – the bottom order is before the patches, the top one is after. Not sure if this is a Stripe API issue or something that will be a problem across merchants. Looking into it now.