Skip to content
Snippets Groups Projects
Webhook.php 5.04 KiB
Newer Older
mattwire's avatar
mattwire committed
<?php

use CRM_Stripe_ExtensionUtil as E;

class CRM_Stripe_Webhook {

mattwire's avatar
mattwire committed
  use CRM_Stripe_WebhookTrait;
mattwire's avatar
mattwire committed

  /**
   * Checks whether the payment processors have a correctly configured webhook
mattwire's avatar
mattwire committed
   *
   * @see stripe_civicrm_check()
   * @param bool $attemptFix If TRUE, try to fix the webhook.
mattwire's avatar
mattwire committed
   */
  public static function check(&$messages, $attemptFix = FALSE) {
mattwire's avatar
mattwire committed
    $result = civicrm_api3('PaymentProcessor', 'get', [
      'class_name' => 'Payment_Stripe',
      'is_active' => 1,
    ]);

    foreach ($result['values'] as $paymentProcessor) {
mattwire's avatar
mattwire committed
      $webhook_path = self::getWebhookPath($paymentProcessor['id']);
mattwire's avatar
mattwire committed

      \Stripe\Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKey($paymentProcessor));
mattwire's avatar
mattwire committed
      try {
        $webhooks = \Stripe\WebhookEndpoint::all(["limit" => 100]);
      }
      catch (Exception $e) {
        $error = $e->getMessage();
        $messages[] = new CRM_Utils_Check_Message(
          'stripe_webhook',
          $error,
          E::ts('Stripe Payment Processor: %1 (%2)', [
mattwire's avatar
mattwire committed
            1 => $paymentProcessor['name'],
            2 => $paymentProcessor['id'],
          ]),
          \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
            $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'),
                ]);
              }
            }
            $messageTexts[] = E::ts('Could not check/update existing webhooks, got error from stripe <em>%1</em>', [
                1 => htmlspecialchars($e->getMessage())
              ]
mattwire's avatar
mattwire committed
        }
      }

      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())
            ]);
          }
mattwire's avatar
mattwire committed
        }
        else {
          $messageTexts[] = E::ts('Stripe Webhook missing! Please visit <a href="%1">Fix Stripe Webhook</a> to fix.', [
            1 => CRM_Utils_System::url('civicrm/stripe/fix-webhook'),
          ]);
mattwire's avatar
mattwire committed
        }
      }

      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'
        );
      }
mattwire's avatar
mattwire committed
    }
  }

  /**
   * Create a new webhook for payment processor
   *
   * @param int $paymentProcessorId
   */
  public static function createWebhook($paymentProcessorId) {
    \Stripe\Stripe::setApiKey(CRM_Core_Payment_Stripe::getSecretKeyById($paymentProcessorId));

    $params = [
      'enabled_events' => self::getDefaultEnabledEvents(),
mattwire's avatar
mattwire committed
      'url' => self::getWebhookPath($paymentProcessorId),
mattwire's avatar
mattwire committed
      'api_version' => CRM_Core_Payment_Stripe::getApiVersion(),
      'connect' => FALSE,
    ];
    \Stripe\WebhookEndpoint::create($params);
  }

mattwire's avatar
mattwire committed
  /**
   * Check and update existing webhook
   *
   * @param array $webhook
   * @return array of correction params. Empty array if it's OK.
mattwire's avatar
mattwire committed
   */
  public static function checkWebhook($webhook) {
    $params = [];

    if (empty($webhook['api_version']) || ($webhook['api_version'] !== CRM_Core_Payment_Stripe::API_VERSION)) {
mattwire's avatar
mattwire committed
      $params['api_version'] = CRM_Core_Payment_Stripe::API_VERSION;
    }
mattwire's avatar
mattwire committed
    if (array_diff(self::getDefaultEnabledEvents(), $webhook['enabled_events'])) {
      $params['enabled_events'] = self::getDefaultEnabledEvents();
    }
mattwire's avatar
mattwire committed
  }

  /**
   * 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',
    ];
  }

}