'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' => "Powered by Stripe", '#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; }