Commit bffaa990 authored by Mathieu Lutfy's avatar Mathieu Lutfy Committed by Aegir user
Browse files

Update Stripe to 5.2

parent d4a5f7b1
<?php
class CRM_Stripe_Customer {
/**
* Find an existing Stripe customer in the CiviCRM database
*
* @param $params
*
* @return null|string
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
public static function find($params) {
$requiredParams = ['is_live', 'processor_id'];
foreach ($requiredParams as $required) {
if (empty($required)) {
throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe Customer (find): Missing required parameter: ' . $required);
}
}
if (empty($params['contact_id'])) {
throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe Customer (find): 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 \Civi\Payment\Exception\PaymentProcessorException
*/
public static function add($params) {
$requiredParams = ['contact_id', 'customer_id', 'is_live', 'processor_id'];
foreach ($requiredParams as $required) {
if (empty($required)) {
throw new \Civi\Payment\Exception\PaymentProcessorException('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, %4)", $queryParams);
}
/**
* @param $params
* @param $paymentProcessor
*
* @return \Stripe\ApiResource
* @throws \CiviCRM_API3_Exception
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
public static function create($params, $paymentProcessor) {
$requiredParams = ['contact_id', 'card_token', 'is_live', 'processor_id'];
// $optionalParams = ['email'];
foreach ($requiredParams as $required) {
if (empty($required)) {
throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe Customer (create): Missing required parameter: ' . $required);
}
}
$contactDisplayName = civicrm_api3('Contact', 'getvalue', [
'return' => 'display_name',
'id' => $params['contact_id'],
]);
$stripeCustomerParams = [
'description' => $contactDisplayName . ' (CiviCRM)',
'card' => $params['card_token'],
'email' => CRM_Utils_Array::value('email', $params),
'metadata' => ['civicrm_contact_id' => $params['contact_id']],
];
try {
$stripeCustomer = \Stripe\Customer::create($stripeCustomerParams);
}
catch (Exception $e) {
$err = CRM_Core_Payment_Stripe::parseStripeException('create_customer', $e, FALSE);
$errorMessage = CRM_Core_Payment_Stripe::handleErrorNotification($err, $params['stripe_error_url']);
throw new \Civi\Payment\Exception\PaymentProcessorException('Failed to create Stripe Customer: ' . $errorMessage);
}
// Store the relationship between CiviCRM's email address for the Contact & Stripe's Customer ID.
$params = [
'contact_id' => $params['contact_id'],
'customer_id' => $stripeCustomer->id,
'is_live' => $params['is_live'],
'processor_id' => $params['processor_id'],
];
self::add($params);
return $stripeCustomer;
}
/**
* Delete a Stripe customer from the CiviCRM database
*
* @param array $params
*
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
public static function delete($params) {
$requiredParams = ['contact_id', 'is_live', 'processor_id'];
foreach ($requiredParams as $required) {
if (empty($required)) {
throw new \Civi\Payment\Exception\PaymentProcessorException('Stripe Customer (delete): Missing required parameter: ' . $required);
}
}
$queryParams = [
1 => [$params['contact_id'], 'String'],
2 => [$params['is_live'], 'Boolean'],
3 => [$params['processor_id'], 'Integer'],
];
$sql = "DELETE FROM civicrm_stripe_customers
WHERE contact_id = %1 AND is_live = %2 AND processor_id = %3";
CRM_Core_DAO::executeQuery($sql, $queryParams);
}
}
<?php
/*--------------------------------------------------------------------+
| CiviCRM version 5.0 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2017 |
+--------------------------------------------------------------------+
| This file is a part of CiviCRM. |
| |
| CiviCRM is free software; you can copy, modify, and distribute it |
| under the terms of the GNU Affero General Public License |
| Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
| |
| CiviCRM is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
| See the GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public |
| License and the CiviCRM Licensing Exception along |
| with this program; if not, contact CiviCRM LLC |
| at info[AT]civicrm[DOT]org. If you have questions about the |
| GNU Affero General Public License or the licensing of CiviCRM, |
| see the CiviCRM license FAQ at http://civicrm.org/licensing |
+-------------------------------------------------------------------*/
/**
* This class implements hooks for Stripe
*/
class CRM_Stripe_Hook {
/**
* This hook allows modifying recurring contribution parameters
*
* @param array $recurContributionParams Recurring contribution params (ContributionRecur.create API parameters)
*
* @return mixed
*/
public static function updateRecurringContribution(&$recurContributionParams) {
return CRM_Utils_Hook::singleton()
->invoke(1, $recurContributionParams, CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject,
CRM_Utils_Hook::$_nullObject, CRM_Utils_Hook::$_nullObject, 'civicrm_stripe_updateRecurringContribution');
}
}
......@@ -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,34 @@ 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;');
}
$this->ctx->log->info('Applying Stripe update 5010. Getting Contact IDs for civicrm_stripe_customers.');
civicrm_api3('StripeCustomer', 'updatecontactids', []);
return TRUE;
}
public function upgrade_5020() {
$this->ctx->log->info('Applying Stripe update 5020. Migrate civicrm_stripe_subscriptions data to recurring contributions.');
civicrm_api3('StripeSubscription', 'updatetransactionids', []);
return TRUE;
}
public function upgrade_5021() {
$this->ctx->log->info('Applying Stripe update 5021. Copy trxn_id to processor_id so we can cancel recurring contributions.');
civicrm_api3('StripeSubscription', 'copytrxnidtoprocessorid', []);
return TRUE;
}
}
CiviCRM Stripe Payment Processor
--------------------------------
# CiviCRM Stripe Payment Processor
## Requirements
* PHP 5.6+
* Jquery 1.10 (Use jquery_update module on Drupal).
* Drupal 7 / Joomla / Wordpress (latest supported release).
*Not currently tested with other CMS but it may work.*
* CiviCRM 5.0+
* Stripe API version: Tested on 2018-02-28
* Drupal webform_civicrm 7.x-4.22+ (if using webform integration)
Integrates the Stripe payment processor (for Credit/Debit cards) into CiviCRM so you can use it to accept Credit / Debit card payments on your site.
### How to update Stripe API version
Go to _Account Settings_ -> _API Keys_ tab -> click _Upgrade available_ button.
More info on how to change: https://stripe.com/docs/upgrades#how-can-i-upgrade-my-api
## Configuration
All configuration is in the standard Payment Processors settings area in CiviCRM admin.
You will enter your "Publishable" & "Secret" key given by stripe.com.
## Installation (Web UI)
This extension has not yet been published for installation via the web UI.
## Installation (CLI, Zip)
Sysadmins and developers may download the `.zip` file for this extension and
install it with the command-line tool [cv](https://github.com/civicrm/cv).
* https://stripe.com/
Latest releases can be found here: https://civicrm.org/extensions/stripe-payment-processor
## Documentation
Please see: https://docs.civicrm.org/stripe/en/latest
## Installation (CLI, Git)
Sysadmins and developers may clone the [Git](https://en.wikipedia.org/wiki/Git) repo for this extension and
install it with the command-line tool [cv](https://github.com/civicrm/cv).
```bash
git clone https://lab.civicrm.org/extensions/stripe.git
cv en stripe
```
## Webhook and Recurring Contributions
Stripe can notify CiviCRM every time a recurring contribution is processed.
In order to take advantage of this feature, you must configure Stripe with the right "Webhook Endpoint".
You can find the location of this setting in your Stripe Dashboard by clicking the API menu item on the left, and then choose the Webhook tab.
Then click Add Endpoint.
Now, you need to figure out what your end point is.
To figure out the "URL to be called" value, you need to check what ID is assigned to your payment processor.
To determine the correct setting, go back to your CiviCRM screen and click `Administer -> System Settings -> Payment Processor`
Click Edit next to the payment processor you are setting up.
Then, check the Address bar. You should see something like the following:
https://ptp.ourpowerbase.net/civicrm/admin/paymentProcessor?action=update&id=3&reset=1
The end of the address contains id=3. That means that this Payment Processor id is 3.
Therefore the call back address for your site will be:
civicrm/payment/ipn/3
See below for the full address to add to the endpoint (replace NN with your actual ID number):
* For Drupal: https://example.com/civicrm/payment/ipn/NN
* For Joomla: https://example.com/index.php/component/civicrm/?task=civicrm/payment/ipn/NN
* For Wordpress: https://example.com/?page=CiviCRM&q=civicrm/payment/ipn/NN
Typically, you only need to configure the end point to send live transactions and you want it to send all events.
### Cancelling Recurring Contributions
You can cancel a recurring contribution from the Stripe.com dashboard. Go to Customers and then to the specific customer.
Inside the customer you will see a Subscriptions section. Click Cancel on the subscription you want to cancel.
Stripe.com will cancel the subscription and will send a webhook to your site (if you have set the webhook options correctly).
Then the stripe_civicrm extension will process the webhook and cancel the Civi recurring contribution.
## API
This extension comes with several APIs to help you troubleshoot problems. These can be run via /civicrm/api or via drush if you are using Drupal (drush cvapi Stripe.XXX).
The api commands are:
* Listevents: Events are the notifications that Stripe sends to the Webhook. Listevents will list all notifications that have been sent. You can further restrict them with the following parameters:
* ppid - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* type - Limit to the given Stripe events type. By default, show invoice.payment_succeeded. Change to 'all' to show all.
* output - What information to show. Defaults to 'brief' which provides a summary. Alternatively use raw to get the raw JSON returned by Stripe.
* limit - Limit number of results returned (100 is max, 10 is default).
* starting_after - Only return results after this event id. This can be used for paging purposes - if you want to retreive more than 100 results.
* Populatelog: If you are running a version of CiviCRM that supports the SystemLog - then this API call will populate your SystemLog with all of your past Stripe Events. You can safely re-run and not create duplicates. With a populated SystemLog - you can selectively replay events that may have caused errors the first time or otherwise not been properly recorded. Parameters:
* ppid - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* Ipn: Replay a given Stripe Event. Parameters. This will always fetch the chosen Event from Stripe before replaying.
* id - The id from the SystemLog of the event to replay.
* evtid - The Event ID as provided by Stripe.
* ppid - Use the given Payment Processor ID. By default, uses the saved, live Stripe payment processor and throws an error if there is more than one.
* noreceipt - Set to 1 if you want to suppress the generation of receipts or set to 0 or leave out to send receipts normally.
# TESTING
### PHPUnit
This extension comes with two PHP Unit tests:
* Ipn - This unit test ensures that a recurring contribution is properly updated after the event is received from Stripe and that it is properly canceled when cancelled via Stripe.
* Direct - This unit test ensures that a direct payment to Stripe is properly recorded in the database.
Tests can be run most easily via an installation made through CiviCRM Buildkit (https://github.com/civicrm/civicrm-buildkit) by changing into the extension directory and running:
phpunit4 tests/phpunit/CRM/Stripe/IpnTest.php
phpunit4 tests/phpunit/CRM/Stripe/DirectTest.php
### Katalon Tests
See the test/katalon folder for instructions on running full web-browser based automation tests.
Expects a drupal (demo) site installed at http://localhost:8001
1. Login: No expected result, just logs into a Drupal CMS.
1. Enable Stripe Extension: Two payment processors are created, can be done manually but processor labels must match or subsequent tests will fail.
1. Offline Contribution, default PP: A contribution is created for Arlyne Adams with default PP.
1. Offline Contribution, alternate PP: A contribution is created for Arlyne Adams with alternate PP.
1. Offline Membership, default PP: A membership/contribution is created for Arlyne Adams with default PP.
1. Offline Membership, alternate PP: A membership/contribution is created for Arlyne Adams with alternate PP.
1. Offline Event Registration, default PP: A participant record/contribution is created for Arlyne Adams with default PP.
1. Offline Event Registration, alternate PP: A participant record/contribution is created for Arlyne Adams with alternate PP.
1. Online Contribution Stripe Default Only: A new contribution record is created.
1. Online Contribution Page 2xStripe, Test proc, use Stripe Alt: A new contribution record is created. **FAIL:
Error Oops! Looks like there was an error. Payment Response:
Type: invalid_request_error
Code: resource_missing
Message: No such token: Stripe Token**
1. Online Contribution Page Stripe Default, Pay Later: A new contribution record is created.
1. Test Webform: A new contribution is created. *Partial test only*
ONLINE contribution, event registration tests
### Manual Tests
1. Test webform submission with payment and user-select, single processor.
1. TODO: Are we testing offline contribution with single/multi-processor properly when stripe is/is not default with katalon tests?
1. Test online contribution page on Wordpress.
1. Test online contribution page on Joomla.
1. Test online event registration.
1. Test online event registration (cart checkout).
#### Drupal Webform Tests
TODO: Add these as Katalon tests.
1. Webform with single payment processor (Stripe) - Amount = 0.
1. Webform with single payment processor (Stripe) - Amount > 0.
1. Webform with multiple payment processor (Stripe selected) - Amount = 0.
1. Webform with multiple payment processor (Stripe selected) - Amount > 0.
1. Webform with multiple payment processor (Pay Later selected) - Amount = 0.
1. Webform with multiple payment processor (Pay Later selected) - Amount > 0.
1. Webform with multiple payment processor (Non-stripe processor selected) - Amount = 0.
1. Webform with multiple payment processor (Non-stripe processor selected) - Amount > 0.
## Credits
### Original Author
Joshua Walker - http://drastikbydesign.com - https://drupal.org/user/433663
### Other Credits
-------------
Peter Hartmann - https://blog.hartmanncomputer.com
## Configuration
All configuration is in the standard Payment Processors settings area in CiviCRM admin.
You will enter your "Publishable" & "Secret" key given by stripe.com.
For bug fixes, new features, and documentation, thanks to:
rgburton, Swingline0, BorislavZlatanov, agh1, jmcclelland, mattwire
## Installation
There are no special installation requirements.
The extension will show up in the extensions browser for automated installation.
Otherwise, download and install as you would for any other CiviCRM extension.
<?php
/**
* Stripe Customer API
*
*/
/**
* StripeCustomer.Get API specification
*
* @param array $spec description of fields supported by this API call
* @return void
* @see http://wiki.civicrm.org/confluence/display/CRMDOC/API+Architecture+Standards
*/
function _civicrm_api3_stripe_customer_get_spec(&$spec) {
$spec['id']['title'] = ts("Stripe Customer ID");
$spec['id']['type'] = CRM_Utils_Type::T_STRING;
$spec['contact_id']['title'] = ts("CiviCRM Contact ID");
$spec['contact_id']['type'] = CRM_Utils_Type::T_INT;
$spec['is_live']['title'] = ts("Is live processor");
$spec['is_live']['type'] = CRM_Utils_Type::T_BOOLEAN;
$spec['processor_id']['title'] = ts("Payment Processor ID");
$spec['processor_id']['type'] = CRM_Utils_Type::T_INT;
}
/**
* StripeCustomer.Get 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_customer_get($params) {
foreach ($params as $key => $value) {
$index = 1;
switch ($key) {
case 'id':
$where[$index] = "{$key}=%{$index}";
$whereParam[$index] = [$value, 'String'];
$index++;
break;
case 'contact_id':
case 'processor_id':
$where[$index] = "{$key}=%{$index}";
$whereParam[$index] = [$value, 'Integer'];
$index++;
break;
case 'is_live':
$where[$index] = "{$key}=%{$index}";
$whereParam[$index] = [$value, 'Boolean'];
$index++;
break;
}
}
$query = "SELECT * FROM civicrm_stripe_customers ";
if (count($where)) {
$whereClause = implode(' AND ', $where);
$query .= "WHERE {$whereClause}";
}
$dao = CRM_Core_DAO::executeQuery($query, $whereParam);
while ($dao->fetch()) {
$result = [
'id' => $dao->id,
'contact_id' => $dao->contact_id,
'is_live' => $dao->is_live,
'processor_id' => $dao->processor_id,
];
if ($dao->email) {
$result['email'] = $dao->email;
}
$results[] = $result;
}
return civicrm_api3_create_success($results);
}
/**
* Stripe.Customer.Updatecontactids 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_customer_updatecontactids($params) {
$dao = CRM_Core_DAO::executeQuery('SELECT email, id FROM civicrm_stripe_customers WHERE contact_id IS NULL');
$counts = [
'updated' => 0,
'failed' => 0,
];
while ($dao->fetch()) {
$contactId = NULL;
try {
$contactId = civicrm_api3('Contact', 'getvalue', [
'return' => "id",
'email' => $dao->email,
]);
}
catch (Exception $e) {
// Most common problem is duplicates.
if(preg_match("/Expected one Contact but found/", $e->getMessage())) {
// If we find more than one, first try to find it via a related subscription record
// using the customer id.
$sql = "SELECT c.id
FROM civicrm_contribution_recur rc
JOIN civicrm_stripe_subscriptions sc ON
rc.id = sc.contribution_recur_id
JOIN civicrm_contact c ON c.id = rc.contact_id
WHERE c.is_deleted = 0 AND customer_id = %0
ORDER BY start_date DESC LIMIT 1";
$dao_contribution = CRM_Core_DAO::executeQuery($sql, [0 => [$dao->id, 'String']]);
$dao_contribution->fetch();
if ($dao_contribution->id) {
$contactId = $dao_contribution->id;
}
if (empty($contactId)) {
// Still no luck. Now get desperate.
$sql = "SELECT c.id
FROM civicrm_contact c JOIN civicrm_email e ON c.id = e.contact_id
JOIN civicrm_contribution cc ON c.id = cc.contact_id
WHERE e.email = %0 AND c.is_deleted = 0 AND is_test = 0 AND
trxn_id LIKE 'ch_%' AND contribution_status_id = 1
ORDER BY receive_date DESC LIMIT 1";
$dao_contribution = CRM_Core_DAO::executeQuery($sql, [0 => [$dao->email, 'String']]);
$dao_contribution->fetch();
if ($dao_contribution->id) {
$contactId = $dao_contribution->id;
}
}
}
if (empty($contactId)) {
// Still no luck. Log it and move on.
Civi::log()->debug('Stripe Upgrader: No contact ID found for stripe customer with email: ' . $dao->email);
$counts['failed']++;
continue;
}
}
$sqlParams = [
1 => [$contactId, 'Integer'],
2 => [$dao->email, 'String'],
];
$sql = 'UPDATE civicrm_stripe_customers SET contact_id=%1 WHERE email=%2';
CRM_Core_DAO::executeQuery($sql, $sqlParams);
$counts['updated']++;
}
return civicrm_api3_create_success($counts);
}
<?php
/**
* Stripe Subscription API
*
*/
/**
* StripeSubscription.Get API specification
*
* @param array $spec description of fields supported by this API call
* @return void