WC_Stripe_Subscription_Renewal_Test::tearDown()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * These tests assert various things about processing a renewal payment for a WooCommerce Subscription.
4
 *
5
 * The responses from HTTP requests are mocked using the WP filter `pre_http_request`.
6
 *
7
 * There are a few methods that need to be mocked in the class WC_Stripe_Subs_Compat, which is
8
 * why that class is mocked even though the method under test is part of that class.
9
 *
10
 * @package     WooCommerce_Stripe/Classes/WC_Stripe_Subscription_Renewal_Test
11
 */
12
13
/**
14
 * WC_Stripe_Subscription_Renewal_Test
15
 */
16
class WC_Stripe_Subscription_Renewal_Test extends WP_UnitTestCase {
17
	/**
18
	 * System under test, and a mock object with some methods mocked for testing
19
	 *
20
	 * @var PHPUnit_Framework_MockObject_MockObject
21
	 */
22
	private $wc_stripe_subs_compat;
23
24
	/**
25
	 * The statement descriptor we'll use in a test.
26
	 *
27
	 * @var string
28
	 */
29
	private $statement_descriptor;
30
31
	/**
32
	 * Sets up things all tests need.
33
	 */
34
	public function setUp() {
35
		parent::setUp();
36
37
		$this->wc_stripe_subs_compat = $this->getMockBuilder( 'WC_Stripe_Subs_Compat' )
38
			->disableOriginalConstructor()
39
			->setMethods( array( 'prepare_order_source', 'has_subscription', 'ensure_subscription_has_customer_id' ) )
40
			->getMock();
41
42
		// Mocked in order to get metadata[payment_type] = recurring in the HTTP request.
43
		$this->wc_stripe_subs_compat
44
			->expects( $this->any() )
45
			->method( 'has_subscription' )
46
			->will(
47
				$this->returnValue( true )
48
			);
49
50
		$this->statement_descriptor = 'This is a statement descriptor.';
51
		add_option(
52
			'woocommerce_stripe_settings',
53
			array(
54
				'statement_descriptor' => $this->statement_descriptor,
55
			)
56
		);
57
	}
58
59
	/**
60
	 * Tears down the stuff we set up.
61
	 */
62
	public function tearDown() {
63
		parent::tearDown();
64
65
		delete_option( 'woocommerce_stripe_settings' );
66
	}
67
68
	/**
69
	 * Overall this test works like this:
70
	 *
71
	 * 1. Several things are set up or mocked.
72
	 * 2. A function that will mock an HTTP response for the payment_intents is created.
73
	 * 3. That same function has some assertions about the things we send to the
74
	 * payments_intents endpoint.
75
	 * 4. The function under test - `process_subscription_payment` - is called.
76
	 * 5. More assertions are made.
77
	 */
78
	public function test_renewal_successful() {
79
		// Arrange: Some variables we'll use later.
80
		$renewal_order                 = WC_Helper_Order::create_order();
81
		$amount                        = 20; // WC Subs sends an amount to be used, instead of using the order amount.
82
		$stripe_amount                 = WC_Stripe_Helper::get_stripe_amount( $amount );
83
		$currency                      = strtolower( $renewal_order->get_currency() );
84
		$customer                      = 'cus_123abc';
85
		$source                        = 'src_123abc';
86
		$statement_descriptor          = WC_Stripe_Helper::clean_statement_descriptor( $this->statement_descriptor );
87
		$should_retry                  = false;
88
		$previous_error                = false;
89
		$payments_intents_api_endpoint = 'https://api.stripe.com/v1/payment_intents';
90
		$urls_used                     = array();
91
92
		$renewal_order->set_payment_method( 'stripe' );
93
94
		// Arrange: Mock prepare_order_source() so that we have a customer and source.
95
		$this->wc_stripe_subs_compat
96
			->expects( $this->any() )
97
			->method( 'prepare_order_source' )
98
			->will(
99
				$this->returnValue(
100
					(object) array(
101
						'token_id'      => false,
102
						'customer'      => $customer,
103
						'source'        => $source,
104
						'source_object' => (object) array(),
105
					)
106
				)
107
			);
108
109
		// Arrange: Add filter that will return a mocked HTTP response for the payment_intent call.
110
		// Note: There are assertions in the callback function.
111
		$pre_http_request_response_callback = function( $preempt, $request_args, $url ) use (
112
			$renewal_order,
113
			$stripe_amount,
114
			$currency,
115
			$customer,
116
			$source,
117
			$statement_descriptor,
118
			$payments_intents_api_endpoint,
119
			&$urls_used
120
		) {
121
			// Add all urls to array so we can later make assertions about which endpoints were used.
122
			array_push( $urls_used, $url );
123
124
			// Continue without mocking the request if it's not the endpoint we care about.
125
			if ( $payments_intents_api_endpoint !== $url ) {
126
				return false;
127
			}
128
129
			// Assert: the request method is POST.
130
			$this->assertArrayHasKey( 'method', $request_args );
131
			$this->assertSame( 'POST', $request_args['method'] );
132
133
			// Assert: the request has a body.
134
			$this->assertArrayHasKey( 'body', $request_args );
135
136
			// Assert: the request body contains these values.
137
			$expected_request_body_values = array(
138
				'source'               => $source,
139
				'amount'               => $stripe_amount,
140
				'currency'             => $currency,
141
				'payment_method_types' => array( 'card' ),
142
				'customer'             => $customer,
143
				'off_session'          => 'true',
144
				'confirm'              => 'true',
145
				'confirmation_method'  => 'automatic',
146
				'statement_descriptor' => $statement_descriptor,
147
			);
148 View Code Duplication
			foreach ( $expected_request_body_values as $key => $value ) {
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...
149
				$this->assertArrayHasKey( $key, $request_args['body'] );
150
				$this->assertSame( $value, $request_args['body'][ $key ] );
151
			}
152
153
			// Assert: the request body contains these keys, without checking for their value.
154
			$expected_request_body_keys = array(
155
				'description',
156
				'metadata',
157
			);
158
			foreach ( $expected_request_body_keys as $key ) {
159
				$this->assertArrayHasKey( $key, $request_args['body'] );
160
			}
161
162
			// Assert: the body metadata has these values.
163
			$order_id                 = (string) $renewal_order->get_id();
164
			$expected_metadata_values = array(
165
				'order_id'     => $order_id,
166
				'payment_type' => 'recurring',
167
			);
168
			foreach ( $expected_metadata_values as $key => $value ) {
169
				$this->assertArrayHasKey( $key, $request_args['body']['metadata'] );
170
				$this->assertSame( $value, $request_args['body']['metadata'][ $key ] );
171
			}
172
173
			// Assert: the body metadata has these keys, without checking for their value.
174
			$expected_metadata_keys = array(
175
				'customer_name',
176
				'customer_email',
177
				'site_url',
178
			);
179
			foreach ( $expected_metadata_keys as $key ) {
180
				$this->assertArrayHasKey( $key, $request_args['body']['metadata'] );
181
			}
182
183
			// Assert: the request body does not contains these keys.
184
			$expected_missing_request_body_keys = array(
185
				'capture', // No need to capture with a payment intent.
186
				'capture_method', // The default ('automatic') is what we want in this case, so we leave it off.
187
				'expand[]',
188
			);
189
			foreach ( $expected_missing_request_body_keys as $key ) {
190
				$this->assertArrayNotHasKey( $key, $request_args['body'] );
191
			}
192
193
			// Arrange: return dummy content as the response.
194
			return array(
195
				'headers'  => array(),
196
				// Too bad we aren't dynamically setting things 'cus_123abc' when using this file.
197
				'body'     => file_get_contents( 'tests/phpunit/dummy-data/subscription_renewal_response_success.json' ),
198
				'response' => array(
199
					'code'    => 200,
200
					'message' => 'OK',
201
				),
202
				'cookies'  => array(),
203
				'filename' => null,
204
			);
205
		};
206
207
		add_filter( 'pre_http_request', $pre_http_request_response_callback, 10, 3 );
208
209
		// Arrange: Make sure to check that an action we care about was called
210
		// by hooking into it.
211
		$mock_action_process_payment = new MockAction();
212
		add_action(
213
			'wc_gateway_stripe_process_payment',
214
			[ &$mock_action_process_payment, 'action' ]
215
		);
216
217
		// Act: call process_subscription_payment().
218
		// We need to use `wc_stripe_subs_compat` here because we mocked this class earlier.
219
		$result = $this->wc_stripe_subs_compat->process_subscription_payment( 20, $renewal_order, $should_retry, $previous_error );
220
221
		// Assert: nothing was returned.
222
		$this->assertEquals( $result, null );
223
224
		// Assert that we saved the payment intent to the order.
225
		$order_id   = $renewal_order->get_id();
226
		$order      = wc_get_order( $order_id );
227
		$order_data = $order->get_meta( '_stripe_intent_id'	);
228
229
		$this->assertEquals( $order_data, 'pi_123abc' );
230
231
		// Transaction ID was saved to order.
232
		$order_transaction_id = $order->get_transaction_id();
233
		$this->assertEquals( $order_transaction_id, 'ch_123abc' );
234
235
		// Assert: the order was marked as processing (this is done in process_response()).
236
		$this->assertEquals( $order->get_status(), 'processing' );
237
238
		// Assert: called payment intents.
239
		$this->assertTrue( in_array( $payments_intents_api_endpoint, $urls_used ) );
240
241
		// Assert: Our hook was called once.
242
		$this->assertEquals( 1, $mock_action_process_payment->get_call_count() );
243
244
		// Assert: Only our hook was called.
245
		$this->assertEquals( array( 'wc_gateway_stripe_process_payment' ), $mock_action_process_payment->get_tags() );
246
247
		// Clean up.
248
		remove_filter( 'pre_http_request', array( $this, 'pre_http_request_response_success' ) );
249
	}
250
251
	/**
252
	 * Overall this test works like this:
253
	 *
254
	 * 1. Several things are set up or mocked.
255
	 * 2. A function that will mock an HTTP response for the payment_intents is created.
256
	 * 3. That same function has some assertions about the things we send to the
257
	 * payments_intents endpoint.
258
	 * 4. The function under test - `process_subscription_payment` - is called.
259
	 * 5. More assertions are made.
260
	 */
261
	public function test_renewal_authorization_required() {
262
		// Arrange: Some variables we'll use later.
263
		$renewal_order                 = WC_Helper_Order::create_order();
264
		$amount                        = 20;
265
		$stripe_amount                 = WC_Stripe_Helper::get_stripe_amount( $amount );
266
		$currency                      = strtolower( $renewal_order->get_currency() );
267
		$customer                      = 'cus_123abc';
268
		$source                        = 'src_123abc';
269
		$should_retry                  = false;
270
		$previous_error                = false;
271
		$payments_intents_api_endpoint = 'https://api.stripe.com/v1/payment_intents';
272
		$urls_used                     = array();
273
274
		// Arrange: Mock prepare_order_source() so that we have a customer and source.
275
		$this->wc_stripe_subs_compat
276
			->expects( $this->any() )
277
			->method( 'prepare_order_source' )
278
			->will(
279
				$this->returnValue(
280
					(object) array(
281
						'token_id'      => false,
282
						'customer'      => $customer,
283
						'source'        => $source,
284
						'source_object' => (object) array(),
285
					)
286
				)
287
			);
288
289
		// Arrange: Add filter that will return a mocked HTTP response for the payment_intent call.
290
		$pre_http_request_response_callback = function( $preempt, $request_args, $url ) use (
291
			$renewal_order,
292
			$stripe_amount,
293
			$currency,
294
			$customer,
295
			$source,
296
			$payments_intents_api_endpoint,
297
			&$urls_used
298
		) {
299
			// Add all urls to array so we can later make assertions about which endpoints were used.
300
			array_push( $urls_used, $url );
301
302
			// Continue without mocking the request if it's not the endpoint we care about.
303
			if ( $payments_intents_api_endpoint !== $url ) {
304
				return false;
305
			}
306
307
			// Arrange: return dummy content as the response.
308
			return array(
309
				'headers'  => array(),
310
				// Too bad we aren't dynamically setting things 'cus_123abc' when using this file.
311
				'body'     => file_get_contents( 'tests/phpunit/dummy-data/subscription_renewal_response_authentication_required.json' ),
312
				'response' => array(
313
					'code'    => 402,
314
					'message' => 'Payment Required',
315
				),
316
				'cookies'  => array(),
317
				'filename' => null,
318
			);
319
		};
320
		add_filter( 'pre_http_request', $pre_http_request_response_callback, 10, 3 );
321
322
		// Arrange: Make sure to check that an action we care about was called
323
		// by hooking into it.
324
		$mock_action_process_payment = new MockAction();
325
		add_action(
326
			'wc_gateway_stripe_process_payment_authentication_required',
327
			[ &$mock_action_process_payment, 'action' ]
328
		);
329
330
		// Act: call process_subscription_payment().
331
		// We need to use `wc_stripe_subs_compat` here because we mocked this class earlier.
332
		$result = $this->wc_stripe_subs_compat->process_subscription_payment( 20, $renewal_order, $should_retry, $previous_error );
333
334
		// Assert: nothing was returned.
335
		$this->assertEquals( $result, null );
336
337
		// Assert that we saved the payment intent to the order.
338
		$order_id             = $renewal_order->get_id();
339
		$order                = wc_get_order( $order_id );
340
		$order_data           = $order->get_meta( '_stripe_intent_id' );
341
		$order_transaction_id = $order->get_transaction_id();
342
343
		// Intent was saved to order even though there was an error in the response body.
344
		$this->assertEquals( $order_data, 'pi_123abc' );
345
346
		// Transaction ID was saved to order.
347
		$this->assertEquals( $order_transaction_id, 'ch_123abc' );
348
349
		// Assert: the order was marked as failed.
350
		$this->assertEquals( $order->get_status(), 'failed' );
351
352
		// Assert: called payment intents.
353
		$this->assertTrue( in_array( $payments_intents_api_endpoint, $urls_used ) );
354
355
		// Assert: Our hook was called once.
356
		$this->assertEquals( 1, $mock_action_process_payment->get_call_count() );
357
358
		// Assert: Only our hook was called.
359
		$this->assertEquals( array( 'wc_gateway_stripe_process_payment_authentication_required' ), $mock_action_process_payment->get_tags() );
360
361
		// Clean up.
362
		remove_filter( 'pre_http_request', array( $this, 'pre_http_request_response_success' ) );
363
	}
364
}
365