'Stripe PHP Library',
'vendor url' => 'http://stripe.com',
'download url' => 'https://github.com/stripe/stripe-php/releases',
'download file url' => 'https://github.com/stripe/stripe-php/archive/v3.20.0.tar.gz',
'version arguments' => array(
'file' => 'VERSION',
'pattern' => '/(\d+\.\d+\.\d+)/',
),
'versions' => array(
'2.2.0' => array(
'files' => array(
'php' => array(
'lib/Util/RequestOptions.php',
'lib/Util/Set.php',
'lib/Util/Util.php',
'lib/Object.php',
'lib/ApiResource.php',
'lib/Account.php',
'lib/ExternalAccount.php',
'lib/AlipayAccount.php',
'lib/ApiRequestor.php',
'lib/ApplicationFee.php',
'lib/ApplicationFeeRefund.php',
'lib/AttachedObject.php',
'lib/SingletonApiResource.php',
'lib/Balance.php',
'lib/BalanceTransaction.php',
'lib/BankAccount.php',
'lib/BitcoinReceiver.php',
'lib/BitcoinTransaction.php',
'lib/Card.php',
'lib/Charge.php',
'lib/Collection.php',
'lib/Coupon.php',
'lib/Customer.php',
'lib/Error/Base.php',
'lib/Error/Api.php',
'lib/Error/ApiConnection.php',
'lib/Error/Authentication.php',
'lib/Error/Card.php',
'lib/Error/InvalidRequest.php',
'lib/Error/RateLimit.php',
'lib/Event.php',
'lib/FileUpload.php',
'lib/HttpClient/ClientInterface.php',
'lib/HttpClient/CurlClient.php',
'lib/Invoice.php',
'lib/InvoiceItem.php',
'lib/Plan.php',
'lib/Recipient.php',
'lib/Refund.php',
'lib/Stripe.php',
'lib/Subscription.php',
'lib/Token.php',
'lib/Transfer.php',
'lib/TransferReversal.php',
),
),
'stripe_api_version' => '2015-06-15',
),
'3.0' => array(
'files' => array(
'php' => array(
'init.php',
),
),
'stripe_api_version' => '2016-03-07',
),
),
);
return $libraries;
}
/**
* Implements hook_payment_gateway to register this payment gateway
* @return array
*/
function uc_stripe_uc_payment_gateway() {
$gateways = array();
$gateways[] = array(
'id' => 'uc_stripe',
'title' => t('Stripe Gateway'),
'description' => t('Process card payments using Stripe JS.'),
'settings' => 'uc_stripe_settings_form',
'credit' => 'uc_stripe_charge',
);
return $gateways;
}
/**
* Implements hook_recurring_info() to integrate with uc_recurring
*
* @return mixed
*/
function uc_stripe_recurring_info() {
$items['uc_stripe'] = array(
'name' => t('Stripe'),
'payment method' => 'credit',
'module' => 'uc_recurring',
'fee handler' => 'uc_stripe',
'process callback' => 'uc_stripe_process',
'renew callback' => 'uc_stripe_renew',
'cancel callback' => 'uc_stripe_cancel',
'own handler' => FALSE,
'menu' => array(
'charge' => UC_RECURRING_MENU_DEFAULT,
'edit' => UC_RECURRING_MENU_DEFAULT,
'cancel' => UC_RECURRING_MENU_DEFAULT,
),
);
return $items;
}
/**
* Implements hook_form_FORMID_alter() to change the checkout form
* All work as a result is done in JS, the ordinary post does not happen.
*
* @param $form
* @param $form_state
* @param $form_id
*/
function uc_stripe_form_uc_cart_checkout_form_alter(&$form, &$form_state) {
$payment_form = &$form['panes']['payment']['details'];
$payment_form['stripe_nojs_warning'] = array(
'#type' => 'item',
'#markup' => '' . t('Sorry, for security reasons your card cannot be processed because Javascript is disabled in your browser.') . '',
'#weight' => -1000,
);
// Powered by Stripe (logo from https://stripe.com/about/resources)
if (variable_get('uc_stripe_poweredby', FALSE)) {
$payment_form['field_message'] = array(
'#type' => 'item',
'#markup' => "
",
'#weight' => 1,
);
}
$payment_form['stripe_token'] = array(
'#type' => 'hidden',
'#default_value' => 'default',
'#attributes' => array(
'id' => 'edit-panes-payment-details-stripe-token',
),
);
// Prevent form Credit card fill and submission if javascript has not removed
// the "disabled" attributes..
// If JS happens to be disabled, we don't want user to be able to enter CC data.
// Note that we can't use '#disabled', as it causes Form API to discard all input,
// so use the disabled attribute instead.
$form['panes']['payment']['details']['cc_number']['#attributes']['disabled'] = 'disabled';
if (empty($form['actions']['continue']['#attributes'])) {
$form['actions']['continue']['#attributes'] = array();
}
$form['actions']['continue']['#attributes']['disabled'] = 'disabled';
$apikey = variable_get('uc_stripe_testmode', TRUE) ? check_plain(variable_get('uc_stripe_api_key_test_publishable', '')) : check_plain(variable_get('uc_stripe_api_key_live_publishable', ''));
// Add custom JS and CSS
$form['#attached']['js']['https://js.stripe.com/v2/'] = array('type' => 'external');
$form['#attached']['js'][] = array('data' => "Stripe.setPublishableKey('$apikey')", 'type' => 'inline');
$form['#attached']['js'][] = drupal_get_path('module', 'uc_stripe') . '/js/uc_stripe.js';
$form['#attached']['css'][] = drupal_get_path('module', 'uc_stripe') . '/css/uc_stripe.css';
// Add custom submit which will do saving away of token during submit.
$form['#submit'][] = 'uc_stripe_checkout_form_customsubmit';
// Add a section for stripe.js error messages (CC validation, etc.)
$form['panes']['messages'] = array(
'#markup' => "
",
);
if (uc_credit_default_gateway() == 'uc_stripe') {
if (variable_get('uc_stripe_testmode', TRUE)) {
$form['panes']['testmode'] = array(
'#prefix' => "",
'#markup' => t("Test mode is ON for the Stripe Payment Gateway. Your card will not be charged. To change this setting, edit the !link", array('!link' => l("Stripe settings", "admin/store/settings/payment/method/credit"))),
'#suffix' => "
",
);
}
}
}
/**
* Implements hook_order_pane to provide the stripe customer info
*
* @return array
*/
function uc_stripe_uc_order_pane() {
$panes[] = array(
'id' => 'uc_stripe',
'callback' => 'uc_stripe_order_pane_stripe',
'title' => t('Stripe Customer Info'),
'desc' => t("Stripe Information"),
'class' => 'pos-left',
'weight' => 3,
'show' => array('view', 'edit'),
);
return $panes;
}
/**
* Implements hook_uc_checkout_complete()
*
* Saves stripe customer_id into the user->data object
*
* @param $order
* @param $account
*/
function uc_stripe_uc_checkout_complete($order, $account) {
if ($order->payment_method == "credit") {
// Pull the stripe customer ID from the session.
// It got there in uc_stripe_checkout_form_customsubmit()
$stripe_customer_id = $_SESSION['stripe']['customer_id'];
$loaded_user = user_load($account->uid);
user_save($loaded_user, array('data' => array('uc_stripe_customer_id' => $stripe_customer_id)));
}
}
/**
* Implements uc_order_pane_callback() specified in 'callback' of
* uc_stripe_uc_order_pane()
*
* Returns text for customer id for order pane.
*
* @param $op
* @param $order
* @param $form
* @param $form_state
* @return array
*/
function uc_stripe_order_pane_stripe($op, $order, &$form = NULL, &$form_state = NULL) {
switch ($op) {
case 'view':
$stripe_customer_id = _uc_stripe_get_customer_id($order->uid);
$output = t("Customer ID: ") . $stripe_customer_id;
return array('#markup' => $output);
default:
return;
}
}
/**
* Provide configuration form for uc_stripe
*
* @return mixed
*/
function uc_stripe_settings_form() {
$form['uc_stripe_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Stripe settings'),
);
$form['uc_stripe_settings']['uc_stripe_api_key_test_secret'] = array(
'#type' => 'textfield',
'#title' => t('Test Secret Key'),
'#default_value' => variable_get('uc_stripe_api_key_test_secret', ''),
'#description' => t('Your Development Stripe API Key. Must be the "secret" key, not the "publishable" one.'),
);
$form['uc_stripe_settings']['uc_stripe_api_key_test_publishable'] = array(
'#type' => 'textfield',
'#title' => t('Test Publishable Key'),
'#default_value' => variable_get('uc_stripe_api_key_test_publishable', ''),
'#description' => t('Your Development Stripe API Key. Must be the "publishable" key, not the "secret" one.'),
);
$form['uc_stripe_settings']['uc_stripe_api_key_live_secret'] = array(
'#type' => 'textfield',
'#title' => t('Live Secret Key'),
'#default_value' => variable_get('uc_stripe_api_key_live_secret', ''),
'#description' => t('Your Live Stripe API Key. Must be the "secret" key, not the "publishable" one.'),
);
$form['uc_stripe_settings']['uc_stripe_api_key_live_publishable'] = array(
'#type' => 'textfield',
'#title' => t('Live Publishable Key'),
'#default_value' => variable_get('uc_stripe_api_key_live_publishable', ''),
'#description' => t('Your Live Stripe API Key. Must be the "publishable" key, not the "secret" one.'),
);
$form['uc_stripe_settings']['uc_stripe_testmode'] = array(
'#type' => 'checkbox',
'#title' => t('Test mode'),
'#description' => 'Testing Mode: Stripe will use the development API key to process the transaction so the card will not actually be charged.',
'#default_value' => variable_get('uc_stripe_testmode', TRUE),
);
$form['uc_stripe_settings']['uc_stripe_poweredby'] = array(
'#type' => 'checkbox',
'#title' => t('Powered by Stripe'),
'#description' => 'Show "powered by Stripe" in checkout.',
'#default_value' => variable_get('uc_stripe_poweredby', FALSE),
);
$form['uc_stripe_settings']['uc_stripe_metadata_titles'] = array(
'#type' => 'checkbox',
'#title' => t('Metadata: Title'),
'#description' => t('Include order item title(s) in Stripe metadata.'),
'#default_value' => variable_get('uc_stripe_metadata_titles', FALSE),
);
$form['uc_stripe_settings']['uc_stripe_metadata_models'] = array(
'#type' => 'checkbox',
'#title' => t('Metadata: Model'),
'#description' => t('Include item model(s) (SKU(s)) in Stripe metadata.'),
'#default_value' => variable_get('uc_stripe_metadata_models', FALSE),
);
return $form;
}
/**
* Implements hook_form_FORMID_alter()
*
* Add validation function for stripe settings
*
* @param $form
* @param $form_state
*/
function uc_stripe_form_uc_payment_method_settings_form_alter(&$form, &$form_state) {
$form['#validate'][] = 'uc_stripe_settings_form_validate';
}
/**
* Validation function and normalize keys (trim spaces)
*
* @param $form
* @param $form_state
*/
function uc_stripe_settings_form_validate($form, &$form_state) {
$elements = array('uc_stripe_api_key_test_secret', 'uc_stripe_api_key_test_publishable', 'uc_stripe_api_key_live_secret', 'uc_stripe_api_key_live_publishable', );
if ($form_state['values']['uc_pg_uc_stripe_enabled']) {
foreach ($elements as $element_name) {
$form_state['values'][$element_name] = _uc_stripe_sanitize_key($form_state['values'][$element_name]);
if (!_uc_stripe_validate_key($form_state['values'][$element_name])) {
form_set_error($element_name, t('@name does not appear to be a valid stripe key', array('@name' => $element_name)));
}
}
}
// Make sure they haven't tried to validate credit card numbers, as uc_stripe will not provide a real one.
if (!empty($form_state['values']['uc_credit_validate_numbers'])) {
form_set_error('uc_credit_validate_numbers', t('When used with Ubercart Stripe, "Validate credit card number at checkout" must be unchecked.'));
}
}
/**
* Sanitize and strip whitespace from Stripe keys
*
* @param $key
*/
function _uc_stripe_sanitize_key($key) {
$key = trim($key);
$key = check_plain($key);
return $key;
}
/**
* Validate Stripe key
*
* @param $key
* @return boolean
*/
function _uc_stripe_validate_key($key) {
$valid = preg_match('/^[a-zA-Z0-9_]+$/', $key);
return $valid;
}
/**
* Custom submit function to store the stripe token
*
* Since we don't have a user account at this step, we're going to store the token
* in the session. We'll grab the token in the charge callback and use it to charge
*
* @param $form
* @param $form_state
*/
function uc_stripe_checkout_form_customsubmit($form, &$form_state) {
// This submit may be entered on another payment type, so don't set session in that case.
if (!empty($form_state['values']['panes']['payment']['details']['stripe_token'])) {
$_SESSION['stripe']['token'] = $form_state['values']['panes']['payment']['details']['stripe_token'];
}
}
/**
* Generic "charge" callback that runs on checkout and via the order's "card" terminal
*
* @param $order_id
* @param $amount
* @param $data
* @return array
*/
function uc_stripe_charge($order_id, $amount, $data) {
global $user;
// Load the stripe PHP API
if (!_uc_stripe_prepare_api()) {
$result = array(
'success' => FALSE,
'comment' => t('Stripe API not found.'),
'message' => t('Stripe API not found. Contact the site administrator.'),
'uid' => $user->uid,
'order_id' => $order_id,
);
return $result;
}
$order = uc_order_load($order_id);
$context = array(
'revision' => 'formatted-original',
'type' => 'amount',
);
$options = array(
'sign' => FALSE,
'thou' => FALSE,
'dec' => FALSE,
'prec' => 2,
);
// Format the amount in cents, which is what Stripe wants
$amount = uc_currency_format($amount, FALSE, FALSE, FALSE);
$stripe_customer_id = FALSE;
// If the user running the order is not the order's owner
// (like if an admin is processing an order on someone's behalf)
// then load the customer ID from the user object.
// Otherwise, make a brand new customer each time a user checks out.
if ($user->uid != $order->uid) {
$stripe_customer_id = _uc_stripe_get_customer_id($order->uid);
}
// Always Create a new customer in stripe for new orders
if (!$stripe_customer_id) {
try {
// If the token is not in the user's session, we can't set up a new customer
if (empty($_SESSION['stripe']['token'])) {
throw new Exception('Token not found');
}
$stripe_token = $_SESSION['stripe']['token'];
$shipping_info = array();
if (!empty($order->delivery_postal_code)) {
$shipping_info = array(
'name' => @"{$order->delivery_first_name} {$order->delivery_last_name}",
'phone' => @$order->delivery_phone,
);
$delivery_country = uc_get_country_data(array('country_id' => $order->delivery_country));
if ($delivery_country === FALSE) {
$delivery_country = array(0 => array('country_iso_code_2' => 'US'));
}
$shipping_info['address'] = array(
'city' => @$order->delivery_city,
'country' => @$delivery_country[0]['country_iso_code_2'],
'line1' => @$order->delivery_street1,
'line2' => @$order->delivery_street2,
'postal_code' => @$order->delivery_postal_code,
);
}
$params = array(
"source" => $stripe_token,
'description' => "OrderID: {$order->order_id}",
'email' => "$order->primary_email"
);
if (!empty($shipping_info)) {
$params['shipping'] = $shipping_info;
}
//Create the customer in stripe
$customer = \Stripe\Customer::create($params);
// Store the customer ID in the session,
// We'll pick it up later to save it in the database since we might not have a $user object at this point anyway
$stripe_customer_id = $_SESSION['stripe']['customer_id'] = $customer->id;
} catch (Exception $e) {
$result = array(
'success' => FALSE,
'comment' => $e->getCode(),
'message' => t("Stripe Customer Creation Failed for order !order: !message", array(
"!order" => $order_id,
"!message" => $e->getMessage()
)),
'uid' => $user->uid,
'order_id' => $order_id,
);
uc_order_comment_save($order_id, $user->uid, $result['message'], 'admin');
watchdog('uc_stripe', 'Failed stripe customer creation: @message', array('@message' => $result['message']));
return $result;
}
}
// Charge the stripe customer the amount in the order
//--Handle transactions for $0
// Stripe can't handle transactions < $0.50, but $0 is a common value
// so we will just return a positive result when the amount is $0.
if ($amount == 0) {
$result = array(
'success' => TRUE,
'message' => t('Payment of $0 approved'),
'uid' => $user->uid,
'trans_id' => md5(uniqid(rand())),
);
uc_order_comment_save($order_id, $user->uid, $result['message'], 'admin');
return $result;
}
// Charge the customer
try {
//Bail if there's no customer ID
if (empty($stripe_customer_id)) {
throw new Exception('No customer ID found');
}
// Set up titles and SKUs
$titles = variable_get('uc_stripe_metadata_titles', FALSE);
$models = variable_get('uc_stripe_metadata_models', FALSE);
$metadata = array();
foreach($order->products as $item){
$titles[] = $item->title;
$models[] = $item->model;}
if (!empty($models)) {
$metadata['models'] = implode(";", $models);
if (!empty($titles)) {
$metadata['titles'] = implode(";", $titles);
$params = array(
"amount" => $amount,
"currency" => strtolower($order->currency),
"customer" => $stripe_customer_id,
"description" => t("Order #@order_id", array("@order_id" => $order_id)),
"metadata" => $metadata,
);
if (!empty($shipping_info)) {
$params['shipping'] = $shipping_info;
}
// charge the Customer the amount in the order
$charge = \Stripe\Charge::create($params);
$formatted_amount = $amount / 100;
$formatted_amount = number_format($formatted_amount, 2);
$result = array(
'success' => TRUE,
'message' => t('Payment of @amount processed successfully, Stripe transaction id @transaction_id.', array('@amount' => $formatted_amount, '@transaction_id' => $charge->id)),
'comment' => t('Stripe transaction ID: @transaction_id', array('@transaction_id' => $charge->id)),
'uid' => $user->uid,
);
uc_order_comment_save($order_id, $user->uid, $result['message'], 'admin');
uc_order_comment_save($order_id, $user->uid, $result['message'], 'order', 'completed', FALSE);
return $result;
} catch (Exception $e) {
$result = array(
'success' => FALSE,
'comment' => $e->getCode(),
'message' => t("Stripe Charge Failed for order !order: !message", array(
"!order" => $order_id,
"!message" => $e->getMessage()
)),
'uid' => $user->uid,
'order_id' => $order_id,
);
uc_order_comment_save($order_id, $user->uid, $result['message'], 'admin');
watchdog('uc_stripe', 'Stripe charge failed for order @order, message: @message', array('@order' => $order_id, '@message' => $result['message']));
return $result;
}
// Default / Fallback procedure to fail if the above conditions aren't met
$result = array(
'success' => FALSE,
'comment' => "Stripe Gateway Error",
'message' => "Stripe Gateway Error",
'uid' => $user->uid,
'order_id' => $order_id,
);
uc_order_comment_save($order_id, $user->uid, $result['message'], 'admin');
watchdog('uc_stripe', 'Stripe gateway error for order @order_id', array('order_id' => $order_id));
return $result;
}
/**
* Handle renewing a recurring fee, called by uc_recurring
*
* Runs when the subscription interval is hit. So once a month or whatever.
* This just charges the stripe customer whatever amount ubercart wants. It does
* not use the Stripe subscription feature.
*
* @param $order
* @param $fee
* @return bool
*/
function uc_stripe_renew($order, &$fee) {
try {
//Load the API
_uc_stripe_prepare_api();
//Get the customer ID
$stripe_customer_id = _uc_stripe_get_customer_id($order->uid);
if (empty($stripe_customer_id)) {
throw new Exception('No stripe customer ID found');
}
//Create the charge
$amount = $fee->fee_amount;
$amount = $amount * 100;
$charge = \Stripe\Charge::create(array(
"amount" => $amount,
"currency" => strtolower($order->currency),
"customer" => $stripe_customer_id
)
);
uc_payment_enter($order->order_id, $order->payment_method, $order->order_total, $fee->uid, $charge, "Success");
$formatted_amount = number_format($fee->fee_amount, 2);
$message = t('Card renewal payment of @amount processed successfully.', array('@amount' => $formatted_amount));
uc_order_comment_save($fee->order_id, $order->uid, $message, 'order', 'completed', FALSE);
return TRUE;
} catch (Exception $e) {
$result = array(
'success' => FALSE,
'comment' => $e->getCode(),
'message' => t("Renewal Failed for order !order: !message", array(
"!order" => $order->order_id,
"!message" => $e->getMessage()
)),
);
uc_order_comment_save($order->order_id, $order->uid, $result['message'], 'admin');
watchdog('uc_stripe', 'Renewal failed for order @order_id, code=@code, message: @message', array('@order_id' => $order->order_id, '@code' => $result['comment'], '@message' => $result['message']));
return FALSE;
}
}
/**
* UC Recurring: Process a new recurring fee.
* This runs when subscriptions are "set up" for the first time.
* There is no action to be taken here except returning TRUE because the customer
* ID is already stored with the user, where it can be accessed when next charge
* takes place.
*
* @param $order
* @param $fee
* @return bool
*/
function uc_stripe_process($order, &$fee) {
return TRUE;
}
/**
* UC Recurring: Cancel a recurring fee.
* This runs when subscriptions are cancelled
* Since we're handling charge intervals in ubercart, this doesn't need to do anything.
*
* @param $order
* @param $op
* @return bool
*/
function uc_stripe_cancel($order, $op) {
$message = t("Subscription Canceled");
uc_order_comment_save($order->order_id, $order->uid, $message, 'order', 'completed', FALSE);
return TRUE;
}
/**
* Load stripe API
*
* @return bool
*/
function _uc_stripe_prepare_api() {
module_load_include('install', 'uc_stripe');
if (!_uc_stripe_load_library()) {
return FALSE;
}
if (!_uc_stripe_check_api_keys()) {
watchdog('uc_stripe', 'Stripe API keys are not configured. Payments cannot be made without them.', array(), WATCHDOG_ERROR);
return FALSE;
}
$secret_key = variable_get('uc_stripe_testmode', TRUE) ? variable_get('uc_stripe_api_key_test_secret', '') : variable_get('uc_stripe_api_key_live_secret', '');
try {
$library = libraries_load('stripe');
\Stripe\Stripe::setApiKey($secret_key);
\Stripe\Stripe::setApiVersion($library['stripe_api_version']);
} catch (Exception $e) {
watchdog('uc_stripe', 'Error setting the Stripe API Key. Payments will not be processed: %error', array('%error' => $e->getMessage()));
}
return TRUE;
}
/**
* Check that all API keys are configured.
*
* @return bool
* TRUE if all 4 keys have a value.
*/
function _uc_stripe_check_api_keys() {
return (variable_get('uc_stripe_api_key_live_publishable', FALSE) &&
variable_get('uc_stripe_api_key_live_secret', FALSE) &&
variable_get('uc_stripe_api_key_test_publishable', FALSE) &&
variable_get('uc_stripe_api_key_test_secret', FALSE));
}
/**
* Retrieve the Stripe customer id for a user
*
* @param $uid
* @return bool
*/
function _uc_stripe_get_customer_id($uid) {
$account = user_load($uid);
$id = !empty($account->data['uc_stripe_customer_id']) ? $account->data['uc_stripe_customer_id'] : FALSE;
return $id;
}
/**
* Implements hook_theme_registry_alter() to make sure that we render
* the entire credit form, including the key returned by JS.
*
* @param $theme_registry
*/
function uc_stripe_theme_registry_alter(&$theme_registry) {
if (!empty($theme_registry['uc_payment_method_credit_form'])) {
$theme_registry['uc_payment_method_credit_form']['function'] = 'uc_stripe_uc_payment_method_credit_form';
}
}
/**
* Replace uc_credit's form themeing with our own - adds stripe_token.
* @param $form
* @return string
*/
function uc_stripe_uc_payment_method_credit_form($form) {
$output = drupal_render($form['stripe_nojs_warning']);
$output .= drupal_render($form['config_error']);
$output .= theme('uc_payment_method_credit_form',$form);
$output .= drupal_render($form['stripe_token']);
$output .= drupal_render($form['dummy_image_load']);
return $output;
}