Webhook.php 5.68 KB
Newer Older
mattwire's avatar
mattwire committed
1
<?php
mattwire's avatar
mattwire committed
2
3
4
/**
 * https://civicrm.org/licensing
 */
mattwire's avatar
mattwire committed
5
6
7

use CRM_Stripe_ExtensionUtil as E;

mattwire's avatar
mattwire committed
8
9
10
/**
 * Class CRM_Stripe_Webhook
 */
mattwire's avatar
mattwire committed
11
12
class CRM_Stripe_Webhook {

mattwire's avatar
mattwire committed
13
  use CRM_Stripe_WebhookTrait;
mattwire's avatar
mattwire committed
14
15

  /**
16
   * Checks whether the payment processors have a correctly configured webhook
mattwire's avatar
mattwire committed
17
18
   *
   * @see stripe_civicrm_check()
19
20
   *
   * @param array $messages
21
   * @param bool $attemptFix If TRUE, try to fix the webhook.
22
23
   *
   * @throws \CiviCRM_API3_Exception
mattwire's avatar
mattwire committed
24
   */
25
  public static function check(&$messages, $attemptFix = FALSE) {
mattwire's avatar
mattwire committed
26
27
28
    $result = civicrm_api3('PaymentProcessor', 'get', [
      'class_name' => 'Payment_Stripe',
      'is_active' => 1,
29
      'domain_id' => CRM_Core_Config::domainID(),
mattwire's avatar
mattwire committed
30
31
32
    ]);

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

36
37
38
      $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessor['id']]));
      $processor->setAPIParams();

mattwire's avatar
mattwire committed
39
40
41
42
43
44
45
      try {
        $webhooks = \Stripe\WebhookEndpoint::all(["limit" => 100]);
      }
      catch (Exception $e) {
        $error = $e->getMessage();
        $messages[] = new CRM_Utils_Check_Message(
          'stripe_webhook',
46
          $error,
47
          self::getTitle($paymentProcessor),
mattwire's avatar
mattwire committed
48
49
50
51
52
53
54
55
56
57
58
59
          \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
60
          try {
61
            $updates = self::checkWebhook($wh);
62
            if ($updates && $wh->status != 'disabled') {
63
64
65
66
67
68
69
70
71
72
73
74
75
76
              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'),
                ]);
              }
            }
77
78
          }
          catch (Exception $e) {
79
80
81
            $messageTexts[] = E::ts('Could not check/update existing webhooks, got error from stripe <em>%1</em>', [
                1 => htmlspecialchars($e->getMessage())
              ]
82
83
            );
          }
mattwire's avatar
mattwire committed
84
85
86
87
        }
      }

      if (!$found_wh) {
88
89
90
91
92
93
94
95
96
97
        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
98
        }
99
        else {
100
101
102
103
104
105
          $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,
            ]
          );
mattwire's avatar
mattwire committed
106
107
        }
      }
108
109
110
111
112

      foreach ($messageTexts as $messageText) {
        $messages[] = new CRM_Utils_Check_Message(
          'stripe_webhook',
          $messageText,
113
          self::getTitle($paymentProcessor),
114
115
116
117
          \Psr\Log\LogLevel::WARNING,
          'fa-money'
        );
      }
mattwire's avatar
mattwire committed
118
119
120
    }
  }

121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
  /**
   * 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'],
    ]);
  }

mattwire's avatar
mattwire committed
137
138
139
140
141
142
  /**
   * Create a new webhook for payment processor
   *
   * @param int $paymentProcessorId
   */
  public static function createWebhook($paymentProcessorId) {
143
144
    $processor = new CRM_Core_Payment_Stripe('', civicrm_api3('PaymentProcessor', 'getsingle', ['id' => $paymentProcessorId]));
    $processor->setAPIParams();
mattwire's avatar
mattwire committed
145
146
147

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

155

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

165
    if (empty($webhook['api_version']) || ($webhook['api_version'] !== CRM_Core_Payment_Stripe::API_VERSION)) {
mattwire's avatar
mattwire committed
166
167
      $params['api_version'] = CRM_Core_Payment_Stripe::API_VERSION;
    }
168

mattwire's avatar
mattwire committed
169
170
171
    if (array_diff(self::getDefaultEnabledEvents(), $webhook['enabled_events'])) {
      $params['enabled_events'] = self::getDefaultEnabledEvents();
    }
172
173

    return $params;
mattwire's avatar
mattwire committed
174
175
176
177
178
179
180
181
182
183
184
185
186
  }

  /**
   * 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',
187
      'charge.captured',
mattwire's avatar
mattwire committed
188
189
190
191
192
193
      'customer.subscription.updated',
      'customer.subscription.deleted',
    ];
  }

}