← All changes
|
app/Modules/PaymentMethods/PayPalGateway/API/Webhook.php
+48
-5
1.3.21
→
1.6.5
View file →
| @@ -16,8 +16,9 @@ | ||
| 16 | 16 | ['name' => 'BILLING.SUBSCRIPTION.ACTIVATED'], |
| 17 | 17 | ['name' => 'BILLING.SUBSCRIPTION.SUSPENDED'], |
| 18 | 18 | ['name' => 'BILLING.SUBSCRIPTION.CANCELLED'], |
| 19 | 19 | ['name' => 'BILLING.SUBSCRIPTION.EXPIRED'], |
| 20 | + ['name' => 'BILLING.SUBSCRIPTION.PAYMENT.FAILED'], | |
| 20 | 21 | ['name' => 'CUSTOMER.DISPUTE.CREATED'], |
| 21 | 22 | ['name' => 'CUSTOMER.DISPUTE.UPDATED'], |
| 22 | 23 | ['name' => 'CUSTOMER.DISPUTE.RESOLVED'], |
| 23 | 24 | |
| @@ -34,9 +35,9 @@ | ||
| 34 | 35 | |
| 35 | 36 | public static function getWebhookURL(): string |
| 36 | 37 | { |
| 37 | 38 | // return 'https://webhook.site/9f647f0d-3514-47b7-acca-95a2c168b917'; |
| 38 | - return site_url() . self::WEBHOOK_ENDPOINT; | |
| 39 | + return trailingslashit(site_url()) . self::WEBHOOK_ENDPOINT; | |
| 39 | 40 | } |
| 40 | 41 | |
| 41 | 42 | public static function webhookInstruction(): string |
| 42 | 43 | { |
| @@ -49,9 +50,9 @@ | ||
| 49 | 50 | %4$s', |
| 50 | 51 | __('Payments and Payout:', 'fluent-cart'), |
| 51 | 52 | __('- Payment capture refunded | Payment sale completed | Payment sale refunded', 'fluent-cart'), |
| 52 | 53 | __('Billing Subscriptions:', 'fluent-cart'), |
| 53 | - __('- Billing subscription activated | Billing subscription cancelled | Billing subscription created | Billing subscription expired | Billing subscription suspended', 'fluent-cart') | |
| 54 | + __('- Billing subscription activated | Billing subscription cancelled | Billing subscription created | Billing subscription expired | Billing subscription suspended | Billing subscription payment failed', 'fluent-cart') | |
| 54 | 55 | ); |
| 55 | 56 | |
| 56 | 57 | return sprintf( |
| 57 | 58 | '<div> |
| @@ -120,9 +121,12 @@ | ||
| 120 | 121 | return $webhook['url'] === $webhookURL; |
| 121 | 122 | }); |
| 122 | 123 | |
| 123 | 124 | if (isset($matched[0])) { |
| 124 | - static::parseAndUpdateSettings($matched[0], $mode); | |
| 125 | + $matchedWebhook = static::maybeSyncWebhookEvents($matched[0], $mode); | |
| 126 | + if (!is_wp_error($matchedWebhook)) { | |
| 127 | + static::parseAndUpdateSettings($matchedWebhook, $mode); | |
| 128 | + } | |
| 125 | 129 | } |
| 126 | 130 | } |
| 127 | 131 | return $webhookData; |
| 128 | 132 | } |
| @@ -141,13 +145,52 @@ | ||
| 141 | 145 | |
| 142 | 146 | if ($webhookId) { |
| 143 | 147 | $webhookData = API::makeRequest('notifications/webhooks/' . $webhookId, 'v1', 'GET', []); |
| 144 | 148 | if (!is_wp_error($webhookData) && Arr::get($webhookData, 'id') === $webhookId) { |
| 145 | - static::parseAndUpdateSettings($webhookData, $mode); // update webhook events | |
| 146 | - return $webhookData; | |
| 149 | + $synced = static::maybeSyncWebhookEvents($webhookData, $mode); | |
| 150 | + if (is_wp_error($synced)) { | |
| 151 | + return $webhookData; // sync failed, keep the last known-good local record | |
| 152 | + } | |
| 153 | + static::parseAndUpdateSettings($synced, $mode); // update webhook events | |
| 154 | + return $synced; | |
| 147 | 155 | } |
| 148 | 156 | } |
| 149 | 157 | |
| 150 | 158 | // If there is no Webhook ID found or webhook isn't found, register a new one |
| 151 | 159 | return (new Webhook())->registerWebhook($mode); |
| 160 | + } | |
| 161 | + | |
| 162 | + /** | |
| 163 | + * PayPal never adds newly introduced event types to an already-registered webhook | |
| 164 | + * on its own - registerWebhook() only sets event_types at creation time. Reconcile | |
| 165 | + * the remote subscription against our EVENTS list on every maybeSetWebhook() call | |
| 166 | + * so a store that connected before an event type was added still receives it. | |
| 167 | + */ | |
| 168 | + public static function maybeSyncWebhookEvents($webhookData, $mode) | |
| 169 | + { | |
| 170 | + $remoteEventNames = array_column((array)Arr::get($webhookData, 'event_types', []), 'name'); | |
| 171 | + $expectedEventNames = array_column(static::EVENTS, 'name'); | |
| 172 | + $missing = array_diff($expectedEventNames, $remoteEventNames); | |
| 173 | + | |
| 174 | + if (empty($missing)) { | |
| 175 | + return $webhookData; | |
| 176 | + } | |
| 177 | + | |
| 178 | + $patched = API::makeRequest('notifications/webhooks/' . Arr::get($webhookData, 'id'), 'v1', 'PATCH', [ | |
| 179 | + [ | |
| 180 | + 'op' => 'replace', | |
| 181 | + 'path' => '/event_types', | |
| 182 | + 'value' => static::EVENTS | |
| 183 | + ] | |
| 184 | + ], $mode); | |
| 185 | + | |
| 186 | + if (is_wp_error($patched)) { | |
| 187 | + return $patched; | |
| 188 | + } | |
| 189 | + | |
| 190 | + // PATCH answers 204 with no body (no id/event_types) - build the updated | |
| 191 | + // representation locally rather than persisting the empty response. | |
| 192 | + $webhookData['event_types'] = static::EVENTS; | |
| 193 | + | |
| 194 | + return $webhookData; | |
| 152 | 195 | } |
| 153 | 196 | } |