Ubercart 3: Programmatically submitting an order from Canada

I’ve been working on a project that, among other things, involves purchasing a particular item with one click: the order is created, payment information is added (how it’s stored is not important here) and the order is submitted, all automatically.

I’ve been working on a project that, among other things, involves purchasing a particular item with one click: the order is created, payment information is added (how it’s stored is not important here) and the order is submitted, all automatically. After a bit of flailing about on my own, I found this post on the Ubercart forums explaining how it’s done, and it totally saved my bacon. In short:

  1. add item to cart using uc_cart_add_item()
  2. populate and submit form ‘uc_cart_checkout_form’
  3. if things are okay, populate and submit form ‘uc_cart_checkout_review_form’ (which actually submits the order)

However, it’s not quite complete. 3 things are missing, in fact, which took me a bit of experimentation to figure out:

1) You need to enter the credit card info in the form_state array, not just in $_POST

The post says to do this:

// Define the credit card information in the $_POST directly.
  // See uc_payment_method_credit($op = 'cart-process') for why this is necessary.
 

$_POST['cc_number']    = 'XXXXXXXXXXXXXXXX';
  $_POST['cc_exp_month'] = 'XX';
  $_POST['cc_exp_year']  = 'XXXX';
  $_POST['cc_cvv']       = 'XXX';

But that didn’t work for me. It looks like you also need to enter it in the form_state, like so

$uc_cart_checkout_form_state['values']['panes']['payment']['details']['cc_number'] = 'XXXXXXXXXXXXXXXX';
	$uc_cart_checkout_form_state['values']['panes']['payment']['details']['cc_exp_month'] = 'XX';
	$uc_cart_checkout_form_state['values']['panes']['payment']['details']['cc_exp_year'] = 'XXXX';
	$uc_cart_checkout_form_state['values']['panes']['payment']['details']['cc_cvv'] = 'XXX';

2) Entering an address not in the store default country

(This post title says “Canada” because the default default store country is the US and that’s what I was testing with.)

Here’s the problem: if you try to enter a Canadian province in the $uc_cart_checkout_form_state['values']['panes']['billing']['billing_zone'] field, the ‘uc_cart_checkout_form’ form submission will fail with the following error message:

An illegal choice has been detected. Please contact the site administrator

Why? Because when the form is built, the allowed Zone values are based on the selected country. If all you can select are US States but you enter “BC” anyway, Drupal will not accept it, and that’s what the error message means.

Here’s the solution: the billing / shipping address panes are built based on the order country, and by default uc_order_new() creates an order with the billing and shipping countries set to the store default. So, all you need to do is set that field to a different value:

//after $order = uc_order_new()
$order->billing_country = 124; // Canada, or same value entered in $uc_cart_checkout_form_state['values']['panes']['billing']['billing_country']
//do the same for shipping country

This will populate the dropdown with Canadian provinces, and allow you to enter a province in $uc_cart_checkout_form_state['values']['panes']['billing']['billing_zone']

3) If you’re not starting from the cart, you need to change the redirection checks

The code checks if drupal_form_submit('uc_cart_checkout_form', $uc_cart_checkout_form_state) finished correctly like this:

if ($uc_cart_checkout_form_state['redirect'] == 'cart/checkout/review') {
    // We're good to move forward!!

And similarly for drupal_form_submit('uc_cart_checkout_review_form', $uc_cart_checkout_review_form_state)

Function uc_cart_checkout_form_submit($form, &$form_state) (defined in uc_cart/uc_cart.pages.inc) sets the form_state redirect:

function uc_cart_checkout_form_submit($form, &$form_state) {
  if ($form_state['checkout_valid'] === FALSE) {
    $url = $form_state['storage']['base_path'] . '/checkout';
  }
  else {
    $url = $form_state['storage']['base_path'] . '/checkout/review';
    $_SESSION['uc_checkout'][$form_state['storage']['order']->order_id]['do_review'] = TRUE;
  }

  unset($form_state['checkout_valid']);

  $form_state['redirect'] = $url;
}

If you’re starting from the cart, then $form_state['storage']['base_path'] will be equal to ‘cart’. But if your goal is a 1-click purchase, chances are you’ll be starting on a completely different page, and the redirect will be different as well. So you’ve got a couple of options:

  • Change $form_state['storage']['base_path'] somehow (is that possible?)
  • If you know the path of the page you started on, use it in the check:
    if ($uc_cart_checkout_form_state['redirect'] == 'yourpath/checkout/review') {
        // We're good to move forward!!
    
  • Be a little more flexible, and only check the last part:
    if (strpos($uc_cart_checkout_form_state['redirect'],'checkout/review') !== FALSE) {
        // We're good to move forward!!