File: D:/HostingSpaces/SBogers95/rentman.io/app/Komma/Shop/Tests/Unit/PSPTest.php
<?php
namespace App\Komma\Shop\Tests\Unit;
use App\Komma\Globalization\RegionInfo;
use App\Komma\Shop\Cart\ShoppingCartServiceInterface;
use App\Komma\Shop\Checkout\CheckoutServiceInterface;
use App\Komma\Shop\Payment\Clients\MultiSafepay\Client;
use App\Komma\Shop\Payment\Clients\MultiSafepay\Enums\OrderStatus;
use App\Komma\Shop\Payment\Clients\MultiSafepay\Models\Order;
use App\Komma\Shop\Payment\Clients\MultiSafepay\Object\Orders;
use App\Komma\Shop\Payment\PaymentServiceInterface;
use App\Komma\Shop\Payment\PSPAdapters\Mollie;
use App\Komma\Shop\Payment\PSPAdapters\MultiSafepay;
use App\Komma\Shop\Payment\Transaction;
use App\Komma\Shop\Payment\TransactionStatus;
use App\Komma\Shop\Products\Product\Product;
use App\Komma\Shop\Tests\TestCase;
use App\Komma\Sites\SiteServiceInterface;
use App\Komma\Users\Models\Role;
use App\Komma\Users\Models\User;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Http\RedirectResponse;
use Mollie\Api\Endpoints\PaymentEndpoint;
use Mollie\Api\MollieApiClient;
use Mollie\Api\Resources\Payment;
use Mollie\Api\Types\PaymentStatus;
use PHPUnit\Framework\MockObject\MockObject;
class PSPTest extends TestCase
{
use DatabaseTransactions; //Automatically rolls back database actions after tests
/**
* Runs before each test
*/
public function setup()
{
parent::setup();
}
/**
* Runs after each test
*/
public function teardown()
{
parent::tearDown();
}
/**
* @group PSP
* @test
*/
public function testMolliePSPPaymentCreation()
{
//Initialize service interfaces
/** @var SiteServiceInterface $siteService */
$siteService = \App::make(SiteServiceInterface::class);
$siteService->setCurrentSiteToDefault();
$customer = User::where('role_id', '=', Role::Customer)->first();
/** @var ShoppingCartServiceInterface $shoppingCartService */
$shoppingCartService = \App::make(ShoppingCartServiceInterface::class);
/** @var PaymentServiceInterface $pspService */
$pspService = \App::make(PaymentServiceInterface::class);
//Add a product into the shoppingcart
$product = Product::inRandomOrder()->first();
$shoppingCartService->addProductable($product);
//User the checkoutService to create an order
/** @var CheckoutServiceInterface $checkoutService */
$checkoutService = \App::make(CheckoutServiceInterface::class);
$order = $checkoutService->createOrderFromShoppingCartItemsForCustomer($shoppingCartService->getItems(), $customer);
//Check that the order has the same price as the product. Then we know we can trust it to the payment service related code
$this->assertEquals($order->total_price, $product->getTotal());
//Define fake paymentData
$fakePaymentPspId = 'tr_8mkgj9TQRAphpunit';
$checkoutUrl = 'https://www.mollie.com/payscreen/select-method/'.explode('_', $fakePaymentPspId)[1];
//Mock / fake the MollieApi client, used by the Mollie adapter, to make it return fake, but real like api call results, like a mollie payment.
\App::bind(MollieApiClient::class, function () use ($order, $checkoutUrl, $fakePaymentPspId) {
$fakeMollieApiClient = $this->createMock(MollieApiClient::class);
//Fake the payment endpoint that is in the fakeMollie api client
$paymentsEndpoint = $this->createMock(PaymentEndpoint::class);
$fakeMollieApiClient->payments = $paymentsEndpoint;
//Create a faked mollie Payment
$fakedPayment = $this->createMock(\Mollie\Api\Resources\Payment::class);
$fakedPayment->id = $fakePaymentPspId;
//returnCallback returns a callback that is executed when calling the "create" method on a payment.
//The paymentData variable will contain data that is passed to that call of the create method.
$paymentsEndpoint->method('create')->will($this->returnCallback(function ($paymentData) use ($fakedPayment, $checkoutUrl, $fakePaymentPspId) {
$fakedPayment->amount = (object) $paymentData['amount'];
$fakedPayment->description = $paymentData['description'];
$fakedPayment->redirectUrl = $paymentData['redirectUrl'];
$fakedPayment->metadata = $paymentData['metadata'];
$fakedPayment->locale = $paymentData['locale'];
$fakedPayment->createdAt = '2019-01-21T11:23:33+00:00';
$fakedPayment->expiresAt = '2019-01-21T11:28:33+00:00';
$fakedPayment->method = 'ideal';
return $fakedPayment;
}));
$fakedPayment->method('getCheckoutUrl')->willReturn($checkoutUrl);
$paymentsEndpoint->method('get')->willReturn($fakedPayment);
return $fakeMollieApiClient;
});
//Make sure the configured psp is mollie
config()->set('payment.payment_service_provider', 'mollie');
//Get an adapter for the currently configured psp (must be mollie)
$pspAdapter = $pspService->getAdapter();
$this->assertInstanceOf(Mollie::class, $pspAdapter);
//Get a transaction for the order (normally done in the checkoutService)
$transaction = $pspAdapter->createTransaction($order);
$this->assertInstanceOf(Transaction::class, $transaction);
//Validate the transaction
$shopRegionInfo = new RegionInfo('NL'); //Shop was build with euros in mind and the Dutch language as the primary one
$this->assertTrue($transaction->exists); //Check that it exists in the database
$this->assertEquals($order->total_price, $transaction->amount);
$this->assertEquals($order, $transaction->order);
$this->assertEquals(TransactionStatus::OPEN, $transaction->status);
$this->assertEquals('tr_8mkgj9TQRAphpunit', $transaction->psp_id);
$this->assertEquals($shopRegionInfo->getISOCurrencySymbol(), $transaction->currency_iso_4217_code);
$this->assertEquals(request()->ip(), $transaction->ip);
$this->assertEquals('ideal', $transaction->payment_method);
$this->assertEquals('open', $transaction->status);
$this->assertEquals(Carbon::parse('2019-01-21T11:28:33+01:00'), $transaction->expire_date);
$redirectResponse = $pspAdapter->redirectForPayment($transaction);
$this->assertInstanceOf(RedirectResponse::class, $redirectResponse);
$this->assertEquals('https://www.mollie.com/payscreen/select-method/'.explode('_', $fakePaymentPspId)[1], $redirectResponse->getTargetUrl());
return $fakePaymentPspId;
}
/**
* @group PSP
* @test
* @throws \Throwable
*/
public function testMolliePSPPaymentRetrieval()
{
//Initialize service interfaces
/** @var SiteServiceInterface $siteService */
$siteService = \App::make(SiteServiceInterface::class);
$siteService->setCurrentSiteToDefault();
$customer = User::where('role_id', '=', Role::Customer)->first();
/** @var ShoppingCartServiceInterface $shoppingCartService */
$shoppingCartService = \App::make(ShoppingCartServiceInterface::class);
/** @var PaymentServiceInterface $pspService */
$pspService = \App::make(PaymentServiceInterface::class);
//Add a product into the shoppingcart
$product = Product::inRandomOrder()->first();
$shoppingCartService->addProductable($product);
//User the checkoutService to create an order
/** @var CheckoutServiceInterface $checkoutService */
$checkoutService = \App::make(CheckoutServiceInterface::class);
$order = $checkoutService->createOrderFromShoppingCartItemsForCustomer($shoppingCartService->getItems(), $customer);
//Define fake paymentData
$fakePaymentPspId = 'tr_8mkgj9TQRAphpunit';
$checkoutUrl = 'https://www.mollie.com/payscreen/select-method/'.explode('_', $fakePaymentPspId)[1];
//Create a mocked / faked mollie Payment
$fakedPayment = $this->createMock(\Mollie\Api\Resources\Payment::class);
$fakedPayment->id = $fakePaymentPspId;
//Mock / fake the MollieApi client, used by the Mollie adapter, to make it return fake, but real like api call results, like a mollie payment.
/** @var Payment|MockObject|null $fakedPayment */
\App::bind(MollieApiClient::class, function () use ($order, $checkoutUrl, $fakePaymentPspId, &$fakedPayment) {
$fakeMollieApiClient = $this->createMock(MollieApiClient::class);
//Fake the payment endpoint that is in the fakeMollie api client
$paymentsEndpoint = $this->createMock(PaymentEndpoint::class);
$fakeMollieApiClient->payments = $paymentsEndpoint;
//returnCallback returns a callback that is executed when calling the "create" method on a payment.
//The paymentData variable will contain data that is passed to that call of the create method.
$paymentsEndpoint->method('create')->will($this->returnCallback(function ($paymentData) use (&$fakedPayment, $checkoutUrl, $fakePaymentPspId) {
$fakedPayment->amount = (object) $paymentData['amount'];
$fakedPayment->description = $paymentData['description'];
$fakedPayment->redirectUrl = $paymentData['redirectUrl'];
$fakedPayment->metadata = $paymentData['metadata'];
$fakedPayment->locale = $paymentData['locale'];
$fakedPayment->createdAt = '2019-01-21T11:23:33+00:00';
$fakedPayment->expiresAt = '2019-01-21T11:28:33+00:00';
$fakedPayment->method = 'ideal';
return $fakedPayment;
}));
$fakedPayment->method('getCheckoutUrl')->willReturn($checkoutUrl);
$paymentsEndpoint->method('get')->will($this->returnCallback(function ($psp_id) use (&$fakedPayment) {
return $fakedPayment;
}));
return $fakeMollieApiClient;
});
//Make sure the configured psp is mollie
config()->set('payment.payment_service_provider', 'mollie');
$pspAdapter = $pspService->getAdapter();
$createdTransaction = $pspAdapter->createTransaction($order);
//Simulate mollie calling our api telling we have an update for a transaction without an id
$this->post(route('transaction.statusupdate', ['order' => $order]))
->assertSee('Please provide a transaction id using the key \"id\"')
->assertStatus(400);
//Simulate mollie calling our api telling we have an update for a transaction with an incorrect id
$route = route('transaction.statusupdate', ['order' => $order]).'?id='.$fakePaymentPspId.'_oops';
$this->post($route)
->assertSee('The transaction \/ payment could not be found')
->assertStatus(404);
//Simulate mollie calling our api telling we have an update for transaction with the given id
$route = route('transaction.statusupdate', ['order' => $order]).'?id='.$fakePaymentPspId;
$this->post($route)
->assertSee('Payment updated successfully!')
->assertStatus(200);
//The payment still must be open at this point since we did not change it (e.g.) The user did not pay for it yet.
$createdTransaction->refresh(); //Reload from db
$this->assertEquals(TransactionStatus::OPEN, $createdTransaction->status);
//Simulate that mollie received a payment. E.g. Transaction was paid at mollie side
//Simulate the payment after verifying we have the correct object.
$this->assertInstanceOf(Payment::class, $fakedPayment);
$this->assertInstanceOf(MockObject::class, $fakedPayment);
$fakedPayment->status = PaymentStatus::STATUS_PAID;
//Simulate the call by mollie to let our api (what mollie calls webhook) know that they processed a payment. This hits the processPSPResponse method from the mollie class via de PSPResponseController
$route = route('transaction.statusupdate', ['order' => $order]).'?id='.$fakePaymentPspId;
$this->post($route)
->assertSee('Payment updated successfully!')
->assertStatus(200);
//Now the transaction should have the status of paid.
$createdTransaction->refresh(); //Reload from db
$this->assertEquals(TransactionStatus::PAYMENT_PAID, $createdTransaction->status);
}
/**
* @group PSP
* @test
*/
public function testMultiSafePayPSPPaymentCreation()
{
//Initialize service interfaces
/** @var SiteServiceInterface $siteService */
$siteService = \App::make(SiteServiceInterface::class);
$siteService->setCurrentSiteToDefault();
$customer = User::where('role_id', '=', Role::Customer)->first();
/** @var ShoppingCartServiceInterface $shoppingCartService */
$shoppingCartService = \App::make(ShoppingCartServiceInterface::class);
/** @var PaymentServiceInterface $pspService */
$pspService = \App::make(PaymentServiceInterface::class);
//Add a product into the shoppingcart
$product = Product::inRandomOrder()->first();
$shoppingCartService->addProductable($product);
//User the checkoutService to create an order
/** @var CheckoutServiceInterface $checkoutService */
$checkoutService = \App::make(CheckoutServiceInterface::class);
$order = $checkoutService->createOrderFromShoppingCartItemsForCustomer($shoppingCartService->getItems(), $customer);
//Check that the order has the same price as the product. Then we know we can trust it to the payment service related code
$this->assertEquals($order->total_price, $product->getTotal());
//Define fake paymentData
$fakePaymentPspId = '3233377';
$checkoutUrl = 'https://testpayv2.multisafepay.com/simulator/ideal?trxid=12364692762434462&ideal=prob&issuerid=4371&merchantReturnURL=https%3A%2F%2Ftestpayv2%2Emultisafepay%2Ecom%2Fconnect%2Fredirect%3Flang%3Dnl%5FNL%26mspid%3D'.$fakePaymentPspId;
//Mock / fake the MollieApi client, used by the Mollie adapter, to make it return fake, but real like api call results, like a mollie payment.
\App::bind(Client::class, function () use ($order, $checkoutUrl, $fakePaymentPspId) {
$fakeMultiSafepayApiClient = $this->createMock(Client::class);
//Fake the payment endpoint that is in the fakeMollie api client
$fakeOrdersEndpoint = $this->createMock(Orders::class);
$fakeMultiSafepayApiClient->orders = $fakeOrdersEndpoint;
//Create a faked multisafepay Order
$fakedOrder = [
'transaction_id' => $fakePaymentPspId,
'order_id' => 26,
'created' => '2019-01-25T12:17:52',
'currency' => 'EUR',
'amount' => 799,
'description' => 'order #26',
'var1' => null,
'var2' => null,
'var3' => null,
'items' => null,
'amount_refunded' => 0,
'status' => 'initialized',
'financial_status' => 'initialized',
'reason' => '',
'reason_code' => '',
'fastcheckout' => 'NO',
'modified' => '2019-01-25T12:18:01',
'payment_url' => $checkoutUrl,
'customer' => [
'locale' => 'nl_NL',
'first_name' => 'Komma',
'last_name' => 'Super Admin',
'address1' => null,
'address2' => null,
'house_number' => 0,
'zip_code' => null,
'city' => null,
'state' => null,
'country' => 'NL',
'country_name' => null,
'phone1' => null,
'phone2' => '',
'email' => 'jules@komma.pro',
],
'payment_details' => [
'recurring_id' => null,
'type' => 'IDEAL',
'account_id' => null,
'account_holder_name' => null,
'external_transaction_id' => 12364692762434462,
//Yeah i know....Multisafepay really did put a checkout url in the account iban..... i leave it here because it is simulates real life situation
'account_iban' => $checkoutUrl,
],
'costs' => [
[
'transaction_id' => 1451590,
'description' => '0.49 For iDEAL Transactions',
'type' => 'SYSTEM',
'amount' => 0.48999999999999999,
],
],
'related_transactions' => null,
'payment_methods' => [
[
'amount' => 799,
'currency' => 'EUR',
'description' => 'order #26',
'external_transaction_id' => 12364692762434462,
'payment_description' => 'iDEAL',
'status' => 'initialized',
'type' => 'IDEAL',
],
],
];
//returnCallback returns a callback that is executed when calling the "create" method on a payment.
//The paymentData variable will contain data that is passed to that call of the create method.
$fakeOrdersEndpoint->method('post')->will($this->returnCallback(function ($paymentData) use ($fakedOrder, $checkoutUrl, $fakePaymentPspId) {
$modifiedOrder = $fakedOrder; //Copies array
unset($modifiedOrder['payment_details']['type']); //MultiSafepay does not give you this at this stage
return (object) $modifiedOrder;
}));
return $fakeMultiSafepayApiClient;
});
//Make sure the configured psp is multisafepay
config()->set('payment.payment_service_provider', 'multisafepay');
//Get an adapter for the currently configured psp (must be the multisafepay adapter)
$pspAdapter = $pspService->getAdapter();
$this->assertInstanceOf(MultiSafepay::class, $pspAdapter);
//Get a transaction for the order (normally done in the checkoutService)
$transaction = $pspAdapter->createTransaction($order);
$this->assertInstanceOf(Transaction::class, $transaction);
//Validate the transaction
$shopRegionInfo = new RegionInfo('NL'); //Shop was build with euros in mind and the Dutch language as the primary one
$this->assertTrue($transaction->exists); //Check that it exists in the database
$this->assertEquals($order->total_price, $transaction->amount);
$this->assertEquals($order, $transaction->order);
$this->assertEquals(TransactionStatus::OPEN, $transaction->status);
$this->assertEquals($fakePaymentPspId, $transaction->psp_id); //They don't give you a psp_id at this stage
$this->assertEquals($shopRegionInfo->getISOCurrencySymbol(), $transaction->currency_iso_4217_code);
$this->assertEquals(request()->ip(), $transaction->ip);
$this->assertEquals(null, $transaction->payment_method); //They don't give you a payment method at this stage
$this->assertEquals('open', $transaction->status);
$this->assertEquals(null, $transaction->expire_date); //They don't give you an expiration date
$redirectResponse = $pspAdapter->redirectForPayment($transaction);
$this->assertInstanceOf(RedirectResponse::class, $redirectResponse);
$this->assertEquals('https://testpayv2.multisafepay.com/simulator/ideal?trxid=12364692762434462&ideal=prob&issuerid=4371&merchantReturnURL=https%3A%2F%2Ftestpayv2%2Emultisafepay%2Ecom%2Fconnect%2Fredirect%3Flang%3Dnl%5FNL%26mspid%3D'.$fakePaymentPspId, $redirectResponse->getTargetUrl());
return $fakePaymentPspId;
}
/**
* @group PSP
* @test
* @throws \Throwable
*/
public function testMultiSafepayPaymentRetrieval()
{
//Initialize service interfaces
/** @var SiteServiceInterface $siteService */
$siteService = \App::make(SiteServiceInterface::class);
$siteService->setCurrentSiteToDefault();
$customer = User::where('role_id', '=', Role::Customer)->first();
/** @var ShoppingCartServiceInterface $shoppingCartService */
$shoppingCartService = \App::make(ShoppingCartServiceInterface::class);
/** @var PaymentServiceInterface $pspService */
$pspService = \App::make(PaymentServiceInterface::class);
//Add a product into the shoppingcart
$product = Product::inRandomOrder()->first();
$shoppingCartService->addProductable($product);
//User the checkoutService to create an order
/** @var CheckoutServiceInterface $checkoutService */
$checkoutService = \App::make(CheckoutServiceInterface::class);
$order = $checkoutService->createOrderFromShoppingCartItemsForCustomer($shoppingCartService->getItems(), $customer);
//Define fake paymentData
$fakePaymentPspId = '3233377';
$checkoutUrl = 'https://testpayv2.multisafepay.com/simulator/ideal?trxid=12364692762434462&ideal=prob&issuerid=4371&merchantReturnURL=https%3A%2F%2Ftestpayv2%2Emultisafepay%2Ecom%2Fconnect%2Fredirect%3Flang%3Dnl%5FNL%26mspid%3D'.$fakePaymentPspId;
//Create fake multisafepay order data
/** @var array $multiSafepayOrderData */
$multiSafepayOrderData = [
'transaction_id' => $fakePaymentPspId,
'order_id' => $order->id,
'created' => '2019-01-25T12:17:52',
'currency' => 'EUR',
'amount' => 799,
'description' => 'order #'.$order->id,
'var1' => null,
'var2' => null,
'var3' => null,
'items' => null,
'amount_refunded' => 0,
'status' => OrderStatus::INITIALIZED,
'financial_status' => OrderStatus::INITIALIZED,
'reason' => '',
'reason_code' => '',
'fastcheckout' => 'NO',
'modified' => '2019-01-25T12:18:01',
'payment_url' => $checkoutUrl,
'customer' => [
'locale' => 'nl_NL',
'first_name' => 'Komma',
'last_name' => 'Super Admin',
'address1' => null,
'address2' => null,
'house_number' => 0,
'zip_code' => null,
'city' => null,
'state' => null,
'country' => 'NL',
'country_name' => null,
'phone1' => null,
'phone2' => '',
'email' => 'jules@komma.pro',
],
'payment_details' => [
'recurring_id' => null,
'type' => 'IDEAL',
'account_id' => null,
'account_holder_name' => null,
'external_transaction_id' => 12364692762434462,
//Yeah i know....Multisafepay really did put a checkout url in the account iban..... i leave it here because it is simulates real life situation
'account_iban' => $checkoutUrl,
],
'costs' => [
[
'transaction_id' => 1451590,
'description' => '0.49 For iDEAL Transactions',
'type' => 'SYSTEM',
'amount' => 0.48999999999999999,
],
],
'related_transactions' => null,
'payment_methods' => [
[
'amount' => 799,
'currency' => 'EUR',
'description' => 'order #'.$order->id,
'external_transaction_id' => 12364692762434462,
'payment_description' => 'iDEAL',
'status' => OrderStatus::INITIALIZED,
'type' => 'IDEAL',
],
],
];
//Mock / fake the MollieApi client, used by the Mollie adapter, to make it return fake, but real like api call results, like a mollie payment.
/** @var Payment|MockObject|null $fakedPayment */
\App::bind(Client::class, function () use ($order, $checkoutUrl, $fakePaymentPspId, &$multiSafepayOrderData) {
$fakeMultiSafepayApiClient = $this->createMock(Client::class);
//Fake the payment endpoint that is in the fakeMollie api client
$fakeOrdersEndpoint = $this->createMock(Orders::class);
$fakeMultiSafepayApiClient->orders = $fakeOrdersEndpoint;
//returnCallback returns a callback that is executed when calling the "create" method on a payment.
//The paymentData variable will contain data that is passed to that call of the create method.
$fakeOrdersEndpoint->method('post')->will($this->returnCallback(function ($paymentData) use (&$multiSafepayOrderData, $checkoutUrl, $fakePaymentPspId) {
$modifiedOrder = $multiSafepayOrderData; //Copies array
unset($modifiedOrder['payment_details']['type']); //MultiSafepay does not give you this at this stage
return (object) $modifiedOrder;
}));
//returnCallback returns a callback that is executed when calling the "create" method on a payment.
//The paymentData variable will contain data that is passed to that call of the create method.
$fakeOrdersEndpoint->method('get')->will($this->returnCallback(function ($endpoint, $transactionId, $body = [], $query_string = false) use (&$multiSafepayOrderData, $checkoutUrl, $fakePaymentPspId) {
return (object) $multiSafepayOrderData;
}));
return $fakeMultiSafepayApiClient;
});
//Make sure the configured psp is multisafepay
config()->set('payment.payment_service_provider', 'multisafepay');
$pspAdapter = $pspService->getAdapter();
$createdTransaction = $pspAdapter->createTransaction($order);
//Let it follow redirects since the statusupdate endpoint will redirect users to the correct page
$this->followingRedirects();
//Simulate MultiSafepay calling our api telling we have an update for a transaction without an transaction id
$this->post(route('transaction.statusupdate', ['order' => $order->id]))
->assertSee('Something went wrong, and because of that we cannot tell you about your payment. Please contact us.')
->assertStatus(400);
//Let it follow redirects since the statusupdate endpoint will redirect users to the correct page
$this->followingRedirects();
//Simulate MultiSafepay calling our api telling we have an update for a transaction with an incorrect id
$route = route('transaction.statusupdate', ['order' => $order->id]).'?transactionid=-1'; //This is what multiSafepay really does
$this->post($route)
->assertSee('Something went wrong, and because of that we cannot tell you about your payment. Please contact us.')
->assertStatus(400);
//The payment still must be open at this point since we did not change it (e.g.) The user did not pay for it yet.
$createdTransaction->refresh(); //Reload from db
$this->assertEquals(TransactionStatus::OPEN, $createdTransaction->status);
//Let it follow redirects since the statusupdate endpoint will redirect users to the correct page
$this->followingRedirects();
//Simulate MultiSafepay calling our api telling we have an update for transaction with the given id
$multiSafepayOrderData['status'] = OrderStatus::COMPLETED;
$multiSafepayOrderData['financial_status'] = OrderStatus::COMPLETED;
$multiSafepayOrderData['payment_methods'][0]['status'] = OrderStatus::COMPLETED;
$route = route('transaction.statusupdate', ['order' => $order->id]).'?transactionid='.$createdTransaction->psp_id; //This is what multiSafepay really does
$this->post($route)
->assertSee('We have received your payment. This is what we are going to do next:')
->assertStatus(200);
//The payment still must be paid at this moment. The multisafepay adapter requested an update and multisafepay client must return the order data.
$createdTransaction->refresh(); //Reload from db
$this->assertEquals(TransactionStatus::PAYMENT_PAID, $createdTransaction->status);
}
}