Skip to content
Snippets Groups Projects
Commit 449d4c22 authored by mattwire's avatar mattwire Committed by mattwire
Browse files

Refactor IPN class to be more object oriented

parent 27837c01
Branches
Tags
1 merge request!1526.6 to master
......@@ -1114,11 +1114,19 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* @throws \Stripe\Exception\UnknownApiErrorException
*/
public function handlePaymentNotification() {
// Set default http response to 200
http_response_code(200);
$rawData = file_get_contents("php://input");
$ipnClass = new CRM_Core_Payment_StripeIPN($rawData);
if ($ipnClass->onReceiveWebhook()) {
http_response_code(200);
}
$event = json_decode($rawData);
$ipnClass = new CRM_Core_Payment_StripeIPN();
$ipnClass->setEventID($event->id);
if (!$ipnClass->setEventType($event->type)) {
// We don't handle this event
return;
};
$ipnClass->setVerifyData(TRUE);
$ipnClass->setPaymentProcessor(CRM_Utils_Request::retrieveValue('processor_id', 'Positive', NULL, FALSE, 'GET'));
$ipnClass->onReceiveWebhook();
}
/**
......@@ -1132,13 +1140,26 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
* Override setting of email receipt if set to 0, 1
*
* @return bool
* @throws \CRM_Core_Exception
* @throws \CiviCRM_API3_Exception
* @throws \Stripe\Exception\UnknownApiErrorException
* @throws \API_Exception
* @throws \Civi\API\Exception\UnauthorizedException
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
public static function processPaymentNotification($paymentProcessorID, $rawData, $verifyRequest = TRUE, $emailReceipt = NULL) {
$_GET['processor_id'] = $paymentProcessorID;
$ipnClass = new CRM_Core_Payment_StripeIPN($rawData, $verifyRequest);
// Set default http response to 200
http_response_code(200);
$event = json_decode($rawData);
$ipnClass = new CRM_Core_Payment_StripeIPN();
$ipnClass->setEventID($event->id);
if (!$ipnClass->setEventType($event->type)) {
// We don't handle this event
return FALSE;
};
$ipnClass->setVerifyData($verifyRequest);
if (!$verifyRequest) {
$ipnClass->setData($event->data);
}
$ipnClass->setPaymentProcessor($paymentProcessorID);
$ipnClass->setExceptionMode(FALSE);
if (isset($emailReceipt)) {
$ipnClass->setSendEmailReceipt($emailReceipt);
......@@ -1146,6 +1167,29 @@ class CRM_Core_Payment_Stripe extends CRM_Core_Payment {
return $ipnClass->processWebhook();
}
/**
* Get help text information (help, description, etc.) about this payment,
* to display to the user.
*
* @param string $context
* Context of the text.
* Only explicitly supported contexts are handled without error.
* Currently supported:
* - contributionPageRecurringHelp (params: is_recur_installments, is_email_receipt)
* - contributionPageContinueText (params: amount, is_payment_to_existing)
* - cancelRecurDetailText:
* params:
* mode, amount, currency, frequency_interval, frequency_unit,
* installments, {membershipType|only if mode=auto_renew},
* selfService (bool) - TRUE if user doesn't have "edit contributions" permission.
* ie. they are accessing via a "self-service" link from an email receipt or similar.
* - cancelRecurNotSupportedText
*
* @param array $params
* Parameters for the field, context specific.
*
* @return string
*/
public function getText($context, $params) {
$text = parent::getText($context, $params);
......
......@@ -21,11 +21,6 @@ class CRM_Core_Payment_StripeIPN {
*/
protected $_paymentProcessor;
/**
* @var \Stripe\StripeObject
*/
protected $_inputParameters;
/**
* The CiviCRM contact ID that maps to the Stripe customer
*
......@@ -33,15 +28,7 @@ class CRM_Core_Payment_StripeIPN {
*/
protected $contactID = NULL;
/**
* Do we send an email receipt for each contribution?
*
* @var int
*/
protected $is_email_receipt = NULL;
// Properties of the event.
protected $eventType = NULL;
protected $subscription_id = NULL;
protected $customer_id = NULL;
protected $charge_id = NULL;
......@@ -51,16 +38,6 @@ class CRM_Core_Payment_StripeIPN {
protected $frequency_unit = NULL;
protected $plan_start = NULL;
/**
* @var int The recurring contribution ID (linked to Stripe Subscription) (if available)
*/
protected $contribution_recur_id = NULL;
/**
* @var string The Stripe Event ID
*/
protected $event_id = NULL;
/**
* @var string The stripe Invoice ID (mapped to trxn_id on a contribution for recurring contributions)
*/
......@@ -87,59 +64,62 @@ class CRM_Core_Payment_StripeIPN {
protected $contribution = NULL;
/**
* CRM_Core_Payment_StripeIPN constructor.
* @var bool
*/
protected $setInputParametersHasRun = FALSE;
/**
* Returns TRUE if we handle this event type, FALSE otherwise
* @param string $eventType
*
* @param string $rawData
* json encoded string
* @param bool $verifyRequest
* Should we check the received event is valid/matches what the payment provider has?
* @return bool
*/
public function __construct($rawData, $verifyRequest = TRUE) {
$data = json_decode($rawData);
$this->setInputParameters($data, $verifyRequest);
public function setEventType($eventType) {
$this->eventType = $eventType;
if (!in_array($this->eventType, CRM_Stripe_Webhook::getDefaultEnabledEvents())) {
return FALSE;
}
return TRUE;
}
/**
* Store input array on the class.
* We override base because our input parameter is an object
* Set and initialise the paymentProcessor object
* @param int $paymentProcessorID
*
* @param \Stripe\StripeObject $data
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
public function setInputParameters($data, $verifyRequest = TRUE) {
// Determine the proper Stripe Processor ID so we can get the secret key
// and initialize Stripe.
$this->getPaymentProcessor();
$this->_paymentProcessor->setAPIParams();
if (!is_object($data)) {
$this->exception('Invalid input data');
public function setPaymentProcessor($paymentProcessorID) {
try {
$this->_paymentProcessor = \Civi\Payment\System::singleton()->getById($paymentProcessorID);
$this->_paymentProcessor->setAPIParams();
}
catch (Exception $e) {
$this->exception('Failed to get payment processor');
}
}
$this->event_id = $data->id;
$this->eventType = $data->type;
// Now re-retrieve the data from Stripe to ensure it's legit.
// Special case if this is the test webhook
if (substr($this->event_id, -15, 15) === '_00000000000000') {
http_response_code(200);
$test = (boolean) $this->_paymentProcessor->getPaymentProcessor()['is_test'] ? '(Test)' : '(Live)';
$name = $this->_paymentProcessor->getPaymentProcessor()['name'];
echo "Test webhook from Stripe ({$this->event_id}) received successfully by CiviCRM: {$name} {$test}.";
exit();
/**
* Store input array on the class.
*/
public function setInputParameters() {
if ($this->setInputParametersHasRun) {
return;
}
if ($verifyRequest) {
$this->_inputParameters = $this->_paymentProcessor->stripeClient->events->retrieve($data->id);
if ($this->getVerifyData()) {
/** @var \Stripe\Event $event */
$event = $this->_paymentProcessor->stripeClient->events->retrieve($this->eventID);
$this->setData($event->data);
}
else {
$this->_inputParameters = $data;
if (!is_object($this->getData())) {
$this->exception('Invalid input data (not an object)');
}
$this->invoice_id = $this->retrieve('invoice_id', 'String', FALSE);
$this->charge_id = $this->retrieve('charge_id', 'String', FALSE);
$this->subscription_id = $this->retrieve('subscription_id', 'String', FALSE);
http_response_code(200);
$this->setInputParametersHasRun = TRUE;
}
/**
......@@ -154,7 +134,7 @@ class CRM_Core_Payment_StripeIPN {
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
public function retrieve($name, $type, $abort = TRUE) {
$value = CRM_Stripe_Api::getObjectParam($name, $this->_inputParameters->data->object);
$value = CRM_Stripe_Api::getObjectParam($name, $this->getData()->object);
$value = CRM_Utils_Type::validate($value, $type, FALSE);
if ($abort && $value === NULL) {
echo "Failure: Missing or invalid parameter " . CRM_Utils_Type::escape($name, 'String');
......@@ -187,6 +167,17 @@ class CRM_Core_Payment_StripeIPN {
return TRUE;
}
// Now re-retrieve the data from Stripe to ensure it's legit.
// Special case if this is the test webhook
if (substr($this->getEventID(), -15, 15) === '_00000000000000') {
$test = (boolean) $this->_paymentProcessor->getPaymentProcessor()['is_test'] ? '(Test)' : '(Live)';
$name = $this->_paymentProcessor->getPaymentProcessor()['name'];
echo "Test webhook from Stripe ({$this->getEventID()}) received successfully by CiviCRM: {$name} {$test}.";
exit();
}
$this->setInputParameters();
$uniqueIdentifier = $this->getWebhookUniqueIdentifier();
// Get all received webhooks with matching identifier which have not been processed
......@@ -225,7 +216,7 @@ class CRM_Core_Payment_StripeIPN {
->addValue('payment_processor_id', $this->_paymentProcessor->getID())
->addValue('trigger', $this->eventType)
->addValue('identifier', $uniqueIdentifier)
->addValue('event_id', $this->event_id)
->addValue('event_id', $this->eventID)
->execute();
if (!$processWebhook) {
......@@ -243,6 +234,7 @@ class CRM_Core_Payment_StripeIPN {
* @throws \Civi\API\Exception\UnauthorizedException
*/
public function processWebhook() {
$this->setInputParameters();
try {
$success = $this->processEventType();
}
......@@ -375,7 +367,7 @@ class CRM_Core_Payment_StripeIPN {
case 'charge.failed':
// If we don't have a customer_id we can't do anything with it!
// It's quite likely to be a fraudulent/spam so we ignore.
if (empty(CRM_Stripe_Api::getObjectParam('customer_id', $this->_inputParameters->data->object))) {
if (empty(CRM_Stripe_Api::getObjectParam('customer_id', $this->getData()->object))) {
return TRUE;
}
......@@ -393,7 +385,7 @@ class CRM_Core_Payment_StripeIPN {
case 'charge.refunded':
// Cancelling an uncaptured paymentIntent triggers charge.refunded but we don't want to process that
if (empty(CRM_Stripe_Api::getObjectParam('captured', $this->_inputParameters->data->object))) {
if (empty(CRM_Stripe_Api::getObjectParam('captured', $this->getData()->object))) {
return TRUE;
};
// This charge was actually captured, so record the refund in CiviCRM
......@@ -401,7 +393,7 @@ class CRM_Core_Payment_StripeIPN {
return TRUE;
}
// This gives us the actual amount refunded
$amountRefunded = CRM_Stripe_Api::getObjectParam('amount_refunded', $this->_inputParameters->data->object);
$amountRefunded = CRM_Stripe_Api::getObjectParam('amount_refunded', $this->getData()->object);
// This gives us the refund date + reason code
$refunds = $this->_paymentProcessor->stripeClient->refunds->all(['charge' => $this->charge_id, 'limit' => 1]);
// This gets the fee refunded
......@@ -434,7 +426,7 @@ class CRM_Core_Payment_StripeIPN {
case 'charge.succeeded':
// For a recurring contribution we can process charge.succeeded once we receive the event with an invoice ID.
// For a single contribution we can't process charge.succeeded because it only triggers BEFORE the charge is captured
if (empty(CRM_Stripe_Api::getObjectParam('customer_id', $this->_inputParameters->data->object))) {
if (empty(CRM_Stripe_Api::getObjectParam('customer_id', $this->getData()->object))) {
return TRUE;
}
// Deliberately missing break here because we process charge.succeeded per charge.captured
......@@ -522,21 +514,21 @@ class CRM_Core_Payment_StripeIPN {
public function setInfo() {
if (!$this->getCustomer()) {
if ((bool)\Civi::settings()->get('stripe_ipndebug')) {
$message = $this->_paymentProcessor->getPaymentProcessorLabel() . ': ' . CRM_Stripe_Api::getParam('id', $this->_inputParameters) . ': Missing customer_id';
$message = $this->_paymentProcessor->getPaymentProcessorLabel() . ': ' . $this->getEventID() . ': Missing customer_id';
Civi::log()->debug($message);
}
return FALSE;
// return FALSE;
}
$this->receive_date = $this->retrieve('receive_date', 'String', FALSE);
$this->amount = $this->retrieve('amount', 'String', FALSE);
if (($this->_inputParameters->data->object->object !== 'charge') && ($this->charge_id !== NULL)) {
if (($this->getData()->object->object !== 'charge') && ($this->charge_id !== NULL)) {
$charge = $this->_paymentProcessor->stripeClient->charges->retrieve($this->charge_id);
$balanceTransactionID = CRM_Stripe_Api::getObjectParam('balance_transaction', $charge);
}
else {
$balanceTransactionID = CRM_Stripe_Api::getObjectParam('balance_transaction', $this->_inputParameters->data->object);
$balanceTransactionID = CRM_Stripe_Api::getObjectParam('balance_transaction', $this->getData()->object);
}
$this->setBalanceTransactionDetails($balanceTransactionID);
......@@ -565,7 +557,7 @@ class CRM_Core_Payment_StripeIPN {
}
catch (Exception $e) {
if ((bool)\Civi::settings()->get('stripe_ipndebug')) {
$message = $this->_paymentProcessor->getPaymentProcessorLabel() . ': ' . CRM_Stripe_Api::getParam('id', $this->_inputParameters) . ': Cannot find recurring contribution for subscription ID: ' . $this->subscription_id;
$message = $this->_paymentProcessor->getPaymentProcessorLabel() . ': ' . $this->getEventID() . ': Cannot find recurring contribution for subscription ID: ' . $this->subscription_id;
Civi::log()->debug($message);
}
return FALSE;
......@@ -622,7 +614,7 @@ class CRM_Core_Payment_StripeIPN {
if (!$contribution['count']) {
if ((bool)\Civi::settings()->get('stripe_ipndebug')) {
$message = $this->_paymentProcessor->getPaymentProcessorLabel() . 'No matching contributions for event ' . CRM_Stripe_Api::getParam('id', $this->_inputParameters);
$message = $this->_paymentProcessor->getPaymentProcessorLabel() . 'No matching contributions for event ' . $this->getEventID();
Civi::log()->debug($message);
}
return FALSE;
......@@ -640,7 +632,7 @@ class CRM_Core_Payment_StripeIPN {
* @throws \Civi\Payment\Exception\PaymentProcessorException
*/
private function getCustomer() {
$this->customer_id = CRM_Stripe_Api::getObjectParam('customer_id', $this->_inputParameters->data->object);
$this->customer_id = CRM_Stripe_Api::getObjectParam('customer_id', $this->getData()->object);
if (empty($this->customer_id)) {
$this->exception('Missing customer_id!');
}
......@@ -656,7 +648,7 @@ class CRM_Core_Payment_StripeIPN {
catch (Exception $e) {
// Customer not found in CiviCRM
if ((bool)\Civi::settings()->get('stripe_ipndebug') && !$this->contribution) {
$message = $this->_paymentProcessor->getPaymentProcessorLabel() . 'Stripe Customer not found in CiviCRM for event ' . CRM_Stripe_Api::getParam('id', $this->_inputParameters);
$message = $this->_paymentProcessor->getPaymentProcessorLabel() . 'Stripe Customer not found in CiviCRM for event ' . $this->getEventID();
Civi::log()->debug($message);
}
return FALSE;
......
......@@ -11,6 +11,13 @@
class CRM_Stripe_Api {
/**
* @param string $name
* @param \Stripe\StripeObject $stripeObject
*
* @return bool|float|int|string|null
* @throws \Stripe\Exception\ApiErrorException
*/
public static function getObjectParam($name, $stripeObject) {
switch ($stripeObject->object) {
case 'charge':
......@@ -165,27 +172,6 @@ class CRM_Stripe_Api {
return $stripeTimestamp ? date('YmdHis', $stripeTimestamp) : NULL;
}
/**
* @param string $name
* @param \Stripe\StripeObject $stripeObject
*
* @return string|null
*/
public static function getParam($name, $stripeObject) {
// Common parameters
switch ($name) {
case 'customer_id':
return (string) $stripeObject->customer;
case 'event_type':
return (string) $stripeObject->type;
case 'id':
return (string) $stripeObject->id;
}
return NULL;
}
/**
* Convert amount to a new currency
*
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment