|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Paypal payment gateway |
|
4
|
|
|
* |
|
5
|
|
|
*/ |
|
6
|
|
|
|
|
7
|
|
|
defined( 'ABSPATH' ) || exit; |
|
8
|
|
|
|
|
9
|
|
|
/** |
|
10
|
|
|
* Paypal Payment Gateway class. |
|
11
|
|
|
* |
|
12
|
|
|
*/ |
|
13
|
|
|
class GetPaid_Paypal_Gateway extends GetPaid_Payment_Gateway { |
|
14
|
|
|
|
|
15
|
|
|
/** |
|
16
|
|
|
* Payment method id. |
|
17
|
|
|
* |
|
18
|
|
|
* @var string |
|
19
|
|
|
*/ |
|
20
|
|
|
public $id = 'paypal'; |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* An array of features that this gateway supports. |
|
24
|
|
|
* |
|
25
|
|
|
* @var array |
|
26
|
|
|
*/ |
|
27
|
|
|
protected $supports = array( 'subscription' ); |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* Payment method order. |
|
31
|
|
|
* |
|
32
|
|
|
* @var int |
|
33
|
|
|
*/ |
|
34
|
|
|
public $order = 1; |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* Stores line items to send to PayPal. |
|
38
|
|
|
* |
|
39
|
|
|
* @var array |
|
40
|
|
|
*/ |
|
41
|
|
|
protected $line_items = array(); |
|
42
|
|
|
|
|
43
|
|
|
/** |
|
44
|
|
|
* Endpoint for requests from PayPal. |
|
45
|
|
|
* |
|
46
|
|
|
* @var string |
|
47
|
|
|
*/ |
|
48
|
|
|
protected $notify_url; |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* Endpoint for requests to PayPal. |
|
52
|
|
|
* |
|
53
|
|
|
* @var string |
|
54
|
|
|
*/ |
|
55
|
|
|
protected $endpoint; |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* Currencies this gateway is allowed for. |
|
59
|
|
|
* |
|
60
|
|
|
* @var array |
|
61
|
|
|
*/ |
|
62
|
|
|
public $currencies = array( 'AUD', 'BRL', 'CAD', 'MXN', 'NZD', 'HKD', 'SGD', 'USD', 'EUR', 'JPY', 'TRY', 'NOK', 'CZK', 'DKK', 'HUF', 'ILS', 'MYR', 'PHP', 'PLN', 'SEK', 'CHF', 'TWD', 'THB', 'GBP', 'RMB', 'RUB', 'INR' ); |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* URL to view a transaction. |
|
66
|
|
|
* |
|
67
|
|
|
* @var string |
|
68
|
|
|
*/ |
|
69
|
|
|
public $view_transaction_url = 'https://www.{sandbox}paypal.com/activity/payment/%s'; |
|
70
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* URL to view a subscription. |
|
73
|
|
|
* |
|
74
|
|
|
* @var string |
|
75
|
|
|
*/ |
|
76
|
|
|
public $view_subscription_url = 'https://www.{sandbox}paypal.com/cgi-bin/webscr?cmd=_profile-recurring-payments&encrypted_profile_id=%s'; |
|
77
|
|
|
|
|
78
|
|
|
/** |
|
79
|
|
|
* Class constructor. |
|
80
|
|
|
*/ |
|
81
|
|
|
public function __construct() { |
|
82
|
|
|
|
|
83
|
|
|
$this->title = __( 'PayPal Standard', 'invoicing' ); |
|
84
|
|
|
$this->method_title = __( 'PayPal Standard', 'invoicing' ); |
|
85
|
|
|
$this->order_button_text = __( 'Proceed to PayPal', 'invoicing' ); |
|
|
|
|
|
|
86
|
|
|
$this->notify_url = wpinv_get_ipn_url( 'paypal' ); |
|
87
|
|
|
|
|
88
|
|
|
add_filter( 'getpaid_paypal_args', array( $this, 'process_subscription' ), 10, 2 ); |
|
89
|
|
|
add_filter( 'wpinv_gateway_description', array( $this, 'sandbox_notice' ), 10, 2 ); |
|
90
|
|
|
|
|
91
|
|
|
parent::__construct(); |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* Process Payment. |
|
96
|
|
|
* |
|
97
|
|
|
* |
|
98
|
|
|
* @param WPInv_Invoice $invoice Invoice. |
|
99
|
|
|
* @param array $submission_data Posted checkout fields. |
|
100
|
|
|
* @param GetPaid_Payment_Form_Submission $submission Checkout submission. |
|
101
|
|
|
* @return array |
|
102
|
|
|
*/ |
|
103
|
|
|
public function process_payment( $invoice, $submission_data, $submission ) { |
|
104
|
|
|
|
|
105
|
|
|
// Get redirect url. |
|
106
|
|
|
$paypal_redirect = $this->get_request_url( $invoice ); |
|
107
|
|
|
|
|
108
|
|
|
// Add a note about the request url. |
|
109
|
|
|
$invoice->add_note( |
|
110
|
|
|
sprintf( |
|
111
|
|
|
__( 'Redirecting to PayPal: %s', 'invoicing' ), |
|
112
|
|
|
esc_url( $paypal_redirect ) |
|
113
|
|
|
), |
|
114
|
|
|
false, |
|
115
|
|
|
false, |
|
116
|
|
|
true |
|
117
|
|
|
); |
|
118
|
|
|
|
|
119
|
|
|
// Redirect to PayPal |
|
120
|
|
|
wp_redirect( $paypal_redirect ); |
|
121
|
|
|
exit; |
|
|
|
|
|
|
122
|
|
|
|
|
123
|
|
|
} |
|
124
|
|
|
|
|
125
|
|
|
/** |
|
126
|
|
|
* Get the PayPal request URL for an invoice. |
|
127
|
|
|
* |
|
128
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
129
|
|
|
* @return string |
|
130
|
|
|
*/ |
|
131
|
|
|
public function get_request_url( $invoice ) { |
|
132
|
|
|
|
|
133
|
|
|
// Endpoint for this request |
|
134
|
|
|
$this->endpoint = $this->is_sandbox( $invoice ) ? 'https://www.sandbox.paypal.com/cgi-bin/webscr?test_ipn=1&' : 'https://www.paypal.com/cgi-bin/webscr?'; |
|
135
|
|
|
|
|
136
|
|
|
// Retrieve paypal args. |
|
137
|
|
|
$paypal_args = map_deep( $this->get_paypal_args( $invoice ), 'urlencode' ); |
|
138
|
|
|
|
|
139
|
|
|
if ( $invoice->is_recurring() ) { |
|
140
|
|
|
$paypal_args['bn'] = 'GetPaid_Subscribe_WPS_US'; |
|
141
|
|
|
} else { |
|
142
|
|
|
$paypal_args['bn'] = 'GetPaid_ShoppingCart_WPS_US'; |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
return add_query_arg( $paypal_args, $this->endpoint ); |
|
146
|
|
|
|
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* Get PayPal Args for passing to PP. |
|
151
|
|
|
* |
|
152
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
153
|
|
|
* @return array |
|
154
|
|
|
*/ |
|
155
|
|
|
protected function get_paypal_args( $invoice ) { |
|
156
|
|
|
|
|
157
|
|
|
// Whether or not to send the line items as one item. |
|
158
|
|
|
$force_one_line_item = apply_filters( 'getpaid_paypal_force_one_line_item', false, $invoice ); |
|
159
|
|
|
|
|
160
|
|
|
if ( $invoice->is_recurring() || ( wpinv_use_taxes() && wpinv_prices_include_tax() ) ) { |
|
161
|
|
|
$force_one_line_item = true; |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
$paypal_args = apply_filters( |
|
165
|
|
|
'getpaid_paypal_args', |
|
166
|
|
|
array_merge( |
|
167
|
|
|
$this->get_transaction_args( $invoice ), |
|
168
|
|
|
$this->get_line_item_args( $invoice, $force_one_line_item ) |
|
169
|
|
|
), |
|
170
|
|
|
$invoice |
|
171
|
|
|
); |
|
172
|
|
|
|
|
173
|
|
|
return $this->fix_request_length( $invoice, $paypal_args ); |
|
174
|
|
|
} |
|
175
|
|
|
|
|
176
|
|
|
/** |
|
177
|
|
|
* Get transaction args for paypal request. |
|
178
|
|
|
* |
|
179
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
180
|
|
|
* @return array |
|
181
|
|
|
*/ |
|
182
|
|
|
protected function get_transaction_args( $invoice ) { |
|
183
|
|
|
|
|
184
|
|
|
return array( |
|
185
|
|
|
'cmd' => '_cart', |
|
186
|
|
|
'business' => wpinv_get_option( 'paypal_email', false ), |
|
187
|
|
|
'no_shipping' => '1', |
|
188
|
|
|
'shipping' => '0', |
|
189
|
|
|
'no_note' => '1', |
|
190
|
|
|
'charset' => 'utf-8', |
|
191
|
|
|
'rm' => is_ssl() ? 2 : 1, |
|
192
|
|
|
'upload' => 1, |
|
193
|
|
|
'currency_code' => $invoice->get_currency(), // https://developer.paypal.com/docs/nvp-soap-api/currency-codes/#paypal |
|
194
|
|
|
'return' => esc_url_raw( $this->get_return_url( $invoice ) ), |
|
195
|
|
|
'cancel_return' => esc_url_raw( $invoice->get_checkout_payment_url() ), |
|
196
|
|
|
'notify_url' => getpaid_limit_length( $this->notify_url, 255 ), |
|
197
|
|
|
'invoice' => getpaid_limit_length( $invoice->get_number(), 127 ), |
|
198
|
|
|
'custom' => $invoice->get_id(), |
|
199
|
|
|
'first_name' => getpaid_limit_length( $invoice->get_first_name(), 32 ), |
|
200
|
|
|
'last_name' => getpaid_limit_length( $invoice->get_last_name(), 64 ), |
|
201
|
|
|
'country' => getpaid_limit_length( $invoice->get_country(), 2 ), |
|
202
|
|
|
'email' => getpaid_limit_length( $invoice->get_email(), 127 ), |
|
203
|
|
|
'cbt' => get_bloginfo( 'name' ) |
|
204
|
|
|
); |
|
205
|
|
|
|
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
/** |
|
209
|
|
|
* Get line item args for paypal request. |
|
210
|
|
|
* |
|
211
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
212
|
|
|
* @param bool $force_one_line_item Create only one item for this invoice. |
|
213
|
|
|
* @return array |
|
214
|
|
|
*/ |
|
215
|
|
|
protected function get_line_item_args( $invoice, $force_one_line_item = false ) { |
|
216
|
|
|
|
|
217
|
|
|
// Maybe send invoice as a single item. |
|
218
|
|
|
if ( $force_one_line_item ) { |
|
219
|
|
|
return $this->get_line_item_args_single_item( $invoice ); |
|
220
|
|
|
} |
|
221
|
|
|
|
|
222
|
|
|
// Send each line item individually. |
|
223
|
|
|
$line_item_args = array(); |
|
224
|
|
|
|
|
225
|
|
|
// Prepare line items. |
|
226
|
|
|
$this->prepare_line_items( $invoice ); |
|
227
|
|
|
|
|
228
|
|
|
// Add taxes to the cart |
|
229
|
|
|
if ( wpinv_use_taxes() && $invoice->is_taxable() ) { |
|
230
|
|
|
$line_item_args['tax_cart'] = wpinv_sanitize_amount( (float) $invoice->get_total_tax(), 2 ); |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
// Add discount. |
|
234
|
|
|
if ( $invoice->get_total_discount() > 0 ) { |
|
235
|
|
|
$line_item_args['discount_amount_cart'] = wpinv_sanitize_amount( (float) $invoice->get_total_discount(), 2 ); |
|
236
|
|
|
} |
|
237
|
|
|
|
|
238
|
|
|
return array_merge( $line_item_args, $this->get_line_items() ); |
|
239
|
|
|
|
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
/** |
|
243
|
|
|
* Get line item args for paypal request as a single line item. |
|
244
|
|
|
* |
|
245
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
246
|
|
|
* @return array |
|
247
|
|
|
*/ |
|
248
|
|
|
protected function get_line_item_args_single_item( $invoice ) { |
|
249
|
|
|
$this->delete_line_items(); |
|
250
|
|
|
|
|
251
|
|
|
$item_name = sprintf( __( 'Invoice #%s', 'invoicing' ), $invoice->get_number() ); |
|
252
|
|
|
$this->add_line_item( $item_name, 1, wpinv_sanitize_amount( (float) $invoice->get_total(), 2 ), $invoice->get_id() ); |
|
|
|
|
|
|
253
|
|
|
|
|
254
|
|
|
return $this->get_line_items(); |
|
255
|
|
|
} |
|
256
|
|
|
|
|
257
|
|
|
/** |
|
258
|
|
|
* Return all line items. |
|
259
|
|
|
*/ |
|
260
|
|
|
protected function get_line_items() { |
|
261
|
|
|
return $this->line_items; |
|
262
|
|
|
} |
|
263
|
|
|
|
|
264
|
|
|
/** |
|
265
|
|
|
* Remove all line items. |
|
266
|
|
|
*/ |
|
267
|
|
|
protected function delete_line_items() { |
|
268
|
|
|
$this->line_items = array(); |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
/** |
|
272
|
|
|
* Prepare line items to send to paypal. |
|
273
|
|
|
* |
|
274
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
275
|
|
|
*/ |
|
276
|
|
|
protected function prepare_line_items( $invoice ) { |
|
277
|
|
|
$this->delete_line_items(); |
|
278
|
|
|
|
|
279
|
|
|
// Items. |
|
280
|
|
|
foreach ( $invoice->get_items() as $item ) { |
|
281
|
|
|
$amount = $invoice->get_template() == 'amount' ? $item->get_price() : $item->get_sub_total(); |
|
282
|
|
|
$quantity = $invoice->get_template() == 'amount' ? 1 : $item->get_quantity(); |
|
283
|
|
|
$this->add_line_item( $item->get_raw_name(), $quantity, $amount, $item->get_id() ); |
|
284
|
|
|
} |
|
285
|
|
|
|
|
286
|
|
|
// Fees. |
|
287
|
|
|
foreach ( $invoice->get_fees() as $fee => $data ) { |
|
288
|
|
|
$this->add_line_item( $fee, 1, $data['amount'] ); |
|
289
|
|
|
} |
|
290
|
|
|
|
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
/** |
|
294
|
|
|
* Add PayPal Line Item. |
|
295
|
|
|
* |
|
296
|
|
|
* @param string $item_name Item name. |
|
297
|
|
|
* @param int $quantity Item quantity. |
|
298
|
|
|
* @param float $amount Amount. |
|
299
|
|
|
* @param string $item_number Item number. |
|
300
|
|
|
*/ |
|
301
|
|
|
protected function add_line_item( $item_name, $quantity = 1, $amount = 0.0, $item_number = '' ) { |
|
302
|
|
|
$index = ( count( $this->line_items ) / 4 ) + 1; |
|
303
|
|
|
|
|
304
|
|
|
$item = apply_filters( |
|
305
|
|
|
'getpaid_paypal_line_item', |
|
306
|
|
|
array( |
|
307
|
|
|
'item_name' => html_entity_decode( getpaid_limit_length( $item_name ? wp_strip_all_tags( $item_name ) : __( 'Item', 'invoicing' ), 127 ), ENT_NOQUOTES, 'UTF-8' ), |
|
308
|
|
|
'quantity' => (int) $quantity, |
|
309
|
|
|
'amount' => wpinv_sanitize_amount( (float) $amount, 2 ), |
|
310
|
|
|
'item_number' => $item_number, |
|
311
|
|
|
), |
|
312
|
|
|
$item_name, |
|
313
|
|
|
$quantity, |
|
314
|
|
|
$amount, |
|
315
|
|
|
$item_number |
|
316
|
|
|
); |
|
317
|
|
|
|
|
318
|
|
|
$this->line_items[ 'item_name_' . $index ] = getpaid_limit_length( $item['item_name'], 127 ); |
|
319
|
|
|
$this->line_items[ 'quantity_' . $index ] = $item['quantity']; |
|
320
|
|
|
|
|
321
|
|
|
// The price or amount of the product, service, or contribution, not including shipping, handling, or tax. |
|
322
|
|
|
$this->line_items[ 'amount_' . $index ] = $item['amount']; |
|
323
|
|
|
$this->line_items[ 'item_number_' . $index ] = getpaid_limit_length( $item['item_number'], 127 ); |
|
324
|
|
|
} |
|
325
|
|
|
|
|
326
|
|
|
/** |
|
327
|
|
|
* If the default request with line items is too long, generate a new one with only one line item. |
|
328
|
|
|
* |
|
329
|
|
|
* https://support.microsoft.com/en-us/help/208427/maximum-url-length-is-2-083-characters-in-internet-explorer. |
|
330
|
|
|
* |
|
331
|
|
|
* @param WPInv_Invoice $invoice Invoice to be sent to Paypal. |
|
332
|
|
|
* @param array $paypal_args Arguments sent to Paypal in the request. |
|
333
|
|
|
* @return array |
|
334
|
|
|
*/ |
|
335
|
|
|
protected function fix_request_length( $invoice, $paypal_args ) { |
|
336
|
|
|
$max_paypal_length = 2083; |
|
337
|
|
|
$query_candidate = http_build_query( $paypal_args, '', '&' ); |
|
338
|
|
|
|
|
339
|
|
|
if ( strlen( $this->endpoint . $query_candidate ) <= $max_paypal_length ) { |
|
340
|
|
|
return $paypal_args; |
|
341
|
|
|
} |
|
342
|
|
|
|
|
343
|
|
|
return apply_filters( |
|
344
|
|
|
'getpaid_paypal_args', |
|
345
|
|
|
array_merge( |
|
346
|
|
|
$this->get_transaction_args( $invoice ), |
|
347
|
|
|
$this->get_line_item_args( $invoice, true ) |
|
348
|
|
|
), |
|
349
|
|
|
$invoice |
|
350
|
|
|
); |
|
351
|
|
|
|
|
352
|
|
|
} |
|
353
|
|
|
|
|
354
|
|
|
/** |
|
355
|
|
|
* Processes recurring invoices. |
|
356
|
|
|
* |
|
357
|
|
|
* @param array $paypal_args PayPal args. |
|
358
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
359
|
|
|
*/ |
|
360
|
|
|
public function process_subscription( $paypal_args, $invoice ) { |
|
361
|
|
|
|
|
362
|
|
|
// Make sure this is a subscription. |
|
363
|
|
|
if ( ! $invoice->is_recurring() || ! $subscription = wpinv_get_subscription( $invoice ) ) { |
|
364
|
|
|
return $paypal_args; |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
|
|
// It's a subscription |
|
368
|
|
|
$paypal_args['cmd'] = '_xclick-subscriptions'; |
|
369
|
|
|
|
|
370
|
|
|
// Subscription name. |
|
371
|
|
|
$paypal_args['item_name'] = sprintf( __( 'Invoice #%s', 'invoicing' ), $invoice->get_number() ); |
|
372
|
|
|
|
|
373
|
|
|
// Get subscription args. |
|
374
|
|
|
$period = strtoupper( substr( $subscription->period, 0, 1) ); |
|
375
|
|
|
$interval = (int) $subscription->frequency; |
|
|
|
|
|
|
376
|
|
|
$bill_times = (int) $subscription->bill_times; |
|
377
|
|
|
$initial_amount = (float) wpinv_sanitize_amount( $invoice->get_initial_total(), 2 ); |
|
378
|
|
|
$recurring_amount = (float) wpinv_sanitize_amount( $invoice->get_recurring_total(), 2 ); |
|
379
|
|
|
$subscription_item = $invoice->get_recurring( true ); |
|
380
|
|
|
|
|
381
|
|
|
if ( $subscription_item->has_free_trial() ) { |
|
382
|
|
|
|
|
383
|
|
|
$paypal_args['a1'] = 0 == $initial_amount ? 0 : $initial_amount; |
|
384
|
|
|
|
|
385
|
|
|
// Trial period length. |
|
386
|
|
|
$paypal_args['p1'] = $subscription_item->get_trial_interval(); |
|
387
|
|
|
|
|
388
|
|
|
// Trial period. |
|
389
|
|
|
$paypal_args['t1'] = $subscription_item->get_trial_period(); |
|
390
|
|
|
|
|
391
|
|
|
} else if ( $initial_amount != $recurring_amount ) { |
|
392
|
|
|
|
|
393
|
|
|
// No trial period, but initial amount includes a sign-up fee and/or other items, so charge it as a separate period. |
|
394
|
|
|
|
|
395
|
|
|
if ( 1 == $bill_times ) { |
|
396
|
|
|
$param_number = 3; |
|
397
|
|
|
} else { |
|
398
|
|
|
$param_number = 1; |
|
399
|
|
|
} |
|
400
|
|
|
|
|
401
|
|
|
$paypal_args[ 'a' . $param_number ] = $initial_amount ? 0 : $initial_amount; |
|
402
|
|
|
|
|
403
|
|
|
// Sign Up interval |
|
404
|
|
|
$paypal_args[ 'p' . $param_number ] = $interval; |
|
405
|
|
|
|
|
406
|
|
|
// Sign Up unit of duration |
|
407
|
|
|
$paypal_args[ 't' . $param_number ] = $period; |
|
408
|
|
|
|
|
409
|
|
|
} |
|
410
|
|
|
|
|
411
|
|
|
// We have a recurring payment |
|
412
|
|
|
if ( ! isset( $param_number ) || 1 == $param_number ) { |
|
413
|
|
|
|
|
414
|
|
|
// Subscription price |
|
415
|
|
|
$paypal_args['a3'] = $recurring_amount; |
|
416
|
|
|
|
|
417
|
|
|
// Subscription duration |
|
418
|
|
|
$paypal_args['p3'] = $interval; |
|
419
|
|
|
|
|
420
|
|
|
// Subscription period |
|
421
|
|
|
$paypal_args['t3'] = $period; |
|
422
|
|
|
|
|
423
|
|
|
} |
|
424
|
|
|
|
|
425
|
|
|
// Recurring payments |
|
426
|
|
|
if ( 1 == $bill_times || ( $initial_amount != $recurring_amount && ! $subscription_item->has_free_trial() && 2 == $bill_times ) ) { |
|
427
|
|
|
|
|
428
|
|
|
// Non-recurring payments |
|
429
|
|
|
$paypal_args['src'] = 0; |
|
430
|
|
|
|
|
431
|
|
|
} else { |
|
432
|
|
|
|
|
433
|
|
|
$paypal_args['src'] = 1; |
|
434
|
|
|
|
|
435
|
|
|
if ( $bill_times > 0 ) { |
|
436
|
|
|
|
|
437
|
|
|
// An initial period is being used to charge a sign-up fee |
|
438
|
|
|
if ( $initial_amount != $recurring_amount && ! $subscription_item->has_free_trial() ) { |
|
439
|
|
|
$bill_times--; |
|
440
|
|
|
} |
|
441
|
|
|
|
|
442
|
|
|
// Make sure it's not over the max of 52 |
|
443
|
|
|
$paypal_args['srt'] = ( $bill_times <= 52 ? absint( $bill_times ) : 52 ); |
|
444
|
|
|
|
|
445
|
|
|
} |
|
446
|
|
|
} |
|
447
|
|
|
|
|
448
|
|
|
// Force return URL so that order description & instructions display |
|
449
|
|
|
$paypal_args['rm'] = 2; |
|
450
|
|
|
|
|
451
|
|
|
// Get rid of redudant items. |
|
452
|
|
|
foreach ( array( 'item_name_1', 'quantity_1', 'amount_1', 'item_number_1' ) as $arg ) { |
|
453
|
|
|
|
|
454
|
|
|
if ( isset( $paypal_args[ $arg ] ) ) { |
|
455
|
|
|
unset( $paypal_args[ $arg ] ); |
|
456
|
|
|
} |
|
457
|
|
|
|
|
458
|
|
|
} |
|
459
|
|
|
|
|
460
|
|
|
return apply_filters( |
|
461
|
|
|
'getpaid_paypal_subscription_args', |
|
462
|
|
|
$paypal_args, |
|
463
|
|
|
$invoice |
|
464
|
|
|
); |
|
465
|
|
|
|
|
466
|
|
|
} |
|
467
|
|
|
|
|
468
|
|
|
/** |
|
469
|
|
|
* Processes ipns and marks payments as complete. |
|
470
|
|
|
* |
|
471
|
|
|
* @return void |
|
472
|
|
|
*/ |
|
473
|
|
|
public function verify_ipn() { |
|
474
|
|
|
|
|
475
|
|
|
// Validate the IPN. |
|
476
|
|
|
if ( empty( $_POST ) || ! $this->validate_ipn() ) { |
|
477
|
|
|
wp_die( 'PayPal IPN Request Failure', 'PayPal IPN', array( 'response' => 500 ) ); |
|
478
|
|
|
} |
|
479
|
|
|
|
|
480
|
|
|
// Process the IPN. |
|
481
|
|
|
$posted = wp_unslash( $_POST ); |
|
482
|
|
|
$invoice = wpinv_get_invoice( $posted['custom'] ); |
|
483
|
|
|
|
|
484
|
|
|
if ( $invoice && $this->id == $invoice->get_gateway() ) { |
|
485
|
|
|
|
|
486
|
|
|
$posted['payment_status'] = strtolower( $posted['payment_status'] ); |
|
487
|
|
|
|
|
488
|
|
|
wpinv_error_log( 'Found invoice #' . $invoice->get_number() ); |
|
489
|
|
|
wpinv_error_log( 'Payment status:' . $posted['payment_status'] ); |
|
490
|
|
|
|
|
491
|
|
|
if ( method_exists( $this, 'ipn_txn_' . $posted['txn_type'] ) ) { |
|
492
|
|
|
call_user_func( array( $this, 'ipn_txn_' . $posted['txn_type'] ), $invoice, $posted ); |
|
|
|
|
|
|
493
|
|
|
} else { |
|
494
|
|
|
wpinv_error_log( 'Aborting, Invalid type:' . $posted['txn_type'] ); |
|
495
|
|
|
} |
|
496
|
|
|
|
|
497
|
|
|
} |
|
498
|
|
|
|
|
499
|
|
|
exit; |
|
|
|
|
|
|
500
|
|
|
|
|
501
|
|
|
} |
|
502
|
|
|
|
|
503
|
|
|
/** |
|
504
|
|
|
* Check PayPal IPN validity. |
|
505
|
|
|
*/ |
|
506
|
|
|
public function validate_ipn() { |
|
507
|
|
|
|
|
508
|
|
|
wpinv_error_log( 'Validating PayPal IPN response' ); |
|
509
|
|
|
|
|
510
|
|
|
// Get received values from post data. |
|
511
|
|
|
$validate_ipn = wp_unslash( $_POST ); |
|
512
|
|
|
$validate_ipn['cmd'] = '_notify-validate'; |
|
513
|
|
|
|
|
514
|
|
|
// Send back post vars to paypal. |
|
515
|
|
|
$params = array( |
|
516
|
|
|
'body' => $validate_ipn, |
|
517
|
|
|
'timeout' => 60, |
|
518
|
|
|
'httpversion' => '1.1', |
|
519
|
|
|
'compress' => false, |
|
520
|
|
|
'decompress' => false, |
|
521
|
|
|
'user-agent' => 'GetPaid/' . WPINV_VERSION, |
|
522
|
|
|
); |
|
523
|
|
|
|
|
524
|
|
|
// Post back to get a response. |
|
525
|
|
|
$response = wp_safe_remote_post( $this->is_sandbox() ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr', $params ); |
|
526
|
|
|
|
|
527
|
|
|
// Check to see if the request was valid. |
|
528
|
|
|
if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 && strstr( $response['body'], 'VERIFIED' ) ) { |
|
529
|
|
|
wpinv_error_log( $response['body'], 'Received valid response from PayPal IPN' ); |
|
530
|
|
|
return true; |
|
531
|
|
|
} |
|
532
|
|
|
|
|
533
|
|
|
if ( is_wp_error( $response ) ) { |
|
534
|
|
|
wpinv_error_log( $response->get_error_message(), 'Received invalid response from PayPal IPN' ); |
|
535
|
|
|
} else { |
|
536
|
|
|
wpinv_error_log( $response['body'], 'Received invalid response from PayPal IPN' ); |
|
537
|
|
|
} |
|
538
|
|
|
|
|
539
|
|
|
return false; |
|
540
|
|
|
|
|
541
|
|
|
} |
|
542
|
|
|
|
|
543
|
|
|
/** |
|
544
|
|
|
* Check currency from IPN matches the invoice. |
|
545
|
|
|
* |
|
546
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
547
|
|
|
* @param string $currency currency to validate. |
|
548
|
|
|
*/ |
|
549
|
|
|
protected function validate_ipn_currency( $invoice, $currency ) { |
|
550
|
|
|
if ( strtolower( $invoice->get_currency() ) !== strtolower( $currency ) ) { |
|
551
|
|
|
wpinv_record_gateway_error( 'IPN Error', "Currencies do not match: {$currency} instead of {$invoice->get_currency()}" ); |
|
552
|
|
|
|
|
553
|
|
|
/* translators: %s: currency code. */ |
|
554
|
|
|
$invoice->update_status( 'wpi-processing', sprintf( __( 'Validation error: PayPal currencies do not match (code %s).', 'invoicing' ), $currency ) ); |
|
555
|
|
|
exit; |
|
|
|
|
|
|
556
|
|
|
} |
|
557
|
|
|
} |
|
558
|
|
|
|
|
559
|
|
|
/** |
|
560
|
|
|
* Check payment amount from IPN matches the invoice. |
|
561
|
|
|
* |
|
562
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
563
|
|
|
* @param float $amount amount to validate. |
|
564
|
|
|
*/ |
|
565
|
|
|
protected function validate_ipn_amount( $invoice, $amount ) { |
|
566
|
|
|
if ( number_format( $invoice->get_total(), 2, '.', '' ) !== number_format( $amount, 2, '.', '' ) ) { |
|
567
|
|
|
wpinv_record_gateway_error( 'IPN Error', "Amounts do not match: {$amount} instead of {$invoice->get_total()}" ); |
|
568
|
|
|
|
|
569
|
|
|
/* translators: %s: Amount. */ |
|
570
|
|
|
$invoice->update_status( 'wpi-processing', sprintf( __( 'Validation error: PayPal amounts do not match (gross %s).', 'invoicing' ), $amount ) ); |
|
571
|
|
|
exit; |
|
|
|
|
|
|
572
|
|
|
} |
|
573
|
|
|
} |
|
574
|
|
|
|
|
575
|
|
|
/** |
|
576
|
|
|
* Verify receiver email from PayPal. |
|
577
|
|
|
* |
|
578
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
579
|
|
|
* @param string $receiver_email Email to validate. |
|
580
|
|
|
*/ |
|
581
|
|
|
protected function validate_ipn_receiver_email( $invoice, $receiver_email ) { |
|
582
|
|
|
$paypal_email = wpinv_get_option( 'paypal_email' ); |
|
583
|
|
|
|
|
584
|
|
|
if ( strcasecmp( trim( $receiver_email ), trim( $paypal_email ) ) !== 0 ) { |
|
|
|
|
|
|
585
|
|
|
wpinv_record_gateway_error( 'IPN Error', "IPN Response is for another account: {$receiver_email}. Your email is {$paypal_email}" ); |
|
586
|
|
|
|
|
587
|
|
|
/* translators: %s: email address . */ |
|
588
|
|
|
$invoice->update_status( 'wpi-processing', sprintf( __( 'Validation error: PayPal IPN response from a different email address (%s).', 'invoicing' ), $receiver_email ) ); |
|
589
|
|
|
exit; |
|
|
|
|
|
|
590
|
|
|
} |
|
591
|
|
|
|
|
592
|
|
|
} |
|
593
|
|
|
|
|
594
|
|
|
/** |
|
595
|
|
|
* Handles one time payments. |
|
596
|
|
|
* |
|
597
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
598
|
|
|
* @param array $posted Posted data. |
|
599
|
|
|
*/ |
|
600
|
|
|
protected function ipn_txn_web_accept( $invoice, $posted ) { |
|
601
|
|
|
|
|
602
|
|
|
// Collect payment details |
|
603
|
|
|
$payment_status = strtolower( $posted['payment_status'] ); |
|
604
|
|
|
$business_email = isset( $posted['business'] ) && is_email( $posted['business'] ) ? trim( $posted['business'] ) : trim( $posted['receiver_email'] ); |
|
605
|
|
|
|
|
606
|
|
|
$this->validate_ipn_receiver_email( $invoice, $business_email ); |
|
607
|
|
|
$this->validate_ipn_currency( $invoice, $posted['mc_currency'] ); |
|
608
|
|
|
|
|
609
|
|
|
// Update the transaction id. |
|
610
|
|
|
if ( ! empty( $posted['txn_id'] ) ) { |
|
611
|
|
|
$invoice->set_transaction_id( wpinv_clean( $posted['txn_id'] ) ); |
|
612
|
|
|
$invoice->save(); |
|
613
|
|
|
} |
|
614
|
|
|
|
|
615
|
|
|
// Process a refund. |
|
616
|
|
|
if ( $payment_status == 'refunded' || $payment_status == 'reversed' ) { |
|
617
|
|
|
|
|
618
|
|
|
update_post_meta( $invoice->get_id(), 'refunded_remotely', 1 ); |
|
619
|
|
|
|
|
620
|
|
|
if ( ! $invoice->is_refunded() ) { |
|
621
|
|
|
$invoice->update_status( 'wpi-refunded', $posted['reason_code'] ); |
|
622
|
|
|
} |
|
623
|
|
|
|
|
624
|
|
|
exit; |
|
|
|
|
|
|
625
|
|
|
} |
|
626
|
|
|
|
|
627
|
|
|
// Process payments. |
|
628
|
|
|
if ( $payment_status == 'completed' ) { |
|
629
|
|
|
|
|
630
|
|
|
if ( $invoice->is_paid() && 'wpi_processing' != $invoice->get_status() ) { |
|
631
|
|
|
wpinv_error_log( 'Aborting, Invoice #' . $invoice->get_number() . ' is already complete.' ); |
|
632
|
|
|
exit; |
|
|
|
|
|
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
$this->validate_ipn_amount( $invoice, $posted['mc_gross'] ); |
|
636
|
|
|
|
|
637
|
|
|
if ( 'completed' === $payment_status || 'pending' === $payment_status ) { |
|
638
|
|
|
|
|
639
|
|
|
$note = ''; |
|
640
|
|
|
|
|
641
|
|
|
if ( ! empty( $posted['mc_fee'] ) ) { |
|
642
|
|
|
$note = sprintf( __( 'PayPal Transaction Fee %s', 'invoicing' ), wpinv_clean( $posted['mc_fee'] ) ); |
|
643
|
|
|
} |
|
644
|
|
|
|
|
645
|
|
|
$invoice->mark_paid( ( ! empty( $posted['txn_id'] ) ? wpinv_clean( $posted['txn_id'] ) : '' ), $note ); |
|
646
|
|
|
|
|
647
|
|
|
} else { |
|
648
|
|
|
|
|
649
|
|
|
/* translators: %s: pending reason. */ |
|
650
|
|
|
$invoice->update_status( 'wpi_processing', sprintf( __( 'Payment pending (%s).', 'invoicing' ), $posted['pending_reason'] ) ); |
|
651
|
|
|
|
|
652
|
|
|
} |
|
653
|
|
|
|
|
654
|
|
|
exit; |
|
|
|
|
|
|
655
|
|
|
|
|
656
|
|
|
} |
|
657
|
|
|
|
|
658
|
|
|
// Process failures. |
|
659
|
|
|
|
|
660
|
|
|
/* translators: %s: payment status. */ |
|
661
|
|
|
$invoice->update_status( 'wpi-failed', sprintf( __( 'Payment %s via IPN.', 'invoicing' ), wpinv_clean( $posted['payment_status'] ) ) ); |
|
662
|
|
|
|
|
663
|
|
|
} |
|
664
|
|
|
|
|
665
|
|
|
/** |
|
666
|
|
|
* Handles one time payments. |
|
667
|
|
|
* |
|
668
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
669
|
|
|
* @param array $posted Posted data. |
|
670
|
|
|
*/ |
|
671
|
|
|
protected function ipn_txn_cart( $invoice, $posted ) { |
|
672
|
|
|
$this->ipn_txn_web_accept( $invoice, $posted ); |
|
673
|
|
|
} |
|
674
|
|
|
|
|
675
|
|
|
/** |
|
676
|
|
|
* Handles subscription sign ups. |
|
677
|
|
|
* |
|
678
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
679
|
|
|
* @param array $posted Posted data. |
|
680
|
|
|
*/ |
|
681
|
|
|
protected function ipn_txn_subscr_signup( $invoice, $posted ) { |
|
682
|
|
|
|
|
683
|
|
|
// Make sure the invoice has a subscription. |
|
684
|
|
|
$subscription = wpinv_get_subscription( $invoice ); |
|
685
|
|
|
|
|
686
|
|
|
if ( empty( $subscription ) ) { |
|
687
|
|
|
wpinv_error_log( 'Aborting, Subscription for the invoice ' . $invoice->get_id() . ' not found' ); |
|
688
|
|
|
} |
|
689
|
|
|
|
|
690
|
|
|
// Update the subscription ids. |
|
691
|
|
|
$subscription->update( |
|
692
|
|
|
array( |
|
693
|
|
|
'profile_id' => sanitize_text_field( $posted['subscr_id'] ), |
|
694
|
|
|
) |
|
695
|
|
|
); |
|
696
|
|
|
|
|
697
|
|
|
// Set the transaction id. |
|
698
|
|
|
if ( ! empty( $posted['txn_id'] ) ) { |
|
699
|
|
|
$invoice->set_transaction_id( $posted['txn_id'] ); |
|
700
|
|
|
} |
|
701
|
|
|
|
|
702
|
|
|
// Update the payment status. |
|
703
|
|
|
$invoice->mark_paid(); |
|
704
|
|
|
|
|
705
|
|
|
$invoice->add_note( sprintf( __( 'PayPal Subscription ID: %s', 'invoicing' ) , $posted['subscr_id'] ), false, false, true ); |
|
706
|
|
|
|
|
707
|
|
|
// Use the action id as the subscription id. |
|
708
|
|
|
$duration = strtotime( $subscription->expiration ) - strtotime( $subscription->created ); |
|
709
|
|
|
$subscription->update( |
|
710
|
|
|
array( |
|
711
|
|
|
'status' => 'trialling' == $subscription->status ? 'trialling' : 'active', |
|
712
|
|
|
'created' => current_time( 'mysql' ), |
|
713
|
|
|
'expiration' => date( 'Y-m-d H:i:s', ( current_time( 'timestamp' ) + $duration ) ), |
|
714
|
|
|
) |
|
715
|
|
|
); |
|
716
|
|
|
|
|
717
|
|
|
} |
|
718
|
|
|
|
|
719
|
|
|
/** |
|
720
|
|
|
* Handles subscription renewals. |
|
721
|
|
|
* |
|
722
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
723
|
|
|
* @param array $posted Posted data. |
|
724
|
|
|
*/ |
|
725
|
|
|
protected function ipn_txn_subscr_payment( $invoice, $posted ) { |
|
726
|
|
|
|
|
727
|
|
|
// Make sure the invoice has a subscription. |
|
728
|
|
|
$subscription = wpinv_get_subscription( $invoice ); |
|
729
|
|
|
|
|
730
|
|
|
if ( empty( $subscription ) ) { |
|
731
|
|
|
wpinv_error_log( 'Aborting, Subscription for the invoice ' . $invoice->get_id() . ' not found' ); |
|
732
|
|
|
} |
|
733
|
|
|
|
|
734
|
|
|
$this->validate_ipn_currency( $invoice, $posted['mc_currency'] ); |
|
735
|
|
|
|
|
736
|
|
|
// Abort if the payment is already recorded. |
|
737
|
|
|
if ( wpinv_get_id_by_transaction_id( $posted['txn_id'] ) ) { |
|
738
|
|
|
return; |
|
739
|
|
|
} |
|
740
|
|
|
|
|
741
|
|
|
// Abort if this is the first payment. |
|
742
|
|
|
if ( date( 'Ynd', $subscription->created ) == date( 'Ynd', strtotime( $posted['payment_date'] ) ) ) { |
|
743
|
|
|
$invoice->set_transaction_id( $posted['txn_id'] ); |
|
744
|
|
|
$invoice->save(); |
|
745
|
|
|
return; |
|
746
|
|
|
} |
|
747
|
|
|
|
|
748
|
|
|
$args = array( |
|
749
|
|
|
'amount' => $posted['mc_gross'], |
|
750
|
|
|
'transaction_id' => $posted['txn_id'], |
|
751
|
|
|
'gateway' => $this->id, |
|
752
|
|
|
); |
|
753
|
|
|
|
|
754
|
|
|
$invoice = wpinv_get_invoice( $subscription->add_payment( $args ) ); |
|
755
|
|
|
|
|
756
|
|
|
if ( empty( $invoice ) ) { |
|
757
|
|
|
return; |
|
758
|
|
|
} |
|
759
|
|
|
|
|
760
|
|
|
$invoice->add_note( wp_sprintf( __( 'PayPal Transaction ID: %s', 'invoicing' ) , $posted['txn_id'] ), false, false, true ); |
|
761
|
|
|
$invoice->add_note( wp_sprintf( __( 'PayPal Subscription ID: %s', 'invoicing' ) , $posted['subscr_id'] ), false, false, true ); |
|
762
|
|
|
|
|
763
|
|
|
$subscription->renew(); |
|
764
|
|
|
|
|
765
|
|
|
} |
|
766
|
|
|
|
|
767
|
|
|
/** |
|
768
|
|
|
* Handles subscription cancelations. |
|
769
|
|
|
* |
|
770
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
771
|
|
|
* @param array $posted Posted data. |
|
772
|
|
|
*/ |
|
773
|
|
|
protected function ipn_txn_subscr_cancel( $invoice, $posted ) { |
|
774
|
|
|
|
|
775
|
|
|
if ( $subscription = wpinv_get_subscription( $invoice ) ) { |
|
776
|
|
|
$subscription->cancel(); |
|
777
|
|
|
} |
|
778
|
|
|
|
|
779
|
|
|
} |
|
780
|
|
|
|
|
781
|
|
|
/** |
|
782
|
|
|
* Handles subscription completions. |
|
783
|
|
|
* |
|
784
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
785
|
|
|
* @param array $posted Posted data. |
|
786
|
|
|
*/ |
|
787
|
|
|
protected function ipn_txn_subscr_eot( $invoice, $posted ) { |
|
788
|
|
|
|
|
789
|
|
|
if ( $subscription = wpinv_get_subscription( $invoice ) ) { |
|
790
|
|
|
$subscription->complete(); |
|
791
|
|
|
} |
|
792
|
|
|
|
|
793
|
|
|
} |
|
794
|
|
|
|
|
795
|
|
|
/** |
|
796
|
|
|
* Handles subscription fails. |
|
797
|
|
|
* |
|
798
|
|
|
* @param WPInv_Invoice $invoice Invoice object. |
|
799
|
|
|
* @param array $posted Posted data. |
|
800
|
|
|
*/ |
|
801
|
|
|
protected function ipn_txn_subscr_failed( $invoice, $posted ) { |
|
802
|
|
|
|
|
803
|
|
|
if ( $subscription = wpinv_get_subscription( $invoice ) ) { |
|
804
|
|
|
$subscription->failing(); |
|
805
|
|
|
} |
|
806
|
|
|
|
|
807
|
|
|
} |
|
808
|
|
|
|
|
809
|
|
|
/** |
|
810
|
|
|
* Displays a notice on the checkout page if sandbox is enabled. |
|
811
|
|
|
*/ |
|
812
|
|
|
public function sandbox_notice( $description, $gateway ) { |
|
813
|
|
|
if ( 'paypal' == $gateway && wpinv_is_test_mode( 'paypal' ) ) { |
|
814
|
|
|
$description .= '<br>' . sprintf( |
|
815
|
|
|
__( 'SANDBOX ENABLED. You can use sandbox testing accounts only. See the %sPayPal Sandbox Testing Guide%s for more details.', 'invoicing' ), |
|
816
|
|
|
'<a href="https://developer.paypal.com/docs/classic/lifecycle/ug_sandbox/">', |
|
817
|
|
|
'</a>' |
|
818
|
|
|
); |
|
819
|
|
|
} |
|
820
|
|
|
return $description; |
|
821
|
|
|
|
|
822
|
|
|
} |
|
823
|
|
|
|
|
824
|
|
|
} |
|
825
|
|
|
|