PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.16.9
GiveWP – Donation Plugin and Fundraising Platform v4.16.9
4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 All 255 releases
give / src / Framework / PaymentGateways / PaymentGateway.php

PaymentGateway.php in GiveWP – Donation Plugin and Fundraising Platform 4.16.9, at src/Framework/PaymentGateways/PaymentGateway.php

486 lines 14.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Give\Framework\PaymentGateways;
4
5 use Give\Donations\Models\Donation;
6 use Give\Framework\Exceptions\Primitives\Exception;
7 use Give\Framework\PaymentGateways\Actions\GenerateGatewayRouteUrl;
8 use Give\Framework\PaymentGateways\Contracts\PaymentGatewayInterface;
9 use Give\Framework\PaymentGateways\Contracts\PaymentGatewayRefundable;
10 use Give\Framework\PaymentGateways\Contracts\Subscription\SubscriptionAmountEditable;
11 use Give\Framework\PaymentGateways\Contracts\Subscription\SubscriptionDashboardLinkable;
12 use Give\Framework\PaymentGateways\Contracts\Subscription\SubscriptionPausable;
13 use Give\Framework\PaymentGateways\Contracts\Subscription\SubscriptionPaymentMethodEditable;
14 use Give\Framework\PaymentGateways\Contracts\Subscription\SubscriptionTransactionsSynchronizable;
15 use Give\Framework\PaymentGateways\Contracts\WebhookNotificationsListener;
16 use Give\Framework\PaymentGateways\DataTransferObjects\GatewayRouteData;
17 use Give\Framework\PaymentGateways\Routes\RouteSignature;
18 use Give\Framework\PaymentGateways\Traits\HandleHttpResponses;
19 use Give\Framework\PaymentGateways\Traits\HasRouteMethods;
20 use Give\Framework\PaymentGateways\Webhooks\Webhook;
21 use Give\Framework\Support\ValueObjects\Money;
22 use Give\Log\Log;
23 use Give\Subscriptions\Models\Subscription;
24 use ReflectionException;
25 use ReflectionMethod;
26 use Give\Framework\Support\Contracts\Arrayable;
27 use JsonSerializable;
28
29 /**
30 * @since 4.6.0 Added JSONSerializable and Arrayable interfaces
31 * @since 2.30.0 added enqueueScript() and formSettings() methods.
32 * @since 2.18.0
33 */
34 abstract class PaymentGateway implements PaymentGatewayInterface,
35 SubscriptionDashboardLinkable,
36 SubscriptionAmountEditable,
37 SubscriptionPaymentMethodEditable,
38 SubscriptionTransactionsSynchronizable,
39 JsonSerializable,
40 Arrayable
41 {
42 use HandleHttpResponses;
43 use HasRouteMethods {
44 supportsMethodRoute as protected SupportsOwnMethodRoute;
45 callRouteMethod as protected CallOwnRouteMethod;
46 }
47
48 /**
49 * @since 2.20.0 Change variable type to SubscriptionModule.
50 * @var SubscriptionModule $subscriptionModule
51 */
52 public $subscriptionModule;
53
54 /**
55 * @since 4.5.0
56 *
57 * @var Webhook $webhook
58 */
59 public $webhook;
60
61 /**
62 * @since 4.6.0 add explicit nullable type to subscriptionModule parameter
63 * @since 4.5.0 Add the webhookEvents property
64 *
65 * @since 2.20.0 Change first argument type to SubscriptionModule abstract class.
66 * @since 2.18.0
67 *
68 * @param SubscriptionModule|null $subscriptionModule
69 */
70 public function __construct(?SubscriptionModule $subscriptionModule = null)
71 {
72 if ($subscriptionModule !== null) {
73 $subscriptionModule->setGateway($this);
74 }
75
76 $this->subscriptionModule = $subscriptionModule;
77 $this->webhook = new Webhook($this);
78 }
79
80 /**
81 * @since 4.5.0
82 */
83 public static function webhook(): Webhook
84 {
85 $instance = new static();
86
87 return $instance->webhook;
88 }
89
90 /**
91 * @since 4.5.0
92 */
93 public function canListeningWebhookNotifications(): bool
94 {
95 return $this instanceof WebhookNotificationsListener;
96 }
97
98 /**
99 * @since 2.30.0
100 */
101 public function supportsFormVersions(): array
102 {
103 $versions = [];
104
105 if (method_exists($this, 'getLegacyFormFieldMarkup') && $this->isFunctionImplementedInGatewayClass(
106 'getLegacyFormFieldMarkup'
107 )) {
108 $versions[] = 2;
109 }
110
111 if (method_exists($this, 'enqueueScript') && $this->isFunctionImplementedInGatewayClass('enqueueScript')) {
112 $versions[] = 3;
113 }
114
115 return $versions;
116 }
117
118 /**
119 * Enqueue gateway scripts using WordPress wp_enqueue_script().
120 *
121 * @since 2.30.0
122 *
123 * @return void
124 */
125 public function enqueueScript(int $formId)
126 {
127 //wp_enqueue_scripts();
128 }
129
130 /**
131 * Convenient way of localizing data to the JS gateway object accessible from `this.settings`.
132 *
133 * @since 2.30.0
134 */
135 public function formSettings(int $formId): array
136 {
137 return [];
138 }
139
140 /**
141 * @inheritDoc
142 * @since 4.6.0 updated to use PaymentGatewayRefundable interface
143 *
144 * @since 2.29.0
145 */
146 public function supportsRefund(): bool
147 {
148 if ($this instanceof PaymentGatewayRefundable) {
149 return true;
150 }
151
152 // backward compatibility for add-on legacy gateways that don't implement the PaymentGatewayRefundable interface
153 return method_exists($this, 'refundDonation');
154 }
155
156 /**
157 * @inheritDoc
158 */
159 public function supportsSubscriptions(): bool
160 {
161 return isset($this->subscriptionModule) || $this->isFunctionImplementedInGatewayClass('createSubscription');
162 }
163
164 /**
165 * If a subscription module isn't wanted this method can be overridden by a child class instead.
166 * Just make sure to override the supportsSubscriptions method as well.
167 *
168 * @inheritDoc
169 */
170 public function createSubscription(
171 Donation $donation,
172 Subscription $subscription,
173 $gatewayData
174 ) {
175 return $this->subscriptionModule->createSubscription($donation, $subscription, $gatewayData);
176 }
177
178 /**
179 * @inheritDoc
180 */
181 public function cancelSubscription(Subscription $subscription)
182 {
183 $this->subscriptionModule->cancelSubscription($subscription);
184 }
185
186 /**
187 * @inheritDoc
188 *
189 * @since 3.17.0
190 */
191 public function pauseSubscription(Subscription $subscription, array $data = []): void
192 {
193 if ($this->subscriptionModule instanceof SubscriptionPausable) {
194 $this->subscriptionModule->pauseSubscription($subscription, $data);
195
196 return;
197 }
198
199 throw new Exception('Gateway does not support pausing the subscription.');
200 }
201
202 /**
203 * @inheritDoc
204 *
205 * @since 3.17.0
206 */
207 public function resumeSubscription(Subscription $subscription): void
208 {
209 if ($this->subscriptionModule instanceof SubscriptionPausable) {
210 $this->subscriptionModule->resumeSubscription($subscription);
211
212 return;
213 }
214
215 throw new Exception('Gateway does not support resuming the subscription.');
216 }
217
218 /**
219 * @inheritDoc
220 *
221 * @since 3.17.0
222 */
223 public function canPauseSubscription(): bool
224 {
225 if ($this->subscriptionModule instanceof SubscriptionPausable) {
226 return $this->subscriptionModule->canPauseSubscription();
227 }
228
229 return false;
230 }
231
232 /**
233 * @since 2.21.2
234 * @inheritDoc
235 */
236 public function canSyncSubscriptionWithPaymentGateway(): bool
237 {
238 if ($this->subscriptionModule) {
239 return $this->subscriptionModule->canSyncSubscriptionWithPaymentGateway();
240 }
241
242 return $this->isFunctionImplementedInGatewayClass('synchronizeSubscription');
243 }
244
245 /**
246 * @since 2.21.2
247 * @inheritDoc
248 */
249 public function canUpdateSubscriptionAmount(): bool
250 {
251 if ($this->subscriptionModule) {
252 return $this->subscriptionModule->canUpdateSubscriptionAmount();
253 }
254
255 return $this->isFunctionImplementedInGatewayClass('updateSubscriptionAmount');
256 }
257
258 /**
259 * @since 2.21.2
260 * @inheritDoc
261 */
262 public function canUpdateSubscriptionPaymentMethod(): bool
263 {
264 if ($this->subscriptionModule) {
265 return $this->subscriptionModule->canUpdateSubscriptionPaymentMethod();
266 }
267
268 return $this->isFunctionImplementedInGatewayClass('updateSubscriptionPaymentMethod');
269 }
270
271 /**
272 * @since 2.25.0 update return logic
273 * @since 2.21.2
274 */
275 public function hasGatewayDashboardSubscriptionUrl(): bool
276 {
277 if ($this->subscriptionModule) {
278 return $this->subscriptionModule->hasGatewayDashboardSubscriptionUrl();
279 }
280
281 return $this->isFunctionImplementedInGatewayClass('gatewayDashboardSubscriptionUrl');
282 }
283
284 /**
285 * @since 2.33.0 Return synchronizeSubscription() instead nothing
286 * @since 2.21.2
287 * @inheritDoc
288 * @throws Exception
289 */
290 public function synchronizeSubscription(Subscription $subscription)
291 {
292 if ($this->subscriptionModule instanceof SubscriptionTransactionsSynchronizable) {
293 return $this->subscriptionModule->synchronizeSubscription($subscription);
294 }
295
296 throw new Exception('Gateway does not support syncing subscriptions.');
297 }
298
299 /**
300 * @since 2.21.2
301 * @inheritDoc
302 * @throws Exception
303 */
304 public function updateSubscriptionAmount(Subscription $subscription, Money $newRenewalAmount)
305 {
306 if ($this->subscriptionModule instanceof SubscriptionAmountEditable) {
307 $this->subscriptionModule->updateSubscriptionAmount($subscription, $newRenewalAmount);
308
309 return;
310 }
311
312 throw new Exception('Gateway does not support updating the subscription amount.');
313 }
314
315 /**
316 * @since 2.21.2
317 * @inheritDoc
318 * @throws Exception
319 */
320 public function updateSubscriptionPaymentMethod(Subscription $subscription, $gatewayData)
321 {
322 if ($this->subscriptionModule instanceof SubscriptionPaymentMethodEditable) {
323 $this->subscriptionModule->updateSubscriptionPaymentMethod($subscription, $gatewayData);
324
325 return;
326 }
327
328 throw new Exception('Gateway does not support updating the subscription payment method.');
329 }
330
331 /**
332 * @since 2.21.2
333 * @inheritDoc
334 */
335 public function gatewayDashboardSubscriptionUrl(Subscription $subscription): string
336 {
337 if ($this->subscriptionModule instanceof SubscriptionDashboardLinkable) {
338 return $this->subscriptionModule->gatewayDashboardSubscriptionUrl($subscription);
339 }
340
341 return false;
342 }
343
344 /**
345 * Generate gateway route url
346 *
347 * @since 2.18.0
348 * @since 2.19.0 remove $donationId param in favor of args
349 */
350 public function generateGatewayRouteUrl(string $gatewayMethod, array $args = []): string
351 {
352 return (new GenerateGatewayRouteUrl())(static::id(), $gatewayMethod, $args);
353 }
354
355 /**
356 * Generate secure gateway route url
357 *
358 * @since 4.16.8 put the signed values on the URL encoded, so a raw value with an ampersand round-trips
359 * @since 4.16.8 cover $args with the signature
360 * @since 2.19.5 replace nonce with hash and expiration
361 * @since 2.19.4 replace RouteSignature args with unique donationId
362 * @since 2.19.0
363 */
364 public function generateSecureGatewayRouteUrl(string $gatewayMethod, int $donationId, array $args = []): string
365 {
366 $args = RouteSignature::normalizeArgs(array_diff_key($args, array_flip(GatewayRouteData::ROUTE_PARAMS)));
367
368 $signature = new RouteSignature(static::id(), $gatewayMethod, $donationId, null, $args);
369
370 return (new GenerateGatewayRouteUrl())(
371 static::id(),
372 $gatewayMethod,
373 array_merge(RouteSignature::encodeArgs($args), [
374 'give-route-signature' => $signature->toHash(),
375 'give-route-signature-id' => $donationId,
376 'give-route-signature-expiration' => $signature->expiration,
377 // Named here so the route knows which args to rebuild the signature from, and so a gateway
378 // appending its own on the way back does not break it. The list is itself signed.
379 'give-route-signature-args' => implode(',', $signature->argKeys),
380 ])
381 );
382 }
383
384 /**
385 * @since 2.20.0
386 */
387 public function supportsMethodRoute(string $method): bool
388 {
389 if ($this->subscriptionModule && $this->subscriptionModule->supportsMethodRoute($method)) {
390 return true;
391 }
392
393 return $this->supportsOwnMethodRoute($method);
394 }
395
396 /**
397 * @since 2.20.0
398 *
399 * @param string $method
400 *
401 * @throws Exception
402 */
403 public function callRouteMethod($method, $queryParams)
404 {
405 if ($this->subscriptionModule && $this->subscriptionModule->supportsMethodRoute($method)) {
406 return $this->subscriptionModule->callRouteMethod($method, $queryParams);
407 }
408
409 return $this->callOwnRouteMethod($method, $queryParams);
410 }
411
412 /**
413 * Checks to see if the provided method is being used by the child gateway class. This is used as a helper in the "can" methods
414 * to see if the gateway is implementing a recurring feature without using a subscription module.
415 *
416 * @since 2.21.2
417 */
418 private function isFunctionImplementedInGatewayClass(string $methodName): bool
419 {
420 try {
421 $reflector = new ReflectionMethod($this, $methodName);
422 } catch (ReflectionException $e) {
423 Log::error(
424 sprintf(
425 'ReflectionException thrown when trying to check if %s::%s is implemented in the gateway class.',
426 static::id(),
427 $methodName
428 ),
429 [
430 'exception' => $e,
431 ]
432 );
433
434 return false;
435 }
436
437 return ($reflector->getDeclaringClass()->getName() === get_class($this));
438 }
439
440 /**
441 * @since 4.6.0
442 */
443 public function getTransactionUrl(Donation $donation): ?string
444 {
445 $link = apply_filters('give_payment_details_transaction_id-' . $donation->gatewayId, $donation->gatewayTransactionId, $donation->id);
446
447 // If no link is returned, return null
448 if (empty($link)) {
449 return null;
450 }
451
452 // Extract URL from anchor tag using regex
453 if (preg_match('/href=["\']([^"\']+)["\']/', $link, $matches)) {
454 return $matches[1];
455 }
456
457 // If it's already a URL (not an anchor tag), return as is
458 if (filter_var($link, FILTER_VALIDATE_URL)) {
459 return $link;
460 }
461
462 return null;
463 }
464
465 /**
466 * @since 4.6.0
467 */
468 public function toArray(): array
469 {
470 return [
471 'id' => $this->id(),
472 'name' => $this->getName(),
473 'label' => $this->getPaymentMethodLabel(),
474 ];
475 }
476
477 /**
478 * @since 4.6.0
479 */
480 #[\ReturnTypeWillChange]
481 public function jsonSerialize()
482 {
483 return $this->toArray();
484 }
485 }
486