2 Commits

Author SHA1 Message Date
8493a9c1f7 Update branch from d.o. 2017-05-19 09:53:14 -05:00
c2a752501f Sync with Drupal.org. 2017-05-07 09:16:56 -05:00
10 changed files with 334 additions and 1136 deletions

2
.gitignore vendored
View File

@ -1,2 +0,0 @@
*.patch

View File

@ -1,8 +1,4 @@
This is an Ubercart payment gateway module for Stripe. It maintains PCI SAQ A
compliance which allows Stripe, the payment processor, to handle prcoessing and
storing of payment card details.
It is compliant with 3D Secure, 3D Secure 2, and Strong Customer Authentication (SCA)
This is an Ubercart payment gateway module for Stripe.
Versions of the Stripe PHP Library and Stripe API that this module currently
supports are found in uc_stripe_libraries_info() in uc_stripe.module.
@ -19,9 +15,9 @@ section, and enable the gateway under the Payment Gateways.
c) On that page, provide your Stripe API keys, from
https://dashboard.stripe.com/account/apikeys
d) Download and install the Stripe PHP Library version 6.38.0 with stripe api
2019-05-16 or newer from https://github.com/stripe/stripe-php/releases. The
recommended technique is to use the command
d) Download and install the Stripe PHP Library version 2.2.0 or >=3.13.0
from https://github.com/stripe/stripe-php/releases. The recommended technique is
to use the command
drush ldl stripe
@ -29,7 +25,8 @@ If you don't use "drush ldl stripe", download and install the Stripe library in
sites/all/libraries/stripe such that the path to VERSION
is sites/all/libraries/stripe/VERSION. YOU MUST CLEAR THE CACHE AFTER
CHANGING THE STRIPE PHP LIBRARY. The Libraries module caches its memory of
libraries like the Stripe Library.
libraries like the Stripe Library. (Version 2.2.0 support is maintained for
existing users; version 3.13.0+ supports PHP 7 and will get ongoing support.)
(With the latest version of the libraries module you can use the command:
e) If you are using recurring payments, install version 2.x
@ -51,35 +48,14 @@ disabled on admin/store/settings/payment/method/credit - uc_credit never sees
the credit card number, so cannot properly validate it (and we don't want it to
ever know the credit card number.)
i) uc_stripe creates it's own payment pane. Ensure the correct ordering by visiting
store->configuration->checkout (admin/store/settings/checkout).
Upgrading from uc_stripe 7.x-2.x
Upgrading from uc_stripe 6.x-1.x or 7.x-1.x
===========================================
7.x-3.x maintains PCI SAQ A compliance and has major implementation changes from
2.x. This version uses it's own payment pane in uc_cart to collect card info.
The card fields such as card number, expiration date, and cvc code have all been
hidden, and is handled entirely by Stripe's Element implementation.
Which means no credit card information gets processed at all by drupal. The last4,
and expiration date are sent back to drupal by Stripe's api.
7.x-3.x no longer creates a new stripe customer for each order. If a drupal user
already has a stripe customer ID, this module will attach future orders to that
exisiting stripe customer ID.
When upgrading from 2.x the ordering of the new stripe payment pane should be
verified at store->configuration->checkout (admin/store/settings/checkout).
An upgrade of the stripe library is required. See installation step d from above.
7.x-3.x Uses the uc_recurring module for recurring payments. It is also equipped
to handle recurring payments that require authentication (See the uc_recurring
steps below). Exisiting recurring payments set up with 7.x-2.x work without any
configuration changes.
Upgrading from uc_stripe 6.x-1.x or 7.x-1.x
============================================
7.x-2.x does not use Stripe subscriptions for recurring payments, but instead
uses the uc_recurring module. This means you have control of recurring
transactions without having to manage them on the Stripe dashboard. (Credit
card numbers and sensitive data are *not* stored on your site; only the Stripe
customer ID is stored.)
The upgrade hooks, however, must move the customer id stored in the obsolete
uc_recurring_stripe table into the user table. When this happens the old
@ -108,11 +84,6 @@ Recurring payments require automatically triggered renewals using
uc_recurring_trigger_renewals ("Enabled triggered renewals" must be enabled
on admin/store/settings/payment/edit/recurring)
You should also set your email message for recurring payments that require
Authentication. The system will email your customers with a link so that they
can authenticate and have their payment processed.
(You can edit from here: admin/store/settings/payment/edit/gateways)
If you were using Stripe subscriptions in v1 of this module, you may have to
disable those subscriptions in order to not double-charge your customers.

View File

@ -30,5 +30,3 @@ a.poweredbylink:hover {
#uc_stripe_messages.hidden {display: none;}
.stripe-warning {color: red; font-style: oblique; }
#edit-panes-payment-details-stripe-card-element{max-width: 600px}

View File

@ -9,185 +9,163 @@
Drupal.behaviors.uc_stripe = {
attach: function (context) {
// Once function prevents stripe from reloading. Any dom changes to stripe area will destroy element
// as a Stripe security feature
$('#uc-cart-checkout-form', context).once('uc_stripe', function(){
var stripe_card_element = '#stripe-card-element';
if (Drupal.settings && Drupal.settings.uc_stripe ) {
var apikey = Drupal.settings.uc_stripe.apikey;
var stripe = Stripe(apikey);
var elements = stripe.elements();
}
// Map stripe names to (partial) Ubercart field names; Ubercart names add "billing_" or "shipping_" on the front.
const address_field_mapping = {
"address_line1": "street1",
"address_line2": "street2",
"address_city": "city",
"address_state": "zone",
"address_zip": "postal_code",
"address_country": "country"
};
var submitButton = $('.uc-cart-checkout-form #edit-continue');
const address_field_mapping = {
"address_line1": "street1",
"address_line2": "street2",
"address_city": "city",
"address_state": "zone",
"address_zip": "postal_code",
"address_country": "country"
};
var submitButton = $('.uc-cart-checkout-form #edit-continue');
// Load the js reference to these fields so that on the review page
// we can input the last 4 and expiration date which is returned to us by stripe paymentMethod call
var cc_container = $('.payment-details-credit');
var cc_num = cc_container.find(':input[id*="edit-panes-payment-details-cc-numbe"]');
var cc_cvv = cc_container.find(':input[id*="edit-panes-payment-details-cc-cv"]');
var cc_exp_month = cc_container.find('#edit-panes-payment-details-cc-exp-month');
var cc_exp_year = cc_container.find('#edit-panes-payment-details-cc-exp-year');
var cc_container = $('.payment-details-credit');
var cc_num = cc_container.find(':input[id*="edit-panes-payment-details-cc-numbe"]');
var cc_cvv = cc_container.find(':input[id*="edit-panes-payment-details-cc-cv"]');
// Make sure that when the page is being loaded the paymentMethod value is reset
// Browser or other caching might do otherwise.
$("[name='panes[payment-stripe][details][stripe_payment_method]']").val('default');
// Make sure that when the page is being loaded the token value is reset
// Browser or other caching might do otherwise.
$("[name='panes[payment][details][stripe_token]']").val('default');
// JS must enable the button; otherwise form might disclose cc info. It starts disabled
submitButton.attr('disabled', false);
$('span#stripe-nojs-warning').parent().hide();
// When this behavior fires, we can clean the form so it will behave properly,
// Remove 'name' from sensitive form elements so there's no way they can be submitted.
cc_num.removeAttr('name').removeAttr('disabled');
$('div.form-item-panes-payment-details-cc-number').removeClass('form-disabled');
cc_cvv.removeAttr('name').removeAttr('disabled');
var cc_val_val = cc_num.val();
if (cc_val_val && cc_val_val.indexOf('Last 4')) {
cc_num.val('');
// JS must enable the button; otherwise form might disclose cc info. It starts disabled
submitButton.attr('disabled', false);
// When this behavior fires, we can clean the form so it will behave properly,
// Remove 'name' from sensitive form elements so there's no way they can be submitted.
cc_num.removeAttr('name').removeAttr('disabled');
$('div.form-item-panes-payment-details-cc-number').removeClass('form-disabled');
cc_cvv.removeAttr('name').removeAttr('disabled');
var cc_val_val = cc_num.val();
if (cc_val_val && cc_val_val.indexOf('Last 4')) {
cc_num.val('');
}
submitButton.click(function (e) {
// We must find the various fields again, because they may have been swapped
// in by ajax action of the form.
cc_container = $('.payment-details-credit');
cc_num = cc_container.find(':input[id*="edit-panes-payment-details-cc-numbe"]');
cc_cvv = cc_container.find(':input[id*="edit-panes-payment-details-cc-cv"]');
// If not credit card processing or no token field, just let the submit go on
// Also continue if we've received the tokenValue
var tokenField = $("[name='panes[payment][details][stripe_token]']");
if (!$("div.payment-details-credit").length || !tokenField.length || tokenField.val().indexOf('tok_') == 0) {
return true;
}
// If we've requested and are waiting for token, prevent any further submit
if (tokenField.val() == 'requested') {
return false; // Prevent any submit processing until token is received
}
// Custom styling can be passed to options when creating an Element.
var style = {
base: {
// Add your base input styles here. For example:
fontSize: '24px',
color: "#000000",
iconColor: "blue",
// Go ahead and request the token
tokenField.val('requested');
try {
var name = undefined;
if ($(':input[name="panes[billing][billing_first_name]"]').length) {
name = $(':input[name="panes[billing][billing_first_name]"]').val() + " " + $(':input[name="panes[billing][billing_last_name]"]').val();
}
};
// Create an instance of the card Element.
var card = elements.create('card', {style: style});
// Add an instance of the card Element into the #stripe-card-element <div>.
card.mount(stripe_card_element);
// Display errors from stripe
card.addEventListener('change', function(event) {
var displayError = document.getElementById('uc_stripe_messages');
if (event.error) {
displayError.textContent = event.error.message;
console.log(event.error.message)
} else {
displayError.textContent = '';
}
});
submitButton.click(function (e) {
// We must find the various fields again, because they may have been swapped
// in by ajax action of the form.
cc_container = $('.payment-details-credit');
cc_num = cc_container.find(':input[id*="edit-panes-payment-details-cc-numbe"]');
cc_cvv = cc_container.find(':input[id*="edit-panes-payment-details-cc-cv"]');
cc_exp_year = cc_container.find('#edit-panes-payment-details-cc-exp-month');
cc_exp_month = cc_container.find('#edit-panes-payment-details-cc-exp-year');
// If not credit card processing or no payment method field, just let the submit go on
// Also continue if we've received the tokenValue
var paymentMethodField = $("[name='panes[payment-stripe][details][stripe_payment_method]']");
if (!$("div.payment-details-credit").length || !paymentMethodField.length || paymentMethodField.val().indexOf('pm_') == 0) {
return true;
if (typeof name === "undefined" && $(':input[name="panes[delivery][delivery_first_name]"]').length) {
name = $(':input[name="panes[delivery][delivery_first_name]"]').val() + " " + $(':input[name="panes[delivery][delivery_last_name]"]').val();
}
// If we've requested and are waiting for token, prevent any further submit
if (paymentMethodField.val() == 'requested') {
return false; // Prevent any submit processing until token is received
}
var params = {
number: cc_num.val(),
cvc: cc_cvv.val(),
exp_month: $(':input[name="panes[payment][details][cc_exp_month]"]').val(),
exp_year: $(':input[name="panes[payment][details][cc_exp_year]"]').val(),
name: name
};
// Go ahead and request the token
paymentMethodField.val('requested');
try {
stripe.createPaymentMethod('card', card).then(function (response) {
if (response.error) {
// Show the errors on the form
$('#uc_stripe_messages')
.removeClass("hidden")
.text(response.error.message);
$('#edit-stripe-messages').val(response.error.message);
// Make the fields visible again for retry
cc_num
.css('visibility', 'visible')
.val('')
.attr('name', 'panes[payment][details][cc_number]');
cc_cvv
.css('visibility', 'visible')
.val('')
.attr('name', 'panes[payment][details][cc_cvv]');
// Turn off the throbber
$('.ubercart-throbber').remove();
// Remove the bogus copy of the submit button added in uc_cart.js ucSubmitOrderThrobber
submitButton.next().remove();
// And show the hidden original button which has the behavior attached to it.
submitButton.show();
paymentMethodField.val('default'); // Make sure token field set back to default
} else {
// token contains id, last4, and card type
var paymentMethodId = response.paymentMethod.id;
// Insert the token into the form so it gets submitted to the server
paymentMethodField.val(paymentMethodId);
// set cc expiration date received from stripe so that it is available on checkout review
cc_exp_year.val(response.paymentMethod.card.exp_month);
cc_exp_month.val(response.paymentMethod.card.exp_year);
// Since we're now submitting, make sure that uc_credit doesn't
// find values it objects to; after "fixing" set the name back on the
// form element.
// add dummy tweleve 5's and the last 4 of credit card so that last 4 show
cc_num
.css('visibility', 'hidden')
.val('555555555555' + response.paymentMethod.card.last4)
.attr('name', 'panes[payment][details][cc_number]');
cc_cvv
.css('visibility', 'hidden')
.val('999')
.attr('name', 'panes[payment][details][cc_cvv]');
// now actually submit to Drupal. The only "real" things going
// are the token and the expiration date and last 4 of cc
submitButton.click();
// Translate the Ubercart billing/shipping fields to Stripe values
for (var key in address_field_mapping) {
const prefixes = ['billing', 'delivery'];
for (var i = 0; i < prefixes.length; i++) {
var prefix = prefixes[i];
var uc_field_name = prefix + '_' + address_field_mapping[key];
var location = ':input[name="panes[' + prefix + '][' + uc_field_name + ']"]';
if ($(location).length) {
params[key] = $(location).val();
if ($(location).attr('type') == 'select-one') {
params[key] = $(location + " option:selected").text();
}
break; // break out of billing/shipping loop because we got the info
}
});
} catch (e) {
$('#uc_stripe_messages')
.removeClass("hidden")
.text(e.message);
$('#edit-stripe-messages').val(e.message);
}
}
// Prevent processing until we get the token back
return false;
});
Stripe.createToken(params, function (status, response) {
if (response.error) {
// Show the errors on the form
$('#uc_stripe_messages')
.removeClass("hidden")
.text(response.error.message);
$('#edit-stripe-messages').val(response.error.message);
// Make the fields visible again for retry
cc_num
.css('visibility', 'visible')
.val('')
.attr('name', 'panes[payment][details][cc_number]');
cc_cvv
.css('visibility', 'visible')
.val('')
.attr('name', 'panes[payment][details][cc_cvv]');
// Turn off the throbber
$('.ubercart-throbber').remove();
// Remove the bogus copy of the submit button added in uc_cart.js ucSubmitOrderThrobber
submitButton.next().remove();
// And show the hidden original button which has the behavior attached to it.
submitButton.show();
tokenField.val('default'); // Make sure token field set back to default
} else {
// token contains id, last4, and card type
var token = response.id;
// Insert the token into the form so it gets submitted to the server
tokenField.val(token);
// Since we're now submitting, make sure that uc_credit doesn't
// find values it objects to; after "fixing" set the name back on the
// form element.
cc_num
.css('visibility', 'hidden')
.val('555555555555' + response.card.last4)
.attr('name', 'panes[payment][details][cc_number]');
cc_cvv
.css('visibility', 'hidden')
.val('999')
.attr('name', 'panes[payment][details][cc_cvv]');
// now actually submit to Drupal. The only "real" things going
// are the token and the expiration date.
submitButton.click();
}
});
} catch (e) {
$('#uc_stripe_messages')
.removeClass("hidden")
.text(e.message);
$('#edit-stripe-messages').val(e.message);
}
// Prevent processing until we get the token back
return false;
});
},
}
};
}(jQuery));

View File

@ -1,91 +0,0 @@
/**
* @file
* uc_stripe.js
*
* Handles all interactions with Stripe on the client side for PCI-DSS compliance
*/
(function ($) {
Drupal.behaviors.uc_stripe_process_payment = {
attach: function (context) {
$('#uc-cart-checkout-review-form, #uc-stripe-authenticate-payment-form', context).once('uc_stripe', function(){
if (Drupal.settings && Drupal.settings.uc_stripe ) {
var apikey = Drupal.settings.uc_stripe.apikey;
var methodId = Drupal.settings.uc_stripe.methodId;
var orderId = Drupal.settings.uc_stripe.orderId
var stripe = Stripe(apikey);
}
var submitButton = $('#edit-submit');
var processed = false;
submitButton.click(function (e) {
if(!processed){
e.preventDefault();
$.ajax({
url: '/uc_stripe/ajax/confirm_payment',
type: "POST",
data: JSON.stringify({ payment_method_id: methodId, order_id: orderId }),
contentType: 'application/json;',
dataType: 'json',
success: function(result){
handleServerResponse(result);
},
error: function(result){
handleServerResponse(result);
}
})
}
});
function handleServerResponse(response) {
if (response.error) {
processed = true;
submitButton.click();
// Show error from server on payment form
} else if (response.requires_action) {
// Use Stripe.js to handle required card action
stripe.handleCardAction(
response.payment_intent_client_secret
).then(function(result) {
if (result.error) {
// Show error in payment form
processed = true;
submitButton.click();
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
$.ajax({
url: '/uc_stripe/ajax/confirm_payment',
type: 'POST',
data: JSON.stringify({ payment_intent_id: result.paymentIntent.id, order_id: orderId }),
contentType: 'application/json;',
dataType: 'json',
success: function(confirmResult){
return handleServerResponse(confirmResult);
},
error: function(confirmResult){
return handleServerResponse(confirmResult);
},
})
}
});
} else {
// Show success message
processed = true;
submitButton.click();
}
}
});
},
};
}(jQuery));

View File

@ -8,8 +8,9 @@ core = 7.x
php = 5.3
; Information added by Drupal.org packaging script on 2019-09-13
version = "7.x-3.1+1-dev"
; Information added by Drupal.org packaging script on 2017-05-19
version = "7.x-2.2+2-dev"
core = "7.x"
project = "uc_stripe"
datestamp = "1568380086"
datestamp = "1495159090"

View File

@ -92,57 +92,6 @@ function uc_stripe_install() {
variable_set('uc_credit_validate_numbers', FALSE);
}
/**
* Implements hook_uninstall().
*/
function uc_stripe_uninstall() {
variable_del('uc_stripe_authentication_required_email');
}
/**
* Implements hook_schema().
*/
function uc_stripe_schema() {
$schema['uc_stripe_pending_auth'] = array(
'description' => 'Ubercart Stripe - Track orders pending authentication',
'fields' => array(
'id' => array(
'description' => 'id of entry',
'type' => 'serial',
'not null' => TRUE
),
'order_id' => array(
'description' => 'Order Id of pending order',
'type' => 'int',
'not null' => TRUE
),
'rfee_id' => array(
'description' => 'Recurring Fee Id of pending order',
'type' => 'int',
'not null' => TRUE
),
'completed' => array(
'description' => 'Competion status of this pending order',
'type' => 'int',
'not null' => TRUE
),
'hash' => array (
'description' => 'The unqiue has of order and payment id',
'type' => 'varchar',
'length' => '100',
'not null' => TRUE
),
),
'unique keys' => array(
'hash' => array(
'hash'
)
),
'primary key' => array('id'),
);
return $schema;
}
/**
* Enable triggered renewals, as uc_recurring manages renewals with this version.
*/
@ -232,61 +181,3 @@ function _uc_stripe_move_customer_id(&$sandbox) {
$sandbox['#finished'] = $sandbox['progress'] / $sandbox['max'];
}
}
/**
* Creates table to track orders that require extra authentication verification.
*/
function uc_stripe_update_7301() {
$table = array(
'description' => 'Ubercart Stripe - Track orders pending authentication',
'fields' => array(
'id' => array(
'description' => 'id of entry',
'type' => 'serial',
'not null' => TRUE
),
'order_id' => array(
'description' => 'Order Id of pending order',
'type' => 'int',
'not null' => TRUE
),
'rfee_id' => array(
'description' => 'Recurring Fee Id of pending order',
'type' => 'int',
'not null' => TRUE
),
'completed' => array(
'description' => 'Competion status of this pending order',
'type' => 'int',
'not null' => TRUE
),
'hash' => array(
'description' => 'The unqiue has of order and payment id',
'type' => 'varchar',
'length' => '100',
'not null' => TRUE
),
),
'unique keys' => array(
'hash' => array(
'hash'
)
),
'primary key' => array('id'),
);
db_create_table('uc_stripe_pending_auth', $table);
}
/**
* Changes typo in variable uc_stripe_authenticaiton_required_email.
*/
function uc_stripe_update_7302() {
$typo_var_name = 'uc_stripe_authenticaiton_required_email';
$value = variable_get($typo_var_name, '');
if (!empty($value)) {
variable_set('uc_stripe_authentication_required_email', $value);
variable_del($typo_var_name);
}
}

View File

@ -1,62 +0,0 @@
<?php
/**
* This function returns the default off session authention email text.
* @return $text - Email text
*/
function _uc_stripe_get_authentication_required_email_text(){
$text = t("Dear [user:name],
We were unable to process your subscription payment.
Your financial institution is requesting additional verification before your subscription can be renewed.
Please visit this link to return to our site and complete the verification step.
[uc_stripe:verification-link]
-- [site:name] team
");
return $text;
}
/**
*
* Token callback that adds the authentication link to user mails.
*
* This function is used by the token_replace() call in uc_stripe_mail() to add
* the url to verify payment information
*
* @param $replacements
* An associative array variable containing mappings from token names to
* values (for use with strtr()).
* @param $data
* An associative array of token replacement values.
* @param $options
* Unused parameter required by the token_replace() function. */
function uc_stripe_mail_tokens(&$replacements, $data, $options) {
global $base_url;
$replacements['[uc_stripe:verification-link]'] = $base_url.'/stripe/authenticate-payment/'.$data['authentication_key'];
}
/**
* Implements hook_mail().
*
* Send mail and replace token with authenticaion link.
*/
function uc_stripe_mail($key, &$message, $params) {
switch ($key) {
case 'authentication_required' :
$message['subject'] = t('Additional Verification Required to Process Subscription.');
$variables = array('user' => $params['user'], 'authentication_key' => $params['hash']);
$message['body'][]= token_replace($params['body'], $variables, array('language' => language_default(), 'callback' => 'uc_stripe_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
break;
}
}

View File

@ -14,57 +14,83 @@
*
* @return mixed
*/
module_load_include('inc', 'uc_stripe', 'uc_stripe.mail');
function uc_stripe_libraries_info() {
$libraries['stripe'] = array(
'name' => '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/v6.38.0.tar.gz',
'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(
'6.38.0' => 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' => '2019-05-16'
)
'stripe_api_version' => '2016-03-07',
),
),
);
return $libraries;
}
/**
* Implements hook_menu().
*/
function uc_stripe_menu() {
$items = array();
$items['uc_stripe/ajax/confirm_payment'] = array(
'access callback' => true,
'page callback' => '_uc_stripe_confirm_payment',
'delivery callback' => 'drupal_json_output',
);
$items['stripe/authenticate-payment/%'] = array(
'access callback' => true,
'page callback' => 'drupal_get_form',
'page arguments' => array('uc_stripe_authenticate_payment_form', 2),
'file' => 'uc_stripe.pages.inc'
);
return $items;
}
/**
* Implements hook_payment_gateway to register this payment gateway
* @return array
@ -105,47 +131,6 @@ function uc_stripe_recurring_info() {
return $items;
}
/**
* Implements hook_form_FORMID_alter() to do JS Stripe processing when processing
* from the order review page
*
* @param unknown $form
* @param unknown $form_state
* @param unknown $form_id
*
*/
function uc_stripe_form_uc_cart_checkout_review_form_alter(&$form, &$form_state, $form_id){
//This alter hook should only take action when payment method is credit.
if($form_state['uc_order']->payment_method != 'credit'){
return;
}
// If payment method is not found, hide submit button, and show error to user
if (empty($_SESSION['stripe']['payment_method'])) {
$form['actions']['submit']['#type'] = 'hidden';
$fail_message = variable_get('uc_credit_fail_message', t('We were unable to process your credit card payment. Please verify your details and try again. If the problem persists, contact us to complete your order.'));
watchdog('uc_stripe', 'Stripe charge failed for order @order, message: @message', array('@order' => $form_state['uc_order']->order_id, '@message' => 'Payment method not found'));
drupal_set_message($fail_message, 'error');
return;
}
// When a payment fails, remove the Submit Order button because it will most
// likely fail again. Instead, the customer should hit back to try again.
if(isset($_SESSION['stripe']['payment_failed'])){
$form['actions']['submit']['#type'] = 'hidden';
}
$stripe_payment_method_id = $_SESSION['stripe']['payment_method'];
$order_id = $form_state['uc_order']->order_id;
$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', ''));
$settings = array('methodId' => $stripe_payment_method_id, 'apikey' => $apikey, 'orderId' => $order_id);
//Attach Stripe v3 JS library and JS for processing payment
$form['#attached']['js']['https://js.stripe.com/v3/'] = array('type' => 'external');
$form['#attached']['js'][] = array('data' => array('uc_stripe' => $settings), 'type' => 'setting');
$form['#attached']['js'][] = drupal_get_path('module', 'uc_stripe') . '/js/uc_stripe_process_payment.js';
}
/**
* Implements hook_form_FORMID_alter() to change the checkout form
@ -157,49 +142,37 @@ function uc_stripe_form_uc_cart_checkout_review_form_alter(&$form, &$form_state,
*/
function uc_stripe_form_uc_cart_checkout_form_alter(&$form, &$form_state) {
$form['panes']['payment-stripe']['#attached']['css'][] = array(
'data' => '#payment-stripe-pane { display: none; }',
'type' => 'inline',
);
$form['panes']['payment-stripe']['#states'] = array(
'visible' => array(
':input[name="panes[payment][payment_method]"]' => array(
'value' => 'credit'
)
)
);
$stripe_payment_form = &$form['panes']['payment-stripe']['details'];
$payment_form = &$form['panes']['payment']['details'];
// Markup text will not be displayed when JS and stripe are functioning properly
// since Stripe Elements will replace the contents of this div
$stripe_payment_form['stripe_card_element'] = array(
'#prefix' => '<div id="stripe-card-element">',
'#weight' => - 10,
'#markup' => '<div class="stripe-warning">' . t('Sorry, for security reasons your card cannot be processed. Please refresh this page and try again. If the problem persists please check that Javascript is enabled your browser.') . '</div>',
'#suffix' => '</div>',
$payment_form['stripe_nojs_warning'] = array(
'#type' => 'item',
'#markup' => '<span id="stripe-nojs-warning" class="stripe-warning">' . t('Sorry, for security reasons your card cannot be processed because Javascript is disabled in your browser.') . '</span>',
'#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' => "<a target='_blank' href='http://stripe.com'><img src=" . '/' . drupal_get_path('module', 'uc_stripe') . '/images/solid-dark.svg' . " alt='Powered by Stripe'></a>",
'#markup' => "<a href='http://stripe.com'><img src=" . '/' . drupal_get_path('module', 'uc_stripe') . '/images/solid-dark.svg' . " alt='Powered by Stripe'></a>",
'#weight' => 1,
);
}
// Used for payment method Id when retrieved from stripe.
$stripe_payment_form['stripe_payment_method'] = array(
$payment_form['stripe_token'] = array(
'#type' => 'hidden',
'#default_value' => 'default',
'#attributes' => array(
'id' => 'edit-panes-stripe-payment-details-stripe-payment-method',
'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();
}
@ -207,38 +180,13 @@ function uc_stripe_form_uc_cart_checkout_form_alter(&$form, &$form_state) {
$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
$settings = array('apikey' => $apikey);
$form['#attached']['js']['https://js.stripe.com/v3/'] = array('type' => 'external');
$form['#attached']['js'][] = array('data' => array('uc_stripe' => $settings), 'type' => 'setting');
$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';
// hide cc fields and set defaults since we rely fully on stripe's dynamic cc fields
$payment_form['cc_number']['#type'] = 'hidden';
$payment_form['cc_number']['#default_value'] = '';
$payment_form['cc_number']['#attributes']['id'] = 'edit-panes-payment-details-cc-number';
$payment_form['cc_cvv']['#type'] = 'hidden';
$payment_form['cc_cvv']['#default_value'] = '';
$payment_form['cc_cvv']['#attributes']['id'] = 'edit-panes-payment-details-cc-cvv';
$payment_form['cc_exp_year']['#type'] = 'hidden';
$payment_form['cc_exp_year']['#attributes']['id'] = 'edit-panes-payment-details-cc-exp-year';
//Stripe CC expiration can be up to 50 years in future. The normal ubercart select
// options only go up to 20 years in the future.
$min = intval(date('Y'));
$max = intval(date('Y')) + 50;
$default = intval(date('Y'));
$payment_form['cc_exp_year']['#options'] = drupal_map_assoc(range($min, $max));
$payment_form['cc_exp_year']['#default_value'] = $default;
$payment_form['cc_exp_month']['#type'] = 'hidden';
$payment_form['cc_exp_month']['#default_value'] = 1;
$payment_form['cc_exp_month']['#attributes']['id'] = 'edit-panes-payment-details-cc-exp-month';
// Add custom submit which will do saving away of token during submit.
$form['#submit'][] = 'uc_stripe_checkout_form_customsubmit';
@ -247,11 +195,6 @@ function uc_stripe_form_uc_cart_checkout_form_alter(&$form, &$form_state) {
'#markup' => "<div id='uc_stripe_messages' class='messages error hidden'></div>",
);
//Clear any previous card payment failures
if(isset($_SESSION['stripe']['payment_failed'])){
unset($_SESSION['stripe']['payment_failed']);
}
if (uc_credit_default_gateway() == 'uc_stripe') {
if (variable_get('uc_stripe_testmode', TRUE)) {
$form['panes']['testmode'] = array(
@ -283,51 +226,6 @@ function uc_stripe_uc_order_pane() {
return $panes;
}
/**
* Implements hook_uc_checkout_pane to add checkout pane for stripe payment details
*
* @return array
*/
function uc_stripe_uc_checkout_pane() {
$panes['payment-stripe'] = array(
'callback' => '_uc_stripe_payment_pane_callback',
'title' => t('Payment Information'),
'desc' => t("Accept stripe payment from customer."),
'weight' => 6,
'process' => FALSE,
'collapsible' => FALSE,
);
return $panes;
}
/**
* Implements uc_checkout_pane_callback() specified in 'callback' of
* uc_stripe_uc_checkout_pane()
*
* Provides empty pane for stripe elements to be added
* @param $op
* @param $order
* @param $form
* @param $form_state
* @return array
*/
function _uc_stripe_payment_pane_callback($op, $order, $form = NULL, &$form_state = NULL) {
// Create separate payment pane for stripe because the normal payment pane is refreshed many times
// by ajax, by country changes, etc.. Refreshing the payment section triggers Stripe Api's security feature
// and destroys the Stripe Element in the DOM.
// Emtpy values needed so that pane still appears.
switch ($op) {
case 'view':
$description = t('');
$contents['stripe_card_element'] = array(
'#markup' => '',
);
return array('description' => $description, 'contents' => $contents);
}
}
/**
* Implements hook_uc_checkout_complete()
*
@ -339,15 +237,12 @@ function _uc_stripe_payment_pane_callback($op, $order, $form = NULL, &$form_stat
function uc_stripe_uc_checkout_complete($order, $account) {
if ($order->payment_method == "credit") {
// Pull the stripe payment method ID from the session.
// Pull the stripe customer ID from the session.
// It got there in uc_stripe_checkout_form_customsubmit()
$stripe_payment_id = $_SESSION['stripe']['payment_method'];
$stripe_customer_id = $order->data['stripe_customer_id'];
$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)));
user_save($loaded_user, array('data' => array('uc_stripe_payment_id' => $stripe_payment_id)));
}
}
@ -414,15 +309,6 @@ function uc_stripe_settings_form() {
'#description' => t('Your Live Stripe API Key. Must be the "publishable" key, not the "secret" one.'),
);
$email_text = _uc_stripe_get_authentication_required_email_text();
$form['uc_stripe_settings']['uc_stripe_authentication_required_email'] = array(
'#type' => 'textarea',
'#title' => t('Email for Recurring payment authentication'),
'#default_value' => variable_get('uc_stripe_authentication_required_email', $email_text),
'#description' => t('If your site uses recurring payments, some transactions will require the customer to return to the site and authenticate before the subscrption payment can be processed.')
);
$form['uc_stripe_settings']['uc_stripe_testmode'] = array(
'#type' => 'checkbox',
'#title' => t('Test mode'),
@ -510,8 +396,8 @@ function _uc_stripe_validate_key($key) {
*/
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-stripe']['details']['stripe_payment_method'])) {
$_SESSION['stripe']['payment_method'] = $form_state['values']['panes']['payment-stripe']['details']['stripe_payment_method'];
if (!empty($form_state['values']['panes']['payment']['details']['stripe_token'])) {
$_SESSION['stripe']['token'] = $form_state['values']['panes']['payment']['details']['stripe_token'];
}
}
@ -557,6 +443,85 @@ function uc_stripe_charge($order_id, $amount, $data) {
// 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
@ -574,67 +539,35 @@ function uc_stripe_charge($order_id, $amount, $data) {
return $result;
}
$stripe_customer_id = null;
if(key_exists('stripe_customer_id', $order->data)){
$stripe_customer_id = $order->data['stripe_customer_id'];
}
// Rexamine Payment Intent and Record payment or failure to the customer
// Charge the customer
try {
if(!key_exists('payment_intent_id', $order->data)){
throw new Exception('The payment Intent has failed.');
}
//Bail if there's no customer ID
if (empty($stripe_customer_id) || is_null($stripe_customer_id)) {
if (empty($stripe_customer_id)) {
throw new Exception('No customer ID found');
}
//Bail if there's no payment method
if (empty($_SESSION['stripe']['payment_method'])) {
throw new Exception('Token not found');
}
$stripe_payment_method_id = $_SESSION['stripe']['payment_method'];
$params = array(
"amount" => $amount,
"currency" => strtolower($order->currency),
"customer" => $stripe_customer_id,
"description" => t("Order #@order_id", array("@order_id" => $order_id)),
"payment_method" => $stripe_payment_method_id,
"payment_method_types" => ['card'],
"confirm" => true,
);
$intent_id = $order->data['payment_intent_id'];
if (!empty($shipping_info)) {
$params['shipping'] = $shipping_info;
\Stripe\PaymentIntent::update($intent_id, ['shipping' => $shipping_info]);
}
// charge the Customer the amount in the order
$payment_intent = \Stripe\PaymentIntent::retrieve($intent_id);
if ($payment_intent->status != 'succeeded') {
throw new Exception($payment_intent['last_payment_error']['message']);
}
$charge_id = $payment_intent->charges->data[0]['id'];
$charge = \Stripe\Charge::create($params);
$formatted_amount = $amount / 100;
$formatted_amount = number_format($formatted_amount, 2);
// $payment_method = \Stripe\PaymentMethod::retrieve($payment_intent->payment_method);
// $payment_method->attach(['customer' => $stripe_customer_id]);
$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)),
'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,
);
@ -657,8 +590,6 @@ function uc_stripe_charge($order_id, $amount, $data) {
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']));
$_SESSION['stripe']['payment_failed'] = TRUE;
return $result;
}
@ -676,8 +607,6 @@ function uc_stripe_charge($order_id, $amount, $data) {
watchdog('uc_stripe', 'Stripe gateway error for order @order_id', array('order_id' => $order_id));
$_SESSION['stripe']['payment_failed'] = TRUE;
return $result;
}
@ -701,65 +630,33 @@ function uc_stripe_renew($order, &$fee) {
//Get the customer ID
$stripe_customer_id = _uc_stripe_get_customer_id($order->uid);
$stripe_payment_id = _uc_stripe_get_payment_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;
//create intent Array
$intent_params = array(
'amount' => $amount,
'currency' => strtolower($order->currency),
'payment_method_types' => ['card'],
'customer' => $stripe_customer_id,
'off_session' => true,
'confirm' => true,
$charge = \Stripe\Charge::create(array(
"amount" => $amount,
"currency" => strtolower($order->currency),
"customer" => $stripe_customer_id
)
);
// Payment methods added with Stripe PaymentIntent API will be saved to customer
// object in drupal. Payment cards saved with 2.x tokens will not have a value
// saved to customer object, but the payment Intent will still continue because
// Stipe will use default payment in those situations.
if($stripe_payment_id){
$intent_params['payment_method'] = $stripe_payment_id;
}
$payment_intent = \Stripe\PaymentIntent::create($intent_params);
uc_payment_enter($order->order_id, $order->payment_method, $order->order_total, $fee->uid, $payment_intent, "Success");
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);
uc_order_comment_save($fee->order_id, $order->uid, $message, 'admin');
return TRUE;
} catch (\Stripe\Error\Card $e) {
if ($e->getDeclineCode() === 'authentication_required') {
$NOT_COMPLETED = 0;
// Create and store hash so that we can prompt user to authenticate payment.
$hash = drupal_hmac_base64(REQUEST_TIME . $order->order_id, drupal_get_hash_salt() . $stripe_payment_id);
db_insert('uc_stripe_pending_auth')->fields(array(
'order_id' => $order->order_id,
'completed' => $NOT_COMPLETED,
'rfee_id' => $fee->rfid,
'hash' => $hash
))->execute();
// Prepare email to alert user that authentication is required.
$params['body'] = variable_get('uc_stripe_authentication_required_email', _uc_stripe_get_authentication_required_email_text());
$params['user'] = user_load($order->uid);
$params['hash'] = $hash;
drupal_mail('uc_stripe', 'authentication_required', $params['user']->mail,language_default(), $params);
};
} catch (Exception $e) {
$result = array(
'success' => FALSE,
'comment' => $e->getCode(),
@ -769,14 +666,10 @@ function uc_stripe_renew($order, &$fee) {
)),
);
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' => $e->getCode(), '@message' => $e->getMessage()));
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;
} catch (Exception $e) {
watchdog('uc_stripe', 'Renewal failed for order @order_id, code=@code, message: @message', array('@order_id' => $order->order_id, '@code' => $e->getCode(), '@message' => $e->getMessage()));
return FALSE;
}
@ -840,13 +733,6 @@ function _uc_stripe_prepare_api() {
} catch (Exception $e) {
watchdog('uc_stripe', 'Error setting the Stripe API Key. Payments will not be processed: %error', array('%error' => $e->getMessage()));
}
try{
$module_info = system_get_info('module', "uc_stripe");
$uc_stripe_version = is_null($module_info['version']) ? 'dev-unknown' : $module_info['version'];
\Stripe\Stripe::setAppInfo("Drupal Ubercart Stripe", $uc_stripe_version, "https://www.drupal.org/project/uc_stripe");
} catch (Exception $e){
watchdog('uc_stripe', 'Error setting Stripe plugin information: %error', array('%error' => $e->getMessage()));
}
return TRUE;
}
@ -877,20 +763,6 @@ function _uc_stripe_get_customer_id($uid) {
return $id;
}
/**
* Retrieve the Stripe payment id for a user
*
* @param $uid
* @return bool
*/
function _uc_stripe_get_payment_id($uid) {
$account = user_load($uid);
$id = !empty($account->data['uc_stripe_payment_id']) ? $account->data['uc_stripe_payment_id'] : FALSE;
return $id;
}
/**
* Implements hook_theme_registry_alter() to make sure that we render
@ -910,222 +782,13 @@ function uc_stripe_theme_registry_alter(&$theme_registry) {
* @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_payment_method']);
$output .= drupal_render($form['stripe_token']);
$output .= drupal_render($form['dummy_image_load']);
return $output;
}
/**
* Used to return the appropriate response after checking Stripe Payment Intent
* status
* @param Object $intent
* @return string response
*/
function _generatePaymentResponse($intent) {
if ($intent->status == 'requires_action' &&
$intent->next_action->type == 'use_stripe_sdk') {
# Tell the client to handle the action
$response = [
'requires_action' => true,
'payment_intent_client_secret' => $intent->client_secret
];
} else if ($intent->status == 'succeeded') {
# The payment didnt need any additional actions and completed!
# Handle post-payment fulfillment
$response = ['success' => true];
} else {
# Invalid status
http_response_code(500);
$response = ['error' => 'Invalid PaymentIntent status'];
}
return $response;
}
/**
* Ajax page callback for callback uc_stripe/ajax/confirm_payment page
* This is used to send payment and intent status back to JS client
* @return string Json response
*/
function _uc_stripe_confirm_payment(){
global $user;
# retrieve json from POST body
$received_json = file_get_contents("php://input", TRUE);
$data = drupal_json_decode($received_json, TRUE);
$order_id = $data['order_id'];
$order = uc_order_load($order_id);
if (!_uc_stripe_prepare_api()) {
$message = 'Stripe API not found.';
watchdog('uc_stripe', 'Error in Stripe API: @message', array('@message' => $message));
return ['error' => $message];
}
// Format the amount in cents, which is what Stripe wants
$amount = uc_currency_format($order->order_total, FALSE, FALSE, FALSE);
$stripe_customer_id = False;
$order_has_stripe_id = key_exists('stripe_customer_id', $order->data) ? True : False;
// Check various places to get the stripe_customer_id. If not found we'll create
// a new stripe user.
if($order_has_stripe_id){
$stripe_customer_id = $order->data['stripe_customer_id'];
}
else if ($user->uid != $order->uid) {
$stripe_customer_id = _uc_stripe_get_customer_id($order->uid);
} else {
$stripe_customer_id = _uc_stripe_get_customer_id($user->uid);
}
// In the case where the stored customer_id is not a valid customer in Stripe
// then we'll need to create a new stripe customer. see #3071712
if($stripe_customer_id && !_uc_stripe_is_stripe_id_valid($stripe_customer_id)){
watchdog('uc_stripe', 'Stripe customer: @customer is not valid in this instance of Stripe. A new customer will be created.', array('@customer' => $stripe_customer_id));
$stripe_customer_id = false;
}
$intent = null;
try {
if (isset($data['payment_method_id'])) {
$params = array(
'payment_method' => $data['payment_method_id'],
"description" => t("Order #@order_id", array("@order_id" => $order_id)),
'amount' => $amount,
'currency' => strtolower($order->currency),
'confirmation_method' => 'manual',
'confirm' => true,
'setup_future_usage' => 'off_session',
'save_payment_method' => true,
);
if(!$stripe_customer_id) {
$customer = _uc_stripe_create_stripe_customer($order, $data['payment_method_id']);
if(!$customer){
$message = 'Customer creation failed.';
$_SESSION['stripe']['payment_failed'] = TRUE;
return ['error' => $message];
}
$stripe_customer_id = $customer->id;
}
$params['customer'] = $stripe_customer_id;
# Create the PaymentIntent
$intent = \Stripe\PaymentIntent::create($params);
if(!$order_has_stripe_id){
$order->data['stripe_customer_id'] = $stripe_customer_id;
}
$order->data['payment_intent_id'] = $intent->id;
uc_order_save($order);
}
if (isset($data['payment_intent_id'])) {
$intent = \Stripe\PaymentIntent::retrieve(
$data['payment_intent_id']
);
$intent->confirm();
$order->data['payment_intent_id'] = $data['payment_intent_id'];
uc_order_save($order);
}
return _generatePaymentResponse($intent);
} catch (Exception $e) {
watchdog('uc_stripe', 'Payment could not be processed: @message', array('@message' => $e->getMessage()));
return ['error' => $e->getMessage()];
}
}
function _uc_stripe_create_stripe_customer($order, $payment_method_id = NULL){
$stripe_customer_id = FALSE;
try {
// If the token is not in the user's session, we can't set up a new customer
$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(
'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);
return $customer;
} catch (Exception $e) {
$message = t("Stripe Customer Creation Failed for order !order: !message", array(
"!order" => $order_id,
"!message" => $e->getMessage()
));
uc_order_comment_save($order_id, $user->uid, $message, 'admin');
watchdog('uc_stripe', 'Failed stripe customer creation: @message', array('@message' => $message));
return false;
}
}
/**
*
* @param string $stripe_id
* @return boolean result - if stripe_id is valid based on stripe api customer call
*/
function _uc_stripe_is_stripe_id_valid($stripe_id){
try{
if (!_uc_stripe_prepare_api()) {
$message = 'Stripe API not found.';
watchdog('uc_stripe', 'Error in Stripe API: @message', array('@message' => $message));
return false;
}
$customer = \Stripe\Customer::retrieve($stripe_id);
// Count deleted stripe customers as invalid
return !$customer->deleted;
} catch (Exception $e) {
// IF customer is not found, an exception is thrown.
return false;
}
}

View File

@ -1,149 +0,0 @@
<?php
/**
* Implements hook_form().
*
* This form allows the user to authenticate in order for their recurring payment
* to be processed.
*/
function uc_stripe_authenticate_payment_form($form, &$form_state, $hash) {
$form = array();
$pending_order = db_select('uc_stripe_pending_auth', 'u')
->fields('u', array('order_id', 'completed', 'rfee_id'))
->condition('hash', $hash)
->execute()
->fetchObject();
if(!$pending_order){
$form['error'] = array(
'#markup' => t('Sorry, we could not verify your payment details. Please verify the link and try again. Contact support if the problem persists.'),
);
return $form;
}
$order_id = $pending_order->order_id;
$completed = $pending_order->completed;
$rfee_id = $pending_order->rfee_id;
if ($completed) {
$form['error'] = array(
'#markup' => t('This payment has already been verified.'),
);
return $form;
};
$form['heading'] = array(
'#markup' => t('<p>Your financial institution has requested additional verification to process your scheduled payment.</p>'),
);
$form['order_id'] = array(
'#type' => 'hidden',
'#value' => $order_id,
);
$form['rfee_id'] = array(
'#type' => 'hidden',
'#value' => $rfee_id,
);
$form['hash'] = array(
'#type' => 'hidden',
'#value' => $hash,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Verify Payment')
);
$order = uc_order_load($order_id);
$user = user_load($order->uid);
$payment_method_id = _uc_stripe_get_payment_id($user->uid);
$stripe_customer_id = _uc_stripe_get_customer_id($user->uid);
$order_id = $order_id;
$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', ''));
$settings = array('apikey' => $apikey, 'methodId' => $payment_method_id, 'orderId' => $order_id);
//Attach Stripe v3 JS library and JS for processing payment
$form['#attached']['js']['https://js.stripe.com/v3/'] = array('type' => 'external');
$form['#attached']['js'][] = array('data' => array('uc_stripe' => $settings), 'type' => 'setting');
$form['#attached']['js'][] = drupal_get_path('module', 'uc_stripe') . '/js/uc_stripe_process_payment.js';
$form['#attached']['css'][] = drupal_get_path('module', 'uc_stripe') . '/css/uc_stripe.css';
return $form;
}
function uc_stripe_authenticate_payment_form_submit($form, &$form_state){
$order_id = $form_state['values']['order_id'];
$rfee_id = $form_state['values']['rfee_id'];
$hash = $form_state['values']['hash'];
$order = uc_order_load($order_id);
$intent_id = $order->data['payment_intent_id'];
try{
_uc_stripe_prepare_api();
$payment_intent = \Stripe\PaymentIntent::retrieve($intent_id);
if ($payment_intent->status != 'succeeded') {
throw new Exception('Payment intent failed');
}
$charge_id = $payment_intent->charges->data[0]['id'];
$amount = uc_currency_format($order->order_total, FALSE, FALSE, FALSE);
$formatted_amount = $amount / 100;
$formatted_amount = number_format($formatted_amount, 2);
$message = t('Payment of @amount processed successfully, Stripe transaction id @transaction_id.', array('@amount' => $formatted_amount, '@transaction_id' => $charge_id));
$COMPLETED = 1;
//Set all orders matching the order id and fee id to completed. This is incase
// there were multiple attempts to process the subscription.
db_update('uc_stripe_pending_auth')
->fields(array(
'completed' => $COMPLETED,
))
->condition('order_id', $order_id)
->condition('rfee_id', $rfee_id)
->execute();
$fee = uc_recurring_fee_user_load($rfee_id);
uc_payment_enter($order->order_id, $order->payment_method, $order->order_total, $fee->uid, $payment_intent, "Success");
// Since we have processed the payment here already, we'll temporarily change the fee
// handler to the the default uc_recurring fee handler that simply returns TRUE
// without any processing.
$fee->fee_handler = 'default';
$id = uc_recurring_renew($fee);
// We need to reset the fee handler for this order back to uc_stripe so that
// future subscriptions work.
$fee = uc_recurring_fee_user_load($fee->rfid);
$fee->fee_handler = 'uc_stripe';
uc_recurring_fee_user_save($fee);
uc_order_comment_save($order_id, $order->uid, $message, 'admin');
uc_order_comment_save($order_id, $order->uid, $message, 'order', 'completed', FALSE);
$form_state['redirect'] = '/';
drupal_set_message('You have successfully completed your payment');
} catch (Exception $e) {
$message = t("Stripe Charge Failed for order !order: !message", array(
"!order" => $order_id,
"!message" => $e->getMessage()
));
uc_order_comment_save($order_id, $order->uid, $message, 'admin');
watchdog('uc_stripe', 'Stripe charge failed for order @order, message: @message', array('@order' => $order_id, '@message' => $message));
$fail_message = variable_get('uc_credit_fail_message', t('We were unable to process your credit card payment. Please verify your details and try again. If the problem persists, contact us to complete your order.'));
drupal_set_message($fail_message, 'error');
}
}