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

fix status check and remove side effects; add confirm page to updating stripe webhooks

parent 91857e75
No related branches found
No related tags found
No related merge requests found
<?php
use CRM_Stripe_ExtensionUtil as E;
/**
* Form controller class
*
* @see https://wiki.civicrm.org/confluence/display/CRMDOC/QuickForm+Reference
*/
class CRM_Stripe_Form_UpdateWebhook extends CRM_Core_Form {
public function buildQuickForm() {
// Defaults.
$this->assign('shouldOfferToFix', 0);
$this->assign('isStillBad', 0);
$this->assign('isAllOk', 0);
// Run check.
$messages = [];
CRM_Stripe_Webhook::check($messages);
if (!$messages) {
$this->assign('isAllOk', 1);
}
else {
$this->assign('shouldOfferToFix', 1);
$this->assignMessages($messages);
$this->addButtons(array(
array(
'type' => 'submit',
'name' => E::ts('Update / Create webhook'),
'isDefault' => TRUE,
),
));
}
// export form elements
$this->assign('elementNames', $this->getRenderableElementNames());
parent::buildQuickForm();
}
public function postProcess() {
$messages = [];
$attemptFix = TRUE;
CRM_Stripe_Webhook::check($messages, $attemptFix);
if ($messages) {
$this->assign('isStillBad', 1);
$this->assign('shouldOfferToFix', 0);
$this->assignMessages($messages);
}
else {
$this->assign('isAllOk', 1);
$this->assign('shouldOfferToFix', 0);
$this->assign('isStillBad', 0);
$this->assign('intro', E::ts('All webhooks update successfully.'));
}
parent::postProcess();
}
/**
* @param array $messages
*/
private function assignMessages($messages) {
$messagesArray = [];
foreach ($messages as $message) {
$messagesArray[] = [
'title' => $message->getTitle(),
'message' => $message->getMessage(),
];
}
$this->assign('messages', $messagesArray);
}
/**
* Get the fields/elements defined in this form.
*
* @return array (string)
*/
public function getRenderableElementNames() {
// The _elements list includes some items which should not be
// auto-rendered in the loop -- such as "qfKey" and "buttons". These
// items don't have labels. We'll identify renderable by filtering on
// the 'label'.
$elementNames = array();
foreach ($this->_elements as $element) {
/** @var HTML_QuickForm_Element $element */
$label = $element->getLabel();
if (!empty($label)) {
$elementNames[] = $element->getName();
}
}
return $elementNames;
}
}
...@@ -7,24 +7,23 @@ class CRM_Stripe_Webhook { ...@@ -7,24 +7,23 @@ class CRM_Stripe_Webhook {
use CRM_Stripe_WebhookTrait; use CRM_Stripe_WebhookTrait;
/** /**
* Checks whether the payment processors have a correctly configured * Checks whether the payment processors have a correctly configured webhook
* webhook (we may want to check the test processors too, at some point, but
* for now, avoid having false alerts that will annoy people).
* *
* @see stripe_civicrm_check() * @see stripe_civicrm_check()
* *
* @param array $messages * @param array $messages
* @param bool $attemptFix If TRUE, try to fix the webhook.
* *
* @return array
* @throws \CiviCRM_API3_Exception * @throws \CiviCRM_API3_Exception
*/ */
public static function check($messages) { public static function check(&$messages, $attemptFix = FALSE) {
$result = civicrm_api3('PaymentProcessor', 'get', [ $result = civicrm_api3('PaymentProcessor', 'get', [
'class_name' => 'Payment_Stripe', 'class_name' => 'Payment_Stripe',
'is_active' => 1, 'is_active' => 1,
]); ]);
foreach ($result['values'] as $paymentProcessor) { foreach ($result['values'] as $paymentProcessor) {
$messageTexts = [];
$webhook_path = self::getWebhookPath($paymentProcessor['id']); $webhook_path = self::getWebhookPath($paymentProcessor['id']);
\Stripe\Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKey($paymentProcessor)); \Stripe\Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKey($paymentProcessor));
...@@ -35,12 +34,11 @@ class CRM_Stripe_Webhook { ...@@ -35,12 +34,11 @@ class CRM_Stripe_Webhook {
$error = $e->getMessage(); $error = $e->getMessage();
$messages[] = new CRM_Utils_Check_Message( $messages[] = new CRM_Utils_Check_Message(
'stripe_webhook', 'stripe_webhook',
E::ts('The %1 (%2) Payment Processor has an error: %3', [ $error,
E::ts('Stripe Payment Processor: %1 (%2)', [
1 => $paymentProcessor['name'], 1 => $paymentProcessor['name'],
2 => $paymentProcessor['id'], 2 => $paymentProcessor['id'],
3 => $error,
]), ]),
E::ts('Stripe - API Key'),
\Psr\Log\LogLevel::ERROR, \Psr\Log\LogLevel::ERROR,
'fa-money' 'fa-money'
); );
...@@ -54,50 +52,63 @@ class CRM_Stripe_Webhook { ...@@ -54,50 +52,63 @@ class CRM_Stripe_Webhook {
$found_wh = TRUE; $found_wh = TRUE;
// Check and update webhook // Check and update webhook
try { try {
self::checkAndUpdateWebhook($wh); $updates = self::checkWebhook($wh);
if ($updates) {
if ($attemptFix) {
// We should try to update the webhook.
$messageTexts[] = E::ts('Unable to update the webhook %1. To correct this please delete the webhook at Stripe and then revisit this page which will recreate it correctly.',
[1 => urldecode($webhook_path)]
);
\Stripe\WebhookEndpoint::update($wh['id'], $updates);
}
else {
$messageTexts[] = E::ts('Problems detected with Stripe webhook %1. Please visit <a href="%2">Fix Stripe Webhook</a> to fix.', [
1 => urldecode($webhook_path),
2 => CRM_Utils_System::url('civicrm/stripe/fix-webhook'),
]);
}
}
} }
catch (Exception $e) { catch (Exception $e) {
$messages[] = new CRM_Utils_Check_Message( $messageTexts[] = E::ts('Could not check/update existing webhooks, got error from stripe <em>%1</em>', [
'stripe_webhook', 1 => htmlspecialchars($e->getMessage())
E::ts('Could not update existing webhook - to fix this delete the existing webhook from your stripe account and re-run this check.<br/>The webhook URL is: %3', [ ]
1 => $paymentProcessor['name'],
2 => $paymentProcessor['id'],
3 => urldecode($webhook_path),
]) . '.<br/>Error from Stripe: <em>' . $e->getMessage() . '</em>',
E::ts('Stripe Webhook: %1 (%2)', [
1 => $paymentProcessor['name'],
2 => $paymentProcessor['id'],
]
),
\Psr\Log\LogLevel::WARNING,
'fa-money'
); );
} }
} }
} }
if (!$found_wh) { if (!$found_wh) {
try { if ($attemptFix) {
self::createWebhook($paymentProcessor['id']); try {
// Try to create one.
self::createWebhook($paymentProcessor['id']);
}
catch (Exception $e) {
$messageTexts[] = E::ts('Could not create webhook, got error from stripe <em>%1</em>', [
1 => htmlspecialchars($e->getMessage())
]);
}
} }
catch (Exception $e) { else {
$messages[] = new CRM_Utils_Check_Message( $messageTexts[] = E::ts('Stripe Webhook missing! Please visit <a href="%1">Fix Stripe Webhook</a> to fix.', [
'stripe_webhook', 1 => CRM_Utils_System::url('civicrm/stripe/fix-webhook'),
E::ts('Could not create webhook. You can review from your Stripe account, under Developers > Webhooks.<br/>The webhook URL is: %3', [ ]);
1 => $paymentProcessor['name'],
2 => $paymentProcessor['id'],
3 => urldecode($webhook_path),
]) . '.<br/>Error from Stripe: <em>' . $e->getMessage() . '</em>',
E::ts('Stripe Webhook: %1 (%2)', [
1 => $paymentProcessor['name'],
2 => $paymentProcessor['id'],
]
),
\Psr\Log\LogLevel::WARNING,
'fa-money'
);
} }
} }
foreach ($messageTexts as $messageText) {
$messages[] = new CRM_Utils_Check_Message(
'stripe_webhook',
$messageText,
E::ts('Stripe Payment Processor Webhook: %1 (%2)', [
1 => $paymentProcessor['name'],
2 => $paymentProcessor['id'],
]),
\Psr\Log\LogLevel::WARNING,
'fa-money'
);
}
} }
} }
...@@ -118,24 +129,25 @@ class CRM_Stripe_Webhook { ...@@ -118,24 +129,25 @@ class CRM_Stripe_Webhook {
\Stripe\WebhookEndpoint::create($params); \Stripe\WebhookEndpoint::create($params);
} }
/** /**
* Check and update existing webhook * Check and update existing webhook
* *
* @param array $webhook * @param array $webhook
* @return array of correction params. Empty array if it's OK.
*/ */
public static function checkAndUpdateWebhook($webhook) { public static function checkWebhook($webhook) {
$update = FALSE; $params = [];
if (empty($webhook['api_version']) || ($webhook['api_version'] !== CRM_Core_Payment_Stripe::API_VERSION)) { if (empty($webhook['api_version']) || ($webhook['api_version'] !== CRM_Core_Payment_Stripe::API_VERSION)) {
$update = TRUE;
$params['api_version'] = CRM_Core_Payment_Stripe::API_VERSION; $params['api_version'] = CRM_Core_Payment_Stripe::API_VERSION;
} }
if (array_diff(self::getDefaultEnabledEvents(), $webhook['enabled_events'])) { if (array_diff(self::getDefaultEnabledEvents(), $webhook['enabled_events'])) {
$update = TRUE;
$params['enabled_events'] = self::getDefaultEnabledEvents(); $params['enabled_events'] = self::getDefaultEnabledEvents();
} }
if ($update) {
\Stripe\WebhookEndpoint::update($webhook['id'], $params); return $params;
}
} }
/** /**
......
## Release 5.4.1
* Don't overwrite system messages when performing webhook checks.
* Add form to handle creating/updating webhooks instead of automatically during system check (Thanks @artfulrobot)
## Release 5.4 ## Release 5.4
This release fixes multiple bugs and introduces a few small features. This release fixes multiple bugs and introduces a few small features.
......
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
<author>Matthew Wire (MJW Consulting)</author> <author>Matthew Wire (MJW Consulting)</author>
<email>mjw@mjwconsult.co.uk</email> <email>mjw@mjwconsult.co.uk</email>
</maintainer> </maintainer>
<releaseDate>2019-07-10</releaseDate> <releaseDate>2019-07-21</releaseDate>
<version>5.4</version> <version>5.4.1</version>
<develStage>stable</develStage> <develStage>stable</develStage>
<compatibility> <compatibility>
<ver>5.13</ver> <ver>5.13</ver>
......
...@@ -263,16 +263,17 @@ function _stripe_civix_find_files($dir, $pattern) { ...@@ -263,16 +263,17 @@ function _stripe_civix_find_files($dir, $pattern) {
*/ */
function _stripe_civix_civicrm_managed(&$entities) { function _stripe_civix_civicrm_managed(&$entities) {
$mgdFiles = _stripe_civix_find_files(__DIR__, '*.mgd.php'); $mgdFiles = _stripe_civix_find_files(__DIR__, '*.mgd.php');
sort($mgdFiles);
foreach ($mgdFiles as $file) { foreach ($mgdFiles as $file) {
$es = include $file; $es = include $file;
foreach ($es as $e) { foreach ($es as $e) {
if (empty($e['module'])) { if (empty($e['module'])) {
$e['module'] = E::LONG_NAME; $e['module'] = E::LONG_NAME;
} }
$entities[] = $e;
if (empty($e['params']['version'])) { if (empty($e['params']['version'])) {
$e['params']['version'] = '3'; $e['params']['version'] = '3';
} }
$entities[] = $e;
} }
} }
} }
...@@ -352,8 +353,10 @@ function _stripe_civix_glob($pattern) { ...@@ -352,8 +353,10 @@ function _stripe_civix_glob($pattern) {
* Inserts a navigation menu item at a given place in the hierarchy. * Inserts a navigation menu item at a given place in the hierarchy.
* *
* @param array $menu - menu hierarchy * @param array $menu - menu hierarchy
* @param string $path - path where insertion should happen (ie. Administer/System Settings) * @param string $path - path to parent of this item, e.g. 'my_extension/submenu'
* @param array $item - menu you need to insert (parent/child attributes will be filled for you) * 'Mailing', or 'Administer/System Settings'
* @param array $item - the item to insert (parent/child attributes will be
* filled for you)
*/ */
function _stripe_civix_insert_navigation_menu(&$menu, $path, $item) { function _stripe_civix_insert_navigation_menu(&$menu, $path, $item) {
// If we are done going down the path, insert menu // If we are done going down the path, insert menu
...@@ -443,3 +446,16 @@ function _stripe_civix_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) { ...@@ -443,3 +446,16 @@ function _stripe_civix_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
$metaDataFolders[] = $settingsDir; $metaDataFolders[] = $settingsDir;
} }
} }
/**
* (Delegated) Implements hook_civicrm_entityTypes().
*
* Find any *.entityType.php files, merge their content, and return.
*
* @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_entityTypes
*/
function _stripe_civix_civicrm_entityTypes(&$entityTypes) {
$entityTypes = array_merge($entityTypes, array (
));
}
...@@ -16,7 +16,7 @@ return [ ...@@ -16,7 +16,7 @@ return [
'description' => 'Stripe Payment Processor', 'description' => 'Stripe Payment Processor',
'class_name' => 'Payment_Stripe', 'class_name' => 'Payment_Stripe',
'user_name_label' => 'Secret Key', 'user_name_label' => 'Secret Key',
'password_label' => 'Public Key', 'password_label' => 'Publishable key',
'url_site_default' => 'https://api.stripe.com/v2', 'url_site_default' => 'https://api.stripe.com/v2',
'url_recur_default' => 'https://api.stripe.com/v2', 'url_recur_default' => 'https://api.stripe.com/v2',
'url_site_test_default' => 'https://api.stripe.com/v2', 'url_site_test_default' => 'https://api.stripe.com/v2',
......
{if $isAllOk}
<div class="alert alert-success">{$intro}</div>
{/if}
{if $shouldOfferToFix}
<h3>Problems discovered:</h3>
<div class="alert alert-warning">
<ul>
{foreach from=$messages item=message}
<li>{$message.title}: {$message.message}</li>
{/foreach}
</ul>
</div>
<div class="alert alert-info">
<p>Please click the Update / Create webhook button to
attempt to fix.</p>
<p>This will attempt to check and correct your Stripe webhooks. Note: do not
run this in a development environment unless you want a webhook set up that
points to your development domain(!).</p>
</div>
{/if}
{if $isStillBad}
<div class="alert alert-danger">
<h3>There were errors updating the webhook(s):</h3>
<ul>
{foreach from=$messages item=message}
<li>{$message.title}: {$message.message}</li>
{/foreach}
</ul>
</div>
<div class="alert alert-info">
The easiest way to fix this, is to
delete your webhooks from your Stripe account(s) and then revisit this page
to recreate them correctly.
</div>
{/if}
{* FOOTER *}
{if $shouldOfferToFix || $isStillBad}
<div class="crm-submit-buttons">
{include file="CRM/common/formButtons.tpl" location="bottom"}
</div>
{/if}
<?xml version="1.0"?>
<menu>
<item>
<path>civicrm/stripe/fix-webhook</path>
<page_callback>CRM_Stripe_Form_UpdateWebhook</page_callback>
<title>Check and Fix Stripe Webhooks</title>
<access_arguments>access CiviCRM</access_arguments>
</item>
</menu>
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