Completed
Push — master ( 0c3099...1fecd7 )
by Mike
07:28
created

WC_Gateway_Paypal_Request::get_line_items()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 10 and the first side effect is on line 4.

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.

Loading history...
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 ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 ) {
0 ignored issues
show
Unused Code introduced by
The parameter $order is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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