Source of file CaptureServiceTest.php
Size: 28,265 Bytes - Last Modified: 2021-12-24T06:34:53+00:00
/var/www/docs.ssmods.com/process/src/tests/CaptureServiceTest.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697 | <?php namespace SilverStripe\Omnipay\Tests; use SilverStripe\Dev\SapphireTest; use SilverStripe\Omnipay\GatewayInfo; use SilverStripe\Omnipay\Model\Message\NotificationSuccessful; use SilverStripe\Omnipay\Model\Message\PartiallyCapturedResponse; use SilverStripe\Omnipay\Service\CaptureService; use Omnipay\Common\Message\NotificationInterface; use SilverStripe\Omnipay\Tests\Extensions\PaymentTestServiceExtensionHooks; use SilverStripe\Omnipay\Tests\Extensions\PaymentTestPaymentExtensionHooks; use SilverStripe\Omnipay\Model\Payment; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; use SilverStripe\Omnipay\Model\Message\AuthorizedResponse; use SilverStripe\Omnipay\Model\Message\CaptureRequest; use SilverStripe\Omnipay\Model\Message\CapturedResponse; use SilverStripe\Omnipay\Model\Message\CaptureError; use SilverStripe\Omnipay\Model\Message\NotificationError; /** * Test the capture service */ class CaptureServiceTest extends BaseNotificationServiceTest { protected $gatewayMethod = 'capture'; protected $fixtureIdentifier = 'payment6'; protected $fixtureReceipt = 'authorizedPaymentReceipt'; protected $startStatus = 'Authorized'; protected $pendingStatus = 'PendingCapture'; protected $endStatus = 'Captured'; protected $successFromFixtureMessages = array( array( // response that was loaded from the fixture 'ClassName' => AuthorizedResponse::class, 'Reference' => 'authorizedPaymentReceipt' ), array( // the generated Capture request 'ClassName' => CaptureRequest::class, 'Reference' => 'authorizedPaymentReceipt' ), array( // the generated Capture response 'ClassName' => CapturedResponse::class, 'Reference' => 'authorizedPaymentReceipt' ) ); protected $successMessages = array( array( // the generated capture request 'ClassName' => CaptureRequest::class, 'Reference' => 'testThisRecipe123' ), array( // the generated capture response 'ClassName' => CapturedResponse::class, 'Reference' => 'testThisRecipe123' ) ); protected $failureMessages = array( array( // response that was loaded from the fixture 'ClassName' => AuthorizedResponse::class, 'Reference' => 'authorizedPaymentReceipt' ), array( // the generated capture request 'ClassName' => CaptureRequest::class, 'Reference' => 'authorizedPaymentReceipt' ), array( // the generated capture response 'ClassName' => CaptureError::class, 'Reference' => 'authorizedPaymentReceipt' ) ); protected $notificationFailureMessages = array( array( 'ClassName' => AuthorizedResponse::class, 'Reference' => 'authorizedPaymentReceipt' ), array( 'ClassName' => CaptureRequest::class, 'Reference' => 'authorizedPaymentReceipt' ), array( 'ClassName' => NotificationError::class, 'Reference' => 'authorizedPaymentReceipt' ) ); protected $errorMessageClass = CaptureError::class; protected $successPaymentExtensionHooks = array( 'onCaptured' ); protected $initiateServiceExtensionHooks = array( 'onBeforeCapture', 'onAfterCapture', 'onAfterSendCapture', 'updateServiceResponse' ); protected $initiateFailedServiceExtensionHooks = array( 'onBeforeCapture', 'onAfterCapture', 'updateServiceResponse' ); public function setUp() { parent::setUp(); $this->logInWithPermission('CAPTURE_PAYMENTS'); CaptureService::add_extension(PaymentTestServiceExtensionHooks::class); } public function tearDown() { parent::tearDown(); CaptureService::remove_extension(PaymentTestServiceExtensionHooks::class); } protected function getService(Payment $payment) { return CaptureService::create($payment); } public function testFullCapture() { // load an authorized payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); $stubGateway = $this->buildPaymentGatewayStub(true, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $service = $this->getService($payment); // We supply the amount, but specify the full amount here. So this should be equal to a full capture $service->initiate(array('amount' => '123.45')); // there should be NO partial payments $this->assertEquals(0, $payment->getPartialPayments()->count()); // check payment status $this->assertEquals($payment->Status, $this->endStatus, 'Payment status should be set to ' . $this->endStatus); $this->assertEquals('123.45', $payment->MoneyAmount); // check existance of messages and existence of references SapphireTest::assertListContains($this->successFromFixtureMessages, $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 testExcessCapture() { // load an authorized payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); Config::modify()->merge(GatewayInfo::class, $payment->Gateway, array( 'max_capture' => '20%', 'can_capture' => 'full' )); $stubGateway = $this->buildPaymentGatewayStub(true, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $service = $this->getService($payment); // We capture ~110% of the authorized payment $service->initiate(array('amount' => '135.80')); // there should be a new partial payment $this->assertEquals(1, $payment->getPartialPayments()->count()); // the partial payment should be the excess amount $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('Void', $partialPayment->Status); $this->assertEquals('12.35', $partialPayment->MoneyAmount); // check payment status $this->assertEquals($payment->Status, $this->endStatus, 'Payment status should be set to ' . $this->endStatus); $this->assertEquals('135.80', $payment->MoneyAmount); // check existance of messages and existence of references SapphireTest::assertListContains($this->successFromFixtureMessages, $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, array('updatePartialPayment')), $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testExcessCaptureViaNotification() { Config::modify()->merge(GatewayInfo::class, 'PaymentExpress_PxPay', array( 'max_capture' => '20%' )); // load a payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); // use notification on the gateway and only allow full captures Config::modify()->merge(GatewayInfo::class, $payment->Gateway, array( 'use_async_notification' => true, 'can_capture' => 'full' )); $stubGateway = $this->buildPaymentGatewayStub(false, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $service = $this->getService($payment); // the full 120%, must work, as excess capture isn't the same as a partial capture $service->initiate(array('amount' => '148.14')); // payment amount should still be the full amount! $this->assertEquals('123.45', $payment->MoneyAmount); // there must be a partial payment $this->assertEquals(1, $payment->getPartialPayments()->count()); // the partial payment should be pending and contain the additional funds to capture $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('PendingCapture', $partialPayment->Status); $this->assertEquals('24.69', $partialPayment->MoneyAmount); // Now a notification comes in $this->get('paymentendpoint/'. $payment->Identifier .'/notify'); // ensure payment hooks were called $this->assertEquals( $this->successPaymentExtensionHooks, PaymentTestPaymentExtensionHooks::findExtensionForID($payment->ID)->getCalledMethods() ); // ensure the correct service hooks were called $this->assertEquals( array_merge($this->initiateServiceExtensionHooks, array('updatePartialPayment', 'updateServiceResponse')), $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); // we'll have to "reload" the payment from the DB now $payment = Payment::get()->byID($payment->ID); // Status should be captured $this->assertEquals('Captured', $payment->Status); $this->assertEquals('148.14', $payment->MoneyAmount); // the partial payment should be void $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('Void', $partialPayment->Status); $this->assertEquals('24.69', $partialPayment->MoneyAmount); // check existance of messages SapphireTest::assertListContains(array( array( 'ClassName' => AuthorizedResponse::class, 'Reference' => 'authorizedPaymentReceipt' ), array( 'ClassName' => CaptureRequest::class, 'Reference' => 'authorizedPaymentReceipt' ), array( 'ClassName' => NotificationSuccessful::class, 'Reference' => 'authorizedPaymentReceipt' ), array( 'ClassName' => CapturedResponse::class, 'Reference' => 'authorizedPaymentReceipt' ) ), $payment->Messages()); // try to complete a second time $service = $this->getService($payment); $serviceResponse = $service->complete(); // the service should not respond with an error, since the payment is already captured $this->assertFalse($serviceResponse->isError()); // since the payment is already completed, we should not touch omnipay again. $this->assertNull($serviceResponse->getOmnipayResponse()); } public function testPartialCapture() { // load an authorized payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); $stubGateway = $this->buildPaymentGatewayStub(true, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $service = $this->getService($payment); // We do a partial capture $service->initiate(array('amount' => '23.45')); // there should be a new partial payment $this->assertEquals(1, $payment->getPartialPayments()->count()); $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('Captured', $partialPayment->Status); $this->assertEquals('23.45', $partialPayment->MoneyAmount); // check payment status. It should be refunded, as the remaining amount wasn't captured $this->assertEquals('Refunded', $payment->Status); // the original payment should now have less balance $this->assertEquals('100.00', $payment->MoneyAmount); // Payment can no longer be captured $this->assertFalse($payment->canCapture(null, true)); // check existance of messages and existence of references SapphireTest::assertListContains(array( array( 'ClassName' => AuthorizedResponse::class, 'Reference' => 'authorizedPaymentReceipt', ), array( 'ClassName' => CaptureRequest::class, 'Reference' => 'authorizedPaymentReceipt', ), array( 'ClassName' => PartiallyCapturedResponse::class, 'Reference' => 'authorizedPaymentReceipt', ), ), $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, array('updatePartialPayment')), $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); } public function testMultiplePartialCaptures() { // load an authorized payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); // allow multiple captures Config::modify()->merge(GatewayInfo::class, $payment->Gateway, array( 'can_capture' => 'multiple' )); $stubGateway = $this->buildPaymentGatewayStub(true, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $service = $this->getService($payment); // We do a partial capture $service->initiate(array('amount' => '23.45')); // there should be a new partial payment $this->assertEquals(1, $payment->getPartialPayments()->count()); $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('Captured', $partialPayment->Status); $this->assertEquals('23.45', $partialPayment->MoneyAmount); // check payment status. It should be Authorized, as not everything was captured $this->assertEquals('Authorized', $payment->Status); // the original payment should now have less balance $this->assertEquals('100.00', $payment->MoneyAmount); // We do another partial capture $service->initiate(array('amount' => '90.00')); // there should be a new partial payment $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('Captured', $partialPayment->Status); $this->assertEquals('90.00', $partialPayment->MoneyAmount); $this->assertEquals('Authorized', $payment->Status); $this->assertEquals('10.00', $payment->MoneyAmount); // We do another partial capture for the remaining amount. Here, there's no partial payment involved. $service->initiate(array('amount' => '10.00')); $this->assertEquals('Captured', $payment->Status); $this->assertEquals('10.00', $payment->MoneyAmount); $this->assertFalse($payment->canCapture(null, true)); } public function testPartialCaptureViaNotification() { // load a payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); // use notification on the gateway Config::modify()->merge(GatewayInfo::class, $payment->Gateway, array( 'use_async_notification' => true, 'can_capture' => 'multiple' )); $stubGateway = $this->buildPaymentGatewayStub(false, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $service = $this->getService($payment); $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->Reset(); $service->initiate(array('amount' => '100.45')); // payment amount should still be the full amount! $this->assertEquals('123.45', $payment->MoneyAmount); // there must be a partial payment $this->assertEquals(1, $payment->getPartialPayments()->count()); // the partial payment should be pending and negative $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('PendingCapture', $partialPayment->Status); $this->assertEquals('-100.45', $partialPayment->MoneyAmount); // Now a notification comes in $this->get('paymentendpoint/'. $payment->Identifier .'/notify'); // ensure payment hooks were called $this->assertEquals( $this->successPaymentExtensionHooks, PaymentTestPaymentExtensionHooks::findExtensionForID($payment->ID)->getCalledMethods() ); // ensure the correct service hooks were called $this->assertEquals( array_merge($this->initiateServiceExtensionHooks, array('updatePartialPayment', 'updateServiceResponse')), $service->getExtensionInstance(PaymentTestServiceExtensionHooks::class)->getCalledMethods() ); // we'll have to "reload" the payment from the DB now $payment = Payment::get()->byID($payment->ID); // Status should still be authorized, as we allow multiple captures $this->assertEquals('Authorized', $payment->Status); // the payment balance is reduced to 23.00 $this->assertEquals('23.00', $payment->MoneyAmount); // the partial payment should no longer be pending and positive $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('Captured', $partialPayment->Status); $this->assertEquals('100.45', $partialPayment->MoneyAmount); // multiple payments are enabled, thus capturing should still be possible $this->assertTrue($payment->canCapture(null, true)); // check existance of messages SapphireTest::assertListContains(array( array( 'ClassName' => AuthorizedResponse::class, 'Reference' => 'authorizedPaymentReceipt' ), array( 'ClassName' => CaptureRequest::class, 'Reference' => 'authorizedPaymentReceipt' ), array( 'ClassName' => NotificationSuccessful::class, 'Reference' => 'authorizedPaymentReceipt' ), array( 'ClassName' => PartiallyCapturedResponse::class, 'Reference' => 'authorizedPaymentReceipt' ) ), $payment->Messages()); // try to complete a second time $service = $this->getService($payment); $serviceResponse = $service->complete(); // the service should respond with an error, since the payment is not (fully) captured $this->assertTrue($serviceResponse->isError()); // since the payment is already completed, we should not touch omnipay again. $this->assertNull($serviceResponse->getOmnipayResponse()); } public function testMultipleInitiateCallsBeforeNotificationArrives() { // load a payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); // use notification on the gateway Config::modify()->merge(GatewayInfo::class, $payment->Gateway, array( 'use_async_notification' => true )); $stubGateway = $this->buildPaymentGatewayStub(false, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $service = $this->getService($payment); // try to initiate two captures without waiting for one to complete $service->initiate(array('amount' => '100.00')); $exception = null; try { // the second attempt must throw an exception! $service->initiate(array('amount' => '23.75')); } catch (\Exception $ex) { $exception = $ex; } $this->assertInstanceOf('SilverStripe\Omnipay\Exception\InvalidConfigurationException', $exception); // there must be a partial payment $this->assertEquals(1, $payment->getPartialPayments()->count()); // the partial payment should be pending and have the first initiated amount $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('PendingCapture', $partialPayment->Status); $this->assertEquals('-100.00', $partialPayment->MoneyAmount); // check existance of messages SapphireTest::assertListContains(array( array( 'ClassName' => AuthorizedResponse::class, 'Reference' => 'authorizedPaymentReceipt' ), array( 'ClassName' => CaptureRequest::class, 'Reference' => 'authorizedPaymentReceipt' ) ), $payment->Messages()); } /** * @expectedException \SilverStripe\Omnipay\Exception\InvalidParameterException */ public function testLargerAmount() { $stubGateway = $this->buildPaymentGatewayStub(true, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); // load a captured payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); $service = $this->getService($payment); // We supply the amount, but specify an amount that is way over what was authorized // This will throw an InvalidParameterException $service->initiate(array('amount' => '1000000.00')); } /** * @expectedException \SilverStripe\Omnipay\Exception\InvalidParameterException */ public function testInvalidAmount() { $stubGateway = $this->buildPaymentGatewayStub(true, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); // load a captured payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); $service = $this->getService($payment); // We supply the amount, but specify an amount that is not a number // This will throw an InvalidParameterException $service->initiate(array('amount' => 'test')); } /** * @expectedException \SilverStripe\Omnipay\Exception\InvalidParameterException */ public function testNegativeAmount() { $stubGateway = $this->buildPaymentGatewayStub(true, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); // load a captured payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); $service = $this->getService($payment); // We supply the amount, but specify an amount that is not a positive number // This will throw an InvalidParameterException $service->initiate(array('amount' => '-1')); } /** * @expectedException \SilverStripe\Omnipay\Exception\InvalidParameterException */ public function testPartialCaptureUnsupported() { $stubGateway = $this->buildPaymentGatewayStub(true, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); // load a captured payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); $service = $this->getService($payment); // only allow full capture, thus disabling partial refunds Config::modify()->merge(GatewayInfo::class, $payment->Gateway, array( 'can_capture' => 'full' )); // We supply a partial amount // This will throw an InvalidParameterException $service->initiate(array('amount' => '10.00')); } public function testPartialCaptureFailed() { $stubGateway = $this->buildPaymentGatewayStub(false, $this->fixtureReceipt); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); // load an authorized payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); $service = $this->getService($payment); $service->initiate(array('amount' => '100.00')); // there should be NO partial payments $this->assertEquals(0, $payment->getPartialPayments()->count()); // Payment should be unaltered $this->assertEquals('Authorized', $payment->Status); $this->assertEquals('123.45', $payment->MoneyAmount); } public function testPartialCaptureViaNotificationFailed() { // load a payment from fixture $payment = $this->objFromFixture(Payment::class, $this->fixtureIdentifier); // use notification on the gateway Config::modify()->merge(GatewayInfo::class, $payment->Gateway, array( 'use_async_notification' => true )); $stubGateway = $this->buildPaymentGatewayStub( false, $this->fixtureReceipt, NotificationInterface::STATUS_FAILED ); // register our mock gateway factory as injection Injector::inst()->registerService($this->stubGatewayFactory($stubGateway), 'Omnipay\Common\GatewayFactory'); $service = $this->getService($payment); $service->initiate(array('amount' => '53.45')); // Now a notification comes in (will fail) $this->get('paymentendpoint/'. $payment->Identifier .'/notify'); // we'll have to "reload" the payment from the DB now $payment = Payment::get()->byID($payment->ID); // Status should be reset $this->assertEquals('Authorized', $payment->Status); // the payment balance is unaltered $this->assertEquals('123.45', $payment->MoneyAmount); // the partial payment should be void $partialPayment = $payment->getPartialPayments()->first(); $this->assertEquals('Void', $partialPayment->Status); $this->assertEquals('-53.45', $partialPayment->MoneyAmount); // check existance of messages SapphireTest::assertListContains($this->notificationFailureMessages, $payment->Messages()); } } |