Skip to content

Instantly share code, notes, and snippets.

@wluisi
Created September 2, 2016 20:20
Show Gist options
  • Save wluisi/6707ddc285199fdaef65327365248890 to your computer and use it in GitHub Desktop.
Save wluisi/6707ddc285199fdaef65327365248890 to your computer and use it in GitHub Desktop.
Programmatically Create Drupal Commerce Order
<?php
/**
* Creates an order for the given user containing the product with the given id.
* This example may be extended easily to create an order containing several
* products, a shipping and so far.
*
* Returns a commerce order that's now saved in the database with the status
* "processing and can be finished by an administrator in the UI.
* Throws an exception, if something is wrong.
*
* @param integer $uid The numeric user id, e.g. "1"
* @param integer $product_id The numeric product id (NOT SKU! ;))
* @return CommerceORder
* @throws Exception
*/
function my_module_api_create_order($uid, $product_id) {
if (empty($uid) || !is_numeric($uid)) {
throw new Exception('User ID may not be empty and must be numeric!');
}
$user = user_load($uid);
if (empty($product_id) || !is_numeric($product_id)) {
throw new Exception('Product ID may not be empty and must be numeric!');
}
// Use a transaction to ensure that no broken order is created!
$transaction = db_transaction();
try {
// Create the new order in checkout; you might also check first to
// see if your user already has an order to use instead of a new one.
$order = commerce_order_new($user->uid, 'checkout_checkout');
// Save the order to get its ID.
commerce_order_save($order);
// Get the order wrapper which provides helper functionality
$order_wrapper = entity_metadata_wrapper('commerce_order', $order);
// Add address profiles.
// IMPORTANT! The address MUST be set before the line item to
// correctly determine the country of supply.
// IMPORTANT2: This address setup is made based on single fields in the user
// account. If you already use an address field in the user account
// this is much simpler and you should have a look at the
// commerce_extra module for address fields ;)
// Billing
// Check that we have at least all REQUIRED fields!
if (empty($user->field_firstname[LANGUAGE_NONE][0]['value'])) {
throw new Exception('field_firstname may not be empty!');
}
if (empty($user->field_lastname[LANGUAGE_NONE][0]['value'])) {
throw new Exception('field_lastname may not be empty!');
}
if (empty($user->field_city[LANGUAGE_NONE][0]['value'])) {
throw new Exception('field_city may not be empty!');
}
if (empty($user->field_zip[LANGUAGE_NONE][0]['value'])) {
throw new Exception('field_zip may not be empty!');
}
if (empty($user->field_street[LANGUAGE_NONE][0]['value'])) {
throw new Exception('field_street may not be empty!');
}
if (empty($user->field_country[LANGUAGE_NONE][0]['value'])) {
throw new Exception('field_country may not be empty!');
}
// We have to get or create the first order profile of the users to set the values into.
// We use a helper function for that!
$billing = _my_module_api_create_order_get_first_customer_profile('billing', $user);
$customer_profile_billing_array = array(
'country' => isset($user->field_country[LANGUAGE_NONE][0]['value']) ? $user->field_country[LANGUAGE_NONE][0]['value'] : NULL, // DE
'name_line' => NULL, // Testperson
'first_name' => isset($user->field_firstname[LANGUAGE_NONE][0]['value']) ? $user->field_firstname[LANGUAGE_NONE][0]['value'] : NULL, // Max
'last_name' => isset($user->field_lastname[LANGUAGE_NONE][0]['value']) ? $user->field_lastname[LANGUAGE_NONE][0]['value'] : NULL, // Mustermann
'organisation_name' => isset($user->field_firma[LANGUAGE_NONE][0]['value']) ? $user->field_firma[LANGUAGE_NONE][0]['value'] : NULL, // Muster AG
'administrative_area' => NULL,
'sub_administrative_area' => NULL,
'locality' => isset($user->field_wohnort[LANGUAGE_NONE][0]['value']) ? $user->field_wohnort[LANGUAGE_NONE][0]['value'] : NULL, // Musterstadt
'dependent_locality' => NULL,
'postal_code' => isset($user->field_zip[LANGUAGE_NONE][0]['value']) ? $user->field_zip[LANGUAGE_NONE][0]['value'] : NULL, // 32457
'thoroughfare' => isset($user->field_street[LANGUAGE_NONE][0]['value']) ? $user->field_street[LANGUAGE_NONE][0]['value'] : NULL, // Musterstraße 10
'premise' => NULL,
'sub_premise' => NULL,
'data' => NULL,
);
// Set the values in the profile entity and attache it to the order
$billing->commerce_customer_address[LANGUAGE_NONE][0] = $customer_profile_billing_array;
commerce_customer_profile_save($billing);
$order->commerce_customer_billing[LANGUAGE_NONE][0]['profile_id'] = $billing->profile_id;
// This is only required if you use the commerce_vat module and you want to
// set the vat id programmatically with implications on the price calculation!
if (!empty($user->field_vat_number[LANGUAGE_NONE][0]['value'])) {
$eu_vat_rc = _my_module_api_create_order_get_first_customer_profile('eu_vat_rc', $user);
$customer_profile_vat_array = array(
'value' => !empty($user->field_vat_number[LANGUAGE_NONE][0]['value']) ? $user->field_vat_number[LANGUAGE_NONE][0]['value'] : NULL,
);
$eu_vat_rc->commerce_vat_number[LANGUAGE_NONE][0] = $customer_profile_vat_array;
commerce_customer_profile_save($eu_vat_rc);
$order->commerce_customer_eu_vat_rc[LANGUAGE_NONE][0]['profile_id'] = $eu_vat_rc->profile_id;
}
// IMPORTANT: If we also use shipping address, this would be the
// place to set it here like we already did in "billing"!
// Save the order again to save the address attached.
$order_wrapper->save();
// Load whatever product represents the item the customer will be
// paying for and create a line item for it.
$product = commerce_product_load($product_id);
// Create a new line item with multiplicity = 1
$line_item = commerce_product_line_item_new($product, 1, $order->order_id);
// You may set line item properties here.
// Calculate the sell price!
rules_invoke_event('commerce_product_calculate_sell_price', $line_item);
// Save the line item
commerce_line_item_save($line_item);
// Attach the line item to the order using the order wrapper.
$order_wrapper->commerce_line_items[] = $line_item;
$order_wrapper->save();
// Now calculate the total price and save the order again.
commerce_order_calculate_total($order);
$order_wrapper->save();
// To create the payment we need some information from the order:
$total = $order_wrapper->commerce_order_total->amount->value();
$currency_code = $order_wrapper->commerce_order_total->currency_code->value();
$charge = array(
'amount' => $total,
'currency_code' => $currency_code,
);
// The following is an example for payment methods commerce_directdebit and bank_transfer.
// You may use this as example for other payment types!
// SEPA if we have the required users direct debit fields, otherwise bank transfer!
if (!empty($user->field_accountholder[LANGUAGE_NONE][0]['value']) && !empty($user->field_bic[LANGUAGE_NONE][0]['value']) && !empty($user->field_iban[LANGUAGE_NONE][0]['value'])) {
// SEPA
$payment_method = array(
'instance_id' => 'commerce_directdebit|commerce_payment_commerce_directdebit',
);
commerce_directdebit_transaction($payment_method, $order, $charge, $user->field_accountholder[LANGUAGE_NONE][0]['value'], '', '', 1, $user->field_bic[LANGUAGE_NONE][0]['value'], $user->field_iban[LANGUAGE_NONE][0]['value']);
}
else {
// BANK TRANSFER
$payment_method = array(
'instance_id' => 'bank_transfer|commerce_payment_bank_transfer',
);
commerce_bank_transfer_transaction($payment_method, $order, $charge);
}
// Set the payment id in the order array to make it available for invoicing
// (no idea why we have to do that manually here. Perhaps there's a better way?)
$order->data['payment_method'] = $payment_method['instance_id'];
$order_wrapper->save();
// Update the status to processing to allow manual finishing later on.
commerce_order_status_update($order, 'processing');
// No explicit transaction commit wanted by drupal...
} catch (Exception $e) {
// Something went wrong! We don't want to save a broken order to the database
// at all. So let's roll back the transaction:
$transaction->rollback();
// Add some information to the exception for easier debugging in an outer try / catch!
$e->user_id = $uid;
$e->product_id = $product_id;
// We've done what we could. Re-throw the exception and let an outer
// try/catch handle it for error logging.
throw $e;
}
// Yeah, the order was created successfully. Provide it to the outer call! :)
return $order;
}
/**
* Helper function to get the users already existing (as implemented: first)
* commerce customer profile and return it.
* If the user has no customer profile yet, create a new one and return that.
*
* @param string $type The type of the customer profile e.g. "billing".
* @param stdClass $user The user object.
* @return stdClass The CommerceCustomerProfile object.
*/
function _my_module_api_create_order_get_first_customer_profile($type, stdClass $user) {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'commerce_customer_profile')
->propertyCondition('uid', $user->uid)
->propertyCondition('type', $type);
$results = $query->execute();
if (!empty($results['commerce_customer_profile'])) {
// Profile already exists. Load!
$profile_info = reset($results['commerce_customer_profile']);
return commerce_customer_profile_load($profile_info->profile_id);
}
else {
// No profile yet. Create one!
return commerce_customer_profile_new($type, $user->uid);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment