WebhookHandlers
4 months ago
APIClass.php
3 years ago
Helpers.php
1 month ago
PaymentHelpers.php
3 months ago
Stripe.php
2 months ago
WebhookHelpers.php
8 months ago
index.php
3 years ago
WebhookHelpers.php
304 lines
| 1 | <?php |
| 2 | |
| 3 | namespace ProfilePress\Core\Membership\PaymentMethods\Stripe; |
| 4 | |
| 5 | use ProfilePress\Core\Membership\PaymentMethods\Stripe\WebhookHandlers\ChargeRefunded; |
| 6 | use ProfilePress\Core\Membership\PaymentMethods\Stripe\WebhookHandlers\CheckoutSessionAsyncPaymentFailed; |
| 7 | use ProfilePress\Core\Membership\PaymentMethods\Stripe\WebhookHandlers\CheckoutSessionAsyncPaymentSucceeded; |
| 8 | use ProfilePress\Core\Membership\PaymentMethods\Stripe\WebhookHandlers\CheckoutSessionCompleted; |
| 9 | use ProfilePress\Core\Membership\PaymentMethods\Stripe\WebhookHandlers\CustomerSubscriptionCreated; |
| 10 | use ProfilePress\Core\Membership\PaymentMethods\Stripe\WebhookHandlers\CustomerSubscriptionDeleted; |
| 11 | use ProfilePress\Core\Membership\PaymentMethods\Stripe\WebhookHandlers\CustomerSubscriptionUpdated; |
| 12 | use ProfilePress\Core\Membership\PaymentMethods\Stripe\WebhookHandlers\InvoicePaymentSucceeded; |
| 13 | use ProfilePress\Core\Membership\PaymentMethods\Stripe\WebhookHandlers\PaymentIntentSucceeded; |
| 14 | |
| 15 | class WebhookHelpers |
| 16 | { |
| 17 | public static function valid_events() |
| 18 | { |
| 19 | return apply_filters('ppress_stripe_webhooks_whitelist', [ |
| 20 | 'checkout.session.completed' => new CheckoutSessionCompleted(), |
| 21 | 'checkout.session.async_payment_succeeded' => new CheckoutSessionAsyncPaymentSucceeded(), |
| 22 | 'checkout.session.async_payment_failed' => new CheckoutSessionAsyncPaymentFailed(), |
| 23 | 'customer.subscription.created' => new CustomerSubscriptionCreated(), |
| 24 | 'customer.subscription.updated' => new CustomerSubscriptionUpdated(), |
| 25 | 'customer.subscription.deleted' => new CustomerSubscriptionDeleted(), //triggers when sub is cancelled |
| 26 | 'invoice.payment_succeeded' => new InvoicePaymentSucceeded(), |
| 27 | 'payment_intent.succeeded' => new PaymentIntentSucceeded(), |
| 28 | 'charge.refunded' => new ChargeRefunded() |
| 29 | ]); |
| 30 | } |
| 31 | |
| 32 | public static function webhook_url() |
| 33 | { |
| 34 | return add_query_arg(['ppress-listener' => 'stripe'], home_url('/')); |
| 35 | } |
| 36 | |
| 37 | public static function add_update_endpoint() |
| 38 | { |
| 39 | try { |
| 40 | |
| 41 | $endpoint = self::exists(); |
| 42 | |
| 43 | if ( ! $endpoint) { |
| 44 | self::create(); |
| 45 | |
| 46 | return; |
| 47 | } |
| 48 | |
| 49 | if (false === self::is_valid($endpoint)) { |
| 50 | self::update($endpoint); |
| 51 | |
| 52 | return; |
| 53 | } |
| 54 | |
| 55 | } catch (\Exception $e) { |
| 56 | // Fail silently. |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | /** |
| 61 | * Determines if the current payment mode has an up-to-date webhook endpoint. |
| 62 | * |
| 63 | * @return mixed|bool |
| 64 | */ |
| 65 | public static function exists() |
| 66 | { |
| 67 | $mode = ppress_is_test_mode() ? 'test' : 'live'; |
| 68 | |
| 69 | $endpoint_id = ppress_get_payment_method_setting('stripe_' . $mode . '_webhook_endpoint_id', ''); |
| 70 | |
| 71 | if (empty($endpoint_id)) return false; |
| 72 | |
| 73 | try { |
| 74 | $endpoint = self::get($endpoint_id); |
| 75 | } catch (\Exception $e) { |
| 76 | return false; |
| 77 | } |
| 78 | |
| 79 | return $endpoint; |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * Determines if a Stripe webhook endpoint is still valid. |
| 84 | * |
| 85 | * @param mixed $endpoint |
| 86 | * |
| 87 | * @return bool True if the webhook does not need to be updated. |
| 88 | * |
| 89 | */ |
| 90 | public static function is_valid($endpoint) |
| 91 | { |
| 92 | $enabled_events = $endpoint['enabled_events']; |
| 93 | |
| 94 | // Enabled events are not * and do not match the defined whitelist. |
| 95 | if ( ! in_array('*', $enabled_events, true) && $enabled_events != self::get_event_whitelist()) { |
| 96 | return false; |
| 97 | } |
| 98 | |
| 99 | if ($endpoint['url'] !== self::webhook_url()) return false; |
| 100 | |
| 101 | if ('enabled' !== $endpoint['status']) return false; |
| 102 | |
| 103 | return true; |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * Retrieves a Stripe webhook endpoint. |
| 108 | * |
| 109 | * @param string $endpoint_id Stripe endpoint ID. |
| 110 | * |
| 111 | * @return array |
| 112 | * |
| 113 | * @throws \Exception |
| 114 | */ |
| 115 | public static function get($endpoint_id) |
| 116 | { |
| 117 | return APIClass::stripeClient()->webhookEndpoints->retrieve($endpoint_id)->toArray(); |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * Creates a Stripe webhook endpoint with the current plugin and site settings. |
| 122 | * |
| 123 | * @return false|mixed |
| 124 | * |
| 125 | * @throws \Exception |
| 126 | */ |
| 127 | public static function create() |
| 128 | { |
| 129 | // Use an existing endpoint if one already exists from a previous setup. |
| 130 | $endpoint = self::get_manual_endpoint(); |
| 131 | |
| 132 | if ( ! $endpoint) { |
| 133 | |
| 134 | $endpoint = APIClass::stripeClient()->webhookEndpoints->create([ |
| 135 | 'url' => self::webhook_url(), |
| 136 | 'enabled_events' => self::get_event_whitelist(), |
| 137 | 'connect' => false, |
| 138 | 'api_version' => PPRESS_STRIPE_API_VERSION, |
| 139 | 'description' => sprintf( |
| 140 | 'ProfilePress (WordPress plugin) endpoint (%s Mode)', |
| 141 | ppress_is_test_mode() ? 'Test' : 'Live' |
| 142 | ), |
| 143 | ])->toArray(); |
| 144 | } |
| 145 | |
| 146 | self::persist($endpoint); |
| 147 | |
| 148 | return $endpoint; |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Creates a Stripe webhook endpoint with the current plugin and site settings. |
| 153 | * |
| 154 | * @param $endpoint_id |
| 155 | * @param bool $without_persist |
| 156 | * |
| 157 | * @return void |
| 158 | * @throws \Exception |
| 159 | */ |
| 160 | public static function delete($endpoint_id, $without_persist = false) |
| 161 | { |
| 162 | if ( ! empty($endpoint_id)) { |
| 163 | |
| 164 | try { |
| 165 | |
| 166 | $endpoint = APIClass::stripeClient()->webhookEndpoints->delete($endpoint_id); |
| 167 | |
| 168 | if ( ! $without_persist) { |
| 169 | self::persist($endpoint, true); |
| 170 | } |
| 171 | |
| 172 | } catch (\Exception $e) { |
| 173 | } |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | /** |
| 178 | * Updates a Stripe webhook endpoint with the current site and plugin settings. |
| 179 | * |
| 180 | * @param $endpoint |
| 181 | * |
| 182 | * @return array |
| 183 | * @throws \Exception |
| 184 | */ |
| 185 | public static function update($endpoint) |
| 186 | { |
| 187 | $enabled_events = $endpoint['enabled_events']; |
| 188 | |
| 189 | // Existing webhook does not accept all events. Merge the new whitelist. |
| 190 | if ( ! in_array('*', $enabled_events, true)) { |
| 191 | $enabled_events = array_values( |
| 192 | array_unique( |
| 193 | array_merge( |
| 194 | $enabled_events, |
| 195 | self::get_event_whitelist() |
| 196 | ) |
| 197 | ) |
| 198 | ); |
| 199 | } |
| 200 | |
| 201 | $endpoint = APIClass::stripeClient()->webhookEndpoints->update( |
| 202 | $endpoint['id'], |
| 203 | [ |
| 204 | 'url' => self::webhook_url(), |
| 205 | 'enabled_events' => $enabled_events, |
| 206 | 'disabled' => false, |
| 207 | ] |
| 208 | )->toArray(); |
| 209 | |
| 210 | self::persist($endpoint); |
| 211 | |
| 212 | return $endpoint; |
| 213 | } |
| 214 | |
| 215 | /** |
| 216 | * Returns a list of Stripe webhook events that are supported by the plugin. |
| 217 | * |
| 218 | * @return array |
| 219 | * |
| 220 | */ |
| 221 | public static function get_event_whitelist() |
| 222 | { |
| 223 | return array_keys(self::valid_events()); |
| 224 | } |
| 225 | |
| 226 | /** |
| 227 | * Persists a Stripe webhook endpoint's information. |
| 228 | * |
| 229 | * @param $endpoint |
| 230 | * |
| 231 | */ |
| 232 | private static function persist($endpoint, $clearOutVal = false) |
| 233 | { |
| 234 | $mode = ppress_is_test_mode() ? 'test' : 'live'; |
| 235 | |
| 236 | ppress_update_payment_method_setting( |
| 237 | 'stripe_' . $mode . '_webhook_endpoint_id', |
| 238 | $clearOutVal ? '' : $endpoint['id'] |
| 239 | ); |
| 240 | |
| 241 | ppress_update_payment_method_setting( |
| 242 | "stripe_{$mode}_webhook_endpoint_events", |
| 243 | $clearOutVal ? '' : $endpoint['enabled_events'] |
| 244 | ); |
| 245 | |
| 246 | ppress_update_payment_method_setting( |
| 247 | "stripe_{$mode}_webhook_endpoint_url", |
| 248 | $clearOutVal ? '' : $endpoint['url'] |
| 249 | ); |
| 250 | |
| 251 | // Secret is only returned on initial creation. |
| 252 | if (isset($endpoint['secret']) || $clearOutVal) { |
| 253 | ppress_update_payment_method_setting( |
| 254 | "stripe_{$mode}_webhook_secret", |
| 255 | $clearOutVal ? '' : $endpoint['secret'] |
| 256 | ); |
| 257 | |
| 258 | // Clear out from a previous connection. |
| 259 | } else { |
| 260 | ppress_update_payment_method_setting( |
| 261 | "stripe_{$mode}_webhook_secret", |
| 262 | '' |
| 263 | ); |
| 264 | } |
| 265 | |
| 266 | ppress_update_payment_method_setting( |
| 267 | "stripe_{$mode}_webhook_api_version", |
| 268 | $clearOutVal ? '' : PPRESS_STRIPE_API_VERSION |
| 269 | ); |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Attempts to locate an endpoint that was manually created. |
| 274 | * |
| 275 | * If we find an endpoint with an exact URL match, persist it and return it. |
| 276 | * |
| 277 | * @return bool|mixed |
| 278 | * |
| 279 | * @throws \Exception |
| 280 | */ |
| 281 | private static function get_manual_endpoint() |
| 282 | { |
| 283 | $endpoints = APIClass::stripeClient()->webhookEndpoints->all(['limit' => 100])->toArray(); |
| 284 | |
| 285 | foreach ($endpoints['data'] as $endpoint) { |
| 286 | |
| 287 | if ($endpoint['url'] === self::webhook_url()) { |
| 288 | |
| 289 | $remote_events = $endpoint['enabled_events']; |
| 290 | $local_events = self::get_event_whitelist(); |
| 291 | |
| 292 | if (count($remote_events) < count($local_events)) { |
| 293 | $endpoint = self::update($endpoint); |
| 294 | } |
| 295 | |
| 296 | self::persist($endpoint); |
| 297 | |
| 298 | return $endpoint; |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | return false; |
| 303 | } |
| 304 | } |