diff --git a/.env.test b/.env.test
index a92e5ea..ae3a6b8 100644
--- a/.env.test
+++ b/.env.test
@@ -2,3 +2,11 @@
KERNEL_CLASS='Swag\Braintree\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
+
+###> braintree/gateway ###
+BRAINTREE_TEST_MERCHANT_ACCOUNT_ID='your_merchant_account_id'
+BRAINTREE_TEST_ENVIRONMENT='sandbox'
+BRAINTREE_TEST_MERCHANT_ID='your_merchant_id'
+BRAINTREE_TEST_PUBLIC_KEY='your_public_key'
+BRAINTREE_TEST_PRIVATE_KEY='your_private_key'
+###< braintree/gateway ###
\ No newline at end of file
diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
index baf4668..d3550a0 100644
--- a/.github/workflows/php.yml
+++ b/.github/workflows/php.yml
@@ -81,6 +81,36 @@ jobs:
with:
files: ./coverage.xml
+ phpunit-application:
+ runs-on: ubuntu-latest
+ services:
+ mariadb:
+ image: mariadb:10.5
+ env:
+ MARIADB_ROOT_PASSWORD: swagbraintree
+ MYSQL_DATABASE: swagbraintree_test
+ ports: [ '3306:3306' ]
+ env:
+ DATABASE_URL: mysql://root:swagbraintree@127.0.0.1:3306/swagbraintree
+ BRAINTREE_TEST_ENVIRONMENT: sandbox
+ BRAINTREE_TEST_MERCHANT_ID: ${{ secrets.BRAINTREE_TEST_MERCHANT_ID }}
+ BRAINTREE_TEST_MERCHANT_ACCOUNT_ID: ${{ secrets.BRAINTREE_TEST_MERCHANT_ACCOUNT_ID }}
+ BRAINTREE_TEST_PUBLIC_KEY: ${{ secrets.BRAINTREE_TEST_PUBLIC_KEY }}
+ BRAINTREE_TEST_PRIVATE_KEY: ${{ secrets.BRAINTREE_TEST_PRIVATE_KEY }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ./.github/actions/setup-php
+ - name: Setup database
+ run: composer setup:test
+ - name: Run PHPUnit
+ continue-on-error: true
+ run: composer phpunit:application -- --coverage-clover=coverage.xml
+ - name: Codecov
+ if: steps.phpunit-application.outcome == 'success' && steps.phpunit-application.conclusion == 'success'
+ uses: codecov/codecov-action@v4
+ with:
+ files: ./coverage.xml
+
infection:
runs-on: ubuntu-latest
services:
diff --git a/composer.json b/composer.json
index d82343d..f94d4d2 100644
--- a/composer.json
+++ b/composer.json
@@ -47,7 +47,8 @@
"autoload-dev": {
"psr-4": {
"Swag\\Braintree\\Tests\\Unit\\": "tests/unit/",
- "Swag\\Braintree\\Tests\\Integration\\": "tests/integration/"
+ "Swag\\Braintree\\Tests\\Integration\\": "tests/integration/",
+ "Swag\\Braintree\\Tests\\Application\\": "tests/application/"
}
},
"replace": {
@@ -93,7 +94,8 @@
"bin/console doctrine:migrations:migrate --env=test -n"
],
"phpunit:unit": "@phpunit --testsuite=SwagBraintreeUnitTest",
- "phpunit:integration": "@phpunit --testsuite=SwagBraintreeIntegrationTest"
+ "phpunit:integration": "@phpunit --testsuite=SwagBraintreeIntegrationTest",
+ "phpunit:application": "@phpunit --testsuite=SwagBraintreeApplicationTest"
},
"conflict": {
"symfony/symfony": "*"
diff --git a/config/services.php b/config/services.php
index 1d856be..2fdb255 100644
--- a/config/services.php
+++ b/config/services.php
@@ -13,4 +13,8 @@
->exclude('../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}');
$container->import(__DIR__ . '/services/braintree.xml', 'xml');
+
+ if ($container->env() === 'test') {
+ $container->import(__DIR__ . '/services/braintree_test.xml', 'xml');
+ }
};
diff --git a/config/services/braintree_test.xml b/config/services/braintree_test.xml
new file mode 100644
index 0000000..9e4f4dd
--- /dev/null
+++ b/config/services/braintree_test.xml
@@ -0,0 +1,27 @@
+
+
+
+ %env(BRAINTREE_TEST_MERCHANT_ACCOUNT_ID)%
+ %env(BRAINTREE_TEST_MERCHANT_ID)%
+ %env(BRAINTREE_TEST_ENVIRONMENT)%
+ %env(BRAINTREE_TEST_PUBLIC_KEY)%
+ %env(BRAINTREE_TEST_PRIVATE_KEY)%
+
+
+
+
+ %env(BRAINTREE_TEST_ENVIRONMENT)%
+ %env(BRAINTREE_TEST_MERCHANT_ID)%
+ %env(BRAINTREE_TEST_PUBLIC_KEY)%
+ %env(BRAINTREE_TEST_PRIVATE_KEY)%
+
+
+
+
+
+
+
diff --git a/infection.json5 b/infection.json5
index 4d4be61..0cad787 100644
--- a/infection.json5
+++ b/infection.json5
@@ -21,6 +21,7 @@
},
"tmpDir": "var/cache",
"testFramework":"phpunit",
+ "testFrameworkOptions": "--testsuite=SwagBraintreeUnitTest,SwagBraintreeIntegrationTest",
"mutators": {
"@default": true,
"LessThan": {
@@ -30,17 +31,17 @@
},
"PlusEqual": {
"ignore": [
- "Swag\\Braintree\\Braintree\\Payment\\OrderInformationService::extractDiscountAmount",
+ "Swag\\Braintree\\Braintree\\Payment\\OrderInformationService::extractDiscountAmount"
]
},
"GreaterThan": {
"ignore": [
- "Swag\\Braintree\\Braintree\\Payment\\OrderInformationService::extractDiscountAmount",
+ "Swag\\Braintree\\Braintree\\Payment\\OrderInformationService::extractDiscountAmount"
]
},
"ArrayItem": {
"ignore": [
- "Swag\\Braintree\\Braintree\\Payment\\BraintreePaymentService::handleTransaction",
+ "Swag\\Braintree\\Braintree\\Payment\\BraintreePaymentService::handleTransaction"
]
}
}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 023d384..2cb784b 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -42,6 +42,10 @@
tests/integration
+
+
+ tests/application
+
diff --git a/src/Tests/ApplicationBootstrapTrait.php b/src/Tests/ApplicationBootstrapTrait.php
new file mode 100644
index 0000000..d8014a5
--- /dev/null
+++ b/src/Tests/ApplicationBootstrapTrait.php
@@ -0,0 +1,56 @@
+get(EntityManagerInterface::class);
+
+ $shop = new ShopEntity(Ids::get('shop-id'), 'https://platform.dev.localhost', 'this-is-shop-secret');
+ $shop->setBraintreeMerchantId($container->getParameter('BRAINTREE_TEST_MERCHANT_ID'));
+ $shop->setBraintreePublicKey($container->getParameter('BRAINTREE_TEST_PUBLIC_KEY'));
+ $shop->setBraintreePrivateKey($container->getParameter('BRAINTREE_TEST_PRIVATE_KEY'));
+ $shop->setShopActive(true);
+ $shop->setShopApiCredentials('this-is-the-client-id', 'this-is-the-client-secret');
+ $shop->setBraintreeSandbox(true);
+
+ $em->persist($shop);
+ $em->flush();
+
+ static::getContainer()
+ ->get(ConfigRepository::class)
+ ->upsert([[
+ 'salesChannelId' => null,
+ 'threeDSecureEnforced' => true,
+ 'shipsFromPostalCode' => '48268',
+ ]], $shop);
+
+ $currencyMapping = new CurrencyMappingEntity();
+ $currencyMapping->setCurrencyId('EUR');
+ $currencyMapping->setMerchantAccountId($container->getParameter('BRAINTREE_TEST_MERCHANT_ACCOUNT_ID'));
+ $currencyMapping->setShop($shop);
+
+ static::getContainer()
+ ->get(CurrencyMappingRepository::class)
+ ->upsert([[
+ 'salesChannelId' => null,
+ 'currencyId' => Ids::get('currency-id'),
+ 'currencyIso' => 'EUR',
+ 'merchantAccountId' => $container->getParameter('BRAINTREE_TEST_MERCHANT_ACCOUNT_ID'),
+ ]], $shop);
+
+ return $shop;
+ }
+}
diff --git a/src/Tests/Braintree/Gateway/BraintreeTestGatewayFactory.php b/src/Tests/Braintree/Gateway/BraintreeTestGatewayFactory.php
new file mode 100644
index 0000000..379846b
--- /dev/null
+++ b/src/Tests/Braintree/Gateway/BraintreeTestGatewayFactory.php
@@ -0,0 +1,31 @@
+ $this->braintreeEnv,
+ 'merchantId' => $this->braintreeMerchantId,
+ 'publicKey' => $this->braintreePublicKey,
+ 'privateKey' => $this->braintreePrivateKey,
+ ]
+ );
+
+ return new Gateway($configuration);
+ }
+}
diff --git a/src/Tests/Contract/IntegrationHelperTrait.php b/src/Tests/Contract/IntegrationHelperTrait.php
index 51a0f6b..d705653 100644
--- a/src/Tests/Contract/IntegrationHelperTrait.php
+++ b/src/Tests/Contract/IntegrationHelperTrait.php
@@ -4,13 +4,16 @@
use Doctrine\ORM\EntityManagerInterface;
use Swag\Braintree\Entity\ShopEntity;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @infection-ignore-all
*/
trait IntegrationHelperTrait
{
- use IntegrationHelperTrait;
+ use ShopHelperTrait;
+
+ abstract protected static function getContainer(): ContainerInterface;
protected static function getEntityManager(): EntityManagerInterface
{
diff --git a/src/Tests/Contract/OrderHelperTrait.php b/src/Tests/Contract/OrderHelperTrait.php
index 4891086..4394746 100644
--- a/src/Tests/Contract/OrderHelperTrait.php
+++ b/src/Tests/Contract/OrderHelperTrait.php
@@ -416,4 +416,141 @@ protected static function createOrderWithDiscount(): Order
'id' => Ids::get('order-discount-id'),
]);
}
+
+ private function createOrderForApplication(): Order
+ {
+ return new Order([
+ 'orderNumber' => '10068',
+ 'salesChannelId' => Ids::get('order-sales-channel-id'),
+ 'price' => [
+ 'netPrice' => 100,
+ 'totalPrice' => 119,
+ 'calculatedTaxes' => [['tax' => 19, 'taxRate' => 19, 'price' => 100]],
+ 'taxRules' => [['taxRate' => 19, 'percentage' => 100]],
+ 'positionPrice' => 119,
+ 'taxStatus' => 'gross',
+ 'rawTotal' => 100,
+ ],
+ 'amountTotal' => 119,
+ 'amountNet' => 100,
+ 'positionPrice' => 100,
+ 'taxStatus' => 'gross',
+ 'shippingTotal' => 0,
+ 'shippingCosts' => [
+ 'unitPrice' => 0,
+ 'quantity' => 0,
+ 'totalPrice' => 0,
+ 'calculatedTaxes' => [['tax' => 19, 'taxRate' => 19, 'price' => 100]],
+ 'taxRules' => [['taxRate' => 19, 'percentage' => 100]],
+ ],
+ 'orderCustomer' => [
+ 'email' => 'test@example.com',
+ 'orderId' => Ids::get('order-id'),
+ 'firstName' => 'Application',
+ 'lastName' => 'Tester',
+ 'title' => null,
+ 'company' => 'shopware AG',
+ 'customerNumber' => '1337',
+ 'customerId' => Ids::get('order-customer-id'),
+ 'id' => Ids::get('order-order-customer-id'),
+ ],
+ 'currency' => [
+ 'isoCode' => 'EUR',
+ 'symbol' => '€',
+ 'shortName' => 'EUR',
+ 'name' => 'Euro',
+ 'itemRounding' => ['decimals' => 2, 'interval' => 0.01, 'roundForNet' => true],
+ 'totalRounding' => ['decimals' => 2, 'interval' => 0.01, 'roundForNet' => true],
+ 'id' => Ids::get('currency-id'),
+ ],
+ 'billingAddress' => [
+ 'firstName' => 'Application',
+ 'lastName' => 'Tester',
+ 'street' => 'Bahnhofstraße 27',
+ 'zipcode' => '10332',
+ 'city' => 'Berlin',
+ 'company' => null,
+ 'title' => null,
+ 'additionalAddressLine1' => null,
+ 'additionalAddressLine2' => null,
+ 'country' => [
+ 'name' => 'Haiti',
+ 'iso' => 'HT',
+ 'iso3' => 'HTI',
+ 'id' => Ids::get('order-country-id'),
+ ],
+ 'id' => Ids::get('order-billing-address-id'),
+ ],
+ 'deliveries' => [[
+ 'shippingCosts' => [
+ 'unitPrice' => 0,
+ 'quantity' => 0,
+ 'totalPrice' => 0,
+ 'calculatedTaxes' => [[
+ 'tax' => 0,
+ 'taxRate' => 0,
+ 'price' => 0,
+ ]],
+ ],
+ 'shippingOrderAddress' => [
+ 'firstName' => 'Application',
+ 'lastName' => 'Tester',
+ 'street' => 'Ebbinghoff 10',
+ 'zipcode' => '1234567890',
+ 'city' => 'Schöppingen',
+ 'company' => 'company',
+ 'title' => null,
+ 'additionalAddressLine1' => 'additionalAddressLine1',
+ 'additionalAddressLine2' => null,
+ 'country' => [
+ 'name' => 'Hungary',
+ 'iso' => 'HU',
+ 'iso3' => 'HUN',
+ 'id' => Ids::get('order-country-id'),
+ ],
+ 'countryState' => [
+ 'name' => 'countryState',
+ ],
+ 'id' => Ids::get('order-shipping-address-id'),
+ ],
+ 'id' => Ids::get('order-delivery-id'),
+ ]],
+ 'lineItems' => [[
+ 'quantity' => 1,
+ 'unitPrice' => 100,
+ 'totalPrice' => 100,
+ 'label' => 'Product 1 - Application test',
+ 'description' => 'Product description 1 from application test',
+ 'good' => true,
+ 'type' => 'product',
+ 'referencedId' => 'product-1',
+ 'payload' => [
+ 'customFields' => [
+ OrderInformationService::LINE_ITEM_COMMODITY_CODE_CUSTOM_FIELD => '1234567890',
+ ],
+ ],
+ 'price' => [
+ 'unitPrice' => 100,
+ 'quantity' => 1,
+ 'totalPrice' => 100,
+ 'calculatedTaxes' => [['tax' => 19, 'taxRate' => 19, 'price' => 100]],
+ 'taxRules' => [['taxRate' => 19, 'percentage' => 100]],
+ ],
+ 'id' => Ids::get('order-line-item-id'),
+ ]],
+ 'transactions' => [[
+ 'amount' => [
+ 'unitPrice' => 100,
+ 'quantity' => 1,
+ 'totalPrice' => 100,
+ 'calculatedTaxes' => [['tax' => 19, 'taxRate' => 10, 'price' => 100]],
+ 'taxRules' => [['taxRate' => 19, 'percentage' => 100]],
+ ],
+ 'id' => Ids::get('order-discount-transaction-id'),
+ ]],
+ 'itemRounding' => ['decimals' => 2, 'interval' => 0.01, 'roundForNet' => true],
+ 'totalRounding' => ['decimals' => 2, 'interval' => 0.01, 'roundForNet' => true],
+ 'id' => Ids::get('order-discount-id'),
+ ]);
+ }
}
diff --git a/src/Tests/Contract/OrderTransactionHelperTrait.php b/src/Tests/Contract/OrderTransactionHelperTrait.php
index 337ada0..b2db38c 100644
--- a/src/Tests/Contract/OrderTransactionHelperTrait.php
+++ b/src/Tests/Contract/OrderTransactionHelperTrait.php
@@ -14,11 +14,11 @@ protected static function createOrderTransaction(): OrderTransaction
{
return new OrderTransaction([
'amount' => [
- 'unitPrice' => 200,
+ 'unitPrice' => 119,
'quantity' => 1,
- 'totalPrice' => 200,
- 'calculatedTaxes' => [['tax' => 20.456, 'taxRate' => 10, 'price' => 200]],
- 'taxRules' => [['taxRate' => 10, 'percentage' => 100]],
+ 'totalPrice' => 119,
+ 'calculatedTaxes' => [['tax' => 19, 'taxRate' => 19, 'price' => 100]],
+ 'taxRules' => [['taxRate' => 19, 'percentage' => 100]],
],
'id' => Ids::get('order-transaction-id'),
]);
diff --git a/src/Tests/Contract/PaymentPayActionHelperTrait.php b/src/Tests/Contract/PaymentPayActionHelperTrait.php
index 8332203..ed02385 100644
--- a/src/Tests/Contract/PaymentPayActionHelperTrait.php
+++ b/src/Tests/Contract/PaymentPayActionHelperTrait.php
@@ -28,4 +28,22 @@ protected static function createPaymentPayAction(ShopInterface $shop, array $req
$requestData,
);
}
+
+ /**
+ * @param array $requestData
+ */
+ private function createPaymentPayActionForApplication(ShopInterface $shop, array $requestData = []): PaymentPayAction
+ {
+ $actionSource = new ActionSource('this-is-url', 'this-is-app-version');
+
+ return new PaymentPayAction(
+ $shop,
+ $actionSource,
+ $this->createOrderForApplication(),
+ $this->createOrderTransaction(),
+ null,
+ null,
+ $requestData,
+ );
+ }
}
diff --git a/tests/application/Braintree/BraintreeReachabilityTest.php b/tests/application/Braintree/BraintreeReachabilityTest.php
new file mode 100644
index 0000000..43cc678
--- /dev/null
+++ b/tests/application/Braintree/BraintreeReachabilityTest.php
@@ -0,0 +1,19 @@
+getContainer()->get(Gateway::class);
+ $merchant = $gateway->merchantAccount()->find($this->getContainer()->getParameter('BRAINTREE_TEST_MERCHANT_ACCOUNT_ID'));
+
+ static::assertSame(MerchantAccount::STATUS_ACTIVE, $merchant->status);
+ static::assertTrue($merchant->default);
+ }
+}
diff --git a/tests/application/Braintree/Payment/BraintreePaymentServiceTest.php b/tests/application/Braintree/Payment/BraintreePaymentServiceTest.php
new file mode 100644
index 0000000..a9d75cc
--- /dev/null
+++ b/tests/application/Braintree/Payment/BraintreePaymentServiceTest.php
@@ -0,0 +1,37 @@
+registerShop();
+ $action = $this->createPaymentPayActionForApplication(
+ $shop,
+ [BraintreePaymentService::BRAINTREE_NONCE => 'fake-three-d-secure-visa-full-authentication-nonce']
+ );
+
+ $service = static::getContainer()->get(BraintreePaymentService::class);
+ $transaction = $service->handleTransaction($action);
+
+ static::assertSame(Transaction::SUBMITTED_FOR_SETTLEMENT, $transaction->status);
+ static::assertSame('119.00', $transaction->amount);
+ static::assertSame('EUR', $transaction->currencyIsoCode);
+
+ static::assertTrue($transaction->threeDSecureInfo->liabilityShifted);
+ static::assertTrue($transaction->threeDSecureInfo->liabilityShiftPossible);
+ static::assertSame(ThreeDSecure::STATUS_AUTHENTICATE_SUCCESSFUL, $transaction->threeDSecureInfo->status);
+ static::assertSame(ThreeDSecure::ENROLLMENT_STATUS_YES, $transaction->threeDSecureInfo->enrolled);
+ }
+}
diff --git a/tests/unit/Braintree/Payment/BraintreePaymentServiceTest.php b/tests/unit/Braintree/Payment/BraintreePaymentServiceTest.php
index 00d3f5a..543648b 100644
--- a/tests/unit/Braintree/Payment/BraintreePaymentServiceTest.php
+++ b/tests/unit/Braintree/Payment/BraintreePaymentServiceTest.php
@@ -104,11 +104,11 @@ public function testHandleTransaction(): void
->method('sale')
->with(static::callback(function (array $sale) {
static::assertEquals('this-is-merchant-id', $sale['merchantAccountId']);
- static::assertEquals(200, $sale['amount']);
+ static::assertEquals(119.0, $sale['amount']);
static::assertEquals(5, $sale['shippingAmount']);
static::assertEquals(10068, $sale['purchaseOrderNumber']);
static::assertEquals('this-is-nonce', $sale['paymentMethodNonce']);
- static::assertEquals(20.46, $sale['taxAmount']);
+ static::assertEquals(19, $sale['taxAmount']);
static::assertEquals(['submitForSettlement' => true], $sale['options']);
static::assertEquals('this-is-device-data', $sale['deviceData']);
static::assertEquals('this-is-nonce', $sale['paymentMethodNonce']);
diff --git a/tests/unit/Braintree/Payment/OrderInformationServiceTest.php b/tests/unit/Braintree/Payment/OrderInformationServiceTest.php
index 150169f..02e5fce 100644
--- a/tests/unit/Braintree/Payment/OrderInformationServiceTest.php
+++ b/tests/unit/Braintree/Payment/OrderInformationServiceTest.php
@@ -42,7 +42,7 @@ protected function setUp(): void
public function testExtractTaxAmount(): void
{
$taxAmount = $this->orderInformationService->extractTaxAmount($this->paymentPayAction);
- static::assertEquals(20.46, $taxAmount);
+ static::assertEquals(19.0, $taxAmount);
}
public function testExtractShippingAddress(): void