Source of file BasePurchaseServiceTest.php
Size: 34,645 Bytes - Last Modified: 2021-12-24T06:34:53+00:00
/var/www/docs.ssmods.com/process/src/tests/BasePurchaseServiceTest.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802 | <?php namespace SilverStripe\Omnipay\Tests; use SilverStripe\Dev\SapphireTest; use SilverStripe\Omnipay\GatewayInfo; use Symfony\Component\HttpFoundation\RedirectResponse; use SilverStripe\Omnipay\Model\Payment; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Config\Config; use SilverStripe\Omnipay\Tests\Extensions\PaymentTestServiceExtensionHooks; use SilverStripe\Omnipay\Tests\Extensions\PaymentTestPaymentExtensionHooks; use Closure; /** * Abstract base-class that implements common tests for "authorize" and "purchase". * Uses variables that have to be configured to the expected values in the subclass. */ abstract class BasePurchaseServiceTest extends PaymentTest { /** @var string The expected payment complete status, "Captured" or "Authorized" */ protected $completeStatus; /** @var string The expected payment pending status, "PendingPurchase" or "PendingAuthorization" */ protected $pendingStatus; /** @var string The omnipay method to call, "purchase" or "authorize" */ protected $omnipayMethod; /** @var string The omnipay method to call to complete, "completePurchase" or "completeAuthorize" */ protected $omnipayCompleteMethod; /** @var array expected messages with a successful onsite payment */ protected $onsiteSuccessMessages; /** @var array expected messages with a failed onsite payment */ protected $onsiteFailMessages; /** @var array expected messages with a payment that fails because of bad configuration */ protected $failMessages; /** @var array expected messages for a successful offsite payment */ protected $offsiteSuccessMessages; /** @var array expected messages for a failed offsite payment */ protected $offsiteFailMessages; /** @var string name of the failure message class */ protected $failureMessageClass; /** @var array expected payment hooks that will be called with a successful payment */ protected $successPaymentExtensionHooks; /** @var array expected payment hooks that will be called with a notification payment */ protected $notifyPaymentExtensionHooks; /** @var array expected service hooks that will be called when initiate method finishes */ protected $initiateServiceExtensionHooks; /** @var array expected service hooks that will be called when initiate method was interrupted by gateway error */ protected $initiateFailedServiceExtensionHooks; /** @var array expected service hooks that will be called when complete method finishes */ protected $completeServiceExtensionHooks; /** @var string The ID of the payment (@see payment.yml) */ protected $paymentId; /** * Create a payment service instance * * @param Payment $payment * @return \SilverStripe\Omnipay\Service\PaymentService */ abstract protected function getService(Payment $payment); public function testDummyOnSitePayment() { $payment = $this->payment; $service = $this->getService($payment); $response = $service->initiate(array( 'firstName' => 'joe', 'lastName' => 'bloggs', 'number' => '4242424242424242', //this creditcard will succeed 'expiryMonth' => '5', 'expiryYear' => date("Y", strtotime("+1 year")) )); $this->assertEquals($this->completeStatus, $payment->Status, "is the status updated"); $this->assertEquals(1222, $payment->Amount); $this->assertEquals("GBP", $payment->Currency); $this->assertEquals("Dummy", $payment->Gateway); $this->assertTrue($response->getOmnipayResponse()->isSuccessful()); $this->assertFalse($response->isRedirect()); $this->assertFalse($response->isError()); $this->assertFalse($response->isCancelled()); $this->assertFalse($response->isAwaitingNotification()); $this->assertFalse($response->isNotification()); //values cannot be changed after successful payment $payment->Amount = 2; $payment->Currency = "NZD"; $payment->Gateway = "XYZ"; $payment->write(); $this->assertEquals(1222, $payment->Amount); $this->assertEquals("GBP", $payment->Currency); $this->assertEquals("Dummy", $payment->Gateway); //check messaging SapphireTest::assertListContains($this->onsiteSuccessMessages, $payment->Messages()); // ensure payment hooks were called $this->assertEquals( $this->successPaymentExtensionHooks, $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called $this->assertEquals( $this->initiateServiceExtensionHooks, $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testFailedDummyOnSitePayment() { $payment = $this->payment; $service = $this->getService($payment); $response = $service->initiate(array( 'firstName' => 'joe', 'lastName' => 'bloggs', 'number' => '4111111111111111', //this creditcard will decline 'expiryMonth' => '5', 'expiryYear' => date("Y", strtotime("+1 year")) )); $this->assertEquals("Created", $payment->Status, "is the status has not been updated"); $this->assertEquals(1222, $payment->Amount); $this->assertEquals("GBP", $payment->Currency); $this->assertFalse($response->getOmnipayResponse()->isSuccessful()); $this->assertTrue($response->isError()); $this->assertFalse($response->isRedirect()); //check messaging SapphireTest::assertListContains($this->onsiteFailMessages, $payment->Messages()); // no extension hook will be called on payment $this->assertEquals( [], $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called $this->assertEquals( $this->initiateServiceExtensionHooks, $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testOnSitePayment() { $payment = $this->payment->setGateway('PaymentExpress_PxPost'); $service = $this->getService($payment); $this->setMockHttpResponse('paymentexpress/tests/Mock/PxPostPurchaseSuccess.txt');//add success mock response from file $response = $service->initiate(array( 'firstName' => 'joe', 'lastName' => 'bloggs', 'number' => '4242424242424242', //this creditcard will succeed 'expiryMonth' => '5', 'expiryYear' => date("Y", strtotime("+1 year")) )); $this->assertTrue($response->getOmnipayResponse()->isSuccessful()); $this->assertFalse($response->isRedirect()); $this->assertFalse($response->isError()); $this->assertSame($this->completeStatus, $payment->Status); //check messaging SapphireTest::assertListContains($this->onsiteSuccessMessages, $payment->Messages()); // ensure payment hooks were called $this->assertEquals( $this->successPaymentExtensionHooks, $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called $this->assertEquals( $this->initiateServiceExtensionHooks, $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testInvalidOnsitePayment() { $payment = $this->payment->setGateway("PaymentExpress_PxPost"); $service = $this->getService($payment); //pass no card details nothing $response = $service->initiate([]); //check messaging $this->assertFalse($response->isRedirect()); $this->assertTrue($response->isError()); SapphireTest::assertListContains($this->failMessages, $payment->Messages()); $this->assertEquals( [], $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called $this->assertEquals( $this->initiateFailedServiceExtensionHooks, $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testFailedOnSitePayment() { $payment = $this->payment->setGateway('PaymentExpress_PxPost'); $service = $this->getService($payment); $this->setMockHttpResponse('paymentexpress/tests/Mock/PxPostPurchaseFailure.txt');//add success mock response from file $response = $service->initiate(array( 'number' => '4111111111111111', //this creditcard will decline 'expiryMonth' => '5', 'expiryYear' => date("Y", strtotime("+1 year")) )); $this->assertFalse($response->getOmnipayResponse()->isSuccessful()); // capturing/authorization wasn't successful $this->assertFalse($response->isRedirect()); $this->assertTrue($response->isError()); $this->assertSame("Created", $payment->Status); //check messaging SapphireTest::assertListContains($this->onsiteFailMessages, $payment->Messages()); $this->assertEquals( [], $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called $this->assertEquals( $this->initiateServiceExtensionHooks, $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testOffSitePayment() { $payment = $this->payment->setGateway('PaymentExpress_PxPay'); $service = $this->getService($payment); $this->setMockHttpResponse('paymentexpress/tests/Mock/PxPayPurchaseSuccess.txt');//add success mock response from file $response = $service->initiate(); $this->assertFalse($response->getOmnipayResponse()->isSuccessful()); // capturing/authorization wasn't successful $this->assertTrue($response->isRedirect()); $this->assertFalse($response->isError()); // this should not be considered to be an error $this->assertSame( 'https://sec.windcave.com/pxpay/pxpay.aspx?userid=Developer&request=v5H7JrBTzH-4Whs__1iQnz4RGSb9qxRKNR4kIuDP8kIkQzIDiIob9GTIjw_9q_AdRiR47ViWGVx40uRMu52yz2mijT39YtGeO7cZWrL5rfnx0Mc4DltIHRnIUxy1EO1srkNpxaU8fT8_1xMMRmLa-8Fd9bT8Oq0BaWMxMquYa1hDNwvoGs1SJQOAJvyyKACvvwsbMCC2qJVyN0rlvwUoMtx6gGhvmk7ucEsPc_Cyr5kNl3qURnrLKxINnS0trdpU4kXPKOlmT6VacjzT1zuj_DnrsWAPFSFq-hGsow6GpKKciQ0V0aFbAqECN8rl_c-aZWFFy0gkfjnUM4qp6foS0KMopJlPzGAgMjV6qZ0WfleOT64c3E-FRLMP5V_-mILs8a', $response->getTargetUrl() ); // Status should be set to pending $this->assertSame($this->pendingStatus, $payment->Status); //... user would normally be redirected to external gateway at this point ... //mock complete Payment response $this->setMockHttpResponse('paymentexpress/tests/Mock/PxPayCompletePurchaseSuccess.txt'); //mock the 'result' get variable into the current request $this->getHttpRequest()->query->replace(array('result' => 'abc123')); $response = $service->complete(); $this->assertTrue($response->getOmnipayResponse()->isSuccessful()); $this->assertSame($this->completeStatus, $payment->Status); $this->assertFalse($response->isError()); // payment should get the transaction reference from Omnipay assigned $reference = $response->getOmnipayResponse()->getTransactionReference(); $this->assertNotNull($reference); $this->assertEquals($payment->TransactionReference, $reference); //check messaging SapphireTest::assertListContains($this->offsiteSuccessMessages, $payment->Messages()); // ensure payment hooks were called $this->assertEquals( $this->successPaymentExtensionHooks, $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called $this->assertEquals( array_merge($this->initiateServiceExtensionHooks, $this->completeServiceExtensionHooks), $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testFailedOffSitePayment() { $payment = $this->payment->setGateway('PaymentExpress_PxPay'); $service = $this->getService($payment); $this->setMockHttpResponse('paymentexpress/tests/Mock/PxPayPurchaseFailure.txt');//add success mock response from file $response = $service->initiate(); $this->assertFalse($response->getOmnipayResponse()->isSuccessful()); // capturing/authorization wasn't successful $this->assertFalse($response->isRedirect()); //redirect won't occur, because of failure $this->assertTrue($response->isError()); $this->assertSame("Created", $payment->Status); //check messaging. // We use the onsite fail messages here, since the payment fails *before* we redirect to the offsite gateway. // Therefore this should generate the same messages as an onsite-payment failure. SapphireTest::assertListContains($this->onsiteFailMessages, $payment->Messages()); $this->assertEquals( [], $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called (only the initiate phase will complete) $this->assertEquals( $this->initiateServiceExtensionHooks, $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testFailedOffSiteCompletePayment() { $this->setMockHttpResponse( 'paymentexpress/tests/Mock/PxPayCompletePurchaseFailure.txt' ); //mock the 'result' get variable into the current request $this->getHttpRequest()->query->replace(array('result' => 'abc123')); //mimic a redirect or request from offsite gateway $response = $this->get("paymentendpoint/$this->paymentId/complete"); //redirect works $this->assertStringEndsWith( '/shop/incomplete', $response->getHeader('Location') ); $payment = Payment::get() ->filter('Identifier', $this->paymentId) ->first(); SapphireTest::assertListContains($this->offsiteFailMessages, $payment->Messages()); $this->assertEquals( [], $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); } /** * @expectedException \Omnipay\Common\Exception\RuntimeException */ public function testNonExistantGateway() { //exception when trying to run functions that require a gateway $payment = $this->payment; $service = $this->getService( $payment->init("FantasyGateway", 100, "NZD")->setSuccessUrl("complete") ); // Will throw an exception since the gateway doesn't exist $service->initiate(); } /** * @expectedException \SilverStripe\Omnipay\Exception\InvalidStateException */ public function testPaymentInvalidStatus() { $payment = $this->payment; $payment->Status = 'Void'; $service = $this->getService($payment); $service->initiate(); } /** * @expectedException \SilverStripe\Omnipay\Exception\InvalidStateException */ public function testCompletePaymentInvalidStatus() { $payment = $this->payment; $payment->Status = 'Void'; $service = $this->getService($payment); $service->complete(); } /** * @expectedException \SilverStripe\Omnipay\Exception\InvalidConfigurationException */ public function testGatewayDoesntSupportMethod() { // Build the dummy gateway $stubGateway = $this->getMockBuilder('Omnipay\Common\AbstractGateway') ->setMethods(array('getName')) ->getMock(); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $this->payment->Status = 'Created'; $service = $this->getService($this->payment); // this should throw an exception, because the gateway doesn't support the payment method $service->initiate(); } /** * @expectedException \SilverStripe\Omnipay\Exception\InvalidConfigurationException */ public function testGatewayDoesntSupportCompleteMethod() { // Build the dummy gateway $stubGateway = $this->getMockBuilder('Omnipay\Common\AbstractGateway') ->setMethods(array('getName')) ->getMock(); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $this->payment->Status = $this->pendingStatus; $service = $this->getService($this->payment); // this should throw an exception, because the gateway doesn't support the complete method $service->complete(); } public function testGatewayCompleteMethodFailure() { // build a stub gateway with the given endpoint $stubGateway = $this->buildPaymentGatewayStub('https://gateway.tld/endpoint', function () { return true; }, true); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $this->payment->Status = $this->pendingStatus; $service = $this->getService($this->payment); // this should return an error response $serviceResponse = $service->complete(); $this->assertTrue($serviceResponse->isError()); $this->assertNull($serviceResponse->getOmnipayResponse()); SapphireTest::assertListContains(array( array( 'ClassName' => $this->failureMessageClass, 'Message' => 'Mock Exception' ) ), $this->payment->Messages()); $this->assertEquals( [], $this->payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called $this->assertEquals( $this->completeServiceExtensionHooks, $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testTokenGateway() { Config::modify()->merge(GatewayInfo::class, 'PaymentExpress_PxPost', array( 'token_key' => 'token' )); $stubGateway = $this->getMockBuilder('Omnipay\Common\AbstractGateway') ->setMethods(array($this->omnipayMethod, 'getName')) ->getMock(); $stubGateway->expects($this->once()) ->method($this->omnipayMethod) ->with( $this->logicalAnd( $this->arrayHasKey('token'), $this->contains('ABC123'), $this->logicalNot($this->arrayHasKey('card')) ) ) ->will( $this->returnValue($this->stubRequest()) ); $payment = $this->payment->setGateway('PaymentExpress_PxPost'); $service = $this->getService($payment); $service->setGatewayFactory($this->stubGatewayFactory($stubGateway)); $service->initiate(array('token' => 'ABC123')); } public function testTokenGatewayWithAlternateKey() { Config::modify()->merge(GatewayInfo::class, 'PaymentExpress_PxPost', array( 'token_key' => 'my_token' )); $stubGateway = $this->getMockBuilder('Omnipay\Common\AbstractGateway') ->setMethods(array($this->omnipayMethod, 'getName')) ->getMock(); $stubGateway->expects($this->once()) ->method($this->omnipayMethod) ->with( $this->logicalAnd( $this->arrayHasKey('token'), // my_token should get normalized to this $this->contains('ABC123'), $this->logicalNot($this->arrayHasKey('card')) ) ) ->will( $this->returnValue($this->stubRequest()) ); $payment = $this->payment->setGateway('PaymentExpress_PxPost'); $service = $this->getService($payment); $service->setGatewayFactory($this->stubGatewayFactory($stubGateway)); $service->initiate(array('my_token' => 'ABC123')); } public function testAsyncPaymentConfirmation() { Config::modify()->merge(GatewayInfo::class, 'PaymentExpress_PxPay', array( 'use_async_notification' => true )); // build a stub gateway with the given endpoint $isNotification = false; $stubGateway = $this->buildPaymentGatewayStub('https://gateway.tld/endpoint', function () use (&$isNotification) { return $isNotification; }); $payment = $this->payment->setGateway('PaymentExpress_PxPay'); $payment->setFailureUrl('my/cancel/url')->setSuccessUrl('my/return/url'); $service = $this->getService($payment); $service->setGatewayFactory($this->stubGatewayFactory($stubGateway)); $serviceResponse = $service->initiate(); // we should get a redirect $this->assertTrue($serviceResponse->isRedirect()); // that redirect should point to the endpoint returned by omnipay $this->assertEquals($serviceResponse->getTargetUrl(), 'https://gateway.tld/endpoint'); // Payment should be pending $this->assertEquals($payment->Status, $this->pendingStatus); $serviceResponse = $service->complete([], $isNotification); // since the confirmation will come in asynchronously, the gateway doesn't report success when coming back $this->assertFalse($serviceResponse->getOmnipayResponse()->isSuccessful(), 'Gateway will not return success'); // Our application considers that fact and doesn't mark the service call as an error! $this->assertFalse($serviceResponse->isError()); // We should get redirected to the success page now $this->assertEquals($serviceResponse->getTargetUrl(), 'my/return/url'); // Payment status should still be pending $this->assertEquals($payment->Status, $this->pendingStatus); // simulate an incoming notification $isNotification = true; $serviceResponse = $service->complete([], $isNotification); // the response from the gateway should now be successful $this->assertTrue($serviceResponse->getOmnipayResponse()->isSuccessful(), 'Response should be successful'); // Should not be an error $this->assertFalse($serviceResponse->isError()); // We should get an HTTP response with "OK" $httpResponse = $serviceResponse->redirectOrRespond(); $this->assertEquals($httpResponse->getBody(), 'OK'); $this->assertEquals($httpResponse->getStatusCode(), 200); // Payment status should be authorized or captured now (completed) $this->assertEquals($payment->Status, $this->completeStatus); // first the notification hook should be called, followed by the success hook $this->assertEquals( array_merge($this->notifyPaymentExtensionHooks, $this->successPaymentExtensionHooks), $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called // complete will be called twice, once from returning from the offsite form and once via the notification $this->assertEquals( array_merge( $this->initiateServiceExtensionHooks, $this->completeServiceExtensionHooks, $this->completeServiceExtensionHooks ), $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } // Test an async response that comes in before the user returns from the offsite form public function testAsyncPaymentConfirmationIncomingFirst() { Config::modify()->merge(GatewayInfo::class, 'PaymentExpress_PxPay', array( 'use_async_notification' => true )); // build a stub gateway with the given endpoint $isNotification = true; $stubGateway = $this->buildPaymentGatewayStub('https://gateway.tld/endpoint', function () use (&$isNotification) { return $isNotification; }); $payment = $this->payment->setGateway('PaymentExpress_PxPay'); $payment->setFailureUrl('my/cancel/url')->setSuccessUrl('my/return/url'); $service = $this->getService($payment); $service->setGatewayFactory($this->stubGatewayFactory($stubGateway)); $serviceResponse = $service->initiate(); // we should get a redirect $this->assertTrue($serviceResponse->isRedirect()); // Payment should be pending $this->assertEquals($payment->Status, $this->pendingStatus); // Notification comes in first! $isNotification = true; $serviceResponse = $service->complete([], $isNotification); // since we're getting the async notification now, payment should be successful $this->assertTrue($serviceResponse->getOmnipayResponse()->isSuccessful(), 'Response should be successful'); // Should not be an error $this->assertFalse($serviceResponse->isError()); // We should get an HTTP response with "OK" $httpResponse = $serviceResponse->redirectOrRespond(); $this->assertEquals($httpResponse->getBody(), 'OK'); $this->assertEquals($httpResponse->getStatusCode(), 200); // Payment status should be captured or authorized (completed) $this->assertEquals($payment->Status, $this->completeStatus); // Now the user comes back from the offsite payment form $isNotification = false; $serviceResponse = $service->complete([], $isNotification); // We won't get an error, our payment is already complete $this->assertFalse($serviceResponse->isError()); // There's no omnipay response since we no longer need to bother with omnipay at this point $this->assertNull($serviceResponse->getOmnipayResponse(), 'No omnipay response, payment already completed'); // We should get redirected to the success page now $this->assertEquals($serviceResponse->getTargetUrl(), 'my/return/url'); // Payment status should still be captured or authorized $this->assertEquals($payment->Status, $this->completeStatus); // only success hook will be called! $this->assertEquals( $this->successPaymentExtensionHooks, $payment->getExtensionInstance(PaymentTestPaymentExtensionHooks::class)->getCalledMethods() ); // ensure the correct service hooks were called // complete will be called twice, but since the payment is already complete at that point, // only a service response will be generated $this->assertEquals( array_merge( $this->initiateServiceExtensionHooks, $this->completeServiceExtensionHooks, array('updateServiceResponse') ), $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } // Test an async response that comes in before the user returns from the offsite form. // Test via PaymentGatewayController public function testPaymentGatewayControllerConfirmationIncomingFirst() { Config::modify()->merge(GatewayInfo::class, 'PaymentExpress_PxPay', array( 'use_async_notification' => true )); // build a stub gateway with the given endpoint $isNotification = true; $stubGateway = $this->buildPaymentGatewayStub('https://gateway.tld/endpoint', function () use (&$isNotification) { return $isNotification; }); $payment = $this->payment->setGateway('PaymentExpress_PxPay'); $payment->setFailureUrl('my/cancel/url')->setSuccessUrl('my/return/url'); $service = $this->getService($payment); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $serviceResponse = $service->initiate(); // we should get a redirect $this->assertTrue($serviceResponse->isRedirect()); // Payment should be pending $this->assertEquals($payment->Status, $this->pendingStatus); // Notification comes in first! $httpResponse = $this->get('paymentendpoint/'. $payment->Identifier .'/notify'); $this->assertEquals($httpResponse->getBody(), 'OK'); $this->assertEquals($httpResponse->getStatusCode(), 200); // reload payment from DB $payment = Payment::get()->byID($payment->ID); // Payment status should be captured or authorized (completed) $this->assertEquals($payment->Status, $this->completeStatus); // Now the user comes back from the offsite payment form $httpResponse = $this->get('paymentendpoint/'. $payment->Identifier .'/complete'); // we should be redirected to the success page $this->assertStringEndsWith('/my/return/url', $httpResponse->getHeader('Location')); $this->assertEquals($httpResponse->getStatusCode(), 302); // reload payment from DB $payment = Payment::get()->byID($payment->ID); // Payment status should still be captured or authorized $this->assertEquals($payment->Status, $this->completeStatus); } protected function buildPaymentGatewayStub($endpoint, Closure $successFunc, $sendMustFail = false) { //-------------------------------------------------------------------------------------------------------------- // Payment request and response $mockPaymentResponse = $this->getMockBuilder('Omnipay\PaymentExpress\Message\Response') ->disableOriginalConstructor()->getMock(); $mockPaymentResponse->expects($this->any()) ->method('isRedirect')->will($this->returnValue(true)); $mockPaymentResponse->expects($this->any()) ->method('getRedirectResponse') ->will($this->returnValue(RedirectResponse::create($endpoint))); $mockPaymentRequest = $this->getMockBuilder('Omnipay\PaymentExpress\Message\PxPayPurchaseRequest') ->disableOriginalConstructor()->getMock(); if ($sendMustFail) { $mockPaymentRequest->expects($this->any())->method('send')->will($this->throwException( new \Omnipay\Common\Exception\RuntimeException('Mock Exception') )); } else { $mockPaymentRequest->expects($this->any())->method('send')->will($this->returnValue($mockPaymentResponse)); } //-------------------------------------------------------------------------------------------------------------- // Complete Payment request and response $mockCompletePaymentResponse = $this->getMockBuilder('Omnipay\PaymentExpress\Message\Response') ->disableOriginalConstructor()->getMock(); // not successful, since we're waiting for async callback from the gateway $mockCompletePaymentResponse->expects($this->any()) ->method('isSuccessful')->will($this->returnCallback($successFunc)); $mockCompletePaymentRequest = $this->getMockBuilder('Omnipay\PaymentExpress\Message\PxPayCompleteAuthorizeRequest') ->disableOriginalConstructor()->getMock(); if ($sendMustFail) { $mockCompletePaymentRequest->expects($this->any())->method('send')->will($this->throwException( new \Omnipay\Common\Exception\RuntimeException('Mock Exception') )); } else { $mockCompletePaymentRequest->expects($this->any()) ->method('send')->will($this->returnValue($mockCompletePaymentResponse)); } //-------------------------------------------------------------------------------------------------------------- // Build the gateway $stubGateway = $this->getMockBuilder('Omnipay\Common\AbstractGateway') ->setMethods(array($this->omnipayMethod, $this->omnipayCompleteMethod, 'getName')) ->getMock(); $stubGateway->expects($sendMustFail ? $this->any() : $this->once()) ->method($this->omnipayMethod) ->will($this->returnValue($mockPaymentRequest)); $stubGateway->expects($this->any()) ->method($this->omnipayCompleteMethod) ->will($this->returnValue($mockCompletePaymentRequest)); return $stubGateway; } /** * @return PHPUnit_Framework_MockObject_MockObject|Omnipay\Common\Message\AbstractRequest */ protected function stubRequest() { $request = $this->getMockBuilder('Omnipay\Common\Message\AbstractRequest') ->disableOriginalConstructor() ->getMock(); $response = $this->getMockBuilder('Omnipay\Common\Message\AbstractResponse') ->disableOriginalConstructor() ->getMock(); $response->expects($this->any())->method('isSuccessful')->will($this->returnValue(true)); $request->expects($this->any())->method('send')->will($this->returnValue($response)); return $request; } } |