Skip to content
Snippets Groups Projects
Commit bc1689aa authored by mattwire's avatar mattwire
Browse files

Use contact_id as reference in civicrm_stripe_customers and don't require an...

Use contact_id as reference in civicrm_stripe_customers and don't require an email address for payment
parent ca29d05d
Branches
Tags
2 merge requests!95.1,!45.x noemail cc
......@@ -6,6 +6,8 @@
class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
use CRM_Core_PaymentTrait;
/**
* We only need one instance of this object. So we use the singleton
* pattern and cache the instance in this variable
......@@ -21,6 +23,13 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
*/
protected $_mode = NULL;
/**
* TRUE if we are dealing with a live transaction
*
* @var boolean
*/
private $_islive = FALSE;
/**
* Constructor
*
......@@ -61,6 +70,22 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
}
}
/**
* Get the currency for the transaction.
*
* Handle any inconsistency about how it is passed in here.
*
* @param $params
*
* @return string
*/
public function getAmount($params) {
// Stripe amount required in cents.
$amount = number_format($params['amount'], 2, '.', '');
$amount = (int) preg_replace('/[^\d]/', '', strval($amount));
return $amount;
}
/**
* Helper log function.
*
......@@ -173,9 +198,8 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
$error_message .= 'Message: ' . $err['message'] . '<br />';
if (is_a($e, 'Stripe_CardError')) {
$newnote = civicrm_api3('Note', 'create', array(
'sequential' => 1,
'entity_id' => $params['contactID'],
civicrm_api3('Note', 'create', array(
'entity_id' => self::getContactId($params),
'contact_id' => $params['contributionID'],
'subject' => $err['type'],
'note' => $err['code'],
......@@ -413,9 +437,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
\Stripe\Stripe::setAppInfo('CiviCRM', CRM_Utils_System::version(), CRM_Utils_System::baseURL());
\Stripe\Stripe::setApiKey($this->_paymentProcessor['user_name']);
// Stripe amount required in cents.
$amount = number_format($params['amount'], 2, '.', '');
$amount = (int) preg_replace('/[^\d]/', '', strval($amount));
$amount = self::getAmount($params);
// Use Stripe.js instead of raw card details.
if (!empty($params['stripe_token'])) {
......@@ -429,122 +451,27 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
Civi::log()->debug('Stripe.js token was not passed! Report this message to the site administrator. $params: ' . print_r($params, TRUE));
}
// Check for existing customer, create new otherwise.
// Possible email fields.
$email_fields = array(
'email',
'email-5',
'email-Primary',
);
// Possible contact ID fields.
$contact_id_fields = array(
'contact_id',
'contactID',
);
// Find out which email field has our yummy value.
foreach ($email_fields as $email_field) {
if (!empty($params[$email_field])) {
$email = $params[$email_field];
break;
}
}
// We didn't find an email, but never fear - this might be a backend contrib.
// We can look for a contact ID field and get the email address.
if (empty($email)) {
foreach ($contact_id_fields as $cid_field) {
if (!empty($params[$cid_field])) {
$email = civicrm_api3('Contact', 'getvalue', array(
'id' => $params[$cid_field],
'return' => 'email',
));
break;
}
}
}
// We still didn't get an email address?! /ragemode on
if (empty($email)) {
CRM_Core_Error::fatal(ts('No email address found. Please report this issue.'));
}
// Prepare escaped query params.
$query_params = array(
1 => array($email, 'String'),
2 => array($this->_paymentProcessor['id'], 'Integer'),
);
$customer_query = CRM_Core_DAO::singleValueQuery("SELECT id
FROM civicrm_stripe_customers
WHERE email = %1 AND is_live = '{$this->_islive}' AND processor_id = %2", $query_params);
/****
* If for some reason you cannot use Stripe.js and you are aware of PCI Compliance issues,
* here is the alternative to Stripe.js:
****/
$contactId = self::getContactId($params);
$email = self::getBillingEmail($params, $contactId);
/*
// Get Cardholder's full name.
$cc_name = $params['first_name'] . " ";
if (strlen($params['middle_name']) > 0) {
$cc_name .= $params['middle_name'] . " ";
}
$cc_name .= $params['last_name'];
// Prepare Card details in advance to use for new Stripe Customer object if we need.
$card_details = array(
'number' => $params['credit_card_number'],
'exp_month' => $params['month'],
'exp_year' => $params['year'],
'cvc' => $params['cvv2'],
'name' => $cc_name,
'address_line1' => $params['street_address'],
'address_state' => $params['state_province'],
'address_zip' => $params['postal_code'],
);
*/
// See if we already have a stripe customer
$customerParams = [
'contact_id' => $contactId,
'card_token' => $card_token,
'is_live' => $this->_islive,
'processor_id' => $this->_paymentProcessor['id'],
'email' => $email,
];
// drastik - Uncomment this for Drupal debugging to dblog.
/*
$zz = print_r(get_defined_vars(), TRUE);
$debug_code = '<pre>' . $zz . '</pre>';
watchdog('Stripe', $debug_code);
*/
$stripeCustomerId = CRM_Stripe_Customer::find($customerParams);
// Customer not in civicrm_stripe database. Create a new Customer in Stripe.
if (!isset($customer_query)) {
$sc_create_params = array(
'description' => 'Donor from CiviCRM',
'card' => $card_token,
'email' => $email,
);
$stripe_customer = $this->stripeCatchErrors('create_customer', $sc_create_params, $params);
// Store the relationship between CiviCRM's email address for the Contact & Stripe's Customer ID.
if (isset($stripe_customer)) {
if ($this->isErrorReturn($stripe_customer)) {
return $stripe_customer;
}
// Prepare escaped query params.
$query_params = array(
1 => array($email, 'String'),
2 => array($stripe_customer->id, 'String'),
3 => array($this->_paymentProcessor['id'], 'Integer'),
);
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_customers
(email, id, is_live, processor_id) VALUES (%1, %2, '{$this->_islive}', %3)", $query_params);
}
else {
CRM_Core_Error::fatal(ts('There was an error saving new customer within Stripe. Is Stripe down?'));
}
if (!isset($stripeCustomerId)) {
$stripe_customer = CRM_Stripe_Customer::create($customerParams, $this);
}
else {
// Customer was found in civicrm_stripe database, fetch from Stripe.
$stripe_customer = $this->stripeCatchErrors('retrieve_customer', $customer_query, $params);
$stripe_customer = $this->stripeCatchErrors('retrieve_customer', $stripeCustomerId, $params);
if (!empty($stripe_customer)) {
if ($this->isErrorReturn($stripe_customer)) {
return $stripe_customer;
......@@ -564,51 +491,16 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
}
}
else {
// Customer was found in civicrm_stripe database, but unable to be
// retrieved from Stripe. Was he deleted?
$sc_create_params = array(
'description' => 'Donor from CiviCRM',
'card' => $card_token,
'email' => $email,
);
$stripe_customer = $this->stripeCatchErrors('create_customer', $sc_create_params, $params);
// Somehow a customer ID saved in the system no longer pairs
// with a Customer within Stripe. (Perhaps deleted using Stripe interface?).
// Store the relationship between CiviCRM's email address for the Contact & Stripe's Customer ID.
if (isset($stripe_customer)) {
/*if ($this->isErrorReturn($stripe_customer)) {
return $stripe_customer;
}*/
// Delete whatever we have for this customer.
$query_params = array(
1 => array($email, 'String'),
2 => array($this->_paymentProcessor['id'], 'Integer'),
);
CRM_Core_DAO::executeQuery("DELETE FROM civicrm_stripe_customers
WHERE email = %1 AND is_live = '{$this->_islive}' AND processor_id = %2", $query_params);
// Create new record for this customer.
$query_params = array(
1 => array($email, 'String'),
2 => array($stripe_customer->id, 'String'),
3 => array($this->_paymentProcessor['id'], 'Integer'),
);
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_customers (email, id, is_live, processor_id)
VALUES (%1, %2, '{$this->_islive}, %3')", $query_params);
}
else {
// Customer was found in civicrm_stripe database, but unable to be
// retrieved from Stripe, and unable to be created in Stripe. What luck :(
CRM_Core_Error::fatal(ts('There was an error saving new customer within Stripe. Is Stripe down?'));
}
// Customer was found in civicrm_stripe database, but not in Stripe.
// Delete existing customer record from CiviCRM and create a new customer
CRM_Stripe_Customer::delete($customerParams);
$stripe_customer = CRM_Stripe_Customer::create($customerParams);
}
}
// Prepare the charge array, minus Customer/Card details.
if (empty($params['description'])) {
$params['description'] = ts('Backend contribution');
$params['description'] = ts('Backend Stripe contribution');
}
// Stripe charge.
......@@ -653,7 +545,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
else {
// There was no response from Stripe on the create charge command.
if (isset($error_url)) {
CRM_Core_Error::statusBounce('Stripe transaction response not recieved! Check the Logs section of your stripe.com account.', $error_url);
CRM_Core_Error::statusBounce('Stripe transaction response not received! Check the Logs section of your stripe.com account.', $error_url);
}
else {
// Don't have return url - return error object to api
......@@ -776,12 +668,6 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
}
$plan_id = "{$membership_type_tag}every-{$frequency_interval}-{$frequency}-{$amount}-{$currency}{$mode_tag}";
// Prepare escaped query params.
$query_params = array(
1 => array($plan_id, 'String'),
);
// Prepare escaped query params.
$query_params = array(
1 => array($plan_id, 'String'),
......@@ -887,8 +773,7 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* @param array $params
* Name value pair of contribution data.
*
* @return void
*
* @throws \CiviCRM_API3_Exception
*/
public function doTransferCheckout(&$params, $component) {
self::doDirectPayment($params);
......
<?php
class CRM_Stripe_Customer {
/**
* Find an existing Stripe customer in the CiviCRM database
*
* @param $params
*
* @return null|string
* @throws \CRM_Core_Exception
*/
public static function find($params) {
$requiredParams = ['is_live', 'processor_id'];
foreach ($requiredParams as $required) {
if (empty($required)) {
throw new CRM_Core_Exception('Stripe Customer (find): Missing required parameter: ' . $required);
}
}
if (empty($params['email']) && empty($params['contact_id'])) {
throw new CRM_Core_Exception('Stripe Customer (find): One of email or contact_id is required');
}
$queryParams = [
1 => [$params['contact_id'], 'String'],
2 => [$params['is_live'], 'Boolean'],
3 => [$params['processor_id'], 'Positive'],
];
return CRM_Core_DAO::singleValueQuery("SELECT id
FROM civicrm_stripe_customers
WHERE contact_id = %1 AND is_live = %2 AND processor_id = %3", $queryParams);
}
/**
* Add a new Stripe customer to the CiviCRM database
*
* @param $params
*
* @throws \CRM_Core_Exception
*/
public static function add($params) {
$requiredParams = ['contact_id', 'customer_id', 'is_live', 'processor_id'];
foreach ($requiredParams as $required) {
if (empty($required)) {
throw new CRM_Core_Exception('Stripe Customer (add): Missing required parameter: ' . $required);
}
}
$queryParams = [
1 => [$params['contact_id'], 'String'],
2 => [$params['customer_id'], 'String'],
3 => [$params['is_live'], 'Boolean'],
4 => [$params['processor_id'], 'Integer'],
];
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_customers
(contact_id, id, is_live, processor_id) VALUES (%1, %2, %3, %3)", $queryParams);
}
public static function create($params, $paymentProcessor) {
$params['processor_id'] = $paymentProcessor->_paymentProcessor['id'];
$requiredParams = ['contact_id', 'card_token', 'is_live', 'processor_id'];
// $optionalParams = ['email'];
foreach ($requiredParams as $required) {
if (empty($required)) {
throw new CRM_Core_Exception('Stripe Customer (create): Missing required parameter: ' . $required);
}
}
$contactDisplayName = civicrm_api3('Contact', 'getvalue', [
'return' => ['display_name'],
'id' => $params['contact_id'],
]);
$sc_create_params = [
'description' => $contactDisplayName . ' (CiviCRM)',
'card' => $params['card_token'],
'email' => CRM_Utils_Array::value('email', $params),
'metadata' => ['civicrm_contact_id' => $params['contact_id']],
];
$stripe_customer = $paymentProcessor->stripeCatchErrors('create_customer', $sc_create_params, $params);
// Store the relationship between CiviCRM's email address for the Contact & Stripe's Customer ID.
if (isset($stripe_customer)) {
if ($paymentProcessor->isErrorReturn($stripe_customer)) {
return $stripe_customer;
}
$params = [
'contact_id' => $params['contact_id'],
'customer_id' => $stripe_customer->id,
'is_live' => $params['is_live'],
'processor_id' => $params['processor_id'],
];
self::add($params);
}
else {
Throw new CRM_Core_Exception(ts('There was an error saving new customer within Stripe.'));
}
return $stripe_customer;
}
/**
* Delete a Stripe customer from the CiviCRM database
*
* @param $params
*
* @throws \CRM_Core_Exception
*/
public static function delete($params) {
$requiredParams = ['contact_id', 'is_live', 'processor_id'];
foreach ($requiredParams as $required) {
if (empty($required)) {
throw new CRM_Core_Exception('Stripe Customer (delete): Missing required parameter: ' . $required);
}
}
$queryParams = [
1 => [$params['contact_id'], 'String'],
2 => [$params['is_live'], 'Boolean'],
3 => [$params['processor_id'], 'Integer'],
];
CRM_Core_DAO::executeQuery("DELETE FROM civicrm_stripe_customers
WHERE contact_id = %1 AND is_live = %2 AND processor_id = %3", $queryParams);
}
}
......@@ -10,18 +10,6 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
// By convention, functions that look like "function upgrade_NNNN()" are
// upgrade tasks. They are executed in order (like Drupal's hook_update_N).
/**
* Standard: run an install sql script
*/
public function install() {
}
/**
* Standard: run an uninstall script
*/
public function uninstall() {
}
/**
* Add is_live column to civicrm_stripe_plans and civicrm_stripe_customers tables.
*
......@@ -379,4 +367,21 @@ class CRM_Stripe_Upgrader extends CRM_Stripe_Upgrader_Base {
}
return TRUE;
}
public function upgrade_5010() {
$this->ctx->log->info('Applying Stripe update 5010. Adding contact_id to civicrm_stripe_customers.');
if (!CRM_Core_BAO_SchemaHandler::checkIfFieldExists('civicrm_stripe_customers', 'contact_id', FALSE)) {
CRM_Core_DAO::executeQuery('ALTER TABLE `civicrm_stripe_customers`
ADD COLUMN `contact_id` int(10) UNSIGNED DEFAULT NULL COMMENT "FK ID from civicrm_contact"');
CRM_Core_DAO::executeQuery('ALTER TABLE `civicrm_stripe_customers`
ADD CONSTRAINT `FK_civicrm_stripe_customers_contact_id` FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact` (`id`) ON DELETE CASCADE;');
CRM_Core_DAO::executeQuery('ALTER TABLE `civicrm_stripe_customers` ADD UNIQUE (contact_id)');
}
$this->ctx->log->info('Applying Stripe update 5010. Getting Contact IDs for civicrm_stripe_customers.');
civicrm_api3('Stripe', 'customercontactids', []);
return TRUE;
}
}
<?php
/**
* Stripe Customer API
*
*/
/**
* Stripe.Customer_Contactids API
* This api will update the civicrm_stripe_customers table and add contact IDs for all known email addresses
*
* @param array $params
* @see civicrm_api3_create_success
*
* @return array
*/
function civicrm_api3_stripe_customercontactids($params) {
$dao = CRM_Core_DAO::executeQuery('SELECT * FROM civicrm_stripe_customers');
$counts = [
'updated' => 0,
'failed' => 0,
];
while ($dao->fetch()) {
try {
$contactId = civicrm_api3('Contact', 'getvalue', [
'return' => "id",
'email' => $dao->email,
]);
CRM_Core_DAO::executeQuery("UPDATE `civicrm_stripe_customers` SET contact_id={$contactId} WHERE email='{$dao->email}'");
$counts['updated']++;
} catch (Exception $e) {
Civi::log()
->debug('Stripe Upgrader: No contact ID found for stripe customer with email: ' . $dao->email);
$counts['failed']++;
}
}
return civicrm_api3_create_success($counts);
}
......@@ -12,15 +12,18 @@
<author>Matthew Wire (MJW Consulting)</author>
<email>mjw@mjwconsult.co.uk</email>
</maintainer>
<releaseDate>2018-09-19</releaseDate>
<version>5.0</version>
<develStage>stable</develStage>
<releaseDate>2018-10-11</releaseDate>
<version>5.1.alpha3</version>
<develStage>beta</develStage>
<compatibility>
<ver>5.0</ver>
<ver>5.3</ver>
</compatibility>
<comments>Original Author: Joshua Walker (drastik) - Drastik by Design.
Jamie Mcclelland (ProgressiveTech) did a lot of the 5.x compatibility work.
</comments>
<requires>
<ext>org.civicrm.paymentlib</ext>
</requires>
<civix>
<namespace>CRM/Stripe</namespace>
</civix>
......
/* Create required tables for Stripe */
CREATE TABLE IF NOT EXISTS `civicrm_stripe_customers` (
`id` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`contact_id` int(10) UNSIGNED DEFAULT NULL COMMENT 'FK ID from civicrm_contact',
`is_live` tinyint(4) NOT NULL COMMENT 'Whether this is a live or test transaction',
`processor_id` int(10) DEFAULT NULL COMMENT 'ID from civicrm_payment_processor',
UNIQUE KEY `id` (`id`),
UNIQUE KEY `contact_id` (`contact_id`),
CONSTRAINT `FK_civicrm_stripe_customers_contact_id` FOREIGN KEY (`contact_id`)
REFERENCES `civicrm_contact` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `civicrm_stripe_plans` (
`plan_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`is_live` tinyint(4) NOT NULL COMMENT 'Whether this is a live or test transaction',
`processor_id` int(10) DEFAULT NULL COMMENT 'ID from civicrm_payment_processor',
UNIQUE KEY `plan_id` (`plan_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE IF NOT EXISTS `civicrm_stripe_subscriptions` (
`subscription_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`customer_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`contribution_recur_id` INT(10) UNSIGNED NULL DEFAULT NULL,
`end_time` int(11) NOT NULL DEFAULT '0',
`is_live` tinyint(4) NOT NULL COMMENT 'Whether this is a live or test transaction',
`processor_id` int(10) DEFAULT NULL COMMENT 'ID from civicrm_payment_processor',
KEY `end_time` (`end_time`), PRIMARY KEY `subscription_id` (`subscription_id`),
CONSTRAINT `FK_civicrm_stripe_contribution_recur_id` FOREIGN KEY (`contribution_recur_id`)
REFERENCES `civicrm_contribution_recur`(`id`) ON DELETE SET NULL ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/* Remove Stripe tables on uninstall. */
DROP TABLE civicrm_stripe_customers;
DROP TABLE civicrm_stripe_plans;
DROP TABLE civicrm_stripe_subscriptions;
\ No newline at end of file
......@@ -25,41 +25,6 @@ function stripe_civicrm_xmlMenu(&$files) {
* Implementation of hook_civicrm_install().
*/
function stripe_civicrm_install() {
// Create required tables for Stripe.
require_once "CRM/Core/DAO.php";
CRM_Core_DAO::executeQuery("
CREATE TABLE IF NOT EXISTS `civicrm_stripe_customers` (
`email` varchar(64) COLLATE utf8_unicode_ci DEFAULT NULL,
`id` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`is_live` tinyint(4) NOT NULL COMMENT 'Whether this is a live or test transaction',
`processor_id` int(10) DEFAULT NULL COMMENT 'ID from civicrm_payment_processor',
UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
CRM_Core_DAO::executeQuery("
CREATE TABLE IF NOT EXISTS `civicrm_stripe_plans` (
`plan_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`is_live` tinyint(4) NOT NULL COMMENT 'Whether this is a live or test transaction',
`processor_id` int(10) DEFAULT NULL COMMENT 'ID from civicrm_payment_processor',
UNIQUE KEY `plan_id` (`plan_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
CRM_Core_DAO::executeQuery("
CREATE TABLE IF NOT EXISTS `civicrm_stripe_subscriptions` (
`subscription_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`customer_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`contribution_recur_id` INT(10) UNSIGNED NULL DEFAULT NULL,
`end_time` int(11) NOT NULL DEFAULT '0',
`is_live` tinyint(4) NOT NULL COMMENT 'Whether this is a live or test transaction',
`processor_id` int(10) DEFAULT NULL COMMENT 'ID from civicrm_payment_processor',
KEY `end_time` (`end_time`), PRIMARY KEY `subscription_id` (`subscription_id`),
CONSTRAINT `FK_civicrm_stripe_contribution_recur_id` FOREIGN KEY (`contribution_recur_id`)
REFERENCES `civicrm_contribution_recur`(`id`) ON DELETE SET NULL ON UPDATE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
_stripe_civix_civicrm_install();
}
......@@ -67,12 +32,6 @@ function stripe_civicrm_install() {
* Implementation of hook_civicrm_uninstall().
*/
function stripe_civicrm_uninstall() {
// Remove Stripe tables on uninstall.
require_once "CRM/Core/DAO.php";
CRM_Core_DAO::executeQuery("DROP TABLE civicrm_stripe_customers");
CRM_Core_DAO::executeQuery("DROP TABLE civicrm_stripe_plans");
CRM_Core_DAO::executeQuery("DROP TABLE civicrm_stripe_subscriptions");
_stripe_civix_civicrm_uninstall();
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment