Commit 0b309206 authored by bgm's avatar bgm Committed by Aegir user

INFRA-261: Add com.drastikbydesign.stripe.

parent 0141ceaf
<?php
/*
* Form Class for Stripe
*/
class CRM_Core_Form_Stripe extends CRM_Core_Form {
/**
* Function to access protected payProcessors array in event registraion forms
*/
public static function get_ppids(&$form) {
$payprocessorIds = $form->_paymentProcessors;
return $payprocessorIds;
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?php
// AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file
/**
* Base class which provides helpers to execute upgrade logic
*/
class CRM_Stripe_Upgrader_Base {
/**
* @var varies, subclass of ttis
*/
static $instance;
/**
* @var CRM_Queue_TaskContext
*/
protected $ctx;
/**
* @var string, eg 'com.example.myextension'
*/
protected $extensionName;
/**
* @var string, full path to the extension's source tree
*/
protected $extensionDir;
/**
* @var array(revisionNumber) sorted numerically
*/
private $revisions;
/**
* Obtain a reference to the active upgrade handler.
*/
static public function instance() {
if (! self::$instance) {
// FIXME auto-generate
self::$instance = new CRM_Stripe_Upgrader(
'com.drastikbydesign.stripe',
realpath(__DIR__ .'/../../../')
);
}
return self::$instance;
}
/**
* Adapter that lets you add normal (non-static) member functions to the queue.
*
* Note: Each upgrader instance should only be associated with one
* task-context; otherwise, this will be non-reentrant.
*
* @code
* CRM_Stripe_Upgrader_Base::_queueAdapter($ctx, 'methodName', 'arg1', 'arg2');
* @endcode
*/
static public function _queueAdapter() {
$instance = self::instance();
$args = func_get_args();
$instance->ctx = array_shift($args);
$instance->queue = $instance->ctx->queue;
$method = array_shift($args);
return call_user_func_array(array($instance, $method), $args);
}
public function __construct($extensionName, $extensionDir) {
$this->extensionName = $extensionName;
$this->extensionDir = $extensionDir;
}
// ******** Task helpers ********
/**
* Run a CustomData file.
*
* @param string $relativePath the CustomData XML file path (relative to this extension's dir)
* @return bool
*/
public function executeCustomDataFile($relativePath) {
$xml_file = $this->extensionDir . '/' . $relativePath;
return $this->executeCustomDataFileByAbsPath($xml_file);
}
/**
* Run a CustomData file
*
* @param string $xml_file the CustomData XML file path (absolute path)
*
* @return bool
*/
protected static function executeCustomDataFileByAbsPath($xml_file) {
require_once 'CRM/Utils/Migrate/Import.php';
$import = new CRM_Utils_Migrate_Import();
$import->run($xml_file);
return TRUE;
}
/**
* Run a SQL file.
*
* @param string $relativePath the SQL file path (relative to this extension's dir)
*
* @return bool
*/
public function executeSqlFile($relativePath) {
CRM_Utils_File::sourceSQLFile(
CIVICRM_DSN,
$this->extensionDir . '/' . $relativePath
);
return TRUE;
}
/**
* Run one SQL query.
*
* This is just a wrapper for CRM_Core_DAO::executeSql, but it
* provides syntatic sugar for queueing several tasks that
* run different queries
*/
public function executeSql($query, $params = array()) {
// FIXME verify that we raise an exception on error
CRM_Core_DAO::executeSql($query, $params);
return TRUE;
}
/**
* Syntatic sugar for enqueuing a task which calls a function in this class.
*
* The task is weighted so that it is processed
* as part of the currently-pending revision.
*
* After passing the $funcName, you can also pass parameters that will go to
* the function. Note that all params must be serializable.
*/
public function addTask($title) {
$args = func_get_args();
$title = array_shift($args);
$task = new CRM_Queue_Task(
array(get_class($this), '_queueAdapter'),
$args,
$title
);
return $this->queue->createItem($task, array('weight' => -1));
}
// ******** Revision-tracking helpers ********
/**
* Determine if there are any pending revisions.
*
* @return bool
*/
public function hasPendingRevisions() {
$revisions = $this->getRevisions();
$currentRevision = $this->getCurrentRevision();
if (empty($revisions)) {
return FALSE;
}
if (empty($currentRevision)) {
return TRUE;
}
return ($currentRevision < max($revisions));
}
/**
* Add any pending revisions to the queue.
*/
public function enqueuePendingRevisions(CRM_Queue_Queue $queue) {
$this->queue = $queue;
$currentRevision = $this->getCurrentRevision();
foreach ($this->getRevisions() as $revision) {
if ($revision > $currentRevision) {
$title = ts('Upgrade %1 to revision %2', array(
1 => $this->extensionName,
2 => $revision,
));
// note: don't use addTask() because it sets weight=-1
$task = new CRM_Queue_Task(
array(get_class($this), '_queueAdapter'),
array('upgrade_' . $revision),
$title
);
$this->queue->createItem($task);
$task = new CRM_Queue_Task(
array(get_class($this), '_queueAdapter'),
array('setCurrentRevision', $revision),
$title
);
$this->queue->createItem($task);
}
}
}
/**
* Get a list of revisions.
*
* @return array(revisionNumbers) sorted numerically
*/
public function getRevisions() {
if (! is_array($this->revisions)) {
$this->revisions = array();
$clazz = new ReflectionClass(get_class($this));
$methods = $clazz->getMethods();
foreach ($methods as $method) {
if (preg_match('/^upgrade_(.*)/', $method->name, $matches)) {
$this->revisions[] = $matches[1];
}
}
sort($this->revisions, SORT_NUMERIC);
}
return $this->revisions;
}
public function getCurrentRevision() {
// return CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName);
$key = $this->extensionName . ':version';
return CRM_Core_BAO_Setting::getItem('Extension', $key);
}
public function setCurrentRevision($revision) {
// We call this during hook_civicrm_install, but the underlying SQL
// UPDATE fails because the extension record hasn't been INSERTed yet.
// Instead, track revisions in our own namespace.
// CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision);
$key = $this->extensionName . ':version';
CRM_Core_BAO_Setting::setItem($revision, 'Extension', $key);
return TRUE;
}
// ******** Hook delegates ********
public function onInstall() {
$files = glob($this->extensionDir . '/sql/*_install.sql');
if (is_array($files)) {
foreach ($files as $file) {
CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
}
}
$files = glob($this->extensionDir . '/xml/*_install.xml');
if (is_array($files)) {
foreach ($files as $file) {
$this->executeCustomDataFileByAbsPath($file);
}
}
if (is_callable(array($this, 'install'))) {
$this->install();
}
$revisions = $this->getRevisions();
if (!empty($revisions)) {
$this->setCurrentRevision(max($revisions));
}
}
public function onUninstall() {
if (is_callable(array($this, 'uninstall'))) {
$this->uninstall();
}
$files = glob($this->extensionDir . '/sql/*_uninstall.sql');
if (is_array($files)) {
foreach ($files as $file) {
CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
}
}
$this->setCurrentRevision(NULL);
}
public function onEnable() {
// stub for possible future use
if (is_callable(array($this, 'enable'))) {
$this->enable();
}
}
public function onDisable() {
// stub for possible future use
if (is_callable(array($this, 'disable'))) {
$this->disable();
}
}
public function onUpgrade($op, CRM_Queue_Queue $queue = NULL) {
switch ($op) {
case 'check':
return array($this->hasPendingRevisions());
case 'enqueue':
return $this->enqueuePendingRevisions($queue);
default:
}
}
}
This diff is collapsed.
CiviCRM Stripe Payment Processor
--------------------------------
Version 1.8+ of this extension *must* use Stripe's latest API version (at least 2013-12-03).
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.
WEBHOOK & RECURRING PAYMENTS
---------
The Webhook.php file is registered to the path of civicrm/stripe/webhook
You will have to make a Webhook rule in your Stripe.com account and enter this path for recurring charges to end!
For Drupal: https://example.com/civicrm/stripe/webhook
For Joomla: https://example.com/index.php/component/civicrm/?task=civicrm/stripe/webhook
For Wordpress: https://example.com/?page=CiviCRM&q=civicrm/stripe/webhook
If you have multiple Stripe accounts on your site, you will need to specify the payment processor ID in the webhook URL.
To find the ID, look at the URL when you are editing the payment processor in CiviCRM: it should include `id=XX`, where `XX` is your payment processor ID.
Add a URL parameter of `ppid=XX` to the webhook URL.
For example, for a payment processor ID of 3, use the following:
For Drupal: https://example.com/civicrm/stripe/webhook?ppid=3
For Joomla: https://example.com/index.php/component/civicrm/?task=civicrm/stripe/webhook&ppid=3
For Wordpress: https://example.com/?page=CiviCRM&q=civicrm/stripe/webhook&ppid=3
INSTALLATION
------------
For CiviCRM 4.4 & up:
1) Your CiviCRM 'Resource URLs' must be set to the extensions directory
relative to Drupal/CRM base. Example: /sites/all/civicrm_extensions/
*NOT the full server path like /var/www/sites/all/civicrm_extensions/*
The admin page for Resource URLs is: /civicrm/admin/setting/url
2) Install extension via CiviCRM's "Manage Extensions" page.
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.
GOOD TO KNOW
------------
* The stripe-php package has been added to this project & no longer needs to be
downloaded separately.
* You do not need the separate civicrm_stripe CMS module for 4.2 & up
* There will no longer be branches for each version. The branches will be:
* Civi's major.minor-dev, and we will create releases (tags) for each new release version.
* Example: 4.6-dev.
AUTHOR INFO
-----------
Joshua Walker
http://drastikbydesign.com
https://drupal.org/user/433663
OTHER CREDITS
-------------
For bug fixes, new features, and documentiation, thanks to:
rgburton, Swingline0, BorislavZlatanov, agh1, & jmcclelland
<?xml version="1.0"?>
<extension key="com.drastikbydesign.stripe" type="module">
<file>stripe</file>
<name>Stripe</name>
<description>Stripe Payment Processor</description>
<urls>
<url desc="Main Extension Page">http://drastikbydesign.com</url>
</urls>
<license>AGPL</license>
<maintainer>
<author>Joshua Walker (drastik) - Drastik by Design</author>
<email>admin@drastikbydesign.com</email>
</maintainer>
<releaseDate>2016-11-06</releaseDate>
<version>4.7.2</version>
<compatibility>
<ver>4.5</ver>
<ver>4.6</ver>
<ver>4.7</ver>
</compatibility>
<comments>http://drastikbydesign.com/blog-entry/civicrm-stripe-payment-processor</comments>
<civix>
<namespace>CRM/Stripe</namespace>
</civix>
</extension>
/**
* @file
* JS Integration between CiviCRM & Stripe.
*/
(function($, CRM) {
var $form, $submit, buttonText;
var isWebform = false;
// Response from Stripe.createToken.
function stripeResponseHandler(status, response) {
if (response.error) {
$('html, body').animate({scrollTop: 0}, 300);
// Show the errors on the form.
if ($(".messages.crm-error.stripe-message").length > 0) {
$(".messages.crm-error.stripe-message").slideUp();
$(".messages.crm-error.stripe-message:first").remove();
}
$form.prepend('<div class="messages crm-error stripe-message">'
+ '<strong>Payment Error Response:</strong>'
+ '<ul id="errorList">'
+ '<li>Error: ' + response.error.message + '</li>'
+ '</ul>'
+ '</div>');
$submit.removeAttr('disabled').attr('value', buttonText);
}
else {
var token = response['id'];
// Update form with the token & submit.
$form.find("input#stripe-token").val(token);
$form.find("input#credit_card_number").removeAttr('name');
$form.find("input#cvv2").removeAttr('name');
$submit.prop('disabled', false);
window.onbeforeunload = null;
$form.get(0).submit();
}
}
// Prepare the form.
$(document).ready(function() {
$.getScript('https://js.stripe.com/v2/', function () {
Stripe.setPublishableKey($('#stripe-pub-key').val());
});
if ($('.webform-client-form').length) {
isWebform = true;
$('form.webform-client-form').addClass('stripe-payment-form');
}
else {
if (!($('.stripe-payment-form').length)) {
$('#crm-container > form').addClass('stripe-payment-form');
}
}
$form = $('form.stripe-payment-form');
if (isWebform) {
$submit = $form.find('.button-primary');
}
else {
$submit = $form.find('input[type="submit"][formnovalidate!="1"]');
// If CiviDiscount button or field is submitted, flag the form.
$form.data('cividiscount-dont-handle', '0');
// This is an ugly hack. Really, the code should positively identify the
// "real" submit button(s) and only respond to them. Otherwise, we're
// chasing down a potentially endless number of exceptions. The problem
// is that it's unclear if CiviCRM consistently names its submit buttons.
$form.find('input[type="submit"][formnovalidate="1"], input[type="submit"].cancel').click( function() {
$form.data('cividiscount-dont-handle', 1);
});
$form.find('input#discountcode').keypress( function(e) {
if (e.which == 13) {
$form.data('cividiscount-dont-handle', 1);
}
});
$submit;
}
// For CiviCRM Webforms.
if (isWebform) {
if (!($('#action').length)) {
$form.append('<input type="hidden" name="op" id="action" />');
}
$(document).keypress(function(event) {
if (event.which == 13) {
event.preventDefault();
$submit.click();
}
});
$(":submit").click(function() {
$('#action').val(this.value);
});
$('#billingcheckbox:input').hide();
$('label[for="billingcheckbox"]').hide();
var webformPrevious = $('input.webform-previous').first().val();
}
else {
// This is native civicrm form - check for existing token.
if ($form.find("input#stripe-token").val()) {
$('.credit_card_info-group').hide();
$('#billing-payment-block').append('<input type="button" value="Edit CC details" id="ccButton" />');
$('#ccButton').click(function() {
$('.credit_card_info-group').show();
$('#ccButton').hide();
$form.find('input#stripe-token').val('');
});
}
}
$submit.removeAttr('onclick');
$form.unbind('submit');
// Intercept form submission.
$form.submit(function (event) {
// Don't handle submits generated by the CiviDiscount button.
if ($form.data('cividiscount-dont-handle') == 1) {
debugging('debug: pvjwy (Discount is in play)');
return true;
}
if (isWebform) {
var $processorFields = $('.civicrm-enabled[name$="civicrm_1_contribution_1_contribution_payment_processor_id]"]');
if ($('#action').attr('value') == webformPrevious) {
debugging('wmlfp');
return true;
}
if ($('#wf-crm-billing-total').length) {
if ($('#wf-crm-billing-total').data('data-amount') == '0') {
debugging('qplfr');
return true;
}
}
if ($processorFields.length) {
if ($processorFields.filter(':checked').val() == '0') {
debugging('evxyh');
return true;
}
if (!($form.find('input[name="stripe_token"]').length)) {
debugging('irjfg');
return true;
}
}
}
// Disable the submit button to prevent repeated clicks, cache button text, restore if Stripe returns error
buttonText = $submit.attr('value');
$submit.prop('disabled', true).attr('value', 'Processing');
// Hide payment if total is 0 and no more participants.
if ($('#priceset').length) {
additionalParticipants = cj("#additional_participants").val();
// The currentTotal is already being calculated in Form/Contribution/Main.tpl.
if(typeof currentTotal !== 'undefined') {
if (currentTotal == 0 && !additionalParticipants) {
// This is also hit when "Going back", but we already have stripe_token.
debugging('ozlkf');
// This should not happen on Confirm Contribution, but seems to on 4.6 for some reason.
//return true;
}
}
}
// Handle multiple payment options and Stripe not being chosen.
if ($form.find(".crm-section.payment_processor-section").length > 0) {
var extMode = $('#ext-mode').val();
var stripeProcessorId = $('#stripe-id').val();
// Support for CiviCRM 4.6 and 4.7 multiple payment options
if (extMode == 1) {
var chosenProcessorId = $form.find('input[name="payment_processor"]:checked').val();
}
else if (extMode == 2) {
var chosenProcessorId = $form.find('input[name="payment_processor_id"]:checked').val();
}
// Bail if we're not using Stripe or are using pay later (option value '0' in payment_processor radio group).
if ((chosenProcessorId != stripeProcessorId) || (chosenProcessorId == 0)) {
debugging('debug: kfoej (Not a Stripe transaction, or pay-later)');
return true;
}
}
else {
debugging('debug: qlmvy (Stripe is the only payprocessor here)');
}
// Handle reuse of existing token
if ($form.find("input#stripe-token").val()) {
$form.find("input#credit_card_number").removeAttr('name');
$form.find("input#cvv2").removeAttr('name');
debugging('debug: zpqef (Re-using Stripe token)');
return true;
}
// If there's no credit card field, no use in continuing (probably wrong
// context anyway)
if (!$form.find('#credit_card_number').length) {
debugging('debug: gvzod (No credit card field)');
return true;
}
event.preventDefault();
event.stopPropagation();
// Handle changes introduced in CiviCRM 4.3.
if ($form.find('#credit_card_exp_date_M').length > 0) {
var cc_month = $form.find('#credit_card_exp_date_M').val();
var cc_year = $form.find('#credit_card_exp_date_Y').val();
}
else {
var cc_month = $form.find('#credit_card_exp_date\\[M\\]').val();
var cc_year = $form.find('#credit_card_exp_date\\[Y\\]').val();
}
Stripe.card.createToken({
name: $form.find('#billing_first_name').val() + ' ' + $form.find('#billing_last_name').val(),
address_zip: $form.find('#billing_postal_code-5').val(),
number: $form.find('#credit_card_number').val(),
cvc: $form.find('#cvv2').val(),
exp_month: cc_month,
exp_year: cc_year
}, stripeResponseHandler);
debugging('debug: ywkvh (Getting Stripe token)');
return false;
});
});