<?php use CRM_Stripe_ExtensionUtil as E; class CRM_Stripe_Webhook { use CRM_Stripe_WebhookTrait; /** * Checks whether the payment processors have a correctly configured webhook * * @see stripe_civicrm_check() * * @param array $messages * @param bool $attemptFix If TRUE, try to fix the webhook. * * @throws \CiviCRM_API3_Exception */ public static function check(&$messages, $attemptFix = FALSE) { $result = civicrm_api3('PaymentProcessor', 'get', [ 'class_name' => 'Payment_Stripe', 'is_active' => 1, 'domain_id' => CRM_Core_Config::domainID(), ]); foreach ($result['values'] as $paymentProcessor) { $messageTexts = []; $webhook_path = self::getWebhookPath($paymentProcessor['id']); $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessor['id']])); $processor->setAPIParams(); try { $webhooks = \Stripe\WebhookEndpoint::all(["limit" => 100]); } catch (Exception $e) { $error = $e->getMessage(); $messages[] = new CRM_Utils_Check_Message( 'stripe_webhook', $error, self::getTitle($paymentProcessor), \Psr\Log\LogLevel::ERROR, 'fa-money' ); continue; } $found_wh = FALSE; foreach ($webhooks->data as $wh) { if ($wh->url == $webhook_path) { $found_wh = TRUE; // Check and update webhook try { $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) { $messageTexts[] = E::ts('Could not check/update existing webhooks, got error from stripe <em>%1</em>', [ 1 => htmlspecialchars($e->getMessage()) ] ); } } } if (!$found_wh) { if ($attemptFix) { 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()) ]); } } else { $messageTexts[] = E::ts('Stripe Webhook missing! Please visit <a href="%1">Fix Stripe Webhook</a> to fix.<br />Expected webhook path is: <a href="%2" target="_blank">%2</a>', [ 1 => CRM_Utils_System::url('civicrm/stripe/fix-webhook'), 2 => $webhook_path, ] ); } } foreach ($messageTexts as $messageText) { $messages[] = new CRM_Utils_Check_Message( 'stripe_webhook', $messageText, self::getTitle($paymentProcessor), \Psr\Log\LogLevel::WARNING, 'fa-money' ); } } } /** * Get the error message title for the system check * @param array $paymentProcessor * * @return string */ private static function getTitle($paymentProcessor) { if (!empty($paymentProcessor['is_test'])) { $paymentProcessor['name'] .= ' (test)'; } return E::ts('Stripe Payment Processor: %1 (%2)', [ 1 => $paymentProcessor['name'], 2 => $paymentProcessor['id'], ]); } /** * Create a new webhook for payment processor * * @param int $paymentProcessorId */ public static function createWebhook($paymentProcessorId) { $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessorId])); $processor->setAPIParams(); $params = [ 'enabled_events' => self::getDefaultEnabledEvents(), 'url' => self::getWebhookPath($paymentProcessorId), 'api_version' => CRM_Core_Payment_Stripe::getApiVersion(), 'connect' => FALSE, ]; \Stripe\WebhookEndpoint::create($params); } /** * Check and update existing webhook * * @param array $webhook * @return array of correction params. Empty array if it's OK. */ public static function checkWebhook($webhook) { $params = []; if (empty($webhook['api_version']) || ($webhook['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'])) { $params['enabled_events'] = self::getDefaultEnabledEvents(); } return $params; } /** * List of webhooks we currently handle * @return array */ public static function getDefaultEnabledEvents() { return [ 'invoice.payment_succeeded', 'invoice.payment_failed', 'charge.failed', 'charge.refunded', 'charge.succeeded', 'customer.subscription.updated', 'customer.subscription.deleted', ]; } }