I've been spending some time today looking at adding a 'create' cardonfile callback for the HostedPCI module. I've got a site that sells subscription content that will soon be processing a number of monthly renewals. The site uses HostedPCI and I'd like to give users a way to add a new card on file to be used by future recurring payments. Currently the only way for them to add a new card is to make a purchase and elect to save the card information.

Since adding support in the HostedPCI module for Card on File 2.x (which started storing cards as entities), these two functions in the HostedPCI module became orphaned, as best I can tell:
commerce_hosted_pci_form_commerce_cardonfile_update_form_alter
commerce_hosted_pci_form_commerce_cardonfile_update_form_validate
These functions seem to have been used to alter the update form for a card on file with the 1.x version of that module, but the form id changed from commerce_cardonfile_update to commerce_cardonfile_card in the update from Card on File 1.x to 2.x. The result of this is when trying to update a stored card using the HostedPCI payment method, no HostedPCI alterations to the form are made and only the card owner and expiration date can be updated.

One option for allowing a user to enter a new card would be to go back and overhaul the update callback and form alteration to show the Hosted PCI form commerce_hosted_pci_submit_form, similar to the way things were done with those two functions above. However, it seems like this would require them to enter the entire card number and CVV code each time they wanted to update their card information.

A second option which I think is a little cleaner is to keep the update operation in its current state and allow users to update the name and expiration date for an existing card only. Then implement the 'create' callback for Card on File as a way for them to enter a new card that can be used for future orders. You'd probably also add a note to the update form that said something like 'if you need to enter a new card go here instead'.

I'm assuming that either way we'd want to verify with the payment gateway that the new card information is good before saving it. Is there a standard way to do that? I was thinking of doing an auth for a small amount (even $0 if possible) and then voiding it immediately if successful.

Looking for some maintainer feedback on the above before I start implementing.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

JulienD’s picture

Hi ajm8372

After several tests I decided to keep the COF administration simple for the customer by allowing them to edit or delete only. The edition is pretty simple, the customer is only able to change the card name and the validity code.

Somewhere on a branch I've started to implement the creation form but I never finished it. Do you think lot of users add cards through this interface instead of during the checkout process ?

Feel free to add a patch if you want to contribute the creation form.

rickmanelius’s picture

Bump. I've reached out to HostedPCI to make sure their API would support a card update without a transaction.

ajm8372’s picture

Hi Rick
I can confirm that this is possible with the cardonfile_create callback to create a new card for a user and get the token from Hosted PCI. I ended up forking the HPCI module to add this but I can work on getting a patch posted here next week when I get back in the office. For my application, we decided not to initiate a purchase of $0 to validate the new card, we just save the token we get back.
The primary function of Commerce on our site is to sell monthly recurring subscriptions and process the renewals, so being able to create a new card without making a purchase is pretty much a requirement.
Apologies for the delayed response - I've been out on vacation.

rickmanelius’s picture

Hi @ajm8372. That's our use case as well (payment plans up to 12 months with the need to update the card if it expires, a new one is needed, etc).

If you can supply a patch, another developer on my team can verify it against the project I just mentioned. And thanks so much for the followup!

rickmanelius’s picture

Hi @ajm8372. Just a friendly followup. Is this still a patch that we could get our hands on for testing purposes?

ajm8372’s picture

FileSize
15.78 KB

I got a patch created today with the changes for new card creation. This also includes some updates to allow users to update the billing address of an existing card. I have not tested this at all yet, but I plan to get to that tomorrow.

ajm8372’s picture

FileSize
21.34 KB

Here's an updated patch after some testing I did this morning. I built up a small sandbox with Commerce, Commerce Recurring, Commerce Cardonfile, and Commerce HPCI. I configured HPCI to connect to my Auth.net test gateway. I was able to add a card and set it as a default, and that card was used when a Recurring order was triggered by Cron.

What's not working:
A customer profile is now associated with each cardonfile. This is created either by copying the profile from the order being checked out if a new card is created that way, or via the fields in the create card page if the user is just adding a card (i.e. not performing any operation on an order). The address can be changed using the cardonfile update form.

Obviously once an order has been completed we don't want to change the customer profile associated with it, but customers will need to updated the address associated with their card any time they move, etc. That updated address must be used when Commerce Recurring creates an order via cron, because the address is usually required for AVS verification.

It appears that with this patch, the old address is still being used when Commerce Recurring creates an order (it just copies the address from the previous order). This is working correctly in my forked version of HPCI, so I probably missed something.

Will spend more time debugging tomorrow, but I think this is ready for you to test Rick.

ajm8372’s picture

FileSize
22.72 KB

Found what was causing the issue in my previous comment. This new patch fixes the issue where new recurring orders were using the billing profile from the original recurring order. They now use the billing profile from the card instead so the most current address information is used to process.

rickmanelius’s picture

Thank you Jeremy! We actually have a client that needs this functionality for their existing recurring billing solution, so we will happily help test/QA and provide feedback. Based on our current schedule, it looks like tomorrow is when we'll get a chance to start.

arknoll’s picture

Status: Active » Needs review
FileSize
22.54 KB

Patch did not apply for me on the latest dev release. Here is the re-rolled patch. I'm reviewing now.

- Alex

arknoll’s picture

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

Found only one bug:

+function commerce_hosted_pci_cardonfile_update_form_submit($form, &$form_state) {
+
+ $card_data = $form_state['card'];

to

+function commerce_hosted_pci_cardonfile_update_form_submit($form, &$form_state) {
+
+ $card_data = $form_state['card_data'];

Moving to RTBC

-Alex

rickmanelius’s picture

I also wanted to chime in to state the the patch in #11 has been tested and is moving into production for one of our clients. Therefore, we believe it's all set to include in the official release of this module.

@JulienD. I'd be happy to discuss in IRC.

JulienD’s picture

Hi guys,

First, thanks for the work you did! Sorry to haven't participated to the issue. Seems you have done a patch that do the job :)

Right now, I'm not able to test it and merge it to the project because I'm busy and close to my vacation but I'll have a glance on it early in September. This means you have almost a month to ensure this patch work ;)

@rickmanelius and other, I'm available when you want (CEST)

rickmanelius’s picture

Hi Julien,

Understood. Enjoy the vacation! In the meantime, we'll just keep track of the patch until it makes it upstream.

-Rick

ajm8372’s picture

Back to the change in #11:

I'm not sure how that works..

The HPCI Card Update callback calls the commerce_cardonfile_card_form() function:

function commerce_hosted_pci_cardonfile_update_form($form, &$form_state, $op, $card_data) {
  $form_state['op'] = $op;
  
  //Get the card form & make sure the 'credit_card' fieldset is shown first
  $form = commerce_cardonfile_card_form($form, $form_state, $op, $card_data);
  $form['credit_card']['#weight'] = -10;
  $form['submit']['#weight'] = 20;

  ...
}

Meanwhile, in the commerce_cardonfile_card_form() function, the card data is saved to $form_state['card'], not $form_state['card_data']:

function commerce_cardonfile_card_form($form, &$form_state, $op, $card) {
  // Load the credit card helper functions from the Payment module.
  module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');

  // Pass along information to the validate and submit handlers.
  $form_state['card'] = $card;
  $form_state['op'] = $op;
  ...
}

I hooked up the debugger and found that in the commerce_hosted_pci_cardonfile_update_form() function, $form_state['card'] is set, but $form_state['card_data'] isn't.

However I'm also not sure how both of you guys could get this to work using $form_state['card_data'], so something doesn't make sense here...

ajm8372’s picture

Status: Reviewed & tested by the community » Needs review

Well I found what might be our problem - check out commit f3774f5 for the Card on File module:
http://cgit.drupalcode.org/commerce_cardonfile/commit/?id=f3774f5c249f1e...

Looks like between beta 2 and beta 3 they changed a lot of the variable names from 'card_data' to 'card'. The line in question above changed from
$form_state['card_data'] = $card
to
$form_state['card'] = $card

Can you guys confirm which version of Card on File you used to test this patch? I'm on beta5.

Moving back to Needs review until we confirm whether or not something needs to change.

ajm8372’s picture

Bump

@rickmanelius @arknoll - Can you confirm the version of Card on File you used for your testing?

rickmanelius’s picture

Hi Jeremy. We're currently using 7.x-2.0-beta4.

ajm8372’s picture

Interesting. It seems to me that the card data should be showing up as $form_state['card'], rather than $form_state['card_data'] if you're using beta 4 based on that commit above.

I'll take a look again when I get back to the office on Monday.

rickmanelius’s picture

Hi Jeremy. I'll confirm with Alex in the morning, but that's what I'm seeing in the repo (unless I'm looking at the wrong branch).

ajm8372’s picture

I applied the patch in #11 and it does not work for me. Just to confirm, I'm using Card on File beta 4. After applying the patch in #11 and trying to edit an existing card, I get the following warnings & exception:

  • Notice: Undefined index: card_data in commerce_hosted_pci_cardonfile_update_form_submit() (line 1107 of /Users/jmeier/Sites/drupal7/sites/all/modules/commerce_hosted_pci/commerce_hosted_pci.module).
  • Notice: Trying to get property of non-object in commerce_hosted_pci_cardonfile_update_form_submit() (line 1109 of /Users/jmeier/Sites/drupal7/sites/all/modules/commerce_hosted_pci/commerce_hosted_pci.module).
  • Warning: Creating default object from empty value in commerce_hosted_pci_cardonfile_update_form_submit() (line 1111 of /Users/jmeier/Sites/drupal7/sites/all/modules/commerce_hosted_pci/commerce_hosted_pci.module).
  • PDOException: SQLSTATE[HY000]: General error: 1364 Field 'payment_method' doesn't have a default value: INSERT INTO {commerce_cardonfile} (card_name, card_exp_month, card_exp_year) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2); Array ( [:db_insert_placeholder_0] => Jeremy Meier [:db_insert_placeholder_1] => 7 [:db_insert_placeholder_2] => 2017 ) in drupal_write_record() (line 7207 of /Users/jmeier/Sites/drupal7/includes/common.inc).

Debugging, I found that when the commerce_hosted_pci_cardonfile_update_form_submit() function is called, $form_state['card_data'] is not set, while $form_state['card'] is. So after line 1107, the value of $card_data is NULL. So the warnings & exception above make sense. The only data that exists for $card_data prior to saving on line 1118 is card_name, card_month, card_year, and the commerce_cardonfile_profile field.

crazysix’s picture

@ajm8372 The problem you mentioned still exists with $form_state['card'] versus $form_state['card_data']. I think the appropriate action here would probably be to either use the latest version of commerce_cardonfile, or we need to account for both possibilities.

crazysix’s picture

I updated the patch to handle either version of cardonfile.

This still seems buggy to me. I cannot get the address fields to update when I change countries...