|
1
|
|
|
<?php |
|
|
|
|
|
|
2
|
|
|
/** |
|
3
|
|
|
* PayPal Standard Gateway |
|
4
|
|
|
* |
|
5
|
|
|
* @package Give |
|
6
|
|
|
* @subpackage Gateways |
|
7
|
|
|
* @copyright Copyright (c) 2016, WordImpress |
|
8
|
|
|
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License |
|
9
|
|
|
* @since 1.0 |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
|
13
|
|
|
exit; |
|
14
|
|
|
} |
|
15
|
|
|
|
|
16
|
|
|
/** |
|
17
|
|
|
* PayPal Remove CC Form |
|
18
|
|
|
* |
|
19
|
|
|
* PayPal Standard does not need a CC form, so remove it. |
|
20
|
|
|
* |
|
21
|
|
|
* @access private |
|
22
|
|
|
* @since 1.0 |
|
23
|
|
|
*/ |
|
24
|
|
|
add_action( 'give_paypal_cc_form', '__return_false' ); |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* Process PayPal Purchase |
|
28
|
|
|
* |
|
29
|
|
|
* @since 1.0 |
|
30
|
|
|
* |
|
31
|
|
|
* @param array $purchase_data Purchase Data |
|
32
|
|
|
* |
|
33
|
|
|
* @return void |
|
34
|
|
|
*/ |
|
35
|
|
|
function give_process_paypal_purchase( $purchase_data ) { |
|
36
|
|
|
|
|
37
|
|
|
if ( ! wp_verify_nonce( $purchase_data['gateway_nonce'], 'give-gateway' ) ) { |
|
38
|
|
|
wp_die( __( 'Nonce verification has failed', 'give' ), __( 'Error', 'give' ), array( 'response' => 403 ) ); |
|
39
|
|
|
} |
|
40
|
|
|
|
|
41
|
|
|
$form_id = intval( $purchase_data['post_data']['give-form-id'] ); |
|
42
|
|
|
$price_id = isset($purchase_data['post_data']['give-price-id']) ? $purchase_data['post_data']['give-price-id'] : ''; |
|
43
|
|
|
|
|
44
|
|
|
// Collect payment data |
|
45
|
|
|
$payment_data = array( |
|
46
|
|
|
'price' => $purchase_data['price'], |
|
47
|
|
|
'give_form_title' => $purchase_data['post_data']['give-form-title'], |
|
48
|
|
|
'give_form_id' => $form_id, |
|
49
|
|
|
'give_price_id' => $price_id, |
|
50
|
|
|
'date' => $purchase_data['date'], |
|
51
|
|
|
'user_email' => $purchase_data['user_email'], |
|
52
|
|
|
'purchase_key' => $purchase_data['purchase_key'], |
|
53
|
|
|
'currency' => give_get_currency(), |
|
54
|
|
|
'user_info' => $purchase_data['user_info'], |
|
55
|
|
|
'status' => 'pending', |
|
56
|
|
|
'gateway' => 'paypal' |
|
57
|
|
|
); |
|
58
|
|
|
|
|
59
|
|
|
// Record the pending payment |
|
60
|
|
|
$payment = give_insert_payment( $payment_data ); |
|
61
|
|
|
|
|
62
|
|
|
// Check payment |
|
63
|
|
|
if ( ! $payment ) { |
|
|
|
|
|
|
64
|
|
|
// Record the error |
|
65
|
|
|
give_record_gateway_error( __( 'Payment Error', 'give' ), sprintf( __( 'Payment creation failed before sending buyer to PayPal. Payment data: %s', 'give' ), json_encode( $payment_data ) ), $payment ); |
|
66
|
|
|
// Problems? send back |
|
67
|
|
|
give_send_back_to_checkout( '?payment-mode=' . $purchase_data['post_data']['give-gateway'] ); |
|
68
|
|
|
} else { |
|
69
|
|
|
// Only send to PayPal if the pending payment is created successfully |
|
70
|
|
|
$listener_url = add_query_arg( 'give-listener', 'IPN', home_url( 'index.php' ) ); |
|
71
|
|
|
|
|
72
|
|
|
// Get the success url |
|
73
|
|
|
$return_url = add_query_arg( array( |
|
74
|
|
|
'payment-confirmation' => 'paypal', |
|
75
|
|
|
'payment-id' => $payment |
|
76
|
|
|
|
|
77
|
|
|
), get_permalink( give_get_option( 'success_page' ) ) ); |
|
78
|
|
|
|
|
79
|
|
|
// Get the PayPal redirect uri |
|
80
|
|
|
$paypal_redirect = trailingslashit( give_get_paypal_redirect() ) . '?'; |
|
81
|
|
|
|
|
82
|
|
|
//Item name - pass level name if variable priced |
|
83
|
|
|
$item_name = $purchase_data['post_data']['give-form-title']; |
|
84
|
|
|
|
|
85
|
|
|
//Verify has variable prices |
|
86
|
|
|
if ( give_has_variable_prices( $form_id ) && isset( $purchase_data['post_data']['give-price-id'] ) ) { |
|
87
|
|
|
|
|
88
|
|
|
$item_price_level_text = give_get_price_option_name( $form_id, $purchase_data['post_data']['give-price-id'] ); |
|
89
|
|
|
|
|
90
|
|
|
$price_level_amount = give_get_price_option_amount( $form_id, $purchase_data['post_data']['give-price-id'] ); |
|
91
|
|
|
|
|
92
|
|
|
//Donation given doesn't match selected level (must be a custom amount) |
|
93
|
|
|
if ( $price_level_amount != give_sanitize_amount( $purchase_data['price'] ) ) { |
|
94
|
|
|
$custom_amount_text = get_post_meta( $form_id, '_give_custom_amount_text', true ); |
|
95
|
|
|
//user custom amount text if any, fallback to default if not |
|
96
|
|
|
$item_name .= ' - ' . ( ! empty( $custom_amount_text ) ? $custom_amount_text : __( 'Custom Amount', 'give' ) ); |
|
97
|
|
|
|
|
98
|
|
|
} //Is there any donation level text? |
|
99
|
|
|
elseif ( ! empty( $item_price_level_text ) ) { |
|
100
|
|
|
$item_name .= ' - ' . $item_price_level_text; |
|
101
|
|
|
} |
|
102
|
|
|
|
|
103
|
|
|
} //Single donation: Custom Amount |
|
104
|
|
|
elseif ( give_get_form_price( $form_id ) !== give_sanitize_amount( $purchase_data['price'] ) ) { |
|
105
|
|
|
$custom_amount_text = get_post_meta( $form_id, '_give_custom_amount_text', true ); |
|
106
|
|
|
//user custom amount text if any, fallback to default if not |
|
107
|
|
|
$item_name .= ' - ' . ( ! empty( $custom_amount_text ) ? $custom_amount_text : __( 'Custom Amount', 'give' ) ); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
// Setup PayPal arguments |
|
111
|
|
|
$paypal_args = array( |
|
112
|
|
|
'business' => give_get_option( 'paypal_email', false ), |
|
113
|
|
|
'email' => $purchase_data['user_email'], |
|
114
|
|
|
'invoice' => $purchase_data['purchase_key'], |
|
115
|
|
|
'amount' => $purchase_data['price'], |
|
116
|
|
|
// The all important donation amount |
|
117
|
|
|
'item_name' => $item_name, |
|
118
|
|
|
// "Purpose" field pre-populated with Form Title |
|
119
|
|
|
'no_shipping' => '1', |
|
120
|
|
|
'shipping' => '0', |
|
121
|
|
|
'no_note' => '1', |
|
122
|
|
|
'currency_code' => give_get_currency(), |
|
123
|
|
|
'charset' => get_bloginfo( 'charset' ), |
|
124
|
|
|
'custom' => $payment, |
|
125
|
|
|
'rm' => '2', |
|
126
|
|
|
'return' => $return_url, |
|
127
|
|
|
'cancel_return' => give_get_failed_transaction_uri( '?payment-id=' . $payment ), |
|
128
|
|
|
'notify_url' => $listener_url, |
|
129
|
|
|
'page_style' => give_get_paypal_page_style(), |
|
130
|
|
|
'cbt' => get_bloginfo( 'name' ), |
|
131
|
|
|
'bn' => 'givewp_SP' |
|
132
|
|
|
); |
|
133
|
|
|
|
|
134
|
|
|
if ( ! empty( $purchase_data['user_info']['address'] ) ) { |
|
135
|
|
|
$paypal_args['address1'] = $purchase_data['user_info']['address']['line1']; |
|
136
|
|
|
$paypal_args['address2'] = $purchase_data['user_info']['address']['line2']; |
|
137
|
|
|
$paypal_args['city'] = $purchase_data['user_info']['address']['city']; |
|
138
|
|
|
$paypal_args['country'] = $purchase_data['user_info']['address']['country']; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
if ( give_get_option( 'paypal_button_type' ) === 'standard' ) { |
|
142
|
|
|
$paypal_extra_args = array( |
|
143
|
|
|
'cmd' => '_xclick', |
|
144
|
|
|
); |
|
145
|
|
|
} else { |
|
146
|
|
|
$paypal_extra_args = array( |
|
147
|
|
|
'cmd' => '_donations', |
|
148
|
|
|
); |
|
149
|
|
|
} |
|
150
|
|
|
|
|
151
|
|
|
$paypal_args = array_merge( $paypal_extra_args, $paypal_args ); |
|
152
|
|
|
$paypal_args = apply_filters( 'give_paypal_redirect_args', $paypal_args, $purchase_data ); |
|
153
|
|
|
|
|
154
|
|
|
// Build query |
|
155
|
|
|
$paypal_redirect .= http_build_query( $paypal_args ); |
|
156
|
|
|
|
|
157
|
|
|
// Fix for some sites that encode the entities |
|
158
|
|
|
$paypal_redirect = str_replace( '&', '&', $paypal_redirect ); |
|
159
|
|
|
|
|
160
|
|
|
// Redirect to PayPal |
|
161
|
|
|
wp_redirect( $paypal_redirect ); |
|
162
|
|
|
exit; |
|
|
|
|
|
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
add_action( 'give_gateway_paypal', 'give_process_paypal_purchase' ); |
|
168
|
|
|
|
|
169
|
|
|
/** |
|
170
|
|
|
* Listens for a PayPal IPN requests and then sends to the processing function |
|
171
|
|
|
* |
|
172
|
|
|
* @since 1.0 |
|
173
|
|
|
* @return void |
|
174
|
|
|
*/ |
|
175
|
|
|
function give_listen_for_paypal_ipn() { |
|
176
|
|
|
// Regular PayPal IPN |
|
177
|
|
|
if ( isset( $_GET['give-listener'] ) && $_GET['give-listener'] == 'IPN' ) { |
|
178
|
|
|
do_action( 'give_verify_paypal_ipn' ); |
|
179
|
|
|
} |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
add_action( 'init', 'give_listen_for_paypal_ipn' ); |
|
183
|
|
|
|
|
184
|
|
|
/** |
|
185
|
|
|
* Process PayPal IPN |
|
186
|
|
|
* |
|
187
|
|
|
* @since 1.0 |
|
188
|
|
|
* @return void |
|
189
|
|
|
*/ |
|
190
|
|
|
function give_process_paypal_ipn() { |
|
191
|
|
|
|
|
192
|
|
|
// Check the request method is POST |
|
193
|
|
|
if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] != 'POST' ) { |
|
194
|
|
|
return; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
// Set initial post data to empty string |
|
198
|
|
|
$post_data = ''; |
|
199
|
|
|
|
|
200
|
|
|
// Fallback just in case post_max_size is lower than needed |
|
201
|
|
|
if ( ini_get( 'allow_url_fopen' ) ) { |
|
202
|
|
|
$post_data = file_get_contents( 'php://input' ); |
|
203
|
|
|
} else { |
|
204
|
|
|
// If allow_url_fopen is not enabled, then make sure that post_max_size is large enough |
|
205
|
|
|
ini_set( 'post_max_size', '12M' ); |
|
206
|
|
|
} |
|
207
|
|
|
// Start the encoded data collection with notification command |
|
208
|
|
|
$encoded_data = 'cmd=_notify-validate'; |
|
209
|
|
|
|
|
210
|
|
|
// Get current arg separator |
|
211
|
|
|
$arg_separator = give_get_php_arg_separator_output(); |
|
212
|
|
|
|
|
213
|
|
|
// Verify there is a post_data |
|
214
|
|
|
if ( $post_data || strlen( $post_data ) > 0 ) { |
|
215
|
|
|
// Append the data |
|
216
|
|
|
$encoded_data .= $arg_separator . $post_data; |
|
217
|
|
|
} else { |
|
218
|
|
|
// Check if POST is empty |
|
219
|
|
|
if ( empty( $_POST ) ) { |
|
220
|
|
|
// Nothing to do |
|
221
|
|
|
return; |
|
222
|
|
|
} else { |
|
223
|
|
|
// Loop through each POST |
|
224
|
|
|
foreach ( $_POST as $key => $value ) { |
|
225
|
|
|
// Encode the value and append the data |
|
226
|
|
|
$encoded_data .= $arg_separator . "$key=" . urlencode( $value ); |
|
227
|
|
|
} |
|
228
|
|
|
} |
|
229
|
|
|
} |
|
230
|
|
|
|
|
231
|
|
|
// Convert collected post data to an array |
|
232
|
|
|
parse_str( $encoded_data, $encoded_data_array ); |
|
233
|
|
|
|
|
234
|
|
|
foreach ( $encoded_data_array as $key => $value ) { |
|
|
|
|
|
|
235
|
|
|
|
|
236
|
|
|
if ( false !== strpos( $key, 'amp;' ) ) { |
|
237
|
|
|
$new_key = str_replace( '&', '&', $key ); |
|
238
|
|
|
$new_key = str_replace( 'amp;', '&' , $new_key ); |
|
239
|
|
|
|
|
240
|
|
|
unset( $encoded_data_array[ $key ] ); |
|
241
|
|
|
$encoded_data_array[ $new_key ] = $value; |
|
242
|
|
|
} |
|
243
|
|
|
|
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
//Validate IPN request w/ PayPal if user hasn't disabled this security measure |
|
247
|
|
|
if ( ! give_get_option( 'disable_paypal_verification' ) ) { |
|
248
|
|
|
|
|
249
|
|
|
$remote_post_vars = array( |
|
250
|
|
|
'method' => 'POST', |
|
251
|
|
|
'timeout' => 45, |
|
252
|
|
|
'redirection' => 5, |
|
253
|
|
|
'httpversion' => '1.1', |
|
254
|
|
|
'blocking' => true, |
|
255
|
|
|
'headers' => array( |
|
256
|
|
|
'host' => 'www.paypal.com', |
|
257
|
|
|
'connection' => 'close', |
|
258
|
|
|
'content-type' => 'application/x-www-form-urlencoded', |
|
259
|
|
|
'post' => '/cgi-bin/webscr HTTP/1.1', |
|
260
|
|
|
|
|
261
|
|
|
), |
|
262
|
|
|
'sslverify' => false, |
|
263
|
|
|
'body' => $encoded_data_array |
|
264
|
|
|
); |
|
265
|
|
|
|
|
266
|
|
|
// Validate the IPN |
|
267
|
|
|
$api_response = wp_remote_post( give_get_paypal_redirect(), $remote_post_vars ); |
|
268
|
|
|
|
|
269
|
|
|
if ( is_wp_error( $api_response ) ) { |
|
270
|
|
|
give_record_gateway_error( __( 'IPN Error', 'give' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'give' ), json_encode( $api_response ) ) ); |
|
271
|
|
|
return; // Something went wrong |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
if ( $api_response['body'] !== 'VERIFIED' && give_get_option( 'disable_paypal_verification', false ) ) { |
|
275
|
|
|
give_record_gateway_error( __( 'IPN Error', 'give' ), sprintf( __( 'Invalid IPN verification response. IPN data: %s', 'give' ), json_encode( $api_response ) ) ); |
|
276
|
|
|
return; // Response not okay |
|
277
|
|
|
} |
|
278
|
|
|
|
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
// Check if $post_data_array has been populated |
|
282
|
|
|
if ( ! is_array( $encoded_data_array ) && ! empty( $encoded_data_array ) ) { |
|
283
|
|
|
return; |
|
284
|
|
|
} |
|
285
|
|
|
|
|
286
|
|
|
$defaults = array( |
|
287
|
|
|
'txn_type' => '', |
|
288
|
|
|
'payment_status' => '' |
|
289
|
|
|
); |
|
290
|
|
|
|
|
291
|
|
|
$encoded_data_array = wp_parse_args( $encoded_data_array, $defaults ); |
|
292
|
|
|
|
|
293
|
|
|
$payment_id = isset( $encoded_data_array['custom'] ) ? absint( $encoded_data_array['custom'] ) : 0; |
|
294
|
|
|
|
|
295
|
|
|
if ( has_action( 'give_paypal_' . $encoded_data_array['txn_type'] ) ) { |
|
296
|
|
|
// Allow PayPal IPN types to be processed separately |
|
297
|
|
|
do_action( 'give_paypal_' . $encoded_data_array['txn_type'], $encoded_data_array, $payment_id ); |
|
298
|
|
|
} else { |
|
299
|
|
|
// Fallback to web accept just in case the txn_type isn't present |
|
300
|
|
|
do_action( 'give_paypal_web_accept', $encoded_data_array, $payment_id ); |
|
301
|
|
|
} |
|
302
|
|
|
exit; |
|
|
|
|
|
|
303
|
|
|
} |
|
304
|
|
|
|
|
305
|
|
|
add_action( 'give_verify_paypal_ipn', 'give_process_paypal_ipn' ); |
|
306
|
|
|
|
|
307
|
|
|
/** |
|
308
|
|
|
* Process web accept (one time) payment IPNs |
|
309
|
|
|
* |
|
310
|
|
|
* @since 1.0 |
|
311
|
|
|
* |
|
312
|
|
|
* @param array $data IPN Data |
|
313
|
|
|
* |
|
314
|
|
|
* @return void |
|
315
|
|
|
*/ |
|
316
|
|
|
function give_process_paypal_web_accept_and_cart( $data, $payment_id ) { |
|
317
|
|
|
|
|
318
|
|
|
if ( $data['txn_type'] != 'web_accept' && $data['txn_type'] != 'cart' && $data['payment_status'] != 'Refunded' ) { |
|
319
|
|
|
return; |
|
320
|
|
|
} |
|
321
|
|
|
|
|
322
|
|
|
if ( empty( $payment_id ) ) { |
|
323
|
|
|
return; |
|
324
|
|
|
} |
|
325
|
|
|
|
|
326
|
|
|
// Collect payment details |
|
327
|
|
|
$purchase_key = isset( $data['invoice'] ) ? $data['invoice'] : $data['item_number']; |
|
328
|
|
|
$paypal_amount = $data['mc_gross']; |
|
329
|
|
|
$payment_status = strtolower( $data['payment_status'] ); |
|
330
|
|
|
$currency_code = strtolower( $data['mc_currency'] ); |
|
331
|
|
|
$business_email = isset( $data['business'] ) && is_email( $data['business'] ) ? trim( $data['business'] ) : trim( $data['receiver_email'] ); |
|
332
|
|
|
$payment_meta = give_get_payment_meta( $payment_id ); |
|
333
|
|
|
|
|
334
|
|
|
|
|
335
|
|
|
if ( give_get_payment_gateway( $payment_id ) != 'paypal' ) { |
|
336
|
|
|
return; // this isn't a PayPal standard IPN |
|
337
|
|
|
} |
|
338
|
|
|
|
|
339
|
|
|
// Verify payment recipient |
|
340
|
|
|
if ( strcasecmp( $business_email, trim( give_get_option( 'paypal_email' ) ) ) != 0 ) { |
|
341
|
|
|
|
|
342
|
|
|
give_record_gateway_error( __( 'IPN Error', 'give' ), sprintf( __( 'Invalid business email in IPN response. IPN data: %s', 'give' ), json_encode( $data ) ), $payment_id ); |
|
343
|
|
|
give_update_payment_status( $payment_id, 'failed' ); |
|
344
|
|
|
give_insert_payment_note( $payment_id, __( 'Payment failed due to invalid PayPal business email.', 'give' ) ); |
|
345
|
|
|
|
|
346
|
|
|
return; |
|
347
|
|
|
} |
|
348
|
|
|
|
|
349
|
|
|
// Verify payment currency |
|
350
|
|
|
if ( $currency_code != strtolower( $payment_meta['currency'] ) ) { |
|
351
|
|
|
|
|
352
|
|
|
give_record_gateway_error( __( 'IPN Error', 'give' ), sprintf( __( 'Invalid currency in IPN response. IPN data: %s', 'give' ), json_encode( $data ) ), $payment_id ); |
|
353
|
|
|
give_update_payment_status( $payment_id, 'failed' ); |
|
354
|
|
|
give_insert_payment_note( $payment_id, __( 'Payment failed due to invalid currency in PayPal IPN.', 'give' ) ); |
|
355
|
|
|
|
|
356
|
|
|
return; |
|
357
|
|
|
} |
|
358
|
|
|
|
|
359
|
|
|
if ( ! give_get_payment_user_email( $payment_id ) ) { |
|
360
|
|
|
|
|
361
|
|
|
// No email associated with purchase, so store from PayPal |
|
362
|
|
|
give_update_payment_meta( $payment_id, '_give_payment_user_email', $data['payer_email'] ); |
|
363
|
|
|
|
|
364
|
|
|
// Setup and store the donors's details |
|
365
|
|
|
$address = array(); |
|
366
|
|
|
$address['line1'] = ! empty( $data['address_street'] ) ? sanitize_text_field( $data['address_street'] ) : false; |
|
367
|
|
|
$address['city'] = ! empty( $data['address_city'] ) ? sanitize_text_field( $data['address_city'] ) : false; |
|
368
|
|
|
$address['state'] = ! empty( $data['address_state'] ) ? sanitize_text_field( $data['address_state'] ) : false; |
|
369
|
|
|
$address['country'] = ! empty( $data['address_country_code'] ) ? sanitize_text_field( $data['address_country_code'] ) : false; |
|
370
|
|
|
$address['zip'] = ! empty( $data['address_zip'] ) ? sanitize_text_field( $data['address_zip'] ) : false; |
|
371
|
|
|
|
|
372
|
|
|
$user_info = array( |
|
373
|
|
|
'id' => '-1', |
|
374
|
|
|
'email' => sanitize_text_field( $data['payer_email'] ), |
|
375
|
|
|
'first_name' => sanitize_text_field( $data['first_name'] ), |
|
376
|
|
|
'last_name' => sanitize_text_field( $data['last_name'] ), |
|
377
|
|
|
'discount' => '', |
|
378
|
|
|
'address' => $address |
|
379
|
|
|
); |
|
380
|
|
|
|
|
381
|
|
|
$payment_meta['user_info'] = $user_info; |
|
382
|
|
|
give_update_payment_meta( $payment_id, '_give_payment_meta', $payment_meta ); |
|
383
|
|
|
} |
|
384
|
|
|
|
|
385
|
|
|
if ( $payment_status == 'refunded' || $payment_status == 'reversed' ) { |
|
386
|
|
|
|
|
387
|
|
|
// Process a refund |
|
388
|
|
|
give_process_paypal_refund( $data, $payment_id ); |
|
389
|
|
|
|
|
390
|
|
|
} else { |
|
391
|
|
|
|
|
392
|
|
|
if ( get_post_status( $payment_id ) == 'publish' ) { |
|
393
|
|
|
return; // Only complete payments once |
|
394
|
|
|
} |
|
395
|
|
|
|
|
396
|
|
|
// Retrieve the total purchase amount (before PayPal) |
|
397
|
|
|
$payment_amount = give_get_payment_amount( $payment_id ); |
|
398
|
|
|
|
|
399
|
|
|
if ( number_format( (float) $paypal_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) { |
|
400
|
|
|
// The prices don't match |
|
401
|
|
|
give_record_gateway_error( __( 'IPN Error', 'give' ), sprintf( __( 'Invalid payment amount in IPN response. IPN data: %s', 'give' ), json_encode( $data ) ), $payment_id ); |
|
402
|
|
|
give_update_payment_status( $payment_id, 'failed' ); |
|
403
|
|
|
give_insert_payment_note( $payment_id, __( 'Payment failed due to invalid amount in PayPal IPN.', 'give' ) ); |
|
404
|
|
|
|
|
405
|
|
|
return; |
|
406
|
|
|
} |
|
407
|
|
|
if ( $purchase_key != give_get_payment_key( $payment_id ) ) { |
|
408
|
|
|
// Purchase keys don't match |
|
409
|
|
|
give_record_gateway_error( __( 'IPN Error', 'give' ), sprintf( __( 'Invalid purchase key in IPN response. IPN data: %s', 'give' ), json_encode( $data ) ), $payment_id ); |
|
410
|
|
|
give_update_payment_status( $payment_id, 'failed' ); |
|
411
|
|
|
give_insert_payment_note( $payment_id, __( 'Payment failed due to invalid purchase key in PayPal IPN.', 'give' ) ); |
|
412
|
|
|
|
|
413
|
|
|
return; |
|
414
|
|
|
} |
|
415
|
|
|
|
|
416
|
|
|
if ( $payment_status == 'completed' || give_is_test_mode() ) { |
|
417
|
|
|
give_insert_payment_note( $payment_id, sprintf( __( 'PayPal Transaction ID: %s', 'give' ), $data['txn_id'] ) ); |
|
418
|
|
|
give_set_payment_transaction_id( $payment_id, $data['txn_id'] ); |
|
419
|
|
|
give_update_payment_status( $payment_id, 'publish' ); |
|
420
|
|
|
} else if ( 'pending' == $payment_status && isset( $data['pending_reason'] ) ) { |
|
421
|
|
|
|
|
422
|
|
|
// Look for possible pending reasons, such as an echeck |
|
423
|
|
|
|
|
424
|
|
|
$note = ''; |
|
425
|
|
|
|
|
426
|
|
|
switch ( strtolower( $data['pending_reason'] ) ) { |
|
427
|
|
|
|
|
428
|
|
|
case 'echeck' : |
|
429
|
|
|
|
|
430
|
|
|
$note = __( 'Payment made via eCheck and will clear automatically in 5-8 days', 'give' ); |
|
431
|
|
|
|
|
432
|
|
|
break; |
|
433
|
|
|
|
|
434
|
|
|
case 'address' : |
|
435
|
|
|
|
|
436
|
|
|
$note = __( 'Payment requires a confirmed donor address and must be accepted manually through PayPal', 'give' ); |
|
437
|
|
|
|
|
438
|
|
|
break; |
|
439
|
|
|
|
|
440
|
|
|
case 'intl' : |
|
441
|
|
|
|
|
442
|
|
|
$note = __( 'Payment must be accepted manually through PayPal due to international account regulations', 'give' ); |
|
443
|
|
|
|
|
444
|
|
|
break; |
|
445
|
|
|
|
|
446
|
|
|
case 'multi-currency' : |
|
447
|
|
|
|
|
448
|
|
|
$note = __( 'Payment received in non-shop currency and must be accepted manually through PayPal', 'give' ); |
|
449
|
|
|
|
|
450
|
|
|
break; |
|
451
|
|
|
|
|
452
|
|
|
case 'paymentreview' : |
|
453
|
|
|
case 'regulatory_review' : |
|
454
|
|
|
|
|
455
|
|
|
$note = __( 'Payment is being reviewed by PayPal staff as high-risk or in possible violation of government regulations', 'give' ); |
|
456
|
|
|
|
|
457
|
|
|
break; |
|
458
|
|
|
|
|
459
|
|
|
case 'unilateral' : |
|
460
|
|
|
|
|
461
|
|
|
$note = __( 'Payment was sent to non-confirmed or non-registered email address.', 'give' ); |
|
462
|
|
|
|
|
463
|
|
|
break; |
|
464
|
|
|
|
|
465
|
|
|
case 'upgrade' : |
|
466
|
|
|
|
|
467
|
|
|
$note = __( 'PayPal account must be upgraded before this payment can be accepted', 'give' ); |
|
468
|
|
|
|
|
469
|
|
|
break; |
|
470
|
|
|
|
|
471
|
|
|
case 'verify' : |
|
472
|
|
|
|
|
473
|
|
|
$note = __( 'PayPal account is not verified. Verify account in order to accept this payment', 'give' ); |
|
474
|
|
|
|
|
475
|
|
|
break; |
|
476
|
|
|
|
|
477
|
|
|
case 'other' : |
|
478
|
|
|
|
|
479
|
|
|
$note = __( 'Payment is pending for unknown reasons. Contact PayPal support for assistance', 'give' ); |
|
480
|
|
|
|
|
481
|
|
|
break; |
|
482
|
|
|
|
|
483
|
|
|
} |
|
484
|
|
|
|
|
485
|
|
|
if ( ! empty( $note ) ) { |
|
486
|
|
|
|
|
487
|
|
|
give_insert_payment_note( $payment_id, $note ); |
|
488
|
|
|
|
|
489
|
|
|
} |
|
490
|
|
|
} |
|
491
|
|
|
} |
|
492
|
|
|
} |
|
493
|
|
|
|
|
494
|
|
|
add_action( 'give_paypal_web_accept', 'give_process_paypal_web_accept_and_cart', 10, 2 ); |
|
495
|
|
|
|
|
496
|
|
|
/** |
|
497
|
|
|
* Process PayPal IPN Refunds |
|
498
|
|
|
* |
|
499
|
|
|
* @since 1.0 |
|
500
|
|
|
* |
|
501
|
|
|
* @param array $data IPN Data |
|
502
|
|
|
* |
|
503
|
|
|
* @return void |
|
504
|
|
|
*/ |
|
505
|
|
|
function give_process_paypal_refund( $data, $payment_id = 0 ) { |
|
506
|
|
|
|
|
507
|
|
|
// Collect payment details |
|
508
|
|
|
|
|
509
|
|
|
if ( empty( $payment_id ) ) { |
|
510
|
|
|
return; |
|
511
|
|
|
} |
|
512
|
|
|
|
|
513
|
|
|
if ( get_post_status( $payment_id ) == 'refunded' ) { |
|
514
|
|
|
return; // Only refund payments once |
|
515
|
|
|
} |
|
516
|
|
|
|
|
517
|
|
|
$payment_amount = give_get_payment_amount( $payment_id ); |
|
518
|
|
|
$refund_amount = $data['payment_gross'] * - 1; |
|
519
|
|
|
|
|
520
|
|
|
if ( number_format( (float) $refund_amount, 2 ) < number_format( (float) $payment_amount, 2 ) ) { |
|
521
|
|
|
|
|
522
|
|
|
give_insert_payment_note( $payment_id, sprintf( __( 'Partial PayPal refund processed: %s', 'give' ), $data['parent_txn_id'] ) ); |
|
523
|
|
|
|
|
524
|
|
|
return; // This is a partial refund |
|
525
|
|
|
|
|
526
|
|
|
} |
|
527
|
|
|
|
|
528
|
|
|
give_insert_payment_note( $payment_id, sprintf( __( 'PayPal Payment #%s Refunded for reason: %s', 'give' ), $data['parent_txn_id'], $data['reason_code'] ) ); |
|
529
|
|
|
give_insert_payment_note( $payment_id, sprintf( __( 'PayPal Refund Transaction ID: %s', 'give' ), $data['txn_id'] ) ); |
|
530
|
|
|
give_update_payment_status( $payment_id, 'refunded' ); |
|
531
|
|
|
} |
|
532
|
|
|
|
|
533
|
|
|
/** |
|
534
|
|
|
* Get PayPal Redirect |
|
535
|
|
|
* |
|
536
|
|
|
* @since 1.0 |
|
537
|
|
|
* |
|
538
|
|
|
* @param bool $ssl_check Is SSL? |
|
539
|
|
|
* |
|
540
|
|
|
* @return string |
|
541
|
|
|
*/ |
|
542
|
|
|
function give_get_paypal_redirect( $ssl_check = false ) { |
|
543
|
|
|
|
|
544
|
|
|
if ( is_ssl() || ! $ssl_check ) { |
|
545
|
|
|
$protocal = 'https://'; |
|
546
|
|
|
} else { |
|
547
|
|
|
$protocal = 'http://'; |
|
548
|
|
|
} |
|
549
|
|
|
|
|
550
|
|
|
// Check the current payment mode |
|
551
|
|
|
if ( give_is_test_mode() ) { |
|
552
|
|
|
// Test mode |
|
553
|
|
|
$paypal_uri = $protocal . 'www.sandbox.paypal.com/cgi-bin/webscr'; |
|
554
|
|
|
} else { |
|
555
|
|
|
// Live mode |
|
556
|
|
|
$paypal_uri = $protocal . 'www.paypal.com/cgi-bin/webscr'; |
|
557
|
|
|
} |
|
558
|
|
|
|
|
559
|
|
|
return apply_filters( 'give_paypal_uri', $paypal_uri ); |
|
560
|
|
|
} |
|
561
|
|
|
|
|
562
|
|
|
/** |
|
563
|
|
|
* Set the Page Style for PayPal Purchase page |
|
564
|
|
|
* |
|
565
|
|
|
* @since 1.0 |
|
566
|
|
|
* @return string |
|
567
|
|
|
*/ |
|
568
|
|
|
function give_get_paypal_page_style() { |
|
569
|
|
|
$page_style = trim( give_get_option( 'paypal_page_style', 'PayPal' ) ); |
|
570
|
|
|
return apply_filters( 'give_paypal_page_style', $page_style ); |
|
571
|
|
|
} |
|
572
|
|
|
|
|
573
|
|
|
/** |
|
574
|
|
|
* PayPal Success Page |
|
575
|
|
|
* |
|
576
|
|
|
* @description: Shows "Donation Processing" message for PayPal payments that are still pending on site return |
|
577
|
|
|
* |
|
578
|
|
|
* @since 1.0 |
|
579
|
|
|
* |
|
580
|
|
|
* @param $content |
|
581
|
|
|
* |
|
582
|
|
|
* @return string |
|
583
|
|
|
* |
|
584
|
|
|
*/ |
|
585
|
|
|
function give_paypal_success_page_content( $content ) { |
|
586
|
|
|
|
|
587
|
|
|
if ( ! isset( $_GET['payment-id'] ) && ! give_get_purchase_session() ) { |
|
588
|
|
|
return $content; |
|
589
|
|
|
} |
|
590
|
|
|
|
|
591
|
|
|
$payment_id = isset( $_GET['payment-id'] ) ? absint( $_GET['payment-id'] ) : false; |
|
592
|
|
|
|
|
593
|
|
|
if ( ! $payment_id ) { |
|
594
|
|
|
$session = give_get_purchase_session(); |
|
595
|
|
|
$payment_id = give_get_purchase_id_by_key( $session['purchase_key'] ); |
|
596
|
|
|
} |
|
597
|
|
|
|
|
598
|
|
|
$payment = get_post( $payment_id ); |
|
599
|
|
|
|
|
600
|
|
|
if ( $payment && 'pending' == $payment->post_status ) { |
|
601
|
|
|
|
|
602
|
|
|
// Payment is still pending so show processing indicator to fix the Race Condition |
|
603
|
|
|
ob_start(); |
|
604
|
|
|
|
|
605
|
|
|
give_get_template_part( 'payment', 'processing' ); |
|
606
|
|
|
|
|
607
|
|
|
$content = ob_get_clean(); |
|
608
|
|
|
|
|
609
|
|
|
} |
|
610
|
|
|
|
|
611
|
|
|
return $content; |
|
612
|
|
|
|
|
613
|
|
|
} |
|
614
|
|
|
|
|
615
|
|
|
add_filter( 'give_payment_confirm_paypal', 'give_paypal_success_page_content' ); |
|
616
|
|
|
|
|
617
|
|
|
/** |
|
618
|
|
|
* Given a Payment ID, extract the transaction ID |
|
619
|
|
|
* |
|
620
|
|
|
* @since 1.0 |
|
621
|
|
|
* |
|
622
|
|
|
* @param string $payment_id Payment ID |
|
623
|
|
|
* |
|
624
|
|
|
* @return string Transaction ID |
|
625
|
|
|
*/ |
|
626
|
|
|
function give_paypal_get_payment_transaction_id( $payment_id ) { |
|
627
|
|
|
|
|
628
|
|
|
$transaction_id = ''; |
|
629
|
|
|
$notes = give_get_payment_notes( $payment_id ); |
|
630
|
|
|
|
|
631
|
|
|
foreach ( $notes as $note ) { |
|
632
|
|
|
if ( preg_match( '/^PayPal Transaction ID: ([^\s]+)/', $note->comment_content, $match ) ) { |
|
633
|
|
|
$transaction_id = $match[1]; |
|
634
|
|
|
continue; |
|
635
|
|
|
} |
|
636
|
|
|
} |
|
637
|
|
|
|
|
638
|
|
|
return apply_filters( 'give_paypal_set_payment_transaction_id', $transaction_id, $payment_id ); |
|
639
|
|
|
} |
|
640
|
|
|
|
|
641
|
|
|
add_filter( 'give_get_payment_transaction_id-paypal', 'give_paypal_get_payment_transaction_id', 10, 1 ); |
|
642
|
|
|
|
|
643
|
|
|
/** |
|
644
|
|
|
* Given a transaction ID, generate a link to the PayPal transaction ID details |
|
645
|
|
|
* |
|
646
|
|
|
* @since 1.0 |
|
647
|
|
|
* |
|
648
|
|
|
* @param string $transaction_id The Transaction ID |
|
649
|
|
|
* @param int $payment_id The payment ID for this transaction |
|
650
|
|
|
* |
|
651
|
|
|
* @return string A link to the PayPal transaction details |
|
652
|
|
|
*/ |
|
653
|
|
|
function give_paypal_link_transaction_id( $transaction_id, $payment_id ) { |
|
|
|
|
|
|
654
|
|
|
|
|
655
|
|
|
$paypal_base_url = 'https://history.paypal.com/cgi-bin/webscr?cmd=_history-details-from-hub&id='; |
|
656
|
|
|
$transaction_url = '<a href="' . esc_url( $paypal_base_url . $transaction_id ) . '" target="_blank">' . $transaction_id . '</a>'; |
|
657
|
|
|
|
|
658
|
|
|
return apply_filters( 'give_paypal_link_payment_details_transaction_id', $transaction_url ); |
|
659
|
|
|
|
|
660
|
|
|
} |
|
661
|
|
|
|
|
662
|
|
|
add_filter( 'give_payment_details_transaction_id-paypal', 'give_paypal_link_transaction_id', 10, 2 ); |
|
663
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.