1
|
|
|
<?php |
|
|
|
|
2
|
|
|
|
3
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
4
|
|
|
exit; |
5
|
|
|
} |
6
|
|
|
|
7
|
|
|
/** |
8
|
|
|
* Generates requests to send to PayPal. |
9
|
|
|
*/ |
10
|
|
|
class WC_Gateway_Paypal_Request { |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Stores line items to send to PayPal. |
14
|
|
|
* @var array |
15
|
|
|
*/ |
16
|
|
|
protected $line_items = array(); |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Pointer to gateway making the request. |
20
|
|
|
* @var WC_Gateway_Paypal |
21
|
|
|
*/ |
22
|
|
|
protected $gateway; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Endpoint for requests from PayPal. |
26
|
|
|
* @var string |
27
|
|
|
*/ |
28
|
|
|
protected $notify_url; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Constructor. |
32
|
|
|
* @param WC_Gateway_Paypal $gateway |
33
|
|
|
*/ |
34
|
|
|
public function __construct( $gateway ) { |
35
|
|
|
$this->gateway = $gateway; |
36
|
|
|
$this->notify_url = WC()->api_request_url( 'WC_Gateway_Paypal' ); |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Get the PayPal request URL for an order. |
41
|
|
|
* @param WC_Order $order |
42
|
|
|
* @param bool $sandbox |
43
|
|
|
* @return string |
44
|
|
|
*/ |
45
|
|
|
public function get_request_url( $order, $sandbox = false ) { |
46
|
|
|
$paypal_args = http_build_query( $this->get_paypal_args( $order ), '', '&' ); |
47
|
|
|
|
48
|
|
|
if ( $sandbox ) { |
49
|
|
|
return 'https://www.sandbox.paypal.com/cgi-bin/webscr?test_ipn=1&' . $paypal_args; |
50
|
|
|
} else { |
51
|
|
|
return 'https://www.paypal.com/cgi-bin/webscr?' . $paypal_args; |
52
|
|
|
} |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Get PayPal Args for passing to PP. |
57
|
|
|
* @param WC_Order $order |
58
|
|
|
* @return array |
59
|
|
|
*/ |
60
|
|
|
protected function get_paypal_args( $order ) { |
61
|
|
|
WC_Gateway_Paypal::log( 'Generating payment form for order ' . $order->get_order_number() . '. Notify URL: ' . $this->notify_url ); |
62
|
|
|
|
63
|
|
|
return apply_filters( 'woocommerce_paypal_args', array_merge( |
64
|
|
|
array( |
65
|
|
|
'cmd' => '_cart', |
66
|
|
|
'business' => $this->gateway->get_option( 'email' ), |
67
|
|
|
'no_note' => 1, |
68
|
|
|
'currency_code' => get_woocommerce_currency(), |
69
|
|
|
'charset' => 'utf-8', |
70
|
|
|
'rm' => is_ssl() ? 2 : 1, |
71
|
|
|
'upload' => 1, |
72
|
|
|
'return' => esc_url_raw( add_query_arg( 'utm_nooverride', '1', $this->gateway->get_return_url( $order ) ) ), |
73
|
|
|
'cancel_return' => esc_url_raw( $order->get_cancel_order_url_raw() ), |
74
|
|
|
'page_style' => $this->gateway->get_option( 'page_style' ), |
75
|
|
|
'paymentaction' => $this->gateway->get_option( 'paymentaction' ), |
76
|
|
|
'bn' => 'WooThemes_Cart', |
77
|
|
|
'invoice' => $this->gateway->get_option( 'invoice_prefix' ) . $order->get_order_number(), |
78
|
|
|
'custom' => json_encode( array( 'order_id' => $order->id, 'order_key' => $order->order_key ) ), |
79
|
|
|
'notify_url' => $this->notify_url, |
80
|
|
|
'first_name' => $order->billing_first_name, |
81
|
|
|
'last_name' => $order->billing_last_name, |
82
|
|
|
'company' => $order->billing_company, |
83
|
|
|
'address1' => $order->billing_address_1, |
84
|
|
|
'address2' => $order->billing_address_2, |
85
|
|
|
'city' => $order->billing_city, |
86
|
|
|
'state' => $this->get_paypal_state( $order->billing_country, $order->billing_state ), |
87
|
|
|
'zip' => $order->billing_postcode, |
88
|
|
|
'country' => $order->billing_country, |
89
|
|
|
'email' => $order->billing_email |
90
|
|
|
), |
91
|
|
|
$this->get_phone_number_args( $order ), |
92
|
|
|
$this->get_shipping_args( $order ), |
93
|
|
|
$this->get_line_item_args( $order ) |
94
|
|
|
), $order ); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Get phone number args for paypal request. |
99
|
|
|
* @param WC_Order $order |
100
|
|
|
* @return array |
101
|
|
|
*/ |
102
|
|
|
protected function get_phone_number_args( $order ) { |
103
|
|
|
if ( in_array( $order->billing_country, array( 'US','CA' ) ) ) { |
104
|
|
|
$phone_number = str_replace( array( '(', '-', ' ', ')', '.' ), '', $order->billing_phone ); |
105
|
|
|
$phone_number = ltrim( $phone_number, '+1' ); |
106
|
|
|
$phone_args = array( |
107
|
|
|
'night_phone_a' => substr( $phone_number, 0, 3 ), |
108
|
|
|
'night_phone_b' => substr( $phone_number, 3, 3 ), |
109
|
|
|
'night_phone_c' => substr( $phone_number, 6, 4 ), |
110
|
|
|
'day_phone_a' => substr( $phone_number, 0, 3 ), |
111
|
|
|
'day_phone_b' => substr( $phone_number, 3, 3 ), |
112
|
|
|
'day_phone_c' => substr( $phone_number, 6, 4 ) |
113
|
|
|
); |
114
|
|
|
} else { |
115
|
|
|
$phone_args = array( |
116
|
|
|
'night_phone_b' => $order->billing_phone, |
117
|
|
|
'day_phone_b' => $order->billing_phone |
118
|
|
|
); |
119
|
|
|
} |
120
|
|
|
return $phone_args; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Get shipping args for paypal request. |
125
|
|
|
* @param WC_Order $order |
126
|
|
|
* @return array |
127
|
|
|
*/ |
128
|
|
|
protected function get_shipping_args( $order ) { |
129
|
|
|
$shipping_args = array(); |
130
|
|
|
|
131
|
|
|
if ( 'yes' == $this->gateway->get_option( 'send_shipping' ) ) { |
132
|
|
|
$shipping_args['address_override'] = $this->gateway->get_option( 'address_override' ) === 'yes' ? 1 : 0; |
133
|
|
|
$shipping_args['no_shipping'] = 0; |
134
|
|
|
|
135
|
|
|
// If we are sending shipping, send shipping address instead of billing |
136
|
|
|
$shipping_args['first_name'] = $order->shipping_first_name; |
137
|
|
|
$shipping_args['last_name'] = $order->shipping_last_name; |
138
|
|
|
$shipping_args['company'] = $order->shipping_company; |
139
|
|
|
$shipping_args['address1'] = $order->shipping_address_1; |
140
|
|
|
$shipping_args['address2'] = $order->shipping_address_2; |
141
|
|
|
$shipping_args['city'] = $order->shipping_city; |
142
|
|
|
$shipping_args['state'] = $this->get_paypal_state( $order->shipping_country, $order->shipping_state ); |
143
|
|
|
$shipping_args['country'] = $order->shipping_country; |
144
|
|
|
$shipping_args['zip'] = $order->shipping_postcode; |
145
|
|
|
} else { |
146
|
|
|
$shipping_args['no_shipping'] = 1; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
return $shipping_args; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Get line item args for paypal request. |
154
|
|
|
* @param WC_Order $order |
155
|
|
|
* @return array |
156
|
|
|
*/ |
157
|
|
|
protected function get_line_item_args( $order ) { |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* Try passing a line item per product if supported. |
161
|
|
|
*/ |
162
|
|
|
if ( ( ! wc_tax_enabled() || ! wc_prices_include_tax() ) && $this->prepare_line_items( $order ) ) { |
163
|
|
|
|
164
|
|
|
$line_item_args = array(); |
165
|
|
|
$line_item_args['tax_cart'] = $this->number_format( $order->get_total_tax(), $order ); |
166
|
|
|
|
167
|
|
|
if ( $order->get_total_discount() > 0 ) { |
168
|
|
|
$line_item_args['discount_amount_cart'] = $this->number_format( $this->round( $order->get_total_discount(), $order ), $order ); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
// Add shipping costs. Paypal ignores anything over 5 digits (999.99 is the max). |
172
|
|
|
if ( $order->get_total_shipping() > 0 && $order->get_total_shipping() < 999.99 ) { |
173
|
|
|
$line_item_args['shipping_1'] = $this->number_format( $order->get_total_shipping(), $order ); |
174
|
|
View Code Duplication |
} elseif ( $order->get_total_shipping() > 0 ) { |
|
|
|
|
175
|
|
|
$this->add_line_item( sprintf( __( 'Shipping via %s', 'woocommerce' ), $order->get_shipping_method() ), 1, $this->number_format( $order->get_total_shipping(), $order ) ); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
$line_item_args = array_merge( $line_item_args, $this->get_line_items() ); |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Send order as a single item. |
182
|
|
|
* |
183
|
|
|
* For shipping, we longer use shipping_1 because paypal ignores it if *any* shipping rules are within paypal, and paypal ignores anything over 5 digits (999.99 is the max). |
184
|
|
|
*/ |
185
|
|
|
} else { |
186
|
|
|
|
187
|
|
|
$this->delete_line_items(); |
188
|
|
|
|
189
|
|
|
$line_item_args = array(); |
190
|
|
|
$all_items_name = $this->get_order_item_names( $order ); |
191
|
|
|
$this->add_line_item( $all_items_name ? $all_items_name : __( 'Order', 'woocommerce' ), 1, $this->number_format( $order->get_total() - $this->round( $order->get_total_shipping() + $order->get_shipping_tax(), $order ), $order ), $order->get_order_number() ); |
192
|
|
|
|
193
|
|
|
// Add shipping costs. Paypal ignores anything over 5 digits (999.99 is the max). |
194
|
|
|
if ( $order->get_total_shipping() > 0 && $order->get_total_shipping() < 999.99 ) { |
195
|
|
|
$line_item_args['shipping_1'] = $this->number_format( $order->get_total_shipping() + $order->get_shipping_tax(), $order ); |
196
|
|
View Code Duplication |
} elseif ( $order->get_total_shipping() > 0 ) { |
|
|
|
|
197
|
|
|
$this->add_line_item( sprintf( __( 'Shipping via %s', 'woocommerce' ), $order->get_shipping_method() ), 1, $this->number_format( $order->get_total_shipping() + $order->get_shipping_tax(), $order ) ); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
$line_item_args = array_merge( $line_item_args, $this->get_line_items() ); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
return $line_item_args; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Get order item names as a string. |
208
|
|
|
* @param WC_Order $order |
209
|
|
|
* @return string |
210
|
|
|
*/ |
211
|
|
|
protected function get_order_item_names( $order ) { |
212
|
|
|
$item_names = array(); |
213
|
|
|
|
214
|
|
|
foreach ( $order->get_items() as $item ) { |
215
|
|
|
$item_names[] = $item['name'] . ' x ' . $item['qty']; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
return implode( ', ', $item_names ); |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
/** |
222
|
|
|
* Get order item names as a string. |
223
|
|
|
* @param WC_Order $order |
224
|
|
|
* @param array $item |
225
|
|
|
* @return string |
226
|
|
|
*/ |
227
|
|
|
protected function get_order_item_name( $order, $item ) { |
|
|
|
|
228
|
|
|
$item_name = $item['name']; |
229
|
|
|
$item_meta = new WC_Order_Item_Meta( $item ); |
230
|
|
|
|
231
|
|
|
if ( $meta = $item_meta->display( true, true ) ) { |
232
|
|
|
$item_name .= ' ( ' . $meta . ' )'; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
return $item_name; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Return all line items. |
240
|
|
|
*/ |
241
|
|
|
protected function get_line_items() { |
242
|
|
|
return $this->line_items; |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* Remove all line items. |
247
|
|
|
*/ |
248
|
|
|
protected function delete_line_items() { |
249
|
|
|
$this->line_items = array(); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Get line items to send to paypal. |
254
|
|
|
* @param WC_Order $order |
255
|
|
|
* @return bool |
256
|
|
|
*/ |
257
|
|
|
protected function prepare_line_items( $order ) { |
258
|
|
|
$this->delete_line_items(); |
259
|
|
|
$calculated_total = 0; |
260
|
|
|
|
261
|
|
|
// Products |
262
|
|
|
foreach ( $order->get_items( array( 'line_item', 'fee' ) ) as $item ) { |
263
|
|
|
if ( 'fee' === $item['type'] ) { |
264
|
|
|
$item_line_total = $this->number_format( $item['line_total'], $order ); |
265
|
|
|
$line_item = $this->add_line_item( $item['name'], 1, $item_line_total ); |
266
|
|
|
$calculated_total += $item_line_total; |
267
|
|
|
} else { |
268
|
|
|
$product = $order->get_product_from_item( $item ); |
269
|
|
|
$item_line_total = $this->number_format( $order->get_item_subtotal( $item, false ), $order ); |
270
|
|
|
$line_item = $this->add_line_item( $this->get_order_item_name( $order, $item ), $item['qty'], $item_line_total, $product->get_sku() ); |
271
|
|
|
$calculated_total += $item_line_total * $item['qty']; |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
if ( ! $line_item ) { |
275
|
|
|
return false; |
276
|
|
|
} |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
// Check for mismatched totals. |
280
|
|
|
if ( $this->number_format( $calculated_total + $order->get_total_tax() + $this->round( $order->get_total_shipping(), $order ) - $this->round( $order->get_total_discount(), $order ), $order ) != $this->number_format( $order->get_total(), $order ) ) { |
281
|
|
|
return false; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
return true; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Add PayPal Line Item. |
289
|
|
|
* @param string $item_name |
290
|
|
|
* @param int $quantity |
291
|
|
|
* @param int $amount |
292
|
|
|
* @param string $item_number |
293
|
|
|
* @return bool successfully added or not |
294
|
|
|
*/ |
295
|
|
|
protected function add_line_item( $item_name, $quantity = 1, $amount = 0, $item_number = '' ) { |
296
|
|
|
$index = ( sizeof( $this->line_items ) / 4 ) + 1; |
297
|
|
|
|
298
|
|
|
if ( $amount < 0 || $index > 9 ) { |
299
|
|
|
return false; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
$this->line_items[ 'item_name_' . $index ] = html_entity_decode( wc_trim_string( $item_name ? $item_name : __( 'Item', 'woocommerce' ), 127 ), ENT_NOQUOTES, 'UTF-8' ); |
303
|
|
|
$this->line_items[ 'quantity_' . $index ] = $quantity; |
304
|
|
|
$this->line_items[ 'amount_' . $index ] = $amount; |
305
|
|
|
$this->line_items[ 'item_number_' . $index ] = $item_number; |
306
|
|
|
|
307
|
|
|
return true; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
/** |
311
|
|
|
* Get the state to send to paypal. |
312
|
|
|
* @param string $cc |
313
|
|
|
* @param string $state |
314
|
|
|
* @return string |
315
|
|
|
*/ |
316
|
|
|
protected function get_paypal_state( $cc, $state ) { |
317
|
|
|
if ( 'US' === $cc ) { |
318
|
|
|
return $state; |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
$states = WC()->countries->get_states( $cc ); |
322
|
|
|
|
323
|
|
|
if ( isset( $states[ $state ] ) ) { |
324
|
|
|
return $states[ $state ]; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
return $state; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* Check if currency has decimals. |
332
|
|
|
* @param string $currency |
333
|
|
|
* @return bool |
334
|
|
|
*/ |
335
|
|
|
protected function currency_has_decimals( $currency ) { |
336
|
|
|
if ( in_array( $currency, array( 'HUF', 'JPY', 'TWD' ) ) ) { |
337
|
|
|
return false; |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
return true; |
341
|
|
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* Round prices. |
345
|
|
|
* @param double $price |
346
|
|
|
* @param WC_Order $order |
347
|
|
|
* @return double |
348
|
|
|
*/ |
349
|
|
|
protected function round( $price, $order ) { |
350
|
|
|
$precision = 2; |
351
|
|
|
|
352
|
|
|
if ( ! $this->currency_has_decimals( $order->get_order_currency() ) ) { |
353
|
|
|
$precision = 0; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
return round( $price, $precision ); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
/** |
360
|
|
|
* Format prices. |
361
|
|
|
* @param float|int $price |
362
|
|
|
* @param WC_Order $order |
363
|
|
|
* @return string |
364
|
|
|
*/ |
365
|
|
|
protected function number_format( $price, $order ) { |
366
|
|
|
$decimals = 2; |
367
|
|
|
|
368
|
|
|
if ( ! $this->currency_has_decimals( $order->get_order_currency() ) ) { |
369
|
|
|
$decimals = 0; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
return number_format( $price, $decimals, '.', '' ); |
373
|
|
|
} |
374
|
|
|
} |
375
|
|
|
|
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.