diff -u b/modules/payment_example/src/Controller/DummyRedirectController.php b/modules/payment_example/src/Controller/DummyRedirectController.php --- b/modules/payment_example/src/Controller/DummyRedirectController.php +++ b/modules/payment_example/src/Controller/DummyRedirectController.php @@ -47,9 +47,12 @@ $cancel = $this->currentRequest->request->get('cancel'); $return = $this->currentRequest->request->get('return'); $total = $this->currentRequest->request->get('total'); - + $query = [ + 'remote_payment_method_id' => 'pm-32768342', + 'cardexpdate' => '1228', + ]; if ($total > 20) { - return new TrustedRedirectResponse($return); + return new TrustedRedirectResponse($return . '?' . $this->buildQuery($query)); } return new TrustedRedirectResponse($cancel); @@ -61,8 +64,8 @@ public function postPaymentMethod() { $month = rand(1,12); if ($month % 2) { - $month = str_pad($month, 2, '0', STR_PAD_LEFT); $return = $this->currentRequest->request->get('return'); + $month = str_pad($month, 2, '0', STR_PAD_LEFT); $query = [ 'remote_payment_method_id' => 'pm-32768342', 'cardexpdate' => $month . '28', @@ -84,9 +87,13 @@ $cancel = $this->currentRequest->query->get('cancel'); $return = $this->currentRequest->query->get('return'); $total = $this->currentRequest->query->get('total'); + $query = [ + 'remote_payment_method_id' => 'pm-32768342', + 'cardexpdate' => '1228', + ]; if ($total > 20) { - return new TrustedRedirectResponse($return); + return new TrustedRedirectResponse($return . '?' . $this->buildQuery($query)); } return new TrustedRedirectResponse($cancel); only in patch2: unchanged: --- a/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php +++ b/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php @@ -156,6 +156,18 @@ class PaymentCheckoutTest extends CommerceWebDriverTestBase { ]); $payment_gateway->save(); + /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */ + $payment_gateway = PaymentGateway::create([ + 'id' => 'stored_offsite', + 'label' => 'Stored off-site', + 'plugin' => 'example_stored_offsite_redirect', + 'configuration' => [ + 'redirect_method' => 'post', + 'payment_method_types' => ['credit_card'], + ], + ]); + $payment_gateway->save(); + /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */ $payment_gateway = PaymentGateway::create([ 'id' => 'manual', @@ -544,6 +556,9 @@ class PaymentCheckoutTest extends CommerceWebDriverTestBase { $manual_gateway = PaymentGateway::load('manual'); $manual_gateway->setStatus(FALSE); $manual_gateway->save(); + $manual_gateway = PaymentGateway::load('stored_offsite'); + $manual_gateway->setStatus(FALSE); + $manual_gateway->save(); $payment_gateway = PaymentGateway::load('offsite'); $payment_gateway->setPluginConfiguration([ @@ -673,6 +688,164 @@ class PaymentCheckoutTest extends CommerceWebDriverTestBase { $this->assertCount(1, $payments); } + /** + * Tests checkout with a stored off-site gateway (POST redirect method). + */ + public function testCheckoutWithStoredOffsiteRedirectPost() { + $this->storedOffsiteCheckoutAssert(1, 1, NULL, TRUE); + } + + /** + * Tests checkout with a stored off-site gateway (POST redirect method, + * manual). + * + * In this scenario the customer must click the submit button on the payment + * page in order to proceed to the gateway. + */ + public function testCheckoutWithStoredOffsiteRedirectPostManual() { + $payment_gateway = PaymentGateway::load('stored_offsite'); + $payment_gateway->setPluginConfiguration([ + 'redirect_method' => 'post_manual', + 'payment_method_types' => ['credit_card'], + ]); + $payment_gateway->save(); + + $this->storedOffsiteCheckoutAssert(1, 1, NULL, TRUE, 'post_manual'); + } + + /** + * Tests checkout with a stored off-site gateway (GET redirect method). + */ + public function testCheckoutWithStoredOffsiteRedirectGet() { + // Checkout must work when the off-site gateway is alone, and the + // radio button hidden. + $onsite_gateway = PaymentGateway::load('onsite'); + $onsite_gateway->setStatus(FALSE); + $onsite_gateway->save(); + $manual_gateway = PaymentGateway::load('manual'); + $manual_gateway->setStatus(FALSE); + $manual_gateway->save(); + $manual_gateway = PaymentGateway::load('offsite'); + $manual_gateway->setStatus(FALSE); + $manual_gateway->save(); + + $payment_gateway = PaymentGateway::load('stored_offsite'); + $payment_gateway->setPluginConfiguration([ + 'redirect_method' => 'get', + 'payment_method_types' => ['credit_card'], + ]); + $payment_gateway->save(); + + $this->storedOffsiteCheckoutAssert(1, 1); + $this->storedOffsiteCheckoutAssert(2, 2, 1); + } + + /** + * Assertions for stored offsite gateway checkouts. + * + * @param int $order_id + * The expected order id. + * @param int $payment_id + * The expected payment id. + * @param int $payment_method_id + * The payment method id to select on checkout. Leave it on NULL to create + * a new payment method. + * @param boolean $multiple_gateways + * If we expect only one gateway to show on checkout (ie.e. no radios). + * @param string $redirect_mehtod + * Set it to 'post_manual' if the payment step needs manual interaction. + */ + protected function storedOffsiteCheckoutAssert($order_id, $payment_id, $payment_method_id = NULL, $multiple_gateways = FALSE, $redirect_mehtod = 'post') { + $this->drupalGet($this->product->toUrl()->toString()); + $this->submitForm([], 'Add to cart'); + $this->drupalGet('checkout/' . $order_id); + if (!$payment_method_id) { + if ($multiple_gateways) { + $radio_button = $this->getSession()->getPage()->findField('Stored offsite'); + $radio_button->click(); + $this->assertSession()->assertWaitOnAjaxRequest(); + } + $this->assertRenderedAddress($this->defaultAddress, 'payment_information[billing_information]'); + $this->submitForm([], 'Continue to review'); + } + else { + $this->assertText('Visa ending in 1111'); + $this->submitForm([ + 'payment_information[payment_method]' => $payment_method_id, + ], 'Continue to review'); + } + + $this->assertSession()->pageTextContains('Payment information'); + $this->assertSession()->pageTextContains('Example'); + $this->assertSession()->pageTextContains('Bryan Centarro'); + $this->assertSession()->pageTextContains('9 Drupal Ave'); + $this->submitForm([], 'Pay and complete purchase'); + if ($redirect_mehtod == 'post_manual') { + $order = Order::load($order_id); + $this->assertSession()->addressEquals('checkout/' . $order_id . '/payment'); + $this->assertTrue($order->isLocked()); + $this->assertEquals('stored_offsite', $order->get('payment_gateway')->target_id); + $this->submitForm([], 'Proceed to Example'); + } + $this->assertSession()->pageTextContains('Your order number is ' . $order_id . '. You can view your order on your account page when logged in.'); + + \Drupal::entityTypeManager()->getStorage('commerce_order')->resetCache(['1']); + $order = Order::load(1); + $this->assertEquals('stored_offsite', $order->get('payment_gateway')->target_id); + $this->assertFalse($order->isLocked()); + // Verify that a payment was created. + $payment = Payment::load($payment_id); + $this->assertNotNull($payment); + $this->assertEquals($payment->getAmount(), $order->getTotalPrice()); + // Verify that a reusable payment method was created. + $payment_method = $payment->getPaymentMethod(); + $this->assertEquals(TRUE, $payment_method->isReusable()); + $this->assertEquals('stored_offsite', $payment_method->getPaymentGatewayId()); + $this->assertEquals(1, $payment_method->id()); + } + + /** + * Tests checkout with an off-site gateway (GET redirect method) that fails. + * + * The off-site form throws an exception, simulating an API fail. + */ + public function testFailedCheckoutWithStoredOffsiteRedirectGet() { + $payment_gateway = PaymentGateway::load('offsite'); + $payment_gateway->setPluginConfiguration([ + 'redirect_method' => 'get', + 'payment_method_types' => ['credit_card'], + ]); + $payment_gateway->save(); + + $this->drupalGet($this->product->toUrl()->toString()); + $this->submitForm([], 'Add to cart'); + $this->drupalGet('checkout/1'); + $radio_button = $this->getSession()->getPage()->findField('Example'); + $radio_button->click(); + $this->assertSession()->assertWaitOnAjaxRequest(); + $this->assertRenderedAddress($this->defaultAddress, 'payment_information[billing_information]'); + $this->getSession()->getPage()->pressButton('billing_edit'); + $this->assertSession()->assertWaitOnAjaxRequest(); + + $this->submitForm([ + 'payment_information[billing_information][address][0][address][family_name]' => 'FAIL', + ], 'Continue to review'); + $this->assertSession()->pageTextContains('Payment information'); + $this->assertSession()->pageTextContains('Example'); + $this->assertSession()->pageTextContains('Bryan FAIL'); + $this->assertSession()->pageTextContains('9 Drupal Ave'); + $this->submitForm([], 'Pay and complete purchase'); + $this->assertSession()->pageTextNotContains('Your order number is 1. You can view your order on your account page when logged in.'); + $this->assertSession()->pageTextContains('We encountered an unexpected error processing your payment. Please try again later.'); + $this->assertSession()->addressEquals('checkout/1/order_information'); + + $order = Order::load(1); + $this->assertFalse($order->isLocked()); + // Verify a payment was not created. + $payment = Payment::load(1); + $this->assertNull($payment); + } + /** * Tests checkout with a manual gateway. */