diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78cc3e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +TpayShopwarePayment-master.zip \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..74c6c6e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +# 1.0.0 +- First version of TpayShopwarePayment \ No newline at end of file diff --git a/CHANGELOG_de-DE.md b/CHANGELOG_de-DE.md new file mode 100644 index 0000000..dc68d15 --- /dev/null +++ b/CHANGELOG_de-DE.md @@ -0,0 +1,2 @@ +# 1.0.0 +- Erste Version der TpayShopwarePayment \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..417c01d --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# TpayShopwarePayment + +### Tpay Payments integration for [Shopware 6](https://github.com/shopware/platform) + + diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..ec8d96c --- /dev/null +++ b/build.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +echo -e "\e[92m######################################################################" +echo -e "\e[92m# #" +echo -e "\e[92m# Start tPay Builder #" +echo -e "\e[92m# #" +echo -e "\e[92m######################################################################" + +echo -e "Release" +echo -e "\e[39m " +echo -e "\e[39m======================================================================" +echo -e "\e[39m " +echo -e "Step 1 of 7 \e[33mRemove old release\e[39m" +# Remove old release +rm -rf TpayShopwarePayment TpayShopwarePayment-*.zip +echo -e "\e[32mOK" + +echo -e "\e[39m " +echo -e "\e[39m======================================================================" +echo -e "\e[39m " +echo -e "Step 2 of 7 \e[33mBuild\e[39m" +cd ../../../ +./bin/build-storefront.sh +./bin/build-administration.sh +cd custom/plugins/TpayShopwarePayment/ +echo -e "\e[32mOK" + +echo -e "\e[39m " +echo -e "\e[39m======================================================================" +echo -e "\e[39m " +echo -e "Step 3 of 7 \e[33mCopy files\e[39m" +rsync -av --progress . TpayShopwarePayment --exclude TpayShopwarePayment +echo -e "\e[32mOK" + + +echo -e "\e[39m " +echo -e "\e[39m======================================================================" +echo -e "\e[39m " +echo -e "Step 4 of 7 \e[33mGo to directory\e[39m" +cd TpayShopwarePayment +echo -e "\e[32mOK" + +echo -e "\e[39m " +echo -e "\e[39m======================================================================" +echo -e "\e[39m " +echo -e "Step 5 of 7 \e[33mDeleting unnecessary files\e[39m" +cd .. +( find ./TpayShopwarePayment -type d -name ".git" && find ./TpayShopwarePayment -name ".gitignore" && find ./TpayShopwarePayment -name "yarn.lock" && find ./TpayShopwarePayment -name ".php_cs.dist" && find ./TpayShopwarePayment -name ".gitmodules" && find ./TpayShopwarePayment -name "build.sh" ) | xargs rm -r +cd TpayShopwarePayment/src/Resources +cd ../../../ +echo -e "\e[32mOK" + + +echo -e "\e[39m " +echo -e "\e[39m======================================================================" +echo -e "\e[39m " +echo -e "Step 6 of 7 \e[33mCreate ZIP\e[39m" +zip -rq TpayShopwarePayment-master.zip TpayShopwarePayment +echo -e "\e[32mOK" + +echo -e "\e[39m " +echo -e "\e[39m======================================================================" +echo -e "\e[39m " +echo -e "Step 7 of 7 \e[33mClear build directory\e[39m" +rm -rf TpayShopwarePayment +echo -e "\e[32mOK" + + +echo -e "\e[92m######################################################################" +echo -e "\e[92m# #" +echo -e "\e[92m# Build Complete #" +echo -e "\e[92m# #" +echo -e "\e[92m######################################################################" +echo -e "\e[39m " +echo " _____ _ _ "; +echo " / ____| | | | | "; +echo " | | _ __ ___| |__ | | ___ _ __ "; +echo " | | | '__/ _ \ '_ \| |/ _ \ '__|"; +echo " | |____| | | __/ | | | | __/ | "; +echo " \_____|_| \___|_| |_|_|\___|_| "; +echo " "; +echo " "; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f1c7b45 --- /dev/null +++ b/composer.json @@ -0,0 +1,46 @@ +{ + "name": "tpay/shopware-payment", + "description": "Tpay Payment for Shopware 6", + "type": "shopware-platform-plugin", + "version": "1.0.14", + "license": "MIT", + "authors": [ + { + "name": "Tpay Krajowy Integrator Płatności S.A", + "homepage": "https://tpay.com", + "role": "Owner" + }, + { + "name": "Jakub Medyński", + "homepage": "https://github.com/jmedynski", + "role": "Developer" + } + ], + "require": { + "shopware/core": "^6.4", + "tpay-com/tpay-php": "^2.3", + "whitecube/lingua": "^1.1" + }, + "require-dev": { + "roave/security-advisories": "dev-master" + }, + "autoload": { + "psr-4": { + "Tpay\\ShopwarePayment\\": "src/" + } + }, + "extra": { + "shopware-plugin-class": "Tpay\\ShopwarePayment\\TpayShopwarePayment", + "copyright": "Tpay Krajowy Integrator Płatności S.A", + "label": { + "pl-PL": "Tpay Payment dla Shopware 6", + "en-GB": "Tpay Payment for Shopware 6", + "de-DE": "Tpay Payment für Shopware 6" + }, + "description": { + "pl-PL": "Nasz plugin “Tpay for Shopware 6” pozwala na szybką i łatwą integrację popularnego dostawcy płatności internetowych Tpay z Twoim sklepem internetowym, który używany jest już w tysiącach sklepów na całym świecie. Moduł ten oferuje szeroki zakres dostępnych metod płatności w ramach Tpay, które możesz teraz udostępnić swoim klientom na platformie Shopware 6. Obsługiwane są następujące metody płatności: Przelewy bankowe, karty kredytowe, e-transfery, płatności ratalne, płatności mobilne z kodami QR, system BLIK i SMS Premium.", + "en-GB": "Our \"Tpay Payment for Shopware 6\" plugin enables you to quickly and easily integrate the popular online payment provider Tpay into your online store. As part of Tpay, the module offers a wide range of available payment methods, which you can now also offer your Shopware 6 shop customers. The following payment methods are supported: bank transfers, credit cards, e-transfers, installment payments, mobile payments with QR codes, BLIK system and SMS premium.", + "de-DE": "Unser Plugin \"Tpay Payment für Shopware 6\" ermöglicht es Ihnen, den bekannten Online-Zahlungsanbieter Tpay, der bereits weltweit in Tausenden von Shops eingesetzt wird, schnell und einfach in Ihrem Webshop zu integrieren. Das Modul bietet im Rahmen von Tpay ein breites Spektrum an verfügbaren Zahlungsmethoden, die Sie nun Ihren Kunden auch bei Shopware 6 zur Verfügung stellen können. Folgende Zahlungsmethoden werden unterstützt: Banküberweisungen, Kreditkarten, e-Überweisungen, Ratenzahlungen, mobile Zahlungen mit QR-Codes, BLIK-System und SMS Premium." + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..9467e4a --- /dev/null +++ b/composer.lock @@ -0,0 +1,397 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "35fb26378779a92c1ff402fead07114c", + "packages": [ + { + "name": "tpay-com/tpay-php", + "version": "2.3.1", + "source": { + "type": "git", + "url": "https://github.com/tpay-com/tpay-php.git", + "reference": "c52f979cc928e84269096284feaf5aab98577505" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tpay-com/tpay-php/zipball/c52f979cc928e84269096284feaf5aab98577505", + "reference": "c52f979cc928e84269096284feaf5aab98577505", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.6.0" + }, + "type": "library", + "extra": [ + { + "engine": "PHP SDK" + } + ], + "autoload": { + "classmap": [ + "tpayLibs/src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0" + ], + "authors": [ + { + "name": "tpay.com", + "homepage": "https://tpay.com" + } + ], + "description": "tpay.com library", + "support": { + "issues": "https://github.com/tpay-com/tpay-php/issues", + "source": "https://github.com/tpay-com/tpay-php/tree/2.3.1" + }, + "time": "2021-06-21T08:13:27+00:00" + }, + { + "name": "whitecube/lingua", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/whitecube/lingua.git", + "reference": "61a72faa21caa0f2ec6ec85c1899793f3eb42cc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/whitecube/lingua/zipball/61a72faa21caa0f2ec6ec85c1899793f3eb42cc6", + "reference": "61a72faa21caa0f2ec6ec85c1899793f3eb42cc6", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.0" + }, + "require-dev": { + "codeclimate/php-test-reporter": "dev-master", + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "WhiteCube\\Lingua\\": "src/Lingua/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "WhiteCube", + "email": "hello@whitecube.be", + "homepage": "http://www.whitecube.be", + "role": "Maintainer" + }, + { + "name": "Toon Van den Bos", + "email": "toon@whitecube.be", + "role": "Developer" + }, + { + "name": "Adrien Leloup", + "email": "adrien@whitecube.be", + "role": "Developer" + } + ], + "description": "A PHP-7 language codes converter, from and to the most common formats (ISO, W3C, PHP and human-readable names).", + "keywords": [ + "BCP-47", + "codes", + "conversion", + "iso", + "language", + "locales", + "localization", + "translate", + "translation" + ], + "time": "2020-02-06T15:01:22+00:00" + } + ], + "packages-dev": [ + { + "name": "roave/security-advisories", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/Roave/SecurityAdvisories.git", + "reference": "81541a731da2f245a08666de73169cb5da7ac573" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/81541a731da2f245a08666de73169cb5da7ac573", + "reference": "81541a731da2f245a08666de73169cb5da7ac573", + "shasum": "" + }, + "conflict": { + "3f/pygmentize": "<1.2", + "adodb/adodb-php": "<5.20.12", + "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", + "amphp/artax": "<1.0.6|>=2,<2.0.6", + "amphp/http": "<1.0.1", + "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6", + "asymmetricrypt/asymmetricrypt": ">=0,<9.9.99", + "aws/aws-sdk-php": ">=3,<3.2.1", + "bagisto/bagisto": "<0.1.5", + "bolt/bolt": "<3.6.10", + "brightlocal/phpwhois": "<=4.2.5", + "buddypress/buddypress": "<5.1.2", + "bugsnag/bugsnag-laravel": ">=2,<2.0.2", + "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.5.18|>=3.6,<3.6.15|>=3.7,<3.7.7", + "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", + "cartalyst/sentry": "<=2.1.6", + "centreon/centreon": "<18.10.8|>=19,<19.4.5", + "cesnet/simplesamlphp-module-proxystatistics": "<3.1", + "codeigniter/framework": "<=3.0.6", + "composer/composer": "<=1-alpha.11", + "contao-components/mediaelement": ">=2.14.2,<2.21.1", + "contao/core": ">=2,<3.5.39", + "contao/core-bundle": ">=4,<4.4.46|>=4.5,<4.8.6", + "contao/listing-bundle": ">=4,<4.4.8", + "datadog/dd-trace": ">=0.30,<0.30.2", + "david-garcia/phpwhois": "<=4.3.1", + "doctrine/annotations": ">=1,<1.2.7", + "doctrine/cache": ">=1,<1.3.2|>=1.4,<1.4.2", + "doctrine/common": ">=2,<2.4.3|>=2.5,<2.5.1", + "doctrine/dbal": ">=2,<2.0.8|>=2.1,<2.1.2", + "doctrine/doctrine-bundle": "<1.5.2", + "doctrine/doctrine-module": "<=0.7.1", + "doctrine/mongodb-odm": ">=1,<1.0.2", + "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", + "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", + "dolibarr/dolibarr": "<=10.0.6", + "dompdf/dompdf": ">=0.6,<0.6.2", + "drupal/core": ">=7,<7.69|>=8,<8.7.12|>=8.8,<8.8.4", + "drupal/drupal": ">=7,<7.69|>=8,<8.7.12|>=8.8,<8.8.4", + "endroid/qr-code-bundle": "<3.4.2", + "enshrined/svg-sanitize": "<0.13.1", + "erusev/parsedown": "<1.7.2", + "ezsystems/demobundle": ">=5.4,<5.4.6.1", + "ezsystems/ezdemo-ls-extension": ">=5.4,<5.4.2.1", + "ezsystems/ezfind-ls": ">=5.3,<5.3.6.1|>=5.4,<5.4.11.1|>=2017.12,<2017.12.0.1", + "ezsystems/ezplatform": ">=1.7,<1.7.9.1|>=1.13,<1.13.5.1|>=2.5,<2.5.4", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.6", + "ezsystems/ezplatform-admin-ui-assets": ">=4,<4.2", + "ezsystems/ezplatform-user": ">=1,<1.0.1", + "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.14.1|>=6,<6.7.9.1|>=6.8,<6.13.6.2|>=7,<7.2.4.1|>=7.3,<7.3.2.1|>=7.5,<7.5.6.2", + "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.14.1|>=2011,<2017.12.7.2|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3|>=2019.3,<2019.3.4.2", + "ezsystems/repository-forms": ">=2.3,<2.3.2.1", + "ezyang/htmlpurifier": "<4.1.1", + "firebase/php-jwt": "<2", + "fooman/tcpdf": "<6.2.22", + "fossar/tcpdf-parser": "<6.2.22", + "friendsofsymfony/oauth2-php": "<1.3", + "friendsofsymfony/rest-bundle": ">=1.2,<1.2.2", + "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", + "fuel/core": "<1.8.1", + "getgrav/grav": "<1.7-beta.8", + "gree/jose": "<=2.2", + "gregwar/rst": "<1.0.3", + "guzzlehttp/guzzle": ">=4-rc.2,<4.2.4|>=5,<5.3.1|>=6,<6.2.1", + "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", + "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", + "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", + "illuminate/encryption": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.40|>=5.6,<5.6.15", + "illuminate/view": ">=7,<7.1.2", + "ivankristianto/phpwhois": "<=4.3", + "james-heinrich/getid3": "<1.9.9", + "joomla/session": "<1.3.1", + "jsmitty12/phpwhois": "<5.1", + "kazist/phpwhois": "<=4.2.6", + "kreait/firebase-php": ">=3.2,<3.8.1", + "la-haute-societe/tcpdf": "<6.2.22", + "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30|>=7,<7.1.2", + "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", + "league/commonmark": "<0.18.3", + "librenms/librenms": "<1.53", + "magento/community-edition": ">=2,<2.2.10|>=2.3,<2.3.3", + "magento/magento1ce": "<1.9.4.3", + "magento/magento1ee": ">=1,<1.14.4.3", + "magento/product-community-edition": ">=2,<2.2.10|>=2.3,<2.3.2-p.2", + "monolog/monolog": ">=1.8,<1.12", + "namshi/jose": "<2.2", + "onelogin/php-saml": "<2.10.4", + "oneup/uploader-bundle": "<1.9.3|>=2,<2.1.5", + "openid/php-openid": "<2.3", + "oro/crm": ">=1.7,<1.7.4", + "oro/platform": ">=1.7,<1.7.4", + "padraic/humbug_get_contents": "<1.1.2", + "pagarme/pagarme-php": ">=0,<3", + "paragonie/random_compat": "<2", + "paypal/merchant-sdk-php": "<3.12", + "pear/archive_tar": "<1.4.4", + "phpfastcache/phpfastcache": ">=5,<5.0.13", + "phpmailer/phpmailer": ">=5,<5.2.27|>=6,<6.0.6", + "phpmyadmin/phpmyadmin": "<4.9.2", + "phpoffice/phpexcel": "<1.8.2", + "phpoffice/phpspreadsheet": "<1.8", + "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", + "phpwhois/phpwhois": "<=4.2.5", + "phpxmlrpc/extras": "<0.6.1", + "pimcore/pimcore": "<6.3", + "prestashop/autoupgrade": ">=4,<4.10.1", + "prestashop/gamification": "<2.3.2", + "prestashop/ps_facetedsearch": "<3.4.1", + "privatebin/privatebin": "<1.2.2|>=1.3,<1.3.2", + "propel/propel": ">=2-alpha.1,<=2-alpha.7", + "propel/propel1": ">=1,<=1.7.1", + "pusher/pusher-php-server": "<2.2.1", + "robrichards/xmlseclibs": "<3.0.4", + "sabre/dav": ">=1.6,<1.6.99|>=1.7,<1.7.11|>=1.8,<1.8.9", + "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", + "sensiolabs/connect": "<4.2.3", + "serluck/phpwhois": "<=4.2.6", + "shopware/shopware": "<5.3.7", + "silverstripe/admin": ">=1.0.3,<1.0.4|>=1.1,<1.1.1", + "silverstripe/assets": ">=1,<1.4.7|>=1.5,<1.5.2", + "silverstripe/cms": "<4.3.6|>=4.4,<4.4.4", + "silverstripe/comments": ">=1.3,<1.9.99|>=2,<2.9.99|>=3,<3.1.1", + "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", + "silverstripe/framework": "<4.4.5|>=4.5,<4.5.2", + "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.1.2", + "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", + "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", + "silverstripe/subsites": ">=2,<2.1.1", + "silverstripe/taxonomy": ">=1.3,<1.3.1|>=2,<2.0.1", + "silverstripe/userforms": "<3", + "simple-updates/phpwhois": "<=1", + "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", + "simplesamlphp/simplesamlphp": "<1.18.6", + "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", + "simplito/elliptic-php": "<1.0.6", + "slim/slim": "<2.6", + "smarty/smarty": "<3.1.33", + "socalnick/scn-social-auth": "<1.15.2", + "spoonity/tcpdf": "<6.2.22", + "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", + "ssddanbrown/bookstack": "<0.25.3", + "stormpath/sdk": ">=0,<9.9.99", + "studio-42/elfinder": "<2.1.49", + "swiftmailer/swiftmailer": ">=4,<5.4.5", + "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", + "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", + "sylius/grid-bundle": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", + "sylius/resource-bundle": "<1.3.13|>=1.4,<1.4.6|>=1.5,<1.5.1|>=1.6,<1.6.3", + "sylius/sylius": "<1.3.16|>=1.4,<1.4.12|>=1.5,<1.5.9|>=1.6,<1.6.5", + "symbiote/silverstripe-multivaluefield": ">=3,<3.0.99", + "symbiote/silverstripe-versionedfiles": "<=2.0.3", + "symfony/cache": ">=3.1,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", + "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/http-kernel": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8", + "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/mime": ">=4.3,<4.3.8", + "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/polyfill": ">=1,<1.10", + "symfony/polyfill-php55": ">=1,<1.10", + "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/routing": ">=2,<2.0.19", + "symfony/security": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7", + "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/serializer": ">=2,<2.0.11", + "symfony/symfony": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/translation": ">=2,<2.0.17", + "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", + "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", + "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", + "tecnickcom/tcpdf": "<6.2.22", + "thelia/backoffice-default-template": ">=2.1,<2.1.2", + "thelia/thelia": ">=2.1-beta.1,<2.1.3", + "theonedemon/phpwhois": "<=4.2.5", + "titon/framework": ">=0,<9.9.99", + "truckersmp/phpwhois": "<=4.3.1", + "twig/twig": "<1.38|>=2,<2.7", + "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", + "typo3/cms-core": ">=8,<8.7.30|>=9,<9.5.12|>=10,<10.2.1", + "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", + "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", + "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", + "ua-parser/uap-php": "<3.8", + "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", + "verot/class.upload.php": "<=1.0.3|>=2,<=2.0.4", + "wallabag/tcpdf": "<6.2.22", + "willdurand/js-translation-bundle": "<2.1.1", + "yii2mod/yii2-cms": "<1.9.2", + "yiisoft/yii": ">=1.1.14,<1.1.15", + "yiisoft/yii2": "<2.0.15", + "yiisoft/yii2-bootstrap": "<2.0.4", + "yiisoft/yii2-dev": "<2.0.15", + "yiisoft/yii2-elasticsearch": "<2.0.5", + "yiisoft/yii2-gii": "<2.0.4", + "yiisoft/yii2-jui": "<2.0.4", + "yiisoft/yii2-redis": "<2.0.8", + "yourls/yourls": "<1.7.4", + "zendframework/zend-cache": ">=2.4,<2.4.8|>=2.5,<2.5.3", + "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", + "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", + "zendframework/zend-diactoros": ">=1,<1.8.4", + "zendframework/zend-feed": ">=1,<2.10.3", + "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-http": ">=1,<2.8.1", + "zendframework/zend-json": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zend-ldap": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.8|>=2.3,<2.3.3", + "zendframework/zend-mail": ">=2,<2.4.11|>=2.5,<2.7.2", + "zendframework/zend-navigation": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-session": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.9|>=2.3,<2.3.4", + "zendframework/zend-validator": ">=2.3,<2.3.6", + "zendframework/zend-view": ">=2,<2.2.7|>=2.3,<2.3.1", + "zendframework/zend-xmlrpc": ">=2.1,<2.1.6|>=2.2,<2.2.6", + "zendframework/zendframework": "<2.5.1", + "zendframework/zendframework1": "<1.12.20", + "zendframework/zendopenid": ">=2,<2.0.2", + "zendframework/zendxml": ">=1,<1.0.1", + "zetacomponents/mail": "<1.8.2", + "zf-commons/zfc-user": "<1.2.2", + "zfcampus/zf-apigility-doctrine": ">=1,<1.0.3", + "zfr/zfr-oauth2-server-module": "<0.1.2" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "role": "maintainer" + }, + { + "name": "Ilya Tribusean", + "email": "slash3b@gmail.com", + "role": "maintainer" + } + ], + "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", + "time": "2020-04-23T00:01:30+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "roave/security-advisories": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/src/Component/TpayPayment/BankList/TpayBankListClient.php b/src/Component/TpayPayment/BankList/TpayBankListClient.php new file mode 100644 index 0000000..25aeb9f --- /dev/null +++ b/src/Component/TpayPayment/BankList/TpayBankListClient.php @@ -0,0 +1,100 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Component\TpayPayment\BankList; + + +use GuzzleHttp\Client; +use GuzzleHttp\Exception\ClientException; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerInterface; +use Shopware\Core\Framework\Struct\ArrayStruct; +use Shopware\Core\Framework\Struct\Collection; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Slim\Http\StatusCode; +use Symfony\Component\Cache\Adapter\PhpArrayAdapter; +use Tpay\ShopwarePayment\Config\Exception\TpayConfigInvalidException; +use Tpay\ShopwarePayment\Config\Service\ConfigServiceInterface; +use Tpay\ShopwarePayment\Config\TpayConfigStruct; +use Tpay\ShopwarePayment\Util\Payments\Blik; +use Tpay\ShopwarePayment\Util\Payments\Card; + +class TpayBankListClient implements TpayBankListInterface +{ + + private const BANK_LIST_CACHE_KEY = 'TpayShopwarePayment_Bank_List'; + + private const BANK_LIST_CACHE_LIFETIME = 3600; + + private const TPAY_ENDPOINT = 'https://secure.tpay.com/'; + + /** @var PhpArrayAdapter */ + private $cache; + + /** @var ConfigServiceInterface */ + private $configService; + + /** @var LoggerInterface */ + private $logger; + + public function __construct(PhpArrayAdapter $cache, ConfigServiceInterface $configService, LoggerInterface $logger) + { + $this->cache = $cache; + $this->configService = $configService; + $this->logger = $logger; + } + + public function getBankList(SalesChannelContext $salesChannelContext): ArrayStruct + { + try { + $tpayConfig = $this->configService->getConfigs($salesChannelContext->getSalesChannel()->getId()); + } catch (TpayConfigInvalidException $exception) { + $this->logger->error('Tpay configuration is not valid:' . PHP_EOL . $exception->getMessage()); + throw $exception; + } + + try { + $bankList = $this->cache->get( + self::BANK_LIST_CACHE_KEY . '_' . $tpayConfig->getMerchantId(), + static function () use ($tpayConfig): array { + return self::getBankListRaw($tpayConfig); + }, self::BANK_LIST_CACHE_LIFETIME + ) ?? []; + } catch (InvalidArgumentException $exception) { + $this->logger->warning('An error occurred while loading Tpau bank list from Shopware cache.' . PHP_EOL . $exception->getMessage()); + + $bankList = self::getBankListRaw($tpayConfig) ?? []; + } + + $bankListStruct = new ArrayStruct($bankList); + $bankListStruct->offsetUnset(Blik::ID); + $bankListStruct->offsetUnset(Card::ID); + + return $bankListStruct; + } + + public static function getBankListRaw(TpayConfigStruct $tpayConfig): ?array + { + $client = new Client(); + + $url = self::TPAY_ENDPOINT . 'groups-' . $tpayConfig->getMerchantID() . $tpayConfig->getChannels() . '.js?json'; + try { + $response = $client->get($url); + $responseBody = $response->getBody()->getContents(); + } catch (ClientException $e) { + $responseBody = ''; + } + + + return json_decode($responseBody, true); + } +} diff --git a/src/Component/TpayPayment/BankList/TpayBankListInterface.php b/src/Component/TpayPayment/BankList/TpayBankListInterface.php new file mode 100644 index 0000000..7fca250 --- /dev/null +++ b/src/Component/TpayPayment/BankList/TpayBankListInterface.php @@ -0,0 +1,22 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Component\TpayPayment\BankList; + + +use Shopware\Core\Framework\Struct\ArrayStruct; +use Shopware\Core\System\SalesChannel\SalesChannelContext; + +interface TpayBankListInterface +{ + public function getBankList(SalesChannelContext $salesChannelContext): ArrayStruct; +} diff --git a/src/Component/TpayPayment/BankList/TpayBankStruct.php b/src/Component/TpayPayment/BankList/TpayBankStruct.php new file mode 100644 index 0000000..b4cd337 --- /dev/null +++ b/src/Component/TpayPayment/BankList/TpayBankStruct.php @@ -0,0 +1,43 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Component\TpayPayment\BankList; + + +use Shopware\Core\Framework\Struct\Struct; + +class TpayBankStruct extends Struct +{ + /** @var int */ + protected $id; + + /** @var string */ + protected $name; + + /** @var string */ + protected $banks; + + /** @var string */ + protected $image; + + /** @var string */ + protected $mainBankId; + + public function __construct(array $bank) + { + $this->id = (int) $bank['id']; + $this->name = $bank['name']; + $this->banks = $bank['banks']; + $this->image = $bank['img']; + $this->mainBankId = $bank['main_bank_id']; + } +} diff --git a/src/Component/TpayPayment/TpayBasicApi.php b/src/Component/TpayPayment/TpayBasicApi.php new file mode 100644 index 0000000..3a7ee7b --- /dev/null +++ b/src/Component/TpayPayment/TpayBasicApi.php @@ -0,0 +1,29 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Component\TpayPayment; + + +use tpayLibs\src\_class_tpay\PaymentBlik; + +class TpayBasicApi extends PaymentBlik +{ + + public function __construct(int $merchantId, string $merchantSecret, string $apiKey, string $apiPass) + { + $this->merchantId = $merchantId; + $this->merchantSecret = $merchantSecret; + $this->trApiKey = $apiKey; + $this->trApiPass = $apiPass; + parent::__construct(); + } +} diff --git a/src/Component/TpayPayment/TpayBasicNotificationHandler.php b/src/Component/TpayPayment/TpayBasicNotificationHandler.php new file mode 100644 index 0000000..5b3d80f --- /dev/null +++ b/src/Component/TpayPayment/TpayBasicNotificationHandler.php @@ -0,0 +1,74 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Component\TpayPayment; + + +use tpayLibs\src\_class_tpay\Notifications\BasicNotificationHandler; +use tpayLibs\src\_class_tpay\Utilities\TException; +use tpayLibs\src\_class_tpay\Validators\PaymentTypes\PaymentTypeBasic; + +class TpayBasicNotificationHandler extends BasicNotificationHandler +{ + public function __construct($merchantId, $merchantSecret) + { + $this->merchantId = $merchantId; + $this->merchantSecret = $merchantSecret; + parent::__construct(); + } + + /** + * @inheritDoc + */ + public function checkPayment($response = 'TRUE'): array + { + + $res = $this->getResponse(new PaymentTypeBasic()); + + $this->setTransactionID($res['tr_id']); + $checkMD5 = $this->isMd5Valid( + $res['md5sum'], + number_format($res['tr_amount'], 2, '.', ''), + $res['tr_crc'] + ); + + if ($this->validateServerIP === true && $this->isTpayServer() === false) { + throw new TException('Request is not from secure server'); + } + if ($checkMD5 === false) { + throw new TException('MD5 checksum is invalid'); + } + + return $res; + } + + /** + * Check md5 sum to validate tpay response. + * The values of variables that md5 sum includes are available only for + * merchant and tpay system. + * + * @param string $md5sum md5 sum received from tpay + * @param float $transactionAmount transaction amount + * @param string $crc transaction crc + * + * @return bool + */ + private function isMd5Valid($md5sum, $transactionAmount, $crc): bool + { + if (!is_string($md5sum) || strlen($md5sum) !== 32) { + return false; + } + + return ($md5sum === md5($this->merchantId . $this->transactionID . + $transactionAmount . $crc . $this->merchantSecret)); + } +} diff --git a/src/Config/Exception/TpayConfigInvalidException.php b/src/Config/Exception/TpayConfigInvalidException.php new file mode 100644 index 0000000..7aa6751 --- /dev/null +++ b/src/Config/Exception/TpayConfigInvalidException.php @@ -0,0 +1,39 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 27 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Config\Exception; + + +use Shopware\Core\Framework\ShopwareHttpException; +use Symfony\Component\HttpFoundation\Response; + +class TpayConfigInvalidException extends ShopwareHttpException +{ + + public function __construct(string $missingConfig) + { + parent::__construct( + 'Required config field "{{ missingSetting }}" is missing or invalid', + ['missingConfig' => $missingConfig] + ); + } + + public function getStatusCode(): int + { + return Response::HTTP_NOT_FOUND; + } + + public function getErrorCode(): string + { + return 'TPAY_SHOPWARE_PAYMENT__REQUIRED_CONFIG_FIELD_INVALID'; + } +} diff --git a/src/Config/Exception/TpayInvalidMerchantCredentialsException.php b/src/Config/Exception/TpayInvalidMerchantCredentialsException.php new file mode 100644 index 0000000..b5c9dbc --- /dev/null +++ b/src/Config/Exception/TpayInvalidMerchantCredentialsException.php @@ -0,0 +1,35 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Config\Exception; + + +use Shopware\Core\Framework\ShopwareHttpException; +use Symfony\Component\HttpFoundation\Response; + +class TpayInvalidMerchantCredentialsException extends ShopwareHttpException +{ + public function __construct($e) + { + parent::__construct($e); + } + + public function getStatusCode(): int + { + return Response::HTTP_FORBIDDEN; + } + + public function getErrorCode(): string + { + return 'TPAY_SHOPWARE_PAYMENT__INVALID_MERCHANT_CREDENTIALS'; + } +} diff --git a/src/Config/Service/ConfigService.php b/src/Config/Service/ConfigService.php new file mode 100644 index 0000000..943bbfb --- /dev/null +++ b/src/Config/Service/ConfigService.php @@ -0,0 +1,51 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Config\Service; + + +use Shopware\Core\System\SystemConfig\SystemConfigService; +use Tpay\ShopwarePayment\Config\Exception\TpayConfigInvalidException; +use Tpay\ShopwarePayment\Config\TpayConfigStructValidator; +use Tpay\ShopwarePayment\Config\TpayConfigStruct; + +class ConfigService implements ConfigServiceInterface +{ + public const SYSTEM_CONFIG_DOMAIN = 'TpayShopwarePayment.config'; + + /** + * @var SystemConfigService + */ + private $systemConfigService; + + public function __construct(SystemConfigService $systemConfigService) + { + $this->systemConfigService = $systemConfigService; + } + + /** + * @throws TpayConfigInvalidException + */ + public function getConfigs(?string $salesChannelId = null): TpayConfigStruct + { + $values = $this->systemConfigService->get( + self::SYSTEM_CONFIG_DOMAIN, + $salesChannelId + ); + + $configStruct = new TpayConfigStruct(); + $configStruct->assign($values); + TpayConfigStructValidator::validate($configStruct); + + return $configStruct; + } +} diff --git a/src/Config/Service/ConfigServiceInterface.php b/src/Config/Service/ConfigServiceInterface.php new file mode 100644 index 0000000..c049ac5 --- /dev/null +++ b/src/Config/Service/ConfigServiceInterface.php @@ -0,0 +1,21 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Config\Service; + + +use Tpay\ShopwarePayment\Config\TpayConfigStruct; + +interface ConfigServiceInterface +{ + public function getConfigs(?string $salesChannelId = null): TpayConfigStruct; +} diff --git a/src/Config/Service/MerchantCredentialsService.php b/src/Config/Service/MerchantCredentialsService.php new file mode 100644 index 0000000..04c89d1 --- /dev/null +++ b/src/Config/Service/MerchantCredentialsService.php @@ -0,0 +1,68 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Config\Service; + + +use Tpay\ShopwarePayment\Component\TpayPayment\BankList\TpayBankListClient; +use Tpay\ShopwarePayment\Component\TpayPayment\TpayBasicApi; +use Tpay\ShopwarePayment\Config\Exception\TpayInvalidMerchantCredentialsException; +use Tpay\ShopwarePayment\Config\TpayConfigStruct; +use Tpay\ShopwarePayment\Config\Util\TestCredentialsTransaction; +use tpayLibs\src\_class_tpay\Utilities\TException; + +class MerchantCredentialsService implements MerchantCredentialsServiceInterface +{ + private TpayBankListClient $tpayBankListClient; + + public function __construct(TpayBankListClient $tpayBankListClient) + { + $this->tpayBankListClient = $tpayBankListClient; + } + + public function testMerchantCredentials(int $merchantId, string $merchantSecret, string $merchantTrApiKey, string $merchantTrApiPass): bool + { + $config = new TpayConfigStruct(); + $config->assign(['merchantId' => $merchantId, 'merchantSecret' => $merchantSecret, 'merchantTrApiKey' => $merchantTrApiKey, 'merchantTrApiPass' => $merchantTrApiPass]); + + $testTransactionConfig = TestCredentialsTransaction::getTestTransactionData(); + + $banks = $this->tpayBankListClient->getBankListRaw($config); + $group = null; + foreach ($banks as $bank) { + if ($bank['name'] === "BLIK") { + $group = (int) $bank['id']; + } + } + if ($group === null) { + $group = (int) $banks[array_key_first($banks)]['id']; + } + + $testTransactionConfig->setGroup($group); + + $basicApi = new TpayBasicApi( + $merchantId, + $merchantSecret, + $merchantTrApiKey, + $merchantTrApiPass + ); + + try { + $basicApi->create($testTransactionConfig->getTransactionConfig()); + } catch (TException $e) { + throw new TpayInvalidMerchantCredentialsException($e->getMessage()); + } + + return true; + } +} + diff --git a/src/Config/Service/MerchantCredentialsServiceInterface.php b/src/Config/Service/MerchantCredentialsServiceInterface.php new file mode 100644 index 0000000..2c4f124 --- /dev/null +++ b/src/Config/Service/MerchantCredentialsServiceInterface.php @@ -0,0 +1,20 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Config\Service; + + +interface MerchantCredentialsServiceInterface +{ + public function testMerchantCredentials(int $merchantId, string $merchantSecret, string $merchantTrApiKey, string $merchantTrApiPass): bool; + +} diff --git a/src/Config/TpayConfigController.php b/src/Config/TpayConfigController.php new file mode 100644 index 0000000..2924b63 --- /dev/null +++ b/src/Config/TpayConfigController.php @@ -0,0 +1,71 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + +namespace Tpay\ShopwarePayment\Config; + + +use Shopware\Core\Framework\Routing\Annotation\RouteScope; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; +use Tpay\ShopwarePayment\Config\Service\MerchantCredentialsServiceInterface; +use tpayLibs\src\_class_tpay\Utilities\Util; + +/** + * @RouteScope(scopes={"api"}) + */ +class TpayConfigController extends AbstractController +{ + + /** @var MerchantCredentialsServiceInterface */ + private $merchantCredentialsService; + + public function __construct(MerchantCredentialsServiceInterface $merchantCredentialsService) + { + $this->merchantCredentialsService = $merchantCredentialsService; + } + + /** + * @Route("/api/_action/tpay/validate-merchant-credentials", name="api.action.tpay.validate.merchant.credentials", methods={"POST"}) + */ + public function validateMerchantCredentials(Request $request): JsonResponse + { + Util::$loggingEnabled = false; + + $merchantId = $request->request->getInt('merchantId'); + if(empty($merchantId)) { + return new JsonResponse(['success' => false, 'code' => 'merchantId']); + } + $merchantSecret = $request->request->get('merchantSecret'); + if(empty($merchantSecret)) { + return new JsonResponse(['success' => false, 'code' => 'merchantSecret']); + } + $merchantTrApiKey = $request->request->get('merchantTrApiKey'); + if(empty($merchantTrApiKey)) { + return new JsonResponse(['success' => false, 'code' => 'merchantTrApiKey']); + } + $merchantTrApiPass = $request->request->get('merchantTrApiPass'); + if(empty($merchantTrApiPass)) { + return new JsonResponse(['success' => false, 'code' => 'merchantTrApiPass']); + } + + try { + $credentialsValid = $this->merchantCredentialsService->testMerchantCredentials($merchantId, $merchantSecret, $merchantTrApiKey, $merchantTrApiPass); + }catch (\Throwable $e) { + return new JsonResponse(['success' => false, 'code' => $e->getMessage()]); + } + + return new JsonResponse(['success' => true, 'credentialsValid' => $credentialsValid]); + } +} diff --git a/src/Config/TpayConfigStruct.php b/src/Config/TpayConfigStruct.php new file mode 100644 index 0000000..2113880 --- /dev/null +++ b/src/Config/TpayConfigStruct.php @@ -0,0 +1,153 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Config; + + +use Shopware\Core\Framework\Struct\Struct; + +class TpayConfigStruct extends Struct +{ + /** @var int */ + protected $merchantId; + + /** @var string */ + protected $merchantSecret; + + /** @var string */ + protected $merchantTrApiKey; + + /** @var string */ + protected $merchantTrApiPass; + + /** @var int */ + protected $channels; + + /** @var bool */ + protected $redirectDirectlyToTheBank = false; + + /** @var bool */ + protected $verificationSenderIpAddressOfPaymentNotification = true; + + /** + * @return string + */ + public function getMerchantId(): int + { + return (int) $this->merchantId; + } + + /** + * @param string $merchantId + */ + public function setMerchantId(string $merchantId): void + { + $this->merchantId = $merchantId; + } + + /** + * @return string + */ + public function getMerchantSecret(): string + { + return $this->merchantSecret; + } + + /** + * @param string $merchantSecret + */ + public function setMerchantSecret(string $merchantSecret): void + { + $this->merchantSecret = $merchantSecret; + } + + /** + * @return string + */ + public function getMerchantTransactionApiKey(): string + { + return $this->merchantTrApiKey; + } + + /** + * @param string $merchantTrApiKey + */ + public function setMerchantTransactionApiKey(string $merchantTrApiKey): void + { + $this->merchantTrApiKey = $merchantTrApiKey; + } + + /** + * @return string + */ + public function getMerchantTransactionApiPassword(): string + { + return $this->merchantTrApiPass; + } + + /** + * @param string $merchantTrApiPass + */ + public function setMerchantTransactionApiPassword(string $merchantTrApiPass): void + { + $this->merchantTrApiPass = $merchantTrApiPass; + } + + /** + * @return int + */ + public function getChannels(): int + { + return (int) $this->channels; + } + + /** + * @param int $channels + */ + public function setChannels(int $channels): void + { + $this->channels = $channels; + } + + /** + * @return bool + */ + public function isRedirectDirectlyToTheBank(): bool + { + return $this->redirectDirectlyToTheBank; + } + + /** + * @param bool $redirectDirectlyToTheBank + */ + public function setRedirectDirectlyToTheBank(bool $redirectDirectlyToTheBank): void + { + $this->redirectDirectlyToTheBank = $redirectDirectlyToTheBank; + } + + /** + * @return bool + */ + public function isVerificationSenderIpAddressOfPaymentNotification(): bool + { + return $this->verificationSenderIpAddressOfPaymentNotification; + } + + /** + * @param bool $verificationSenderIpAddressOfPaymentNotification + */ + public function setVerificationSenderIpAddressOfPaymentNotification(bool $verificationSenderIpAddressOfPaymentNotification): void + { + $this->verificationSenderIpAddressOfPaymentNotification = $verificationSenderIpAddressOfPaymentNotification; + } + +} diff --git a/src/Config/TpayConfigStructValidator.php b/src/Config/TpayConfigStructValidator.php new file mode 100644 index 0000000..4c7f758 --- /dev/null +++ b/src/Config/TpayConfigStructValidator.php @@ -0,0 +1,49 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Config; + + +use Tpay\ShopwarePayment\Config\Exception\TpayConfigInvalidException; + +class TpayConfigStructValidator +{ + /** + * @throws TpayConfigInvalidException + */ + public static function validate(TpayConfigStruct $generalStruct): void + { + try { + $generalStruct->getMerchantId(); + } catch (\TypeError $error) { + throw new TpayConfigInvalidException('MerchantId'); + } + + try { + $generalStruct->getMerchantSecret(); + } catch (\TypeError $error) { + throw new TpayConfigInvalidException('MerchantSecret'); + } + + try { + $generalStruct->getMerchantTransactionApiKey(); + } catch (\TypeError $error) { + throw new TpayConfigInvalidException('MerchantTransactionApiKey'); + } + + try { + $generalStruct->getMerchantTransactionApiPassword(); + } catch (\TypeError $error) { + throw new TpayConfigInvalidException('MerchantTransactionApiPassword'); + } + } +} diff --git a/src/Config/TpayTransactionConfigStruct.php b/src/Config/TpayTransactionConfigStruct.php new file mode 100644 index 0000000..0e5eaaa --- /dev/null +++ b/src/Config/TpayTransactionConfigStruct.php @@ -0,0 +1,383 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 24 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + + +namespace Tpay\ShopwarePayment\Config; + + +use Shopware\Core\Checkout\Customer\CustomerEntity; +use Shopware\Core\Framework\Struct\Struct; + +class TpayTransactionConfigStruct extends Struct +{ + /** @var float */ + protected $amount; + + /** @var string */ + protected $crc; + + /** @var int */ + protected $group; + + /** @var string */ + protected $name; + + /** @var string */ + protected $city; + + /** @var string */ + protected $address; + + /** @var string */ + protected $zip; + + /** @var string */ + protected $return_url; + + /** @var string */ + protected $return_error_url; + + /** @var string */ + protected $result_url; + + /** @var string */ + protected $email; + + /** @var string */ + protected $language; + + /** @var string */ + protected $module = 'Shopware'; + + /** @var string */ + protected $description; + + /** @var string */ + protected $phone; + + /** @var string */ + protected $country; + + /** + * @return mixed + */ + public function getAmount() + { + return $this->amount; + } + + /** + * @param $amount + * + * @return $this + */ + public function setAmount($amount): TpayTransactionConfigStruct + { + $this->amount = $amount; + return $this; + } + + /** + * @return mixed + */ + public function getCrc() + { + return $this->crc; + } + + /** + * @param mixed $crc + * + * @return TpayTransactionConfigStruct + */ + public function setCrc($crc): TpayTransactionConfigStruct + { + $this->crc = $crc; + return $this; + } + + public function getGroup(): int + { + return $this->group; + } + + + public function setGroup(int $group): TpayTransactionConfigStruct + { + $this->group = $group; + return $this; + } + + public function getName(): string + { + return $this->name; + } + + /** + * @param mixed $name + * + * @return TpayTransactionConfigStruct + */ + public function setName($name): TpayTransactionConfigStruct + { + $this->name = $name; + return $this; + } + + /** + * @return mixed + */ + public function getCity() + { + return $this->city; + } + + /** + * @param mixed $city + * + * @return TpayTransactionConfigStruct + */ + public function setCity($city): TpayTransactionConfigStruct + { + $this->city = $city; + return $this; + } + + /** + * @return mixed + */ + public function getAddress() + { + return $this->address; + } + + /** + * @param mixed $address + * + * @return TpayTransactionConfigStruct + */ + public function setAddress($address): TpayTransactionConfigStruct + { + $this->address = $address; + return $this; + } + + /** + * @return mixed + */ + public function getZip() + { + return $this->zip; + } + + /** + * @param mixed $zip + * + * @return TpayTransactionConfigStruct + */ + public function setZip($zip): TpayTransactionConfigStruct + { + $this->zip = $zip; + return $this; + } + + /** + * @return mixed + */ + public function getReturnUrl() + { + return $this->return_url; + } + + /** + * @param mixed $return_url + * + * @return TpayTransactionConfigStruct + */ + public function setReturnUrl($return_url): TpayTransactionConfigStruct + { + $this->return_url = $return_url; + return $this; + } + + /** + * @return mixed + */ + public function getReturnErrorUrl() + { + return $this->return_error_url; + } + + /** + * @param mixed $return_error_url + * + * @return TpayTransactionConfigStruct + */ + public function setReturnErrorUrl($return_error_url): TpayTransactionConfigStruct + { + $this->return_error_url = $return_error_url; + return $this; + } + + /** + * @return mixed + */ + public function getResultUrl() + { + return $this->result_url; + } + + /** + * @param mixed $result_url + * + * @return TpayTransactionConfigStruct + */ + public function setResultUrl($result_url): TpayTransactionConfigStruct + { + $this->result_url = $result_url; + return $this; + } + + /** + * @return mixed + */ + public function getEmail() + { + return $this->email; + } + + /** + * @param mixed $email + * + * @return TpayTransactionConfigStruct + */ + public function setEmail($email): TpayTransactionConfigStruct + { + $this->email = $email; + return $this; + } + + /** + * @return mixed + */ + public function getLanguage() + { + return $this->language; + } + + /** + * @param mixed $language + * + * @return TpayTransactionConfigStruct + */ + public function setLanguage($language): TpayTransactionConfigStruct + { + $this->language = $language; + return $this; + } + + /** + * @return mixed + */ + public function getModule() + { + return $this->module; + } + + /** + * @param mixed $module + * + * @return TpayTransactionConfigStruct + */ + public function setModule($module): TpayTransactionConfigStruct + { + $this->module = $module; + return $this; + } + + /** + * @return mixed + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param mixed $description + * + * @return TpayTransactionConfigStruct + */ + public function setDescription($description): TpayTransactionConfigStruct + { + $this->description = $description; + return $this; + } + + /** + * @return mixed + */ + public function getPhone() + { + return $this->phone; + } + + /** + * @param mixed $phone + * + * @return TpayTransactionConfigStruct + */ + public function setPhone($phone): TpayTransactionConfigStruct + { + $this->phone = $phone; + return $this; + } + + /** + * @return mixed + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param mixed $country + * + * @return TpayTransactionConfigStruct + */ + public function setCountry($country): TpayTransactionConfigStruct + { + $this->country = $country; + return $this; + } + + public function getTransactionConfig(): array + { + return array_filter($this->getVars()); + } + + public function setBuyer(CustomerEntity $buyer): TpayTransactionConfigStruct + { + $billingAddress = $buyer->getActiveBillingAddress(); + + $this->name = $buyer->getFirstName() . ' ' . $buyer->getLastName(); + $this->email = $buyer->getEmail(); + $this->address = $billingAddress->getStreet(); + $this->zip = $billingAddress->getZipcode(); + $this->city = $billingAddress->getCity(); + $this->country = $billingAddress->getCountry()->getTranslated()['name']; + $this->phone = $this->getPhone(); + + return $this; + } +} diff --git a/src/Config/Util/TestCredentialsTransaction.php b/src/Config/Util/TestCredentialsTransaction.php new file mode 100644 index 0000000..0ae6459 --- /dev/null +++ b/src/Config/Util/TestCredentialsTransaction.php @@ -0,0 +1,30 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Config\Util; + + +use Tpay\ShopwarePayment\Config\TpayTransactionConfigStruct; + +class TestCredentialsTransaction +{ + public static function getTestTransactionData(): TpayTransactionConfigStruct + { + return (new TpayTransactionConfigStruct()) + ->setAmount(100) + ->setGroup(150) + ->setEmail('test@example.com') + ->setName('TEST MERCHANT CREDENTIALS BY SHOPWARE') + ->setDescription('TEST MERCHANT CREDENTIALS BY SHOPWARE'); + } + +} diff --git a/src/DependencyInjection/component.xml b/src/DependencyInjection/component.xml new file mode 100644 index 0000000..4874142 --- /dev/null +++ b/src/DependencyInjection/component.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/src/DependencyInjection/config.xml b/src/DependencyInjection/config.xml new file mode 100644 index 0000000..68d25b1 --- /dev/null +++ b/src/DependencyInjection/config.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/DependencyInjection/entity.xml b/src/DependencyInjection/entity.xml new file mode 100644 index 0000000..4c698cd --- /dev/null +++ b/src/DependencyInjection/entity.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/DependencyInjection/payment.xml b/src/DependencyInjection/payment.xml new file mode 100644 index 0000000..4bcd81e --- /dev/null +++ b/src/DependencyInjection/payment.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/DependencyInjection/subscriber.xml b/src/DependencyInjection/subscriber.xml new file mode 100644 index 0000000..cd26fe2 --- /dev/null +++ b/src/DependencyInjection/subscriber.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/DependencyInjection/util.xml b/src/DependencyInjection/util.xml new file mode 100644 index 0000000..03b1dca --- /dev/null +++ b/src/DependencyInjection/util.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + tpay_shopware_payment + + + + diff --git a/src/DependencyInjection/webhook.xml b/src/DependencyInjection/webhook.xml new file mode 100644 index 0000000..f32673f --- /dev/null +++ b/src/DependencyInjection/webhook.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Entity/PaymentTokenDefinition.php b/src/Entity/PaymentTokenDefinition.php new file mode 100644 index 0000000..485d0a2 --- /dev/null +++ b/src/Entity/PaymentTokenDefinition.php @@ -0,0 +1,31 @@ +addFlags(new PrimaryKey(), new Required()), + (new StringField('token', 'token', 1024))->addFlags(new Required()), + ]); + } +} diff --git a/src/Migration/Migration1614241149PaymentToken.php b/src/Migration/Migration1614241149PaymentToken.php new file mode 100644 index 0000000..24383bd --- /dev/null +++ b/src/Migration/Migration1614241149PaymentToken.php @@ -0,0 +1,32 @@ +executeStatement(" + CREATE TABLE IF NOT EXISTS `tpay_payment_tokens` ( + `id` BINARY(16) NOT NULL, + `token` VARCHAR(1024) NULL, + `created_at` DATETIME(3) NOT NULL, + `updated_at` DATETIME(3) NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + "); + } + + public function updateDestructive(Connection $connection): void + { + // implement update destructive + } +} diff --git a/src/Payment/BankTransferPaymentHandler.php b/src/Payment/BankTransferPaymentHandler.php new file mode 100644 index 0000000..32f60aa --- /dev/null +++ b/src/Payment/BankTransferPaymentHandler.php @@ -0,0 +1,90 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment; + + +use Psr\Log\LoggerInterface; +use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException; +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler; +use Shopware\Core\Checkout\Payment\Cart\PaymentHandler\AsynchronousPaymentHandlerInterface; +use Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct; +use Shopware\Core\Checkout\Payment\Exception\AsyncPaymentProcessException; +use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Tpay\ShopwarePayment\Payment\Builder\PaymentBuilderInterface; +use tpayLibs\src\_class_tpay\Utilities\TException; +use tpayLibs\src\_class_tpay\Utilities\Util; + +class BankTransferPaymentHandler implements AsynchronousPaymentHandlerInterface +{ + use TpayResponseHandlerTrait; + + /** @var PaymentBuilderInterface */ + private $bankTransferBuilder; + + /** @var LoggerInterface */ + private $logger; + + public function __construct(PaymentBuilderInterface $bankTransferBuilder, LoggerInterface $logger) + { + $this->bankTransferBuilder = $bankTransferBuilder; + $this->logger = $logger; + } + + /** + * @inheritDoc + */ + public function pay( + AsyncPaymentTransactionStruct $transaction, + RequestDataBag $dataBag, SalesChannelContext $salesChannelContext + ): RedirectResponse + { + Util::$loggingEnabled = false; + + $customer = $salesChannelContext->getCustomer(); + if ($customer === null) { + throw new AsyncPaymentProcessException( + $transaction->getOrderTransaction()->getId(), + (new CustomerNotLoggedInException())->getMessage() + ); + } + + try { + $tpayResponse = $this->bankTransferBuilder->createTransaction($transaction, $salesChannelContext, $customer); + + return $this->handleTpayResponse($tpayResponse, $transaction); + } catch (TException $exception) { + $this->logger->error('Tpay connection error' . PHP_EOL . $exception->getMessage()); + } + + throw new AsyncPaymentProcessException($transaction->getOrderTransaction()->getId(), 'Tpay transaction error'); + } + + /** + * @inheritDoc + */ + public function finalize( + AsyncPaymentTransactionStruct $transaction, + Request $request, + SalesChannelContext $salesChannelContext + ): void + { + /** + * @See \Tpay\ShopwarePayment\Payment\FinalizePaymentController + * Nothing to do here. + */ + } + +} diff --git a/src/Payment/BlikPaymentHandler.php b/src/Payment/BlikPaymentHandler.php new file mode 100644 index 0000000..9137cc6 --- /dev/null +++ b/src/Payment/BlikPaymentHandler.php @@ -0,0 +1,82 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment; + + +use Psr\Log\LoggerInterface; +use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException; +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler; +use Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct; +use Shopware\Core\Checkout\Payment\Cart\PaymentHandler\SynchronousPaymentHandlerInterface; +use Shopware\Core\Checkout\Payment\Cart\SyncPaymentTransactionStruct; +use Shopware\Core\Checkout\Payment\Exception\SyncPaymentProcessException; +use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Symfony\Component\HttpFoundation\Request; +use Tpay\ShopwarePayment\Payment\Builder\BlikPaymentBuilderInterface; +use Tpay\ShopwarePayment\Payment\Exception\InvalidBlikCodeException; +use tpayLibs\src\_class_tpay\Utilities\Util; + +class BlikPaymentHandler implements SynchronousPaymentHandlerInterface +{ + use TpayResponseHandlerTrait; + + /** @var BlikPaymentBuilderInterface */ + private $blikPaymentBuilder; + + /** @var LoggerInterface */ + private $logger; + + public function __construct(BlikPaymentBuilderInterface $blikPaymentBuilder, LoggerInterface $logger) + { + $this->blikPaymentBuilder = $blikPaymentBuilder; + $this->logger = $logger; + } + + /** + * @inheritDoc + */ + public function pay(SyncPaymentTransactionStruct $transaction, RequestDataBag $dataBag, SalesChannelContext $salesChannelContext): void + { + Util::$loggingEnabled = false; + + $customer = $salesChannelContext->getCustomer(); + if ($customer === null) { + throw new SyncPaymentProcessException( + $transaction->getOrderTransaction()->getId(), + (new CustomerNotLoggedInException())->getMessage() + ); + } + + $responseBlik = $this->blikPaymentBuilder->createBlikTransaction($transaction, $salesChannelContext, $customer, $dataBag->getDigits('blikCode')); + + if (isset($responseBlik['result']) && (int) $responseBlik['result'] !== 1 ) { + if ($responseBlik['err'] === 'ERR63') { + throw new InvalidBlikCodeException(); + } + + $this->tpayResponseError($responseBlik, $transaction); + } + } + + /** + * @inheritDoc + */ + public function finalize(AsyncPaymentTransactionStruct $transaction, Request $request, SalesChannelContext $salesChannelContext): void + { + /** + * @See Tpay\ShopwarePayment\Payment\FinalizePaymentController + * Nothing to do here. + */ + } +} diff --git a/src/Payment/Builder/AbstractPaymentBuilder.php b/src/Payment/Builder/AbstractPaymentBuilder.php new file mode 100644 index 0000000..ec6d64e --- /dev/null +++ b/src/Payment/Builder/AbstractPaymentBuilder.php @@ -0,0 +1,177 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\Builder; + + +use Psr\Log\LoggerInterface; +use Shopware\Core\Checkout\Customer\CustomerEntity; +use Shopware\Core\Checkout\Order\OrderEntity; +use Shopware\Core\Checkout\Payment\Cart\SyncPaymentTransactionStruct; +use Shopware\Core\Checkout\Payment\Cart\Token\TokenFactoryInterfaceV2; +use Shopware\Core\Checkout\Payment\Cart\Token\TokenStruct; +use Shopware\Core\Framework\Adapter\Translation\Translator; +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; +use Shopware\Core\Framework\Uuid\Uuid; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouterInterface; +use Tpay\ShopwarePayment\Config\Exception\TpayConfigInvalidException; +use Tpay\ShopwarePayment\Config\Service\ConfigServiceInterface; +use Tpay\ShopwarePayment\Config\TpayConfigStruct; +use Tpay\ShopwarePayment\Config\TpayTransactionConfigStruct; +use Tpay\ShopwarePayment\Util\Locale\LocaleProvider; +use Tpay\ShopwarePayment\Component\TpayPayment\TpayBasicApi; +use tpayLibs\src\_class_tpay\Utilities\TException; + +abstract class AbstractPaymentBuilder implements PaymentBuilderInterface +{ + + /** @var ConfigServiceInterface */ + protected $configService; + + /** @var EntityRepositoryInterface */ + private $tpayPaymentTokenRepository; + + /** @var TpayConfigStruct */ + protected $config; + + /** @var LocaleProvider */ + protected $localeProvider; + + /** @var TokenFactoryInterfaceV2 */ + protected $tokenFactory; + + /** @var RouterInterface */ + protected $router; + + /** @var Translator */ + protected $translator; + + /** @var LoggerInterface */ + protected $logger; + + public function __construct( + ConfigServiceInterface $configService, + LocaleProvider $localeProvider, + TokenFactoryInterfaceV2 $tokenFactory, + RouterInterface $router, + Translator $translator, + LoggerInterface $logger, + EntityRepositoryInterface $tpayPaymentTokenRepository + ) { + $this->configService = $configService; + $this->localeProvider = $localeProvider; + $this->tokenFactory = $tokenFactory; + $this->router = $router; + $this->translator = $translator; + $this->logger = $logger; + $this->tpayPaymentTokenRepository = $tpayPaymentTokenRepository; + } + + /** + * @throws TException + */ + public function createTransaction(SyncPaymentTransactionStruct $transaction, SalesChannelContext $salesChannelContext, CustomerEntity $customer): array + { + $order = $transaction->getOrder(); + + try { + $this->config = $this->configService->getConfigs($salesChannelContext->getSalesChannel()->getId()); + } catch (TpayConfigInvalidException $exception) { + $this->logger->error('Tpay configuration is not valid:' . PHP_EOL . $exception->getMessage()); + throw $exception; + } + + $tpayTransactionConfig = $this->getTpayTransactionConfig($transaction, $order, $customer, $salesChannelContext); + + $basicApi = $this->createBasicApi(); + + return $basicApi->create($tpayTransactionConfig->getTransactionConfig()); + } + + protected function getTpayTransactionConfig(SyncPaymentTransactionStruct $transaction, OrderEntity $order, CustomerEntity $customer, SalesChannelContext $salesChannelContext): TpayTransactionConfigStruct + { + $tpayTransactionConfig = new TpayTransactionConfigStruct(); + $token = $this->handleToken($transaction); + + $tpayTransactionConfig + ->setAmount($order->getAmountTotal()) + + ->setLanguage($this->localeProvider->getLocaleCodeFromContext($salesChannelContext->getContext())) + ->setBuyer($customer) + ->setResultUrl($this->assembleResultUrl($token, $salesChannelContext->getContext())) + ->setReturnUrl($this->assembleReturnUrl($token, $salesChannelContext->getContext())) + ->setDescription($this->translator->trans('tpay.config.transaction.description') . ' ' . $order->getOrderNumber()) + ->setCrc($transaction->getOrderTransaction()->getId()); + + return $tpayTransactionConfig; + } + + final public function createBasicApi(?SalesChannelContext $salesChannelContext = null): TpayBasicApi + { + $config = $salesChannelContext ? $this->config ?? $this->configService->getConfigs($salesChannelContext->getSalesChannel()->getId()) : $this->config; + + return new TpayBasicApi( + (int) $config->getMerchantID(), + $config->getMerchantSecret(), + $config->getMerchantTransactionApiKey(), + $config->getMerchantTransactionApiPassword() + ); + } + + final public function handleToken(SyncPaymentTransactionStruct $transaction): string + { + $tokenStruct = new TokenStruct( + $transaction->getOrder()->getId(), + null, + $transaction->getOrderTransaction()->getPaymentMethodId(), + $transaction->getOrderTransaction()->getId(), + null, + 259200, // 3 days + null + ); + + return $this->tokenFactory->generateToken($tokenStruct); + } + + private function assembleResultUrl(string $token, Context $context): string + { + $id = Uuid::randomHex(); + $this->tpayPaymentTokenRepository->upsert([ + [ + 'id' => $id, + 'token' => $token + ] + ], $context); + + $parameter = ['tokenId' => $id]; + + return $this->router->generate('action.tpay.webhook.notify', $parameter, UrlGeneratorInterface::ABSOLUTE_URL); + } + + private function assembleReturnUrl(string $token, Context $context): string + { + $id = Uuid::randomHex(); + $this->tpayPaymentTokenRepository->upsert([ + [ + 'id' => $id, + 'token' => $token + ] + + ], $context); + $parameter = ['tokenId' => $id]; + + return $this->router->generate('tpay.payment.return-url', $parameter, UrlGeneratorInterface::ABSOLUTE_URL); + } +} diff --git a/src/Payment/Builder/BankTransferPaymentBuilder.php b/src/Payment/Builder/BankTransferPaymentBuilder.php new file mode 100644 index 0000000..c33cda8 --- /dev/null +++ b/src/Payment/Builder/BankTransferPaymentBuilder.php @@ -0,0 +1,65 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\Builder; + + +use Shopware\Core\Checkout\Customer\CustomerEntity; +use Shopware\Core\Checkout\Order\OrderEntity; +use Shopware\Core\Checkout\Payment\Cart\SyncPaymentTransactionStruct; +use Shopware\Core\Checkout\Payment\Exception\AsyncPaymentProcessException; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Tpay\ShopwarePayment\Config\TpayTransactionConfigStruct; +use Tpay\ShopwarePayment\Payment\Exception\MissingRequiredBankIdException; +use Tpay\ShopwarePayment\TpayShopwarePayment; + +class BankTransferPaymentBuilder extends AbstractPaymentBuilder +{ + public function createTransaction(SyncPaymentTransactionStruct $transaction, SalesChannelContext $salesChannelContext, CustomerEntity $customer): array + { + $tpayTransaction = parent::createTransaction($transaction, $salesChannelContext, $customer); + $tpayTransaction['url'] = $this->handleUrl($tpayTransaction['url']); + + return $tpayTransaction; + } + + protected function getTpayTransactionConfig( + SyncPaymentTransactionStruct $transaction, + OrderEntity $order, + CustomerEntity $customer, + SalesChannelContext $salesChannelContext + ): TpayTransactionConfigStruct { + $tpayTransactionConfig = parent::getTpayTransactionConfig($transaction, $order, $customer, $salesChannelContext); + $bankId = (int) $customer->getCustomFields()[TpayShopwarePayment::CUSTOMER_CUSTOM_FIELDS_TPAY_SELECTED_BANK]['id']; + + if (!$bankId) { + $exceptionMessage = (new MissingRequiredBankIdException())->getMessage(); + $this->logger->error('Missing required bank id.' . PHP_EOL . $exceptionMessage, $transaction->jsonSerialize()); + throw new AsyncPaymentProcessException( + $transaction->getOrderTransaction()->getId(), + $exceptionMessage + ); + } + $tpayTransactionConfig->setGroup($bankId); + + return $tpayTransactionConfig; + } + + private function handleUrl(string $url): string + { + if ($this->config->isRedirectDirectlyToTheBank()) { + $url = str_replace('?gtitle=', '?title=', $url); + } + + return $url; + } +} diff --git a/src/Payment/Builder/BlikPaymentBuilder.php b/src/Payment/Builder/BlikPaymentBuilder.php new file mode 100644 index 0000000..e9507ef --- /dev/null +++ b/src/Payment/Builder/BlikPaymentBuilder.php @@ -0,0 +1,79 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\Builder; + + +use Psr\Log\LoggerInterface; +use Shopware\Core\Checkout\Customer\CustomerEntity; +use Shopware\Core\Checkout\Order\OrderEntity; +use Shopware\Core\Checkout\Payment\Cart\SyncPaymentTransactionStruct; +use Shopware\Core\Checkout\Payment\Cart\Token\TokenFactoryInterfaceV2; +use Shopware\Core\Framework\Adapter\Translation\Translator; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\RouterInterface; +use Tpay\ShopwarePayment\Config\Service\ConfigServiceInterface; +use Tpay\ShopwarePayment\Config\TpayTransactionConfigStruct; +use Tpay\ShopwarePayment\Util\Locale\LocaleProvider; +use Tpay\ShopwarePayment\Util\Payments\Blik; +use tpayLibs\src\_class_tpay\Utilities\TException; + +class BlikPaymentBuilder extends AbstractPaymentBuilder implements BlikPaymentBuilderInterface +{ + + public const BLIK_TRANSACTION_SESSION_KEY = 'blikTransaction'; + + /** @var SessionInterface */ + private $session; + + public function __construct( + ConfigServiceInterface $configService, + LocaleProvider $localeProvider, + TokenFactoryInterfaceV2 $tokenFactory, + RouterInterface $router, + Translator $translator, + SessionInterface $session, + LoggerInterface $logger, + EntityRepositoryInterface $tpayPaymentTokenRepository + ) { + parent::__construct($configService, $localeProvider, $tokenFactory, $router, $translator, $logger, $tpayPaymentTokenRepository); + $this->session = $session; + } + + /** + * @throws TException + */ + public function createBlikTransaction(SyncPaymentTransactionStruct $paymentTransactionStruct, SalesChannelContext $salesChannelContext, CustomerEntity $customer, string $blikCode) + { + if ($this->session->has(self::BLIK_TRANSACTION_SESSION_KEY)) { + $tpayTransaction = $this->session->get(self::BLIK_TRANSACTION_SESSION_KEY); + } else { + $tpayTransaction = $this->createTransaction($paymentTransactionStruct, $salesChannelContext, $customer); + $this->session->set(self::BLIK_TRANSACTION_SESSION_KEY, $tpayTransaction); + } + $basicApi = $this->createBasicApi($salesChannelContext); + + return $basicApi->blik($tpayTransaction['title'], $blikCode); + } + + protected function getTpayTransactionConfig(SyncPaymentTransactionStruct $transaction, OrderEntity $order, CustomerEntity $customer, SalesChannelContext $salesChannelContext): TpayTransactionConfigStruct + { + $tpayTransactionConfig = parent::getTpayTransactionConfig($transaction, $order, $customer, $salesChannelContext); + $tpayTransactionConfig->setGroup(Blik::ID); + + return $tpayTransactionConfig; + } + + +} diff --git a/src/Payment/Builder/BlikPaymentBuilderInterface.php b/src/Payment/Builder/BlikPaymentBuilderInterface.php new file mode 100644 index 0000000..0af9747 --- /dev/null +++ b/src/Payment/Builder/BlikPaymentBuilderInterface.php @@ -0,0 +1,23 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 cze 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\Builder; + + +use Shopware\Core\Checkout\Customer\CustomerEntity; +use Shopware\Core\Checkout\Payment\Cart\SyncPaymentTransactionStruct; +use Shopware\Core\System\SalesChannel\SalesChannelContext; + +interface BlikPaymentBuilderInterface extends PaymentBuilderInterface +{ + public function createBlikTransaction(SyncPaymentTransactionStruct $paymentTransactionStruct, SalesChannelContext $salesChannelContext, CustomerEntity $customer, string $blikCode); +} diff --git a/src/Payment/Builder/CardPaymentBuilder.php b/src/Payment/Builder/CardPaymentBuilder.php new file mode 100644 index 0000000..8e6a790 --- /dev/null +++ b/src/Payment/Builder/CardPaymentBuilder.php @@ -0,0 +1,32 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\Builder; + + +use Shopware\Core\Checkout\Customer\CustomerEntity; +use Shopware\Core\Checkout\Order\OrderEntity; +use Shopware\Core\Checkout\Payment\Cart\SyncPaymentTransactionStruct; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Tpay\ShopwarePayment\Config\TpayTransactionConfigStruct; +use Tpay\ShopwarePayment\Util\Payments\Card; + +class CardPaymentBuilder extends AbstractPaymentBuilder +{ + protected function getTpayTransactionConfig(SyncPaymentTransactionStruct $transaction, OrderEntity $order, CustomerEntity $customer, SalesChannelContext $salesChannelContext): TpayTransactionConfigStruct + { + $tpayTransactionConfig = parent::getTpayTransactionConfig($transaction, $order, $customer, $salesChannelContext); + $tpayTransactionConfig->setGroup(Card::ID); + + return $tpayTransactionConfig; + } +} diff --git a/src/Payment/Builder/PaymentBuilderFactory.php b/src/Payment/Builder/PaymentBuilderFactory.php new file mode 100644 index 0000000..4cd0caa --- /dev/null +++ b/src/Payment/Builder/PaymentBuilderFactory.php @@ -0,0 +1,110 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\Builder; + + +use Psr\Log\LoggerInterface; +use Shopware\Core\Checkout\Payment\Cart\Token\TokenFactoryInterfaceV2; +use Shopware\Core\Framework\Adapter\Translation\Translator; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Routing\RouterInterface; +use Tpay\ShopwarePayment\Config\Service\ConfigServiceInterface; +use Tpay\ShopwarePayment\Util\Locale\LocaleProvider; + +class PaymentBuilderFactory +{ + /** @var ConfigServiceInterface */ + private $configService; + + /** @var EntityRepositoryInterface */ + private $tpayPaymentTokenRepository; + + /** @var LocaleProvider */ + private $localeProvider; + + /** @var TokenFactoryInterfaceV2 */ + private $tokenFactory; + + /** @var RouterInterface */ + private $router; + + /** @var Translator */ + private $translator; + + /** @var Session */ + private $session; + + /** @var LoggerInterface */ + private $logger; + + public function __construct( + ConfigServiceInterface $configService, + LocaleProvider $localeProvider, + TokenFactoryInterfaceV2 $tokenFactory + , RouterInterface $router, + Translator $translator, + Session $session, + LoggerInterface $logger, + EntityRepositoryInterface $tpayPaymentTokenRepository + ) { + $this->configService = $configService; + $this->tokenFactory = $tokenFactory; + $this->localeProvider = $localeProvider; + $this->router = $router; + $this->translator = $translator; + $this->session = $session; + $this->logger = $logger; + $this->tpayPaymentTokenRepository = $tpayPaymentTokenRepository; + } + + public function createCardBuilder(): PaymentBuilderInterface + { + return new CardPaymentBuilder( + $this->configService, + $this->localeProvider, + $this->tokenFactory, + $this->router, + $this->translator, + $this->logger, + $this->tpayPaymentTokenRepository + ); + } + + public function createBankTransferBuilder(): PaymentBuilderInterface + { + return new BankTransferPaymentBuilder( + $this->configService, + $this->localeProvider, + $this->tokenFactory, + $this->router, + $this->translator, + $this->logger, + $this->tpayPaymentTokenRepository + ); + } + + public function createBlikBuilder(): BlikPaymentBuilderInterface + { + return new BlikPaymentBuilder( + $this->configService, + $this->localeProvider, + $this->tokenFactory, + $this->router, + $this->translator, + $this->session, + $this->logger, + $this->tpayPaymentTokenRepository + ); + } +} diff --git a/src/Payment/Builder/PaymentBuilderInterface.php b/src/Payment/Builder/PaymentBuilderInterface.php new file mode 100644 index 0000000..5d3e180 --- /dev/null +++ b/src/Payment/Builder/PaymentBuilderInterface.php @@ -0,0 +1,27 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\Builder; + + +use Shopware\Core\Checkout\Customer\CustomerEntity; +use Shopware\Core\Checkout\Payment\Cart\SyncPaymentTransactionStruct; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use tpayLibs\src\_class_tpay\Utilities\TException; + +interface PaymentBuilderInterface +{ + /** + * @throws TException + */ + public function createTransaction(SyncPaymentTransactionStruct $paymentTransactionStruct, SalesChannelContext $salesChannelContext, CustomerEntity $customer): array; +} diff --git a/src/Payment/CardPaymentHandler.php b/src/Payment/CardPaymentHandler.php new file mode 100644 index 0000000..4b44156 --- /dev/null +++ b/src/Payment/CardPaymentHandler.php @@ -0,0 +1,82 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment; + + +use Psr\Log\LoggerInterface; +use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException; +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler; +use Shopware\Core\Checkout\Payment\Cart\PaymentHandler\AsynchronousPaymentHandlerInterface; +use Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct; +use Shopware\Core\Checkout\Payment\Exception\AsyncPaymentProcessException; +use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Tpay\ShopwarePayment\Payment\Builder\PaymentBuilderInterface; +use tpayLibs\src\_class_tpay\Utilities\TException; +use tpayLibs\src\_class_tpay\Utilities\Util; + +class CardPaymentHandler implements AsynchronousPaymentHandlerInterface +{ + use TpayResponseHandlerTrait; + + /** @var PaymentBuilderInterface */ + private $cardPaymentBuilder; + + /** @var LoggerInterface */ + private $logger; + + public function __construct(PaymentBuilderInterface $cardPaymentBuilder, LoggerInterface $logger) + { + $this->cardPaymentBuilder = $cardPaymentBuilder; + $this->logger = $logger; + } + + /** + * @inheritDoc + */ + public function pay(AsyncPaymentTransactionStruct $transaction, RequestDataBag $dataBag, SalesChannelContext $salesChannelContext): RedirectResponse + { + Util::$loggingEnabled = false; + + $customer = $salesChannelContext->getCustomer(); + if ($customer === null) { + throw new AsyncPaymentProcessException( + $transaction->getOrderTransaction()->getId(), + (new CustomerNotLoggedInException())->getMessage() + ); + } + + try { + $tpayResponse = $this->cardPaymentBuilder->createTransaction($transaction, $salesChannelContext, $customer); + + return $this->handleTpayResponse($tpayResponse, $transaction); + } catch (TException $exception) { + $this->logger->error('Tpay connection error' . PHP_EOL . $exception->getMessage()); + } + + throw new AsyncPaymentProcessException($transaction->getOrderTransaction()->getId(), 'Tpay transaction error'); + } + + /** + * @inheritDoc + */ + public function finalize(AsyncPaymentTransactionStruct $transaction, Request $request, SalesChannelContext $salesChannelContext): void + { + /** + * @See Tpay\ShopwarePayment\Payment\FinalizePaymentController + * Nothing to do here. + */ + } +} diff --git a/src/Payment/Exception/InvalidBlikCodeException.php b/src/Payment/Exception/InvalidBlikCodeException.php new file mode 100644 index 0000000..d4625c2 --- /dev/null +++ b/src/Payment/Exception/InvalidBlikCodeException.php @@ -0,0 +1,35 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 23 cze 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\Exception; + + +use Shopware\Core\Framework\ShopwareHttpException; +use Symfony\Component\HttpFoundation\Response; + +class InvalidBlikCodeException extends ShopwareHttpException +{ + public function __construct() + { + parent::__construct('Invalid blik code'); + } + + public function getStatusCode(): int + { + return Response::HTTP_NOT_FOUND; + } + + public function getErrorCode(): string + { + return 'TPAY_SHOPWARE_PAYMENT__INVALID_BLIK_CODE'; + } +} diff --git a/src/Payment/Exception/MissingRequiredBankIdException.php b/src/Payment/Exception/MissingRequiredBankIdException.php new file mode 100644 index 0000000..422113b --- /dev/null +++ b/src/Payment/Exception/MissingRequiredBankIdException.php @@ -0,0 +1,35 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 05 maj 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\Exception; + + +use Shopware\Core\Framework\ShopwareHttpException; +use Symfony\Component\HttpFoundation\Response; + +class MissingRequiredBankIdException extends ShopwareHttpException +{ + public function __construct() + { + parent::__construct('Property BankId is required'); + } + + public function getStatusCode(): int + { + return Response::HTTP_NOT_FOUND; + } + + public function getErrorCode(): string + { + return 'TPAY_SHOPWARE_PAYMENT__REQUIRED_BANK_ID'; + } +} diff --git a/src/Payment/FinalizePaymentController.php b/src/Payment/FinalizePaymentController.php new file mode 100644 index 0000000..de2f708 --- /dev/null +++ b/src/Payment/FinalizePaymentController.php @@ -0,0 +1,57 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 07 maj 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment; + + +use Shopware\Core\Checkout\Payment\Exception\AsyncPaymentFinalizeException; +use Shopware\Core\Checkout\Payment\Exception\CustomerCanceledAsyncPaymentException; +use Shopware\Core\Checkout\Payment\Exception\InvalidTransactionException; +use Shopware\Core\Checkout\Payment\Exception\TokenExpiredException; +use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException; +use Shopware\Core\Framework\Routing\Annotation\RouteScope; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Annotation\Route; + +class FinalizePaymentController extends AbstractController +{ + /** @var TpayPaymentService */ + private $paymentService; + + public function __construct(TpayPaymentService $paymentService) + { + $this->paymentService = $paymentService; + } + + /** + * @RouteScope(scopes={"storefront"}) + * @Route("/tpay/finalize-transaction", defaults={"auth_required"=false}, name="tpay.finalize.transaction", methods={"GET"}) + * + * @throws AsyncPaymentFinalizeException + * @throws CustomerCanceledAsyncPaymentException + * @throws InvalidTransactionException + * @throws TokenExpiredException + * @throws UnknownPaymentMethodException + */ + public function finalizeTransaction(Request $request, SalesChannelContext $salesChannelContext): Response + { + $paymentToken = $request->get('_sw_payment_token'); + + return $this->paymentService->finalizeTransaction( + $paymentToken, + $salesChannelContext + ); + } +} diff --git a/src/Payment/SalesChannel/BlikPaymentController.php b/src/Payment/SalesChannel/BlikPaymentController.php new file mode 100644 index 0000000..c8234dd --- /dev/null +++ b/src/Payment/SalesChannel/BlikPaymentController.php @@ -0,0 +1,233 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 07 maj 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\SalesChannel; + + +use Shopware\Core\Checkout\Cart\Error\Error; +use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException; +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity; +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates; +use Shopware\Core\Checkout\Order\OrderEntity; +use Shopware\Core\Checkout\Order\SalesChannel\AbstractOrderRoute; +use Shopware\Core\Checkout\Order\SalesChannel\AbstractSetPaymentOrderRoute; +use Shopware\Core\Checkout\Order\SalesChannel\OrderService; +use Shopware\Core\Checkout\Payment\Exception\AsyncPaymentProcessException; +use Shopware\Core\Checkout\Payment\Exception\InvalidOrderException; +use Shopware\Core\Checkout\Payment\Exception\PaymentProcessException; +use Shopware\Core\Checkout\Payment\Exception\SyncPaymentProcessException; +use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException; +use Shopware\Core\Checkout\Payment\PaymentService; +use Shopware\Core\Checkout\Payment\SalesChannel\AbstractHandlePaymentMethodRoute; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; +use Shopware\Core\Framework\Routing\Annotation\RouteScope; +use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; +use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException; +use Shopware\Core\System\SalesChannel\Context\SalesChannelContextService; +use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface; +use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters; +use Shopware\Core\System\SalesChannel\SalesChannel\AbstractContextSwitchRoute; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Shopware\Storefront\Framework\AffiliateTracking\AffiliateTrackingListener; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\Annotation\Route; +use Tpay\ShopwarePayment\Payment\Builder\BlikPaymentBuilder; +use Tpay\ShopwarePayment\Payment\Exception\InvalidBlikCodeException; + +/** + * @RouteScope(scopes={"store-api"}) + */ +class BlikPaymentController extends AbstractController +{ + private OrderService $orderService; + private PaymentService $paymentService; + private EntityRepositoryInterface $orderTransactionRepository; + private AbstractHandlePaymentMethodRoute $handlePaymentMethodRoute; + private SessionInterface $session; + private AbstractOrderRoute $orderRoute; + private AbstractContextSwitchRoute $contextSwitchRoute; + private SalesChannelContextServiceInterface $contextService; + private AbstractSetPaymentOrderRoute $setPaymentOrderRoute; + + public function __construct( + OrderService $orderService, + PaymentService $paymentService, + EntityRepositoryInterface $orderTransactionRepository, + AbstractHandlePaymentMethodRoute $handlePaymentMethodRoute, + SessionInterface $session, + AbstractOrderRoute $orderRoute, + AbstractContextSwitchRoute $contextSwitchRoute, + SalesChannelContextServiceInterface $contextService, + AbstractSetPaymentOrderRoute $setPaymentOrderRoute + + ) { + $this->orderService = $orderService; + $this->paymentService = $paymentService; + $this->orderTransactionRepository = $orderTransactionRepository; + $this->handlePaymentMethodRoute = $handlePaymentMethodRoute; + $this->session = $session; + $this->orderRoute = $orderRoute; + $this->contextSwitchRoute = $contextSwitchRoute; + $this->contextService = $contextService; + $this->setPaymentOrderRoute = $setPaymentOrderRoute; + } + + /** + * @Route("/store-api/tpay/blik-payment/register-transaction", name="store-api.tpay.blik-payment.register-transaction", methods={"POST"}) + */ + public function registerTransaction(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): JsonResponse + { + if (!$context->getCustomer()) { + throw new CustomerNotLoggedInException(); + } + + try { + $this->addAffiliateTracking($dataBag, $request->getSession()); + $orderId = $this->orderService->createOrder($dataBag, $context); + $finishUrl = $this->generateUrl('frontend.checkout.finish.page', ['orderId' => $orderId]); + + $this->paymentService->handlePaymentByOrder($orderId, $dataBag, $context, $finishUrl); + $this->session->remove(BlikPaymentBuilder::BLIK_TRANSACTION_SESSION_KEY); + + return $this->json(['success' => true, 'orderId' => $orderId, 'finishUrl' => $finishUrl]); + } catch (InvalidBlikCodeException $exception) { + return $this->json(['success'=> false, 'orderId' => $orderId, 'blikCodeValid' => false ,'message' => $exception->getMessage()]); + } catch (ConstraintViolationException $formViolations) { + } catch (Error $blockedError) { + } catch (AsyncPaymentProcessException | InvalidOrderException | SyncPaymentProcessException | UnknownPaymentMethodException $e) { + $this->session->remove(BlikPaymentBuilder::BLIK_TRANSACTION_SESSION_KEY); + throw $e; + } + $this->session->remove(BlikPaymentBuilder::BLIK_TRANSACTION_SESSION_KEY); + return $this->json(['success' => false, 'orderId' => $orderId, 'finishUrl' => null]); + } + + /** + * @Route("/store-api/tpay/blik-payment/register-transaction-again", name="store-api.tpay.blik-payment.register-transaction-again", methods={"POST"}) + */ + public function registerTransactionAgain(RequestDataBag $dataBag, Request $request, SalesChannelContext $context): JsonResponse + { + $orderId = $dataBag->getAlnum('orderId'); + $changedPayment = $dataBag->get('changedPayment'); + + if ($changedPayment == true) { + $finishUrl = $this->generateUrl('frontend.checkout.finish.page', [ + 'orderId' => $orderId, + 'changedPayment' => true, + ]); + + $errorUrl = $this->generateUrl('frontend.account.edit-order.page', ['orderId' => $orderId]); + + /** @var OrderEntity|null $order */ + $order = $this->orderRoute->load($request, $context, new Criteria([$orderId]))->getOrders()->first(); + + if ($context->getCurrency()->getId() !== $order->getCurrencyId()) { + $this->contextSwitchRoute->switchContext( + new RequestDataBag([SalesChannelContextService::CURRENCY_ID => $order->getCurrencyId()]), + $context + ); + + $context = $this->contextService->get( + new SalesChannelContextServiceParameters( + $context->getSalesChannelId(), + $context->getToken(), + $context->getContext()->getLanguageId() + ) + ); + } + + $setPaymentRequest = new Request(); + $setPaymentRequest->request->set('orderId', $orderId); + $setPaymentRequest->request->set('paymentMethodId', $request->get('paymentMethodId')); + $this->setPaymentOrderRoute->setPayment($setPaymentRequest, $context); + } else { + $finishUrl = $this->generateUrl('frontend.checkout.finish.page', ['orderId' => $orderId]); + + $errorUrl = $this->generateUrl('frontend.checkout.finish.page', [ + 'orderId' => $orderId, + 'paymentFailed' => true, + ]); + } + + $handlePaymentRequest = new Request(); + $handlePaymentRequest->request->set('orderId', $request->get('orderId')); + $handlePaymentRequest->request->set('finishUrl', $finishUrl); + $handlePaymentRequest->request->set('errorUrl', $errorUrl); + $handlePaymentRequest->request->set('blikCode', $request->get('blikCode')); + + try { + $routeResponse = $this->handlePaymentMethodRoute->load($handlePaymentRequest, $context); + $response = $routeResponse->getRedirectResponse(); + $this->session->remove(BlikPaymentBuilder::BLIK_TRANSACTION_SESSION_KEY); + } catch (InvalidBlikCodeException $exception) { + return $this->json(['success'=> false, 'orderId' => $orderId, 'blikCodeValid' => false ,'message' => $exception->getMessage()]); + } catch (PaymentProcessException $paymentProcessException) { + $this->session->remove(BlikPaymentBuilder::BLIK_TRANSACTION_SESSION_KEY); + return $this->json(['success' => false, 'orderId' => $orderId, 'finishUrl' => null]); + } + if ($response === null) { + $this->session->remove(BlikPaymentBuilder::BLIK_TRANSACTION_SESSION_KEY); + return $this->json(['success' => true, 'orderId' => $orderId, 'finishUrl' => $finishUrl]); + } + $this->session->remove(BlikPaymentBuilder::BLIK_TRANSACTION_SESSION_KEY); + return $this->json(['success' => false, 'orderId' => $orderId, 'finishUrl' => null]); + } + + /** + * @Route("/store-api/tpay/blik-payment/check-payment-state", name="store-api.tpay.blik-payment.check-payment-state", methods={"POST"}) + */ + public function checkPaymentState(Request $request, SalesChannelContext $context ): JsonResponse + { + $criteria = new Criteria(); + $criteria->addAssociation('paymentMethod'); + $criteria->addFilter(new EqualsFilter('orderId', $request->get('orderId'))); + + /** @var OrderTransactionEntity $transaction */ + $transaction = $this->orderTransactionRepository->search($criteria, $context->getContext())->last(); + + $stateName = $transaction->getStateMachineState()->getTechnicalName(); + + if ($stateName === OrderTransactionStates::STATE_PAID) { + $responseData = [ + 'waiting' => false, + 'success' => true, + ]; + } else if ($stateName === OrderTransactionStates::STATE_OPEN) { + $responseData = [ + 'waiting' => true + ]; + } else { + $responseData = [ + 'waiting' => false, + 'success' => false, + ]; + } + $this->session->remove(BlikPaymentBuilder::BLIK_TRANSACTION_SESSION_KEY); + return $this->json($responseData); + } + + private function addAffiliateTracking(RequestDataBag $dataBag, SessionInterface $session): void + { + $affiliateCode = $session->get(AffiliateTrackingListener::AFFILIATE_CODE_KEY); + $campaignCode = $session->get(AffiliateTrackingListener::CAMPAIGN_CODE_KEY); + if ($affiliateCode !== null && $campaignCode !== null) { + $dataBag->set(AffiliateTrackingListener::AFFILIATE_CODE_KEY, $affiliateCode); + $dataBag->set(AffiliateTrackingListener::CAMPAIGN_CODE_KEY, $campaignCode); + } + } + +} diff --git a/src/Payment/SalesChannel/Page/TpayCheckPaymentPage.php b/src/Payment/SalesChannel/Page/TpayCheckPaymentPage.php new file mode 100644 index 0000000..128f5e7 --- /dev/null +++ b/src/Payment/SalesChannel/Page/TpayCheckPaymentPage.php @@ -0,0 +1,45 @@ +orderId; + } + + /** + * @param string $orderId + */ + public function setOrderId(string $orderId): void + { + $this->orderId = $orderId; + } + + /** + * @return string + */ + public function getTransactionId(): string + { + return $this->transactionId; + } + + /** + * @param string $transactionId + */ + public function setTransactionId(string $transactionId): void + { + $this->transactionId = $transactionId; + } +} diff --git a/src/Payment/SalesChannel/TpayCheckPaymentController.php b/src/Payment/SalesChannel/TpayCheckPaymentController.php new file mode 100644 index 0000000..4c8c88e --- /dev/null +++ b/src/Payment/SalesChannel/TpayCheckPaymentController.php @@ -0,0 +1,87 @@ +orderTransactionRepository = $orderTransactionRepository; + } + + /** + * @Route("/store-api/tpay-payment-check", name="store-api.tpay-payment-check", methods={"POST"}) + */ + public function checkPaymentState(Request $request, SalesChannelContext $context ): JsonResponse + { + $transactionId = $request->get('transactionId'); + + if ($transactionId === null) { + return $this->json([ + 'waiting' => false, + 'success' => false, + ]); + } + + $isOrderPaid = $this->isOrderPaid($transactionId, $context->getContext()); + + switch ($isOrderPaid) { + case true: + $responseData = [ + 'waiting' => false, + 'success' => true, + ]; + break; + case false: + $responseData = [ + 'waiting' => true, + 'success' => false + ]; + break; + default: + $responseData = [ + 'waiting' => false, + 'success' => false, + ]; + break; + } + + return $this->json($responseData); + } + + private function isOrderPaid(string $transactionId, Context $context): ?bool + { + $criteria = new Criteria([$transactionId]); + + /** @var OrderTransactionEntity $transaction */ + $transaction = $this->orderTransactionRepository->search($criteria, $context)->first(); + + $stateName = $transaction->getStateMachineState()->getTechnicalName(); + + if ($stateName === OrderTransactionStates::STATE_PAID) { + return true; + + } else if ($stateName === OrderTransactionStates::STATE_OPEN) { + return false; + } else { + return null; + } + } +} diff --git a/src/Payment/SalesChannel/TpayPaymentController.php b/src/Payment/SalesChannel/TpayPaymentController.php new file mode 100644 index 0000000..165e231 --- /dev/null +++ b/src/Payment/SalesChannel/TpayPaymentController.php @@ -0,0 +1,178 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 07 maj 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment\SalesChannel; + +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity; +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStates; +use Shopware\Core\Checkout\Payment\Cart\Token\TokenFactoryInterfaceV2; +use Shopware\Core\Checkout\Payment\Cart\Token\TokenStruct; +use Shopware\Core\Checkout\Payment\Exception\TokenExpiredException; +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; +use Shopware\Core\Framework\Routing\Annotation\RouteScope; +use Shopware\Core\Framework\Struct\ArrayEntity; +use Shopware\Core\Framework\Validation\DataBag\RequestDataBag; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Shopware\Storefront\Controller\StorefrontController; +use Shopware\Storefront\Page\GenericPageLoader; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; +use Tpay\ShopwarePayment\Payment\SalesChannel\Page\TpayCheckPaymentPage; + + +/** + * @RouteScope(scopes={"storefront"}) + */ +class TpayPaymentController extends StorefrontController +{ + protected EntityRepositoryInterface $tpayPaymentTokenRepository; + protected EntityRepositoryInterface $orderTransactionRepository; + protected GenericPageLoader $genericLoader; + protected TokenFactoryInterfaceV2 $tokenFactory; + + public function __construct( + EntityRepositoryInterface $tpayPaymentTokenRepository, + EntityRepositoryInterface $orderTransactionRepository, + GenericPageLoader $genericLoader, + TokenFactoryInterfaceV2 $tokenFactory + ) { + $this->tpayPaymentTokenRepository = $tpayPaymentTokenRepository; + $this->orderTransactionRepository = $orderTransactionRepository; + $this->genericLoader = $genericLoader; + $this->tokenFactory = $tokenFactory; + } + + /** + * @Route("/tpay/payment/return-url/{tokenId}", name="tpay.payment.return-url", methods={"GET"}) + */ + public function createReturnUrl(?string $tokenId, RequestDataBag $dataBag, Request $request, SalesChannelContext $context): RedirectResponse + { + if (null === $tokenId) { + throw new \Exception('Token is empty'); + } + + $tokenEntity = $this->getToken($tokenId, $context->getContext()); + + if (null === $tokenEntity) { + throw new \Exception('Token not found'); + } + + $paymentToken = $this->parseToken($tokenEntity->get('token')); + $transactionId = $paymentToken->getTransactionId(); + + $isOrderPaid = $this->isOrderPaid($transactionId, $context->getContext()); + + if ($isOrderPaid === true) { + $parameter = ['_sw_payment_token' => $tokenEntity->get('token')]; + + return $this->redirectToRoute('tpay.finalize.transaction', $parameter); + } else { + $parameter = ['transactionId' => $transactionId]; + + return $this->redirectToRoute('tpay.payment.check-payment', $parameter); + } + } + + /** + * @Route("/tpay/payment/result-url/{tokenId}", name="tpay.payment.result-url", methods={"GET"}) + */ + public function createResultUrl(?string $tokenId, RequestDataBag $dataBag, Request $request, SalesChannelContext $context): Response + { + if (null === $tokenId) { + throw new \Exception('Token is empty'); + } + + $tokenEntity = $this->getToken($tokenId, $context->getContext()); + + if (null === $tokenEntity) { + throw new \Exception('Token not found'); + } + + $parameter = ['_sw_token' => $tokenEntity->get('token')]; + + return $this->redirectToRoute('action.tpay.webhook.notify', $parameter); + } + + /** + * @Route("/tpay/payment/check-payment", name="tpay.payment.check-payment", methods={"GET"}) + */ + public function checkPayment(Request $request, SalesChannelContext $salesChannelContext): Response + { + $transactionId = $request->get('transactionId'); + + $page = $this->genericLoader->load($request, $salesChannelContext); + $page = TpayCheckPaymentPage::createFrom($page); + + $page->setTransactionId($transactionId); + $page->setOrderId($this->getOrderId($transactionId, $salesChannelContext->getContext())); + + return $this->renderStorefront('@TpayShopwarePayment/storefront/page/tpay/check-payment.html.twig', ['page' => $page]); + } + + private function getOrderId(string $transactionId, Context $context): ?string + { + $criteria = new Criteria([$transactionId]); + + /** @var OrderTransactionEntity $transaction */ + $transaction = $this->orderTransactionRepository->search($criteria, $context)->first(); + + if ($transaction instanceof OrderTransactionEntity) { + return $transaction->getOrderId(); + } + + return null; + } + + private function isOrderPaid(string $transactionId, Context $context): ?bool + { + $criteria = new Criteria([$transactionId]); + + /** @var OrderTransactionEntity $transaction */ + $transaction = $this->orderTransactionRepository->search($criteria, $context)->first(); + + $stateName = $transaction->getStateMachineState()->getTechnicalName(); + + if ($stateName === OrderTransactionStates::STATE_PAID) { + return true; + + } else if ($stateName === OrderTransactionStates::STATE_OPEN) { + return false; + } else { + return null; + } + } + + private function getToken(string $tokenId, Context $context): ?ArrayEntity + { + return $this->tpayPaymentTokenRepository->search(new Criteria([$tokenId]), $context)->first(); + } + + /** + * @throws TokenExpiredException + */ + private function parseToken(string $token): TokenStruct + { + $tokenStruct = $this->tokenFactory->parseToken($token); + + if ($tokenStruct->isExpired()) { + throw new TokenExpiredException($tokenStruct->getToken()); + } + + return $tokenStruct; + } +} diff --git a/src/Payment/TpayPaymentService.php b/src/Payment/TpayPaymentService.php new file mode 100644 index 0000000..61c00ba --- /dev/null +++ b/src/Payment/TpayPaymentService.php @@ -0,0 +1,183 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 07 maj 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment; + + +use Psr\Log\LoggerInterface; +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionEntity; +use Shopware\Core\Checkout\Order\Aggregate\OrderTransaction\OrderTransactionStateHandler; +use Shopware\Core\Checkout\Payment\Cart\AsyncPaymentTransactionStruct; +use Shopware\Core\Checkout\Payment\Cart\Token\TokenFactoryInterfaceV2; +use Shopware\Core\Checkout\Payment\Cart\Token\TokenStruct; +use Shopware\Core\Checkout\Payment\Exception\AsyncPaymentFinalizeException; +use Shopware\Core\Checkout\Payment\Exception\CustomerCanceledAsyncPaymentException; +use Shopware\Core\Checkout\Payment\Exception\InvalidTransactionException; +use Shopware\Core\Checkout\Payment\Exception\TokenExpiredException; +use Shopware\Core\Checkout\Payment\Exception\UnknownPaymentMethodException; +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\System\SalesChannel\SalesChannelContext; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RouterInterface; +use Tpay\ShopwarePayment\TpayShopwarePayment; + +class TpayPaymentService +{ + /** + * @var TokenFactoryInterfaceV2 + */ + private $tokenFactory; + + /** + * @var EntityRepositoryInterface + */ + private $orderTransactionRepository; + + /** + * @var OrderTransactionStateHandler + */ + private $transactionStateHandler; + + /** @var RouterInterface */ + private $router; + + /** @var LoggerInterface */ + private $logger; + + public function __construct( + TokenFactoryInterfaceV2 $tokenFactory, + EntityRepositoryInterface $orderTransactionRepository, + OrderTransactionStateHandler $transactionStateHandler, + RouterInterface $router, + LoggerInterface $logger + ) { + $this->tokenFactory = $tokenFactory; + $this->orderTransactionRepository = $orderTransactionRepository; + $this->transactionStateHandler = $transactionStateHandler; + $this->router = $router; + $this->logger = $logger; + } + + + /** + * @throws AsyncPaymentFinalizeException + * @throws CustomerCanceledAsyncPaymentException + * @throws InvalidTransactionException + * @throws TokenExpiredException + * @throws UnknownPaymentMethodException + */ + public function finalizeTransaction( + string $paymentToken, + SalesChannelContext $salesChannelContext + ): RedirectResponse { + $paymentTokenStruct = $this->parseToken($paymentToken); + $transactionId = $paymentTokenStruct->getTransactionId(); + $context = $salesChannelContext->getContext(); + $paymentTransactionStruct = $this->getPaymentTransactionStruct($transactionId, $context); + + if ($paymentTokenStruct->getException() !== null) { + $this->logger->warning('Token has expired.', $paymentTransactionStruct->jsonSerialize()); + return new RedirectResponse($paymentTokenStruct->getErrorUrl()); + } + + return new RedirectResponse($this->router->generate('frontend.checkout.finish.page', ['orderId' => $paymentTransactionStruct->getOrder()->getId()], UrlGeneratorInterface::ABSOLUTE_URL)); + } + + public function process(array $notification, string $paymentToken, SalesChannelContext $salesChannelContext): Response + { + $paymentTokenStruct = $this->parseToken($paymentToken); + $transactionId = $paymentTokenStruct->getTransactionId(); + $context = $salesChannelContext->getContext(); + $transactionStruct = $this->getPaymentTransactionStruct($transactionId, $context); + + $this->addTpayTransactionId($transactionStruct, $notification['tr_id'], $context); + + return $this->handlePaymentStatus($notification, $transactionStruct, $context); + } + + /** + * @throws TokenExpiredException + */ + private function parseToken(string $token): TokenStruct + { + $tokenStruct = $this->tokenFactory->parseToken($token); + + if ($tokenStruct->isExpired()) { + throw new TokenExpiredException($tokenStruct->getToken()); + } + + return $tokenStruct; + } + + /** + * @throws InvalidTransactionException + */ + private function getPaymentTransactionStruct(string $orderTransactionId, Context $context): AsyncPaymentTransactionStruct + { + $criteria = new Criteria([$orderTransactionId]); + $criteria->addAssociation('order'); + /** @var OrderTransactionEntity|null $orderTransaction */ + $orderTransaction = $this->orderTransactionRepository->search($criteria, $context)->first(); + + if ($orderTransaction === null) { + throw new InvalidTransactionException($orderTransactionId); + } + + return new AsyncPaymentTransactionStruct($orderTransaction, $orderTransaction->getOrder(), ''); + } + + private function handlePaymentStatus(array $notification, AsyncPaymentTransactionStruct $transaction, Context $context): Response + { + $transactionId = $transaction->getOrderTransaction()->getId(); + if ($notification['tr_status'] === 'CHARGEBACK') { + $this->transactionStateHandler->refund($transactionId, $context); + + return new Response('TRUE'); + } + + $paidAmount = (int) $notification['tr_paid'] * 100; + $orderAmount = (int) $transaction->getOrder()->getAmountTotal() * 100; + + if ($paidAmount < $orderAmount || $notification['tr_error'] === 'surcharge') { + $this->transactionStateHandler->payPartially($transactionId, $context); + + return new Response('TRUE'); + } + + if (($notification['tr_error'] === 'none' || $notification['tr_error'] === 'overpay') + && $notification['tr_status'] === 'TRUE') { + $this->transactionStateHandler->paid($transactionId, $context); + + return new Response('TRUE'); + } + + return new Response("FALSE"); + } + + private function addTpayTransactionId( + AsyncPaymentTransactionStruct $transaction, + string $tpayTransactionId, + Context $context + ): void { + $data = [ + 'id' => $transaction->getOrderTransaction()->getId(), + 'customFields' => [ + TpayShopwarePayment::ORDER_TRANSACTION_CUSTOM_FIELDS_TPAY_TRANSACTION_ID => $tpayTransactionId, + ], + ]; + $this->orderTransactionRepository->update([$data], $context); + } +} diff --git a/src/Payment/TpayResponseHandlerTrait.php b/src/Payment/TpayResponseHandlerTrait.php new file mode 100644 index 0000000..97001ef --- /dev/null +++ b/src/Payment/TpayResponseHandlerTrait.php @@ -0,0 +1,50 @@ + + * + * @author Jakub Medyński + * @support Tpay + * @created 30 cze 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Tpay\ShopwarePayment\Payment; + + +use Shopware\Core\Checkout\Payment\Cart\SyncPaymentTransactionStruct; +use Shopware\Core\Checkout\Payment\Exception\SyncPaymentProcessException; +use Symfony\Component\HttpFoundation\RedirectResponse; +use tpayLibs\src\Dictionaries\ErrorCodes\TransactionApiErrors; + +trait TpayResponseHandlerTrait +{ + private function tpayResponseError(array $tpayResponse, SyncPaymentTransactionStruct $transaction): void + { + $this->logger->error(TransactionApiErrors::ERROR_CODES[$tpayResponse['err']], $transaction->jsonSerialize()); + throw new SyncPaymentProcessException + ($transaction->getOrderTransaction()->getId(), + 'Tpay transaction error' + ); + } + + /** + * @param array $tpayResponse + * @param SyncPaymentTransactionStruct $transaction + * @return RedirectResponse + * @throws \Exception + */ + private function handleTpayResponse(array $tpayResponse, SyncPaymentTransactionStruct $transaction): RedirectResponse + { + if (isset($tpayResponse['result']) && (int) $tpayResponse['result'] === 1) { + return RedirectResponse::create($tpayResponse['url']); + } + + try { + $this->tpayResponseError($tpayResponse, $transaction); + } catch (\Exception $e) { + throw $e; + } + } +} diff --git a/src/Resources/app/administration/src/core/service/api/tpay-merchant-credentials.service.js b/src/Resources/app/administration/src/core/service/api/tpay-merchant-credentials.service.js new file mode 100644 index 0000000..5d3cdaf --- /dev/null +++ b/src/Resources/app/administration/src/core/service/api/tpay-merchant-credentials.service.js @@ -0,0 +1,34 @@ +/** + * @copyright 2020 Tpay Krajowy Integrator Płatności S.A. + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +const ApiService = Shopware.Classes.ApiService; + + +export default class TpayMerchantCredentialsService extends ApiService { + constructor(httpClient, loginService, apiEndpoint = 'tpay') { + super(httpClient, loginService, apiEndpoint); + } + + validateMerchantCredentials(merchantId, merchantSecret, merchantTrApiKey, merchantTrApiPass) { + return this.httpClient + .post( + `_action/${this.getApiBasePath()}/validate-merchant-credentials`, + { merchantId, merchantSecret, merchantTrApiKey, merchantTrApiPass }, + { + headers: this.getBasicHeaders() + } + ) + .then((response) => { + return ApiService.handleResponse(response); + }); + } + +} diff --git a/src/Resources/app/administration/src/init/api-service.init.js b/src/Resources/app/administration/src/init/api-service.init.js new file mode 100644 index 0000000..ca3a332 --- /dev/null +++ b/src/Resources/app/administration/src/init/api-service.init.js @@ -0,0 +1,9 @@ +import TpayMerchantCredentialsService from "../core/service/api/tpay-merchant-credentials.service"; + +const { Application } = Shopware; + +Application.addServiceProvider('TpayMerchantCredentialsService', (container) => { + const initContainer = Application.getContainer('init'); + + return new TpayMerchantCredentialsService(initContainer.httpClient, container.loginService); +}) diff --git a/src/Resources/app/administration/src/main.js b/src/Resources/app/administration/src/main.js new file mode 100644 index 0000000..3c5b6d2 --- /dev/null +++ b/src/Resources/app/administration/src/main.js @@ -0,0 +1,13 @@ +import './module/sw-plugin/component/tpay-test-merchant-credentials-button/index'; +import './module/sw-order/component/sw-order-user-card/index'; + +import './init/api-service.init'; + +import deDE from './snippets/de-DE.json'; +import enGB from './snippets/en-GB.json'; +import plPL from './snippets/pl-PL.json'; + +// Extend Snippets +Shopware.Locale.extend('de-DE', deDE); +Shopware.Locale.extend('en-GB', enGB); +Shopware.Locale.extend('pl-PL', plPL); diff --git a/src/Resources/app/administration/src/module/sw-order/component/sw-order-user-card/index.js b/src/Resources/app/administration/src/module/sw-order/component/sw-order-user-card/index.js new file mode 100644 index 0000000..cd0fc09 --- /dev/null +++ b/src/Resources/app/administration/src/module/sw-order/component/sw-order-user-card/index.js @@ -0,0 +1,15 @@ +import template from './sw-order-user-card.html.twig'; + +const { Component } = Shopware; + +Component.override('sw-order-user-card', { + template, + + computed : { + isTpayPayment() { + const customFields = this.currentOrder.transactions.last().customFields; + + return customFields !== null && customFields.hasOwnProperty('tpay_shopware_payment_transaction_id') && customFields.tpay_shopware_payment_transaction_id.length > 0; + } + } +}); diff --git a/src/Resources/app/administration/src/module/sw-order/component/sw-order-user-card/sw-order-user-card.html.twig b/src/Resources/app/administration/src/module/sw-order/component/sw-order-user-card/sw-order-user-card.html.twig new file mode 100644 index 0000000..870a8f2 --- /dev/null +++ b/src/Resources/app/administration/src/module/sw-order/component/sw-order-user-card/sw-order-user-card.html.twig @@ -0,0 +1,7 @@ +{% block sw_order_detail_base_secondary_info_payment %} + {% parent %} + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/sw-plugin/component/sw-plugin-config/index.js b/src/Resources/app/administration/src/module/sw-plugin/component/sw-plugin-config/index.js new file mode 100644 index 0000000..3a0c9a0 --- /dev/null +++ b/src/Resources/app/administration/src/module/sw-plugin/component/sw-plugin-config/index.js @@ -0,0 +1,26 @@ +/** + * @copyright 2020 Tpay Krajowy Integrator Płatności S.A. + * + * @author Jakub Medyński + * @support Tpay + * @created 23 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import template from './sw-plugin-config.html.twig'; + + +Shopware.Component.override('sw-plugin-config', { + template, + + inject: ['TpayMerchantCredentialsService'], + + methods: { + tpayTestMerchantCredentials() { + + } + } + +}); diff --git a/src/Resources/app/administration/src/module/sw-plugin/component/sw-plugin-config/sw-plugin-config.html.twig b/src/Resources/app/administration/src/module/sw-plugin/component/sw-plugin-config/sw-plugin-config.html.twig new file mode 100644 index 0000000..1a8480e --- /dev/null +++ b/src/Resources/app/administration/src/module/sw-plugin/component/sw-plugin-config/sw-plugin-config.html.twig @@ -0,0 +1,10 @@ +{% block sw_plugin_config_actions_save %} + + +{% endblock %} diff --git a/src/Resources/app/administration/src/module/sw-plugin/component/tpay-test-merchant-credentials-button/index.js b/src/Resources/app/administration/src/module/sw-plugin/component/tpay-test-merchant-credentials-button/index.js new file mode 100644 index 0000000..f351347 --- /dev/null +++ b/src/Resources/app/administration/src/module/sw-plugin/component/tpay-test-merchant-credentials-button/index.js @@ -0,0 +1,125 @@ +/** + * @copyright 2020 Tpay Krajowy Integrator Płatności S.A. + * + * @author Jakub Medyński + * @support Tpay + * @created 29 kwi 2020 + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import template from './tpay-test-merchant-credentials-button.html.twig'; + +const {Component, Mixin} = Shopware; + +const TPAY_CONFIG_NAMESPACE = 'TpayShopwarePayment.config.'; + + +Component.register('tpay-test-merchant-credentials-button', { + template: template, + + inject: ['TpayMerchantCredentialsService'], + + mixins: [ + Mixin.getByName('notification') + ], + + data() { + return { + isLoading: false, + isSuccess: false, + fields: [] + } + }, + + methods: { + tpayTestMerchantCredentials() { + this.isLoading = true; + this.fields = this.$parent.$parent.$parent.$children; + + const merchantId = this.getValue('merchantId'); + const merchantSecret = this.getValue('merchantSecret') + const merchantTrApiKey = this.getValue('merchantTrApiKey') + const merchantTrApiPass = this.getValue('merchantTrApiPass') + + this.TpayMerchantCredentialsService.validateMerchantCredentials(merchantId, merchantSecret, merchantTrApiKey, merchantTrApiPass) + .then((response) => { + this.isLoading = false; + if (response.success === false) { + this.onInvalidData(response.code); + return; + } + if (!response.credentialsValid) { + this.onError(); + return; + } + this.onSuccess(); + }).catch(() => { + this.isLoading = false; + this.onError(); + }) + + }, + + onSuccess() { + const that = this; + + this.isSuccess = true; + this.createNotificationSuccess({ + title: this.$tc('tpay-shopware-payment.config.successTestMerchantCredentialsNotificationTitle'), + message: this.$tc('tpay-shopware-payment.config.successTestMerchantCredentialsNotificationMessage'), + autoClose: true + }); + setTimeout(() => that.isSuccess = false, 2000); + }, + + onError() { + this.createNotificationError({ + title: this.$tc('tpay-shopware-payment.config.errorTestMerchantCredentialsNotificationTitle'), + message: this.$tc('tpay-shopware-payment.config.errorTestMerchantCredentialsNotificationMessage'), + autoClose: true + }); + }, + + onInvalidData(code) { + let message = ''; + switch (code) { + case 'merchantId': + message = this.$tc('tpay-shopware-payment.config.emptyMerchantId'); + break; + case 'merchantSecret': + message = this.$tc('tpay-shopware-payment.config.emptyMerchantSecret'); + break; + case 'merchantTrApiKey': + message = this.$tc('tpay-shopware-payment.config.emptyMerchantTrApiKey'); + break; + case 'merchantTrApiPass': + message = this.$tc('tpay-shopware-payment.config.emptyMerchantTrApiPass'); + break; + default: + message = code; + } + this.createNotificationError({ + title: this.$tc('tpay-shopware-payment.config.invalidTestMerchantCredentialsNotificationTitle'), + message: message, + autoClose: true + }); + }, + + getFieldByName(field, name) { + return field.$children[0].$attrs.name === TPAY_CONFIG_NAMESPACE + name; + }, + + getValue(name) { + const field = this.fields.find((field) => { + return this.getFieldByName(field, name) + }) + if (typeof field.currentValue === 'undefined' || "" === field.currentValue) { + return field.$attrs.placeholder; + } + + return field.currentValue; + } + } +}); diff --git a/src/Resources/app/administration/src/module/sw-plugin/component/tpay-test-merchant-credentials-button/tpay-test-merchant-credentials-button.html.twig b/src/Resources/app/administration/src/module/sw-plugin/component/tpay-test-merchant-credentials-button/tpay-test-merchant-credentials-button.html.twig new file mode 100644 index 0000000..b825c6c --- /dev/null +++ b/src/Resources/app/administration/src/module/sw-plugin/component/tpay-test-merchant-credentials-button/tpay-test-merchant-credentials-button.html.twig @@ -0,0 +1,5 @@ +{% block tpay_shopware_payment_test_merchant_credentials_button %} + + {{ $tc('tpay-shopware-payment.config.buttonCredentialsTest') }} + +{% endblock %} diff --git a/src/Resources/app/administration/src/snippets/de-DE.json b/src/Resources/app/administration/src/snippets/de-DE.json new file mode 100644 index 0000000..f0d4168 --- /dev/null +++ b/src/Resources/app/administration/src/snippets/de-DE.json @@ -0,0 +1,19 @@ +{ + "tpay-shopware-payment": { + "config": { + "successTestMerchantCredentialsNotificationTitle": "Verbindung testen", + "successTestMerchantCredentialsNotificationMessage": "ine Testverbindung zum Tpay-Server wurde erfolgreich hergestellt.", + "errorTestMerchantCredentialsNotificationTitle": "Testverbindung fehlgeschlagen", + "errorTestMerchantCredentialsNotificationMessage": "Es gab ein Problem mit der Testverbindung. Bitte überprüfen Sie die angegebenen Daten", + "buttonCredentialsTest": "Verbindung testen", + "invalidTestMerchantCredentialsNotificationTitle": "Fehlende Daten", + "emptyMerchantId": "Vervollständigen Sie die Händler-ID", + "emptyMerchantSecret": "Füllen Sie den Sicherheitsschlüssel aus", + "emptyMerchantTrApiKey": "Vervollständigen Sie den API-Schlüssel", + "emptyMerchantTrApiPass": "Vervollständigen Sie das API-Passwort" + }, + "order": { + "tpayTransactionIdLabel": "Tpay-Transaktionskennzeichen" + } + } +} diff --git a/src/Resources/app/administration/src/snippets/en-GB.json b/src/Resources/app/administration/src/snippets/en-GB.json new file mode 100644 index 0000000..f6a06d7 --- /dev/null +++ b/src/Resources/app/administration/src/snippets/en-GB.json @@ -0,0 +1,19 @@ +{ + "tpay-shopware-payment": { + "config": { + "successTestMerchantCredentialsNotificationTitle": "Valid merchant credentials", + "successTestMerchantCredentialsNotificationMessage": "A test connection to Tpay server was successfully established", + "errorTestMerchantCredentialsNotificationTitle": "Invalid merchant credentials", + "errorTestMerchantCredentialsNotificationMessage": "There was a problem with the test connection. Check the correctness of the provided data.", + "buttonCredentialsTest": "Test credentials", + "invalidTestMerchantCredentialsNotificationTitle": "Missing data", + "emptyMerchantId": "Complete the Merchant ID", + "emptyMerchantSecret": "Fill the security key", + "emptyMerchantTrApiKey": "Complete the API key", + "emptyMerchantTrApiPass": "Complete the API password" + }, + "order": { + "tpayTransactionIdLabel": "Tpay transaction identifier" + } + } +} diff --git a/src/Resources/app/administration/src/snippets/pl-PL.json b/src/Resources/app/administration/src/snippets/pl-PL.json new file mode 100644 index 0000000..d469d19 --- /dev/null +++ b/src/Resources/app/administration/src/snippets/pl-PL.json @@ -0,0 +1,19 @@ +{ + "tpay-shopware-payment": { + "config": { + "successTestMerchantCredentialsNotificationTitle": "Testowe połączenie", + "successTestMerchantCredentialsNotificationMessage": "Pomyślnie nawiązano testowe połączenie z serwerem Tpay", + "errorTestMerchantCredentialsNotificationTitle": "Nieudane testowe połączenie", + "errorTestMerchantCredentialsNotificationMessage": "Wystąpił problem z tetowym połączeniem. Sprawdź poprawność podanych danych", + "buttonCredentialsTest": "Przetestuj połączenie", + "invalidTestMerchantCredentialsNotificationTitle": "Brakujące dane", + "emptyMerchantId": "Uzupełnij identyfikator sprzedawcy", + "emptyMerchantSecret": "Uzupełnij klucz bezpieczeństwa", + "emptyMerchantTrApiKey": "Uzupełnij klucz API", + "emptyMerchantTrApiPass": "Uzupełnij hasło API" + }, + "order": { + "tpayTransactionIdLabel": "Identyfikator transakcji Tpay" + } + } +} diff --git a/src/Resources/app/storefront/dist/storefront/js/tpay-shopware-payment.js b/src/Resources/app/storefront/dist/storefront/js/tpay-shopware-payment.js new file mode 100644 index 0000000..7e64f8d --- /dev/null +++ b/src/Resources/app/storefront/dist/storefront/js/tpay-shopware-payment.js @@ -0,0 +1 @@ +(window.webpackJsonp=window.webpackJsonp||[]).push([["tpay-shopware-payment"],{W1qi:function(t,e,n){"use strict";n.r(e);var i=n("FGIj"),o=n("ERap"),r=n("gHbT");function a(t){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function s(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function l(t,e){for(var n=0;n0){if(o>=4&&!i)return this.el.value=(t=this.el.value,e=" ",n=3,"".concat(t.slice(0,n)).concat(e).concat(t.slice(n))),void this.validateBlik();for(;o>0&&!S[o-1].test(this.el.value);)this.el.value=this.el.value.slice(0,-1),o=this.el.value.length}else o>=8&&o<=9&&(this.el.value=this.el.value.slice(0,-1));this.el.value.length>=8&&(this.el.value=this.el.value.slice(0,7-this.el.value.length))}}])&&v(n.prototype,i),o&&v(n,o),e}(i.a),C=n("Cxgn"),O=n("p4AR");function P(t,e){for(var n=0;n0?(this._errors+=t.error,1):!t.success&&!t.blikCodeValid&&(this._notPaidOrderId=t.orderId,this.$blikModal.querySelector(".blik--modal").classList.add(this.options.isInvalidClass,"is-blik-invalid"),1)))}},{key:"_tosValidation",value:function(){var t=this;if(0===this.$allRequiredTosPositions.length)return!0;o.a.iterate(this.$allRequiredTosPositions,(function(e){e.checked?e.classList.remove(t.options.isInvalidClass):e.classList.add(t.options.isInvalidClass)}));var e=[];return[].forEach.call(this.$allRequiredTosPositions,(function(n){n.classList.contains(t.options.isInvalidClass)&&e.push(!0)})),0===e.length}},{key:"_blikCodeValidation",value:function(t){return void 0===t&&(t=this.$blikCodeInput),0===t.value.length?(t.classList.add(this.options.isInvalidClass),!1):J.test(t.value)}}])&&A(n.prototype,i),r&&A(n,r),e}(i.a);function V(t){return(V="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function W(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function D(t,e){for(var n=0;n