Skip to content
Snippets Groups Projects
Commit 6b12cc84 authored by Joshua Walker's avatar Joshua Walker
Browse files

restructure 4.2 branch

parent 49c68b16
No related branches found
No related tags found
No related merge requests found
Showing
with 0 additions and 1536 deletions
------------
Please Read:
There are 3 versions of this extension available. This is:
extension-4.1: Extension for CiviCRM 4.1 and earlier.
You also need a corresponding module for your CMS. Here is where the modules can be found:
Drupal: git clone --recursive --branch master http://git.drupal.org/sandbox/drastik/1719796.git civicrm_stripe
Joomla: TBD
WordPress: TBD
IMPORTANT:
The Webhook.php file is in the 'extern' folder.
You have to make a Webhook rule in your Stripe account and enter the path to Webhook.php for recurring charges to end!
------------
Installation Instructions:
------------
For CiviCRM 4.1:
Install extension
Place civicrm_templates folder anywhere and inform CiviCRM of your "Custom Templates" location in this admin page: site.com/civicrm/admin/setting/path
Copy files in extern to your CiviCRM extern folder "civicrm/extern"
Make a Webhook rule in your Stripe account and enter the path to Webhook.php
Copy Stripe's PHP library folder 'stripe-php' to civicrm/packages/stripe-php
You can get Stripe's PHP library here: https://github.com/stripe/stripe-php
------------
\ No newline at end of file
{*
+--------------------------------------------------------------------+
| CiviCRM version 4.1 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2011 |
+--------------------------------------------------------------------+
| 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 |
+--------------------------------------------------------------------+
*}
{if $form.credit_card_number or $form.bank_account_number}
<!-- START Stripe -->
{if $paymentProcessor.payment_processor_type == 'Stripe'}
<script type="text/javascript">
var stripe_publishable_key = '{$paymentProcessor.password}';
{literal}
cj(function() {
cj(document).ready(function(){
cj.getScript('https://js.stripe.com/v1/', function(){
Stripe.setPublishableKey(stripe_publishable_key);
});
/*
* Identify the payment form.
* Don't reference by form#id since it changes between payment pages (Contribution / Event / etc).
*/
cj("#crm-container>form").addClass('stripe-payment-form');
cj("form.stripe-payment-form").submit(function(event) {
// disable the submit button to prevent repeated clicks
cj('form.stripe-payment-form input.form-submit').attr("disabled", "disabled");
Stripe.createToken({
number: cj('#credit_card_number').val(),
cvc: cj('#cvv2').val(),
exp_month: cj('#credit_card_exp_date\\[M\\]').val(),
exp_year: cj('#credit_card_exp_date\\[Y\\]').val()
}, stripeResponseHandler);
// prevent the form from submitting with the default action
return false;
});
});
//Response from Stripe.createToken.
function stripeResponseHandler(status, response) {
if (response.error) {
// show the errors on the form
if(cj(".messages.crm-error.stripe-message").length > 0) {
cj(".messages.crm-error.stripe-message").slideUp();
cj(".messages.crm-error.stripe-message").remove();
}
cj("form.stripe-payment-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>');
cj('form.stripe-payment-form input.form-submit').removeAttr("disabled");
} else {
var token = response['id'];
// Update form with the token & submit
cj("input#stripe-token").val(token);
cj("form.stripe-payment-form").get(0).submit();
}
}
});
{/literal}
</script>
{/if}
<!-- END Stripe -->
<div id="payment_information">
<fieldset class="billing_mode-group {if $paymentProcessor.payment_type & 2}direct_debit_info-group{else}credit_card_info-group{/if}">
<legend>
{if $paymentProcessor.payment_type & 2}
{ts}Direct Debit Information{/ts}
{else}
{ts}Credit Card Information{/ts}
{/if}
</legend>
{if $paymentProcessor.billing_mode & 2 and !$hidePayPalExpress }
<div class="crm-section no-label paypal_button_info-section">
<div class="content description">
{ts}If you have a PayPal account, you can click the PayPal button to continue. Otherwise, fill in the credit card and billing information on this form and click <strong>Continue</strong> at the bottom of the page.{/ts}
</div>
</div>
<div class="crm-section no-label {$form.$expressButtonName.name}-section">
<div class="content description">
{$form.$expressButtonName.html}
<div class="description">Save time. Checkout securely. Pay without sharing your financial information. </div>
</div>
</div>
{/if}
{if $paymentProcessor.billing_mode & 1}
<div class="crm-section billing_mode-section {if $paymentProcessor.payment_type & 2}direct_debit_info-section{else}credit_card_info-section{/if}">
{if $paymentProcessor.payment_type & 2}
<div class="crm-section {$form.account_holder.name}-section">
<div class="label">{$form.account_holder.label}</div>
<div class="content">{$form.account_holder.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.bank_account_number.name}-section">
<div class="label">{$form.bank_account_number.label}</div>
<div class="content">{$form.bank_account_number.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.bank_identification_number.name}-section">
<div class="label">{$form.bank_identification_number.label}</div>
<div class="content">{$form.bank_identification_number.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.bank_name.name}-section">
<div class="label">{$form.bank_name.label}</div>
<div class="content">{$form.bank_name.html}</div>
<div class="clear"></div>
</div>
{else}
<div class="crm-section {$form.credit_card_type.name}-section">
<div class="label">{$form.credit_card_type.label}</div>
<div class="content">{$form.credit_card_type.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.credit_card_number.name}-section">
<div class="label">{$form.credit_card_number.label}</div>
<div class="content">{$form.credit_card_number.html}
<div class="description">{ts}Enter numbers only, no spaces or dashes.{/ts}</div>
</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.cvv2.name}-section">
<div class="label">{$form.cvv2.label}</div>
<div class="content">
{$form.cvv2.html}
<img src="{$config->resourceBase}i/mini_cvv2.gif" alt="{ts}Security Code Location on Credit Card{/ts}" style="vertical-align: text-bottom;" />
<div class="description">{ts}Usually the last 3-4 digits in the signature area on the back of the card.{/ts}</div>
</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.credit_card_exp_date.name}-section">
<div class="label">{$form.credit_card_exp_date.label}</div>
<div class="content">{$form.credit_card_exp_date.html}</div>
<div class="clear"></div>
</div>
{/if}
</div>
</fieldset>
<fieldset class="billing_name_address-group">
<legend>{ts}Billing Name and Address{/ts}</legend>
<div class="crm-section billing_name_address-section">
<div class="crm-section billingNameInfo-section">
<div class="content description">
{if $paymentProcessor.payment_type & 2}
{ts}Enter the name of the account holder, and the corresponding billing address.{/ts}
{else}
{ts}Enter the name as shown on your credit or debit card, and the billing address for this card.{/ts}
{/if}
</div>
</div>
<div class="crm-section {$form.billing_first_name.name}-section">
<div class="label">{$form.billing_first_name.label}</div>
<div class="content">{$form.billing_first_name.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.billing_middle_name.name}-section">
<div class="label">{$form.billing_middle_name.label}</div>
<div class="content">{$form.billing_middle_name.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.billing_last_name.name}-section">
<div class="label">{$form.billing_last_name.label}</div>
<div class="content">{$form.billing_last_name.html}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_street_address-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_city-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_country_id-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html|crmReplace:class:big}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_state_province_id-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html|crmReplace:class:big}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_postal_code-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html}</div>
<div class="clear"></div>
</div>
</div>
</fieldset>
{else}
</fieldset>
{/if}
</div>
{/if}
\ No newline at end of file
<?php
/*
* Handle Stripe Webhooks for recurring payments
*/
session_start();
require_once '../civicrm.config.php';
require_once 'CRM/Core/Config.php';
$config = & CRM_Core_Config::singleton();
//Get the data from stripe
$data_raw = file_get_contents("php://input");
$data = json_decode($data_raw);
if(! $data) {
CRM_Core_Error::Fatal("Stripe Callback: cannot json_decode data, exiting. <br /> $data");
}
$test_mode = ! $data->livemode;
$stripe_key = CRM_Core_DAO::singleValueQuery("SELECT user_name FROM civicrm_payment_processor WHERE payment_processor_type = 'Stripe' AND is_test = '$test_mode'");
require_once ("packages/stripe-php/lib/Stripe.php");
Stripe::setApiKey($stripe_key);
//Retrieve Event from Stripe using ID even though we already have the values now.
//This is for extra security precautions mentioned here: https://stripe.com/docs/webhooks
$stripe_event_data = Stripe_Event::retrieve($data->id);
$customer_id = $stripe_event_data->data->object->customer;
switch($stripe_event_data->type) {
//Successful recurring payment
case 'invoice.payment_succeeded' :
//Get the Stripe charge object
try {
$charge = Stripe_Charge::retrieve($stripe_event_data->data->object->charge);
} catch(Exception $e) {
CRM_Core_Error::Fatal("Failed to retrieve Stripe charge. Message: " . $e->getMessage());
break;
}
//Find the recurring contribution in CiviCRM by mapping it from Stripe
$rel_info_query = CRM_Core_DAO::executeQuery("SELECT invoice_id, end_time FROM civicrm_stripe_subscriptions WHERE customer_id = '$customer_id'");
if(! empty($rel_info_query)) {
$rel_info_query->fetch();
$invoice_id = $rel_info_query->invoice_id;
$end_time = $rel_info_query->end_time;
} else {
CRM_Core_Error::Fatal("Error relating this customer ($customer_id) to the one in civicrm_stripe_subscriptions");
}
//Compare against now + 24hrs to prevent charging 1 extra day.
$time_compare = time() + 86400;
//Fetch Civi's info about this recurring object
$recur_contrib_query = CRM_Core_DAO::executeQuery("SELECT id, contact_id, currency, contribution_status_id, is_test, contribution_type_id, payment_instrument_id, campaign_id FROM civicrm_contribution_recur WHERE invoice_id = '$invoice_id'");
if(! empty($recur_contrib_query)) {
$recur_contrib_query->fetch();
} else {
CRM_Core_Error::Fatal("ERROR: Stripe triggered a Webhook on an invoice not found in civicrm_contribution_recur: " . $stripe_event_data);
}
//Build some params
$stripe_customer = Stripe_Customer::retrieve($customer_id);
$recieve_date = date("Y-m-d H:i:s", $charge->created);
$total_amount = $charge->amount / 100;
$fee_amount = $charge->fee / 100;
$net_amount = $total_amount - $fee_amount;
$transaction_id = $charge->id;
$new_invoice_id = $stripe_event_data->data->object->id;
if(empty($recur_contrib_query->campaign_id)) {
$recur_contrib_query->campaign_id = 'NULL';
}
$first_contrib_check = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_contribution WHERE invoice_id = '$invoice_id' AND contribution_status_id = '2'");
if(! empty($first_contrib_check)) {
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution SET contribution_status_id = '1' WHERE id = '$first_contrib_check'");
return;
}
//Create this instance of the contribution for accounting in CiviCRM
CRM_Core_DAO::executeQuery("
INSERT INTO civicrm_contribution (
contact_id, contribution_type_id, payment_instrument_id, receive_date,
total_amount, fee_amount, net_amount, trxn_id, invoice_id, currency,
contribution_recur_id, is_test, contribution_status_id, campaign_id
) VALUES (
'$recur_contrib_query->contact_id', '$recur_contrib_query->contribution_type_id', '$recur_contrib_query->payment_instrument_id', '$recieve_date',
'$total_amount', '$fee_amount', '$net_amount', '$transaction_id', '$new_invoice_id', '$recur_contrib_query->currency',
'$recur_contrib_query->id', '$recur_contrib_query->is_test', '1', $recur_contrib_query->campaign_id
)");
if($time_compare > $end_time) {
$end_date = date("Y-m-d H:i:s", $end_time);
//Final payment. Recurring contribution complete
$stripe_customer->cancelSubscription();
CRM_Core_DAO::executeQuery("DELETE FROM civicrm_stripe_subscriptions WHERE invoice_id = '$invoice_id'");
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution_recur SET end_date = '$end_date', contribution_status_id = '1' WHERE invoice_id = '$invoice_id'");
return;
}
//Successful charge & more to come so set recurring contribution status to In Progress
if($recur_contrib_query->contribution_status_id != 5) {
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution_recur SET contribution_status_id = 5 WHERE invoice_id = '$invoice_id'");
return;
}
break;
//Failed recurring payment
case 'invoice.payment_failed' :
//Get the Stripe charge object
try {
$charge = Stripe_Charge::retrieve($stripe_event_data->data->object->charge);
} catch(Exception $e) {
CRM_Core_Error::Fatal("Failed to retrieve Stripe charge. Message: " . $e->getMessage());
break;
}
//Find the recurring contribution in CiviCRM by mapping it from Stripe
$invoice_id = CRM_Core_DAO::singleValueQuery("SELECT invoice_id FROM civicrm_stripe_subscriptions WHERE customer_id = '$customer_id'");
if(empty($invoice_id)) {
CRM_Core_Error::Fatal("Error relating this customer ($customer_id) to the one in civicrm_stripe_subscriptions");
}
//Fetch Civi's info about this recurring object
$recur_contrib_query = CRM_Core_DAO::executeQuery("SELECT id, contact_id, currency, contribution_status_id, is_test, contribution_type_id, payment_instrument_id, campaign_id FROM civicrm_contribution_recur WHERE invoice_id = '$invoice_id'");
if(! empty($recur_contrib_query)) {
$recur_contrib_query->fetch();
} else {
CRM_Core_Error::Fatal("ERROR: Stripe triggered a Webhook on an invoice not found in civicrm_contribution_recur: " . $stripe_event_data);
}
//Build some params
$recieve_date = date("Y-m-d H:i:s", $charge->created);
$total_amount = $charge->amount / 100;
$fee_amount = $charge->fee / 100;
$net_amount = $total_amount - $fee_amount;
$transaction_id = $charge->id;
if(empty($recur_contrib_query->campaign_id)) {
$recur_contrib_query->campaign_id = 'NULL';
}
//Create this instance of the contribution for accounting in CiviCRM
CRM_Core_DAO::executeQuery("
INSERT INTO civicrm_contribution (
contact_id, contribution_type_id, payment_instrument_id, receive_date,
total_amount, fee_amount, net_amount, trxn_id, invoice_id, currency,
contribution_recur_id, is_test, contribution_status_id, campaign_id
) VALUES (
'$recur_contrib_query->contact_id', '$recur_contrib_query->contribution_type_id', '$recur_contrib_query->payment_instrument_id', '$recieve_date',
'$total_amount', '$fee_amount', '$net_amount', '$transaction_id', '$invoice_id', '$recur_contrib_query->currency',
'$recur_contrib_query->id', '$recur_contrib_query->is_test', '4', $recur_contrib_query->campaign_id
)");
//Failed charge. Set to status to: Failed
if($recur_contrib_query->contribution_status_id != 4) {
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution_recur SET contribution_status_id = 4 WHERE invoice_id = '$invoice_id'");
return;
} else { //This has failed more than once. Now what?
}
break;
//One-time donation and per invoice payment
case 'charge.succeeded' :
//Not implemented
break;
}
<?xml version="1.0" encoding="utf-8"?>
<extension key="com.drastikbydesign.payment.stripe" type="payment">
<downloadUrl>https://github.com/downloads/drastik/civicrm_stripe/com.drastikbydesign.payment.stripe-4.1.zip</downloadUrl>
<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 (at) drastikbydesign.com</email>
</maintainer>
<releaseDate>2012-08-23</releaseDate>
<version>1.1</version>
<develStage>stable</develStage>
<compatibility><ver>4.1</ver></compatibility>
<comments></comments>
<typeInfo>
<userNameLabel>Secret Key</userNameLabel>
<passwordLabel>Publishable Key</passwordLabel>
<signatureLabel></signatureLabel>
<subjectLabel></subjectLabel>
<urlSiteDefault>https://api.stripe.com/v1</urlSiteDefault>
<urlApiDefault></urlApiDefault>
<urlRecurDefault>https://api.stripe.com/v1</urlRecurDefault>
<urlSiteTestDefault>https://api.stripe.com/v1</urlSiteTestDefault>
<urlApiTestDefault></urlApiTestDefault>
<urlRecurTestDefault>https://api.stripe.com/v1</urlRecurTestDefault>
<urlButtonDefault></urlButtonDefault>
<urlButtonTestDefault></urlButtonTestDefault>
<billingMode>form</billingMode>
<isRecur>1</isRecur>
<paymentType>1</paymentType>
</typeInfo>
</extension>
<?php
require_once 'CRM/Core/Payment.php';
class com_drastikbydesign_payment_stripe extends CRM_Core_Payment {
/**
* We only need one instance of this object. So we use the singleton
* pattern and cache the instance in this variable
*
* @var object
* @static
*/
static private $_singleton = null;
/**
* mode of operation: live or test
*
* @var object
* @static
*/
static protected $_mode = null;
/**
* Constructor
*
* @param string $mode the mode of operation: live or test
*
* @return void
*/
function __construct($mode, &$paymentProcessor) {
$this->_mode = $mode;
$this->_paymentProcessor = $paymentProcessor;
$this->_processorName = ts('Stripe');
}
/**
* Singleton function used to manage this object
*
* @param string $mode the mode of operation: live or test
*
* @return object
* @static
*
*/
static function &singleton($mode, &$paymentProcessor) {
$processorName = $paymentProcessor['name'];
if (self::$_singleton[$processorName] === NULL ) {
self::$_singleton[$processorName] = new self($mode, $paymentProcessor);
}
return self::$_singleton[$processorName];
}
/**
* This function checks to see if we have the right config values
*
* @return string the error message if any
* @public
*/
function checkConfig() {
$config = CRM_Core_Config::singleton();
$error = array();
//Create database tables if they haven't been.
if(!CRM_Core_DAO::checkTableExists('civicrm_stripe_customers')) {
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,
UNIQUE KEY `email` (`email`)
) 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,
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` (
`customer_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`invoice_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`end_time` int(11) NOT NULL DEFAULT '0',
`is_live` tinyint(4) NOT NULL COMMENT 'Whether this is a live or test transaction',
KEY `end_time` (`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
CRM_Core_Error::debug('Stripe Database tables created. <br />This is the only time this message will be displayed. You do not need to take any further actions.');
}
if (empty($this->_paymentProcessor['user_name'])) {
$error[] = ts('The "Secret Key" is not set in the Stripe Payment Processor settings.');
}
if (empty($this->_paymentProcessor['password'])) {
$error[] = ts('The "Publishable Key" is not set in the Stripe Payment Processor settings.');
}
if (!empty($error)) {
return implode('<p>', $error);
}
else {
return NULL;
}
}
/*
* CiviCRM extension uninstall()
* Not functioning in <=CiviCRM 4.1
*/
public function 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");
}
/**
* Submit a payment using Stripe's PHP API:
* https://stripe.com/docs/api?lang=php
*
* @param array $params assoc array of input parameters for this transaction
*
* @return array the result in a nice formatted array (or an error object)
* @public
*/
function doDirectPayment(&$params) {
//Include Stripe library & Set API credentials.
require_once("stripe-php/lib/Stripe.php");
Stripe::setApiKey($this->_paymentProcessor['user_name']);
//Stripe amount required in cents.
$amount = $params['amount'] * 100;
//It would require 3 digits after the decimal for one to make it this far, CiviCRM prevents this, but let's be redundant.
$amount = number_format($amount, 0, '', '');
//Check for existing customer, create new otherwise.
$email = $params['email'];
$customer_query = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_stripe_customers WHERE email = '$email'");
//Use Stripe.js instead of raw card details.
if(isset($params['stripe_token'])) {
$card_details = $params['stripe_token'];
} else {
CRM_Core_Error::fatal(ts('Stripe.js token was not passed! Have you turned on the CiviCRM-Stripe CMS module?'));
}
/****
* If for some reason you cannot use Stripe.js and you are aware of PCI Compliance issues, here is the alternative to Stripe.js:
****/
//Prepare Card details in advance to use for new Stripe Customer object if we need.
/*
$cc_name = $params['first_name'] . " ";
if (strlen($params['middle_name']) > 0) {
$cc_name .= $params['middle_name'] . " ";
}
$cc_name .= $params['last_name'];
$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'],
);
*/
//Create a new Customer in Stripe
if(!isset($customer_query)) {
$stripe_customer = Stripe_Customer::create(array(
'description' => 'Payment from CiviCRM',
'card' => $card_details,
'email' => $email,
));
//Store the relationship between CiviCRM's email address for the Contact & Stripe's Customer ID
if(isset($stripe_customer)) {
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_customers (email, id) VALUES ('$email', '$stripe_customer->id')");
} else {
CRM_Core_Error::fatal(ts('There was an error saving new customer within Stripe. Is Stripe down?'));
}
} else {
$stripe_customer = Stripe_Customer::retrieve($customer_query);
if(!empty($stripe_customer)) {
$stripe_customer->card = $card_details;
$stripe_customer->save();
} else {
$stripe_customer = Stripe_Customer::create(array(
'description' => 'Donor from CiviCRM',
'card' => $card_details,
'email' => $email,
));
//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)) {
CRM_Core_DAO::executeQuery("DELETE FROM civicrm_stripe_customers WHERE email = '$email'");
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_customers (email, id) VALUES ('$email', '$stripe_customer->id')");
} else {
CRM_Core_Error::fatal(ts('There was an error saving new customer within Stripe. Is Stripe down?'));
}
}
}
//Prepare the charge array, minus Customer/Card details.
$stripe_charge = array(
'amount' => $amount,
'currency' => 'usd',
'description' => '# CiviCRM Donation Page # ' . $params['description'] . ' # Invoice ID # ' . $params['invoiceID'],
);
//Use Stripe Customer if we have a valid one. Otherwise just use the card.
if(!empty($stripe_customer->id)) {
$stripe_charge['customer'] = $stripe_customer->id;
} else {
$stripe_charge['card'] = $card_details;
}
//Handle recurring payments in doRecurPayment().
if (CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']) {
return $this->doRecurPayment($params, $amount, $stripe_customer);
}
//Fire away!
$stripe_response = Stripe_Charge::create($stripe_charge);
$params['trxn_id'] = $stripe_response->id;
return $params;
}
/**
* Submit a recurring payment using Stripe's PHP API:
* https://stripe.com/docs/api?lang=php
*
* @param array $params assoc array of input parameters for this transaction
* @param int $amount transaction amount in USD cents
* @param object $stripe_customer Stripe customer object generated by Stripe API
*
* @return array the result in a nice formatted array (or an error object)
* @public
*/
function doRecurPayment(&$params, $amount, $stripe_customer) {
switch($this->_mode) {
case 'test':
$transaction_mode = 0;
break;
case 'live':
$transaction_mode = 1;
}
$frequency = $params['frequency_unit'];
$installments = $params['installments'];
$plan_id = "$frequency-$amount";
$stripe_plan_query = CRM_Core_DAO::singleValueQuery("SELECT plan_id FROM civicrm_stripe_plans WHERE plan_id = '$plan_id'");
if(!isset($stripe_plan_query)) {
$formatted_amount = "$" . number_format(($amount / 100), 2);
//Create a new Plan
$stripe_plan = Stripe_Plan::create(array(
"amount" => $amount,
"interval" => $frequency,
"name" => "CiviCRM $frequency" . 'ly ' . $formatted_amount,
"currency" => "usd",
"id" => $plan_id));
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_plans (plan_id) VALUES ('$plan_id')");
}
//Attach the Subscription to the Stripe Customer
$stripe_response = $stripe_customer->updateSubscription(array('prorate' => FALSE, 'plan' => $plan_id));
$existing_subscription_query = CRM_Core_DAO::singleValueQuery("SELECT invoice_id FROM civicrm_stripe_subscriptions WHERE customer_id = '$stripe_customer->id'");
if(!empty($existing_subscription_query)) {
//Cancel existing Recurring Contribution in CiviCRM
$cancel_date = date("Y-m-d H:i:s");
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution_recur SET cancel_date = '$cancel_date', contribution_status_id = '3' WHERE invoice_id = '$existing_subscription_query'");
//Delete the Stripe Subscription from our cron watch list.
CRM_Core_DAO::executeQuery("DELETE FROM civicrm_stripe_subscriptions WHERE invoice_id = '$existing_subscription_query'");
}
//Calculate timestamp for the last installment
$end_time = strtotime("+$installments $frequency");
$invoice_id = $params['invoiceID'];
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_subscriptions (customer_id, invoice_id, end_time, is_live) VALUES ('$stripe_customer->id', '$invoice_id', '$end_time', '$transaction_mode')");
$params['trxn_id'] = $stripe_response->id;
return $params;
}
/**
* Transfer method not in use
*
* @param array $params name value pair of contribution data
*
* @return void
* @access public
*
*/
function doTransferCheckout(&$params, $component) {
CRM_Core_Error::fatal(ts('Use direct billing instead of Transfer method.'));
}
}
\ No newline at end of file
<?php
require_once 'CRM/Core/Payment.php';
class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
/**
* We only need one instance of this object. So we use the singleton
* pattern and cache the instance in this variable
*
* @var object
* @static
*/
static private $_singleton = null;
/**
* mode of operation: live or test
*
* @var object
* @static
*/
static protected $_mode = null;
/**
* Constructor
*
* @param string $mode the mode of operation: live or test
*
* @return void
*/
function __construct($mode, &$paymentProcessor) {
$this->_mode = $mode;
$this->_paymentProcessor = $paymentProcessor;
$this->_processorName = ts('Stripe');
}
/**
* Singleton function used to manage this object
*
* @param string $mode the mode of operation: live or test
*
* @return object
* @static
*
*/
static function &singleton($mode, &$paymentProcessor) {
$processorName = $paymentProcessor['name'];
if (self::$_singleton[$processorName] === NULL ) {
self::$_singleton[$processorName] = new self($mode, $paymentProcessor);
}
return self::$_singleton[$processorName];
}
/**
* This function checks to see if we have the right config values
*
* @return string the error message if any
* @public
*/
function checkConfig() {
$config = CRM_Core_Config::singleton();
$error = array();
//Create database tables if they haven't been.
if(!CRM_Core_DAO::checkTableExists('civicrm_stripe_customers')) {
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,
UNIQUE KEY `email` (`email`)
) 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,
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` (
`customer_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`invoice_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`end_time` int(11) NOT NULL DEFAULT '0',
`is_live` tinyint(4) NOT NULL COMMENT 'Whether this is a live or test transaction',
KEY `end_time` (`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
");
CRM_Core_Error::debug('Stripe Database tables created.');
}
if (empty($this->_paymentProcessor['user_name'])) {
$error[] = ts('The "Secret Key" is not set in the Stripe Payment Processor settings.');
}
if (empty($this->_paymentProcessor['password'])) {
$error[] = ts('The "Publishable Key" is not set in the Stripe Payment Processor settings.');
}
if (!empty($error)) {
return implode('<p>', $error);
}
else {
return NULL;
}
}
/**
* Submit a payment using Stripe's PHP API:
* https://stripe.com/docs/api?lang=php
*
* @param array $params assoc array of input parameters for this transaction
*
* @return array the result in a nice formatted array (or an error object)
* @public
*/
function doDirectPayment(&$params) {
//Include Stripe library & Set API credentials.
require_once("stripe-php/lib/Stripe.php");
Stripe::setApiKey($this->_paymentProcessor['user_name']);
//Stripe amount required in cents.
$amount = $params['amount'] * 100;
//It would require 3 digits after the decimal for one to make it this far, CiviCRM prevents this, but let's be redundant.
$amount = number_format($amount, 0, '', '');
//Check for existing customer, create new otherwise.
$email = $params['email'];
$customer_query = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_stripe_customers WHERE email = '$email'");
//Use Stripe.js instead of raw card details.
if(isset($params['stripe_token'])) {
$card_details = $params['stripe_token'];
} else {
CRM_Core_Error::fatal(ts('Stripe.js token was not passed! Have you turned on the CiviCRM-Stripe CMS module?'));
}
/****
* If for some reason you cannot use Stripe.js and you are aware of PCI Compliance issues, here is the alternative to Stripe.js:
****/
//Prepare Card details in advance to use for new Stripe Customer object if we need.
/*
$cc_name = $params['first_name'] . " ";
if (strlen($params['middle_name']) > 0) {
$cc_name .= $params['middle_name'] . " ";
}
$cc_name .= $params['last_name'];
$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'],
);
*/
//Create a new Customer in Stripe
if(!isset($customer_query)) {
$stripe_customer = Stripe_Customer::create(array(
'description' => 'Payment from CiviCRM',
'card' => $card_details,
'email' => $email,
));
//Store the relationship between CiviCRM's email address for the Contact & Stripe's Customer ID
if(isset($stripe_customer)) {
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_customers (email, id) VALUES ('$email', '$stripe_customer->id')");
} else {
CRM_Core_Error::fatal(ts('There was an error saving new customer within Stripe. Is Stripe down?'));
}
} else {
$stripe_customer = Stripe_Customer::retrieve($customer_query);
if(!empty($stripe_customer)) {
$stripe_customer->card = $card_details;
$stripe_customer->save();
} else {
$stripe_customer = Stripe_Customer::create(array(
'description' => 'Donor from CiviCRM',
'card' => $card_details,
'email' => $email,
));
//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)) {
CRM_Core_DAO::executeQuery("DELETE FROM civicrm_stripe_customers WHERE email = '$email'");
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_customers (email, id) VALUES ('$email', '$stripe_customer->id')");
} else {
CRM_Core_Error::fatal(ts('There was an error saving new customer within Stripe. Is Stripe down?'));
}
}
}
//Prepare the charge array, minus Customer/Card details.
$stripe_charge = array(
'amount' => $amount,
'currency' => 'usd',
'description' => '# CiviCRM Donation Page # ' . $params['description'] . ' # Invoice ID # ' . $params['invoiceID'],
);
//Use Stripe Customer if we have a valid one. Otherwise just use the card.
if(!empty($stripe_customer->id)) {
$stripe_charge['customer'] = $stripe_customer->id;
} else {
$stripe_charge['card'] = $card_details;
}
//Handle recurring payments in doRecurPayment().
if (CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']) {
return $this->doRecurPayment($params, $amount, $stripe_customer);
}
//Fire away!
$stripe_response = Stripe_Charge::create($stripe_charge);
$params['trxn_id'] = $stripe_response->id;
return $params;
}
/**
* Submit a recurring payment using Stripe's PHP API:
* https://stripe.com/docs/api?lang=php
*
* @param array $params assoc array of input parameters for this transaction
* @param int $amount transaction amount in USD cents
* @param object $stripe_customer Stripe customer object generated by Stripe API
*
* @return array the result in a nice formatted array (or an error object)
* @public
*/
function doRecurPayment(&$params, $amount, $stripe_customer) {
switch($this->_mode) {
case 'test':
$transaction_mode = 0;
break;
case 'live':
$transaction_mode = 1;
}
$frequency = $params['frequency_unit'];
$installments = $params['installments'];
$plan_id = "$frequency-$amount";
$stripe_plan_query = CRM_Core_DAO::singleValueQuery("SELECT plan_id FROM civicrm_stripe_plans WHERE plan_id = '$plan_id'");
if(!isset($stripe_plan_query)) {
$formatted_amount = "$" . number_format(($amount / 100), 2);
//Create a new Plan
$stripe_plan = Stripe_Plan::create(array(
"amount" => $amount,
"interval" => $frequency,
"name" => "CiviCRM $frequency" . 'ly ' . $formatted_amount,
"currency" => "usd",
"id" => $plan_id));
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_plans (plan_id) VALUES ('$plan_id')");
}
//Attach the Subscription to the Stripe Customer
$stripe_response = $stripe_customer->updateSubscription(array('prorate' => FALSE, 'plan' => $plan_id));
$existing_subscription_query = CRM_Core_DAO::singleValueQuery("SELECT invoice_id FROM civicrm_stripe_subscriptions WHERE customer_id = '$stripe_customer->id'");
if(!empty($existing_subscription_query)) {
//Cancel existing Recurring Contribution in CiviCRM
$cancel_date = date("Y-m-d H:i:s");
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution_recur SET cancel_date = '$cancel_date', contribution_status_id = '3' WHERE invoice_id = '$existing_subscription_query'");
//Delete the Stripe Subscription from our cron watch list.
CRM_Core_DAO::executeQuery("DELETE FROM civicrm_stripe_subscriptions WHERE invoice_id = '$existing_subscription_query'");
}
//Calculate timestamp for the last installment
$end_time = strtotime("+$installments $frequency");
$invoice_id = $params['invoiceID'];
CRM_Core_DAO::executeQuery("INSERT INTO civicrm_stripe_subscriptions (customer_id, invoice_id, end_time, is_live) VALUES ('$stripe_customer->id', '$invoice_id', '$end_time', '$transaction_mode')");
$params['trxn_id'] = $stripe_response->id;
return $params;
}
/**
* Transfer method not in use
*
* @param array $params name value pair of contribution data
*
* @return void
* @access public
*
*/
function doTransferCheckout(&$params, $component) {
CRM_Core_Error::fatal(ts('Use direct billing instead of Transfer method.'));
}
}
\ No newline at end of file
------------
Please Read:
There are 3 versions of this extension available. This is:
source: Pre-extension method, folder structure is in tact, manually place files accordingly.
You also need a corresponding module for your CMS. Here is where the modules can be found:
Drupal: git clone --recursive --branch master http://git.drupal.org/sandbox/drastik/1719796.git civicrm_stripe
Joomla: TBD
WordPress: TBD
IMPORTANT:
The Webhook.php file is in the 'extern' folder.
You have to make a Webhook rule in your Stripe account and enter the path to Webhook.php for recurring charges to end!
-You will need to run the .sql file to make sure the database tables get created & Stripe is added as a payment processor option.
------------
Installation Instructions:
------------
Pre-extension (source) instructions:
Folder structure is left in tact.
Place Stripe.php in civicrm/CRM/Core/Payment/Stripe.php
Place civicrm_templates folder anywhere and inform CiviCRM of your "Custom Templates" location in this admin page: site.com/civicrm/admin/setting/path
Copy files in extern to your CiviCRM extern folder "civicrm/extern"
Make a Webhook rule in your Stripe account and enter the path to Webhook.php
Copy Stripe's PHP library folder 'stripe-php' to civicrm/packages/stripe-php
You can get Stripe's PHP library here: https://github.com/stripe/stripe-php
Run the included SQL file "civicrm_stripe.sql" to handle the DB-related needs.
------------
\ No newline at end of file
INSERT INTO `civicrm_payment_processor_type` (`id`, `name`, `title`, `description`, `is_active`, `is_default`, `user_name_label`, `password_label`, `signature_label`, `subject_label`, `class_name`, `url_site_default`, `url_api_default`, `url_recur_default`, `url_button_default`, `url_site_test_default`, `url_api_test_default`, `url_recur_test_default`, `url_button_test_default`, `billing_mode`, `is_recur`, `payment_type`) VALUES
('', 'Stripe', 'Stripe', NULL, 1, NULL, 'Secret Key', 'Publishable Key', NULL, NULL, 'Payment_Stripe', 'https://api.stripe.com/v1', NULL, 'https://api.stripe.com/v1', NULL, 'https://api.stripe.com/v1', NULL, 'https://api.stripe.com/v1', NULL, 1, 1, 1);
--
-- Table structure for table `civicrm_stripe_customers`
--
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,
UNIQUE KEY `email` (`email`)
) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
-- Table structure for table `civicrm_stripe_plans`
--
CREATE TABLE IF NOT EXISTS `civicrm_stripe_plans` (
`plan_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
UNIQUE KEY `plan_id` (`plan_id`)
) DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
--
-- Table structure for table `civicrm_stripe_subscriptions`
--
CREATE TABLE IF NOT EXISTS `civicrm_stripe_subscriptions` (
`customer_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`invoice_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`end_time` int(11) NOT NULL DEFAULT '0',
`is_live` tinyint(4) NOT NULL COMMENT 'Whether this is a live or test transaction',
KEY `end_time` (`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
{*
+--------------------------------------------------------------------+
| CiviCRM version 4.1 |
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC (c) 2004-2011 |
+--------------------------------------------------------------------+
| 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 |
+--------------------------------------------------------------------+
*}
{if $form.credit_card_number or $form.bank_account_number}
<!-- START Stripe -->
{if $paymentProcessor.payment_processor_type == 'Stripe'}
<script type="text/javascript">
var stripe_publishable_key = '{$paymentProcessor.password}';
{literal}
cj(function() {
cj(document).ready(function(){
cj.getScript('https://js.stripe.com/v1/', function(){
Stripe.setPublishableKey(stripe_publishable_key);
});
/*
* Identify the payment form.
* Don't reference by form#id since it changes between payment pages (Contribution / Event / etc).
*/
cj("#crm-container>form").addClass('stripe-payment-form');
cj("form.stripe-payment-form").submit(function(event) {
// disable the submit button to prevent repeated clicks
cj('form.stripe-payment-form input.form-submit').attr("disabled", "disabled");
Stripe.createToken({
number: cj('#credit_card_number').val(),
cvc: cj('#cvv2').val(),
exp_month: cj('#credit_card_exp_date\\[M\\]').val(),
exp_year: cj('#credit_card_exp_date\\[Y\\]').val()
}, stripeResponseHandler);
// prevent the form from submitting with the default action
return false;
});
});
//Response from Stripe.createToken.
function stripeResponseHandler(status, response) {
if (response.error) {
// show the errors on the form
if(cj(".messages.crm-error.stripe-message").length > 0) {
cj(".messages.crm-error.stripe-message").slideUp();
cj(".messages.crm-error.stripe-message").remove();
}
cj("form.stripe-payment-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>');
cj('form.stripe-payment-form input.form-submit').removeAttr("disabled");
} else {
var token = response['id'];
// Update form with the token & submit
cj("input#stripe-token").val(token);
cj("form.stripe-payment-form").get(0).submit();
}
}
});
{/literal}
</script>
{/if}
<!-- END Stripe -->
<div id="payment_information">
<fieldset class="billing_mode-group {if $paymentProcessor.payment_type & 2}direct_debit_info-group{else}credit_card_info-group{/if}">
<legend>
{if $paymentProcessor.payment_type & 2}
{ts}Direct Debit Information{/ts}
{else}
{ts}Credit Card Information{/ts}
{/if}
</legend>
{if $paymentProcessor.billing_mode & 2 and !$hidePayPalExpress }
<div class="crm-section no-label paypal_button_info-section">
<div class="content description">
{ts}If you have a PayPal account, you can click the PayPal button to continue. Otherwise, fill in the credit card and billing information on this form and click <strong>Continue</strong> at the bottom of the page.{/ts}
</div>
</div>
<div class="crm-section no-label {$form.$expressButtonName.name}-section">
<div class="content description">
{$form.$expressButtonName.html}
<div class="description">Save time. Checkout securely. Pay without sharing your financial information. </div>
</div>
</div>
{/if}
{if $paymentProcessor.billing_mode & 1}
<div class="crm-section billing_mode-section {if $paymentProcessor.payment_type & 2}direct_debit_info-section{else}credit_card_info-section{/if}">
{if $paymentProcessor.payment_type & 2}
<div class="crm-section {$form.account_holder.name}-section">
<div class="label">{$form.account_holder.label}</div>
<div class="content">{$form.account_holder.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.bank_account_number.name}-section">
<div class="label">{$form.bank_account_number.label}</div>
<div class="content">{$form.bank_account_number.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.bank_identification_number.name}-section">
<div class="label">{$form.bank_identification_number.label}</div>
<div class="content">{$form.bank_identification_number.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.bank_name.name}-section">
<div class="label">{$form.bank_name.label}</div>
<div class="content">{$form.bank_name.html}</div>
<div class="clear"></div>
</div>
{else}
<div class="crm-section {$form.credit_card_type.name}-section">
<div class="label">{$form.credit_card_type.label}</div>
<div class="content">{$form.credit_card_type.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.credit_card_number.name}-section">
<div class="label">{$form.credit_card_number.label}</div>
<div class="content">{$form.credit_card_number.html}
<div class="description">{ts}Enter numbers only, no spaces or dashes.{/ts}</div>
</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.cvv2.name}-section">
<div class="label">{$form.cvv2.label}</div>
<div class="content">
{$form.cvv2.html}
<img src="{$config->resourceBase}i/mini_cvv2.gif" alt="{ts}Security Code Location on Credit Card{/ts}" style="vertical-align: text-bottom;" />
<div class="description">{ts}Usually the last 3-4 digits in the signature area on the back of the card.{/ts}</div>
</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.credit_card_exp_date.name}-section">
<div class="label">{$form.credit_card_exp_date.label}</div>
<div class="content">{$form.credit_card_exp_date.html}</div>
<div class="clear"></div>
</div>
{/if}
</div>
</fieldset>
<fieldset class="billing_name_address-group">
<legend>{ts}Billing Name and Address{/ts}</legend>
<div class="crm-section billing_name_address-section">
<div class="crm-section billingNameInfo-section">
<div class="content description">
{if $paymentProcessor.payment_type & 2}
{ts}Enter the name of the account holder, and the corresponding billing address.{/ts}
{else}
{ts}Enter the name as shown on your credit or debit card, and the billing address for this card.{/ts}
{/if}
</div>
</div>
<div class="crm-section {$form.billing_first_name.name}-section">
<div class="label">{$form.billing_first_name.label}</div>
<div class="content">{$form.billing_first_name.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.billing_middle_name.name}-section">
<div class="label">{$form.billing_middle_name.label}</div>
<div class="content">{$form.billing_middle_name.html}</div>
<div class="clear"></div>
</div>
<div class="crm-section {$form.billing_last_name.name}-section">
<div class="label">{$form.billing_last_name.label}</div>
<div class="content">{$form.billing_last_name.html}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_street_address-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_city-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_country_id-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html|crmReplace:class:big}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_state_province_id-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html|crmReplace:class:big}</div>
<div class="clear"></div>
</div>
{assign var=n value=billing_postal_code-$bltID}
<div class="crm-section {$form.$n.name}-section">
<div class="label">{$form.$n.label}</div>
<div class="content">{$form.$n.html}</div>
<div class="clear"></div>
</div>
</div>
</fieldset>
{else}
</fieldset>
{/if}
</div>
{/if}
\ No newline at end of file
<?php
/*
* Handle Stripe Webhooks for recurring payments
*/
session_start();
require_once '../civicrm.config.php';
require_once 'CRM/Core/Config.php';
$config = & CRM_Core_Config::singleton();
//Get the data from stripe
$data_raw = file_get_contents("php://input");
$data = json_decode($data_raw);
if(! $data) {
CRM_Core_Error::Fatal("Stripe Callback: cannot json_decode data, exiting. <br /> $data");
}
$test_mode = ! $data->livemode;
$stripe_key = CRM_Core_DAO::singleValueQuery("SELECT user_name FROM civicrm_payment_processor WHERE payment_processor_type = 'Stripe' AND is_test = '$test_mode'");
require_once ("packages/stripe-php/lib/Stripe.php");
Stripe::setApiKey($stripe_key);
//Retrieve Event from Stripe using ID even though we already have the values now.
//This is for extra security precautions mentioned here: https://stripe.com/docs/webhooks
$stripe_event_data = Stripe_Event::retrieve($data->id);
$customer_id = $stripe_event_data->data->object->customer;
switch($stripe_event_data->type) {
//Successful recurring payment
case 'invoice.payment_succeeded' :
//Get the Stripe charge object
try {
$charge = Stripe_Charge::retrieve($stripe_event_data->data->object->charge);
} catch(Exception $e) {
CRM_Core_Error::Fatal("Failed to retrieve Stripe charge. Message: " . $e->getMessage());
break;
}
//Find the recurring contribution in CiviCRM by mapping it from Stripe
$rel_info_query = CRM_Core_DAO::executeQuery("SELECT invoice_id, end_time FROM civicrm_stripe_subscriptions WHERE customer_id = '$customer_id'");
if(! empty($rel_info_query)) {
$rel_info_query->fetch();
$invoice_id = $rel_info_query->invoice_id;
$end_time = $rel_info_query->end_time;
} else {
CRM_Core_Error::Fatal("Error relating this customer ($customer_id) to the one in civicrm_stripe_subscriptions");
}
//Compare against now + 24hrs to prevent charging 1 extra day.
$time_compare = time() + 86400;
//Fetch Civi's info about this recurring object
$recur_contrib_query = CRM_Core_DAO::executeQuery("SELECT id, contact_id, currency, contribution_status_id, is_test, contribution_type_id, payment_instrument_id, campaign_id FROM civicrm_contribution_recur WHERE invoice_id = '$invoice_id'");
if(! empty($recur_contrib_query)) {
$recur_contrib_query->fetch();
} else {
CRM_Core_Error::Fatal("ERROR: Stripe triggered a Webhook on an invoice not found in civicrm_contribution_recur: " . $stripe_event_data);
}
//Build some params
$stripe_customer = Stripe_Customer::retrieve($customer_id);
$recieve_date = date("Y-m-d H:i:s", $charge->created);
$total_amount = $charge->amount / 100;
$fee_amount = $charge->fee / 100;
$net_amount = $total_amount - $fee_amount;
$transaction_id = $charge->id;
$new_invoice_id = $stripe_event_data->data->object->id;
if(empty($recur_contrib_query->campaign_id)) {
$recur_contrib_query->campaign_id = 'NULL';
}
$first_contrib_check = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_contribution WHERE invoice_id = '$invoice_id' AND contribution_status_id = '2'");
if(! empty($first_contrib_check)) {
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution SET contribution_status_id = '1' WHERE id = '$first_contrib_check'");
return;
}
//Create this instance of the contribution for accounting in CiviCRM
CRM_Core_DAO::executeQuery("
INSERT INTO civicrm_contribution (
contact_id, contribution_type_id, payment_instrument_id, receive_date,
total_amount, fee_amount, net_amount, trxn_id, invoice_id, currency,
contribution_recur_id, is_test, contribution_status_id, campaign_id
) VALUES (
'$recur_contrib_query->contact_id', '$recur_contrib_query->contribution_type_id', '$recur_contrib_query->payment_instrument_id', '$recieve_date',
'$total_amount', '$fee_amount', '$net_amount', '$transaction_id', '$new_invoice_id', '$recur_contrib_query->currency',
'$recur_contrib_query->id', '$recur_contrib_query->is_test', '1', $recur_contrib_query->campaign_id
)");
if($time_compare > $end_time) {
$end_date = date("Y-m-d H:i:s", $end_time);
//Final payment. Recurring contribution complete
$stripe_customer->cancelSubscription();
CRM_Core_DAO::executeQuery("DELETE FROM civicrm_stripe_subscriptions WHERE invoice_id = '$invoice_id'");
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution_recur SET end_date = '$end_date', contribution_status_id = '1' WHERE invoice_id = '$invoice_id'");
return;
}
//Successful charge & more to come so set recurring contribution status to In Progress
if($recur_contrib_query->contribution_status_id != 5) {
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution_recur SET contribution_status_id = 5 WHERE invoice_id = '$invoice_id'");
return;
}
break;
//Failed recurring payment
case 'invoice.payment_failed' :
//Get the Stripe charge object
try {
$charge = Stripe_Charge::retrieve($stripe_event_data->data->object->charge);
} catch(Exception $e) {
CRM_Core_Error::Fatal("Failed to retrieve Stripe charge. Message: " . $e->getMessage());
break;
}
//Find the recurring contribution in CiviCRM by mapping it from Stripe
$invoice_id = CRM_Core_DAO::singleValueQuery("SELECT invoice_id FROM civicrm_stripe_subscriptions WHERE customer_id = '$customer_id'");
if(empty($invoice_id)) {
CRM_Core_Error::Fatal("Error relating this customer ($customer_id) to the one in civicrm_stripe_subscriptions");
}
//Fetch Civi's info about this recurring object
$recur_contrib_query = CRM_Core_DAO::executeQuery("SELECT id, contact_id, currency, contribution_status_id, is_test, contribution_type_id, payment_instrument_id, campaign_id FROM civicrm_contribution_recur WHERE invoice_id = '$invoice_id'");
if(! empty($recur_contrib_query)) {
$recur_contrib_query->fetch();
} else {
CRM_Core_Error::Fatal("ERROR: Stripe triggered a Webhook on an invoice not found in civicrm_contribution_recur: " . $stripe_event_data);
}
//Build some params
$recieve_date = date("Y-m-d H:i:s", $charge->created);
$total_amount = $charge->amount / 100;
$fee_amount = $charge->fee / 100;
$net_amount = $total_amount - $fee_amount;
$transaction_id = $charge->id;
if(empty($recur_contrib_query->campaign_id)) {
$recur_contrib_query->campaign_id = 'NULL';
}
//Create this instance of the contribution for accounting in CiviCRM
CRM_Core_DAO::executeQuery("
INSERT INTO civicrm_contribution (
contact_id, contribution_type_id, payment_instrument_id, receive_date,
total_amount, fee_amount, net_amount, trxn_id, invoice_id, currency,
contribution_recur_id, is_test, contribution_status_id, campaign_id
) VALUES (
'$recur_contrib_query->contact_id', '$recur_contrib_query->contribution_type_id', '$recur_contrib_query->payment_instrument_id', '$recieve_date',
'$total_amount', '$fee_amount', '$net_amount', '$transaction_id', '$invoice_id', '$recur_contrib_query->currency',
'$recur_contrib_query->id', '$recur_contrib_query->is_test', '4', $recur_contrib_query->campaign_id
)");
//Failed charge. Set to status to: Failed
if($recur_contrib_query->contribution_status_id != 4) {
CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution_recur SET contribution_status_id = 4 WHERE invoice_id = '$invoice_id'");
return;
} else { //This has failed more than once. Now what?
}
break;
//One-time donation and per invoice payment
case 'charge.succeeded' :
//Not implemented
break;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment