PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.6.5
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.6.5
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
← All changes | app/Modules/PaymentMethods/PayPalGateway/API/Webhook.php +48 -5 1.3.27 → 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 }