PluginProbe ʕ •ᴥ•ʔ
GiveWP – Donation Plugin and Fundraising Platform / 4.5.0
GiveWP – Donation Plugin and Fundraising Platform v4.5.0
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 2.31.0 2.31.1 2.32.0 2.33.0 2.33.1 2.33.2 2.33.3 2.33.4 2.33.5 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.5.0 2.5.1 2.5.10 2.5.11 2.5.12 2.5.13 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0 2.6.1 2.6.2 2.6.3 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.8.0 2.8.1 2.9.0 2.9.1 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.10.0 3.11.0 3.12.0 3.12.1 3.12.2 3.12.3 3.13.0 3.14.0 3.14.1 3.14.2 3.15.0 3.15.1 3.16.0 3.16.1 3.16.2 3.16.3 3.16.4 3.16.5 3.17.0 3.17.1 3.17.2 3.18.0 3.19.0 3.19.1 3.19.2 3.19.3 3.19.4 3.2.0 3.2.1 3.2.2 3.20.0 3.21.0 3.21.1 3.22.0 3.22.1 3.22.2 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.6.0 3.6.1 3.6.2 3.7.0 3.8.0 3.9.0 4.0.0 4.1.0 4.1.1 4.10.0 4.10.1 4.11.0 4.12.0 4.13.0 4.13.1 4.13.2 4.14.0 4.14.1 4.14.2 4.14.3 4.14.4 4.14.5 4.14.6 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.1 4.7.0 4.7.1 4.8.0 4.8.1 4.9.0 trunk 1.9.0 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.2 2.11.3 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.13.1 2.13.2 2.13.3 2.13.4 2.14.0 2.15.0 2.16.0 2.16.1 2.17.0 2.17.1 2.17.3 2.18.0 2.18.1 2.19.1 2.19.2 2.19.3 2.19.4 2.19.5 2.19.6 2.19.7 2.19.8 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.20.0 2.20.1 2.20.2 2.21.0 2.21.1 2.21.2 2.21.3 2.21.4 2.22.0 2.22.1 2.22.2 2.22.3 2.23.0 2.23.1 2.23.2 2.24.0 2.24.1 2.24.2 2.25.0 2.25.1 2.25.2 2.25.3 2.26.0 2.27.0 2.27.1 2.27.2 2.27.3 2.28.0 2.29.0 2.29.1 2.29.2
give / src / PaymentGateways / PayPalCommerce / PayPalCommerce.php
give / src / PaymentGateways / PayPalCommerce Last commit date
Banners 2 years ago DataTransferObjects 1 year ago Exceptions 3 years ago Migrations 3 years ago Models 2 years ago PayPalCheckoutSdk 1 year ago Repositories 1 year ago Webhooks 2 years ago AccountAdminNotices.php 4 years ago AdminSettingFields.php 1 year ago AdvancedCardFields.php 4 years ago AjaxRequestHandler.php 1 year ago DonationDetailsPage.php 4 years ago DonationFormPaymentMethod.php 2 years ago PayPalClient.php 2 years ago PayPalCommerce.php 1 year ago RefreshToken.php 3 years ago RefundPaymentHandler.php 4 years ago ScriptLoader.php 1 year ago Utils.php 2 years ago onBoardingRedirectHandler.php 1 year ago
PayPalCommerce.php
371 lines
1 <?php
2
3 namespace Give\PaymentGateways\PayPalCommerce;
4
5 use Give\Donations\Models\Donation;
6 use Give\Framework\Exceptions\Primitives\Exception;
7 use Give\Framework\PaymentGateways\Commands\GatewayCommand;
8 use Give\Framework\PaymentGateways\Commands\PaymentComplete;
9 use Give\Framework\PaymentGateways\Exceptions\PaymentGatewayException;
10 use Give\Framework\PaymentGateways\PaymentGateway;
11 use Give\Framework\Support\ValueObjects\Money;
12 use Give\Log\Log;
13 use Give\PaymentGateways\PayPalCommerce\Models\MerchantDetail;
14
15 /**
16 * Class PayPalCommerce
17 *
18 * Boots the PayPalCommerce gateway and provides its basic registration properties
19 *
20 * @since 2.9.0
21 */
22 class PayPalCommerce extends PaymentGateway
23 {
24 /**
25 * @deprecated
26 *
27 * Use getId or id function to access payment gateway id.
28 *
29 */
30 const GATEWAY_ID = 'paypal-commerce';
31
32 /**
33 * @since 2.19.0
34 */
35 public function getLegacyFormFieldMarkup(int $formId, array $args): string
36 {
37 return give(AdvancedCardFields::class)->addCreditCardForm($formId);
38 }
39
40 /**
41 * @since 2.19.0
42 *
43 * @return string
44 */
45 public static function id(): string
46 {
47 return 'paypal-commerce';
48 }
49
50 /**
51 * @since 2.19.0
52 *
53 * @return string
54 */
55 public function getId(): string
56 {
57 return self::id();
58 }
59
60 /**
61 * @since 2.19.0
62 *
63 * @return string
64 */
65 public function getName(): string
66 {
67 return esc_html__('PayPal Donations', 'give');
68 }
69
70 /**
71 * @since 2.19.0
72 *
73 * @return string
74 */
75 public function getPaymentMethodLabel(): string
76 {
77 return esc_html__('Credit Card', 'give');
78 }
79
80 /**
81 * @since 4.2.1 updated to use updateOrderFromDonation
82 * @since 4.1.0 updated to include 3D Secure validation
83 * @since 4.0.0 updated to update and capture payment
84 * @since 2.19.0
85 *
86 * @param array{payPalOrderId: string|null, payPalAuthorizationId: string|null} $gatewayData
87 * @throws \Exception
88 */
89 public function createPayment(Donation $donation, $gatewayData): GatewayCommand
90 {
91 $payPalOrderId = $gatewayData['payPalOrderId'];
92
93 /** @var Repositories\PayPalOrder $payPalOrderRepository */
94 $payPalOrderRepository = give(Repositories\PayPalOrder::class);
95
96 $payPalOrder = $payPalOrderRepository->getApprovedOrder($payPalOrderId);
97
98 if ($payPalOrder->status === 'COMPLETED') {
99 $this->validatePayPalOrder($payPalOrder);
100
101 $transactionId = $payPalOrder->purchase_units[0]->payments->captures[0]->id;
102
103 } elseif ($payPalOrder->status === 'APPROVED' || $payPalOrder->status === 'CREATED') {
104 $this->validate3dSecure($payPalOrder);
105
106 if ($this->shouldUpdateOrder($donation, $payPalOrder)){
107 $payPalOrderRepository->updateOrderFromDonation($payPalOrderId, $donation);
108 }
109
110 // ready to capture order, response is the updated PayPal order.
111 $response = $payPalOrderRepository->approveOrder($payPalOrderId);
112
113 $this->validatePayPalOrder($response);
114
115 $transactionId = $response->purchase_units[0]->payments->captures[0]->id;
116 } else {
117 throw new PaymentGatewayException('PayPal Order status is not found.');
118 }
119
120 give()->payment_meta->update_meta(
121 $donation->id,
122 '_give_order_id',
123 $payPalOrderId
124 );
125
126 return PaymentComplete::make($transactionId)
127 ->setPaymentNotes(
128 sprintf(
129 __('Transaction Successful. PayPal Transaction ID: %1$s PayPal Order ID: %2$s', 'give'),
130 $transactionId,
131 $payPalOrderId
132 )
133 );
134 }
135
136 /**
137 * @since 4.5.0 Add Accept Credit Card (Smart Buttons Only) setting.
138 * @since 3.0.0 Conditionally add "Transaction Type" setting.
139 * @since 2.33.0 Register new payment field type setting.
140 * @since 2.27.3 Enable Venmo payment method by default.
141 * @since 2.16.2 Add setting "Transaction type".
142 */
143 public function getOptions()
144 {
145 /* @var MerchantDetail $merchantDetails */
146 $merchantDetails = give(MerchantDetail::class);
147
148 $settings = [
149 [
150 'type' => 'title',
151 'id' => 'give_gateway_settings_1',
152 'table_html' => false,
153 ],
154 [
155 'id' => 'paypal_commerce_introduction',
156 'type' => 'paypal_commerce_introduction',
157 ],
158 [
159 'type' => 'sectionend',
160 'id' => 'give_gateway_settings_1',
161 'table_html' => false,
162 ],
163 [
164 'type' => 'title',
165 'id' => 'give_gateway_settings_2',
166 ],
167 [
168 'name' => esc_html__('Account Country', 'give'),
169 'id' => 'paypal_commerce_account_country',
170 'type' => 'paypal_commerce_account_country',
171 ],
172 [
173 'name' => esc_html__('Connect With Paypal', 'give'),
174 'id' => 'paypal_commerce_account_manger',
175 'type' => 'paypal_commerce_account_manger',
176 ],
177 [
178 'name' => esc_html__('Accept Venmo', 'give'),
179 'id' => 'paypal_commerce_accept_venmo',
180 'type' => 'radio_inline',
181 'desc' => esc_html__(
182 'Displays a button allowing Donors to pay with Venmo (US-only). Donations still come into your PayPal account and are subject to normal PayPal transaction fees.',
183 'give'
184 ),
185 'default' => 'enabled',
186 'options' => [
187 'enabled' => esc_html__('Enabled', 'give'),
188 'disabled' => esc_html__('Disabled', 'give'),
189 ],
190 ],
191 [
192 'name' => esc_html__('Payment Field Type', 'give'),
193 'desc' => sprintf(
194 esc_html__(
195 '"Auto" provides the most payment options to the donor as possible, based on your account. "Smart Buttons Only" shows only the payment buttons. There is no "Hosted Only" option at this time due to limitations with PayPal\'s hosted fields. Not sure what this means? %1$sRead here%2$s.',
196 'give'
197 ),
198 '<a href="https://docs.givewp.com/paypal-settings" target="_blank">',
199 '</a>'
200 ),
201 'id' => 'paypal_payment_field_type',
202 'type' => 'radio_inline',
203 'options' => [
204 'auto' => esc_html__('Auto', 'give'),
205 'smart-buttons' => esc_html__('Smart Buttons Only', 'give'),
206 ],
207 'default' => 'auto',
208 ],
209 [
210 'name' => esc_html__('Accept Credit Card (Smart Buttons Only)', 'give'),
211 'id' => 'paypal_commerce_accept_credit_card',
212 'type' => 'radio_inline',
213 'desc' => esc_html__(
214 'Displays a button allowing Donors to pay with Credit Card. This option is only available if "Smart Buttons Only" is selected on Payment Field Type.',
215 'give'
216 ),
217 'default' => 'enabled',
218 'options' => [
219 'enabled' => esc_html__('Enabled', 'give'),
220 'disabled' => esc_html__('Disabled', 'give'),
221 ],
222 ],
223 [
224 'name' => esc_html__('PayPal Donations Gateway Settings Docs Link', 'give'),
225 'id' => 'paypal_commerce_gateway_settings_docs_link',
226 'url' => esc_url('http://docs.givewp.com/paypal-donations'),
227 'title' => esc_html__('PayPal Donations Gateway Settings', 'give'),
228 'type' => 'give_docs_link',
229 ],
230 [
231 'type' => 'sectionend',
232 'id' => 'give_gateway_settings_2',
233 ],
234 ];
235
236 if (Utils::isDonationTransactionTypeSupported($merchantDetails->accountCountry ?: '')) {
237 $settings = give_settings_array_insert(
238 $settings,
239 'paypal_commerce_accept_venmo',
240 [
241 [
242 'name' => esc_html__('Transaction Type', 'give'),
243 'desc' => esc_html__(
244 'Nonprofits must verify their status to withdraw donations they receive via PayPal. PayPal users that are not verified nonprofits must demonstrate how their donations will be used, once they raise more than $10,000. By default, GiveWP transactions are sent to PayPal as donations. You may change the transaction type using this option if you feel you may not meet PayPal\'s donation requirements.',
245 'give'
246 ),
247 'id' => 'paypal_commerce_transaction_type',
248 'type' => 'radio_inline',
249 'options' => [
250 'donation' => esc_html__('Donation', 'give'),
251 'standard' => esc_html__('Standard Transaction', 'give'),
252 ],
253 'default' => 'donation',
254 ]
255 ]
256 );
257 }
258
259 if ($merchantDetails->accountIsReady) {
260 $settings = give_settings_array_insert(
261 $settings,
262 'paypal_commerce_gateway_settings_docs_link',
263 [
264 [
265 'name' => esc_html__('Collect Billing Details', 'give'),
266 'id' => 'paypal_commerce_collect_billing_details',
267 'type' => 'radio_inline',
268 'desc' => esc_html__(
269 'If enabled, required billing address fields are added to PayPal Donations Donation forms. These fields are required to process the transaction when enabled. Billing address details are added to both the donation and donor record in GiveWP.',
270 'give'
271 ),
272 'default' => 'disabled',
273 'options' => [
274 'enabled' => esc_html__('Enabled', 'give'),
275 'disabled' => esc_html__('Disabled', 'give'),
276 ],
277 ],
278 ]
279 );
280 }
281
282 /**
283 * filter the settings
284 *
285 * @since 2.9.6
286 */
287 return apply_filters('give_get_settings_paypal_commerce', $settings);
288 }
289
290 /**
291 * @since 2.20.0
292 * @inerhitDoc
293 * @throws Exception
294 */
295 public function refundDonation(Donation $donation)
296 {
297 throw new Exception('Method has not been implemented yet. Please use the legacy method in the meantime.');
298 }
299
300 /**
301 * @since 4.0.0
302 */
303 private function shouldUpdateOrder(Donation $donation, $payPalOrder): bool
304 {
305 $orderAmount = $payPalOrder->purchase_units[0]->amount->value;
306 $orderCurrency = $payPalOrder->purchase_units[0]->amount->currency_code;
307 $currentOrderAmount = Money::fromDecimal($orderAmount, $orderCurrency);
308
309 if (!$currentOrderAmount->equals($donation->amount)) {
310 Log::error(
311 sprintf(
312 'Initial PayPal Order amount does not match donation amount. PayPal Order ID: %s, Donation ID: %s',
313 $payPalOrder->id,
314 $donation->id
315 )
316 );
317
318 return true;
319 }
320
321 return false;
322 }
323
324 /**
325 * @throws PaymentGatewayException
326 */
327 private function validatePayPalOrder(object $payPalOrder): void
328 {
329 $transaction = $payPalOrder->purchase_units[0]->payments->captures[0];
330
331 $errors = property_exists($payPalOrder, 'details') ? $payPalOrder->details[0] : [];
332
333 if (!$transaction) {
334 throw new PaymentGatewayException('PayPal Order does not have a transaction.');
335 }
336
337 if ($transaction->status === "DECLINED") {
338 $errorMessage = sprintf(
339 __('PayPal Order has been declined. Transaction status:: %s', 'give'),
340 $transaction->status
341 );
342
343 throw new PaymentGatewayException($errorMessage);
344 }
345
346 if (!empty($errors)) {
347 $errorMessage = sprintf(
348 __('PayPal Order has an error: %s', 'give'),
349 $errors->issue[0]->description
350 );
351
352 throw new PaymentGatewayException($errorMessage);
353 }
354
355 $this->validate3dSecure($payPalOrder);
356 }
357
358 /**
359 * @since 4.1.0
360 *
361 * @throws PaymentGatewayException
362 */
363 private function validate3dSecure(object $payPalOrder): void
364 {
365 // Check if the order is not ready for 3D Secure authentication
366 if (isset($payPalOrder->payment_source->card->authentication_result->liability_shift) && !in_array($payPalOrder->payment_source->card->authentication_result->liability_shift, ['POSSIBLE', 'YES'])) {
367 throw new PaymentGatewayException('Card type and issuing bank are not ready to complete a 3D Secure authentication.');
368 }
369 }
370 }
371