From a2e84383731822d6e42a0fbb774787886eac508d Mon Sep 17 00:00:00 2001 From: Michael Furtner Date: Wed, 25 Jan 2017 17:48:59 +0100 Subject: [PATCH] Initial commit --- README.md | 49 + .../Adminhtml/Customer/Edit/Tab/Alias.php | 172 + .../Customer/Renderer/PaymentMethod.php | 27 + .../Adminhtml/Customer/Renderer/State.php | 21 + .../Block/Adminhtml/Kwixo/Shipping/Edit.php | 86 + .../Adminhtml/Kwixocategory/CategoryTree.php | 106 + .../Block/Adminhtml/Kwixocategory/Edit.php | 58 + .../Adminhtml/Kwixocategory/Edit/Form.php | 75 + .../Creditmemo/ClosedTransaction/Warning.php | 33 + .../Order/Creditmemo/Totals/Checkbox.php | 33 + .../Netresearch/OPS/Block/Alias/List.php | 69 + .../Block/Checkout/DeviceFingerprinting.php | 79 + .../OPS/Block/Checkout/SubscriptionNotice.php | 202 + .../community/Netresearch/OPS/Block/Form.php | 363 ++ .../Netresearch/OPS/Block/Form/Alias.php | 55 + .../OPS/Block/Form/BankTransfer.php | 42 + .../Netresearch/OPS/Block/Form/Cc.php | 178 + .../OPS/Block/Form/DirectDebit.php | 47 + .../OPS/Block/Form/DirectEbanking.php | 42 + .../Netresearch/OPS/Block/Form/Flex.php | 70 + .../Netresearch/OPS/Block/Form/Ideal.php | 33 + .../Netresearch/OPS/Block/Form/InterSolve.php | 52 + .../OPS/Block/Form/Kwixo/ApresReception.php | 27 + .../OPS/Block/Form/Kwixo/Comptant.php | 30 + .../OPS/Block/Form/Kwixo/Credit.php | 28 + .../OPS/Block/Form/OpenInvoice.php | 63 + .../Netresearch/OPS/Block/Form/OpsId.php | 17 + .../Netresearch/OPS/Block/Form/PayPerMail.php | 43 + .../OPS/Block/Form/RecurringCc.php | 37 + .../Netresearch/OPS/Block/Frauddetection.php | 74 + .../Netresearch/OPS/Block/Info/Alias.php | 32 + .../Netresearch/OPS/Block/Info/Bancontact.php | 54 + .../Netresearch/OPS/Block/Info/Cc.php | 43 + .../Netresearch/OPS/Block/Info/Flex.php | 55 + .../Netresearch/OPS/Block/Info/OpsId.php | 43 + .../Netresearch/OPS/Block/Info/PayPerMail.php | 44 + .../OPS/Block/Info/RecurringCc.php | 35 + .../Netresearch/OPS/Block/Info/Redirect.php | 42 + .../Netresearch/OPS/Block/Paypage.php | 41 + .../Netresearch/OPS/Block/Placeform.php | 191 + .../OPS/Block/Placeform3dsecure.php | 42 + .../Netresearch/OPS/Block/RetryPayment.php | 103 + .../OPS/Block/RetryPayment/Methods.php | 80 + .../Block/System/Config/Form/Field/Brand.php | 53 + .../Block/System/Config/Form/Field/Image.php | 87 + .../Block/System/Config/Form/Field/Method.php | 59 + .../Config/Form/Field/RecurringActive.php | 55 + .../System/Config/Kwixoconfiguration.php | 35 + .../OPS/Block/System/Config/Mode.php | 32 + .../OPS/Block/System/Config/PaymentForm.php | 47 + .../OPS/Block/System/Config/Support.php | 170 + .../Netresearch/OPS/Controller/Abstract.php | 181 + .../Netresearch/OPS/Helper/Address.php | 181 + .../Netresearch/OPS/Helper/Alias.php | 514 +++ .../community/Netresearch/OPS/Helper/Api.php | 126 + .../Netresearch/OPS/Helper/Creditcard.php | 79 + .../community/Netresearch/OPS/Helper/Data.php | 193 + .../Netresearch/OPS/Helper/Debitcard.php | 21 + .../Netresearch/OPS/Helper/DirectDebit.php | 150 + .../Netresearch/OPS/Helper/Directlink.php | 262 ++ .../Netresearch/OPS/Helper/Kwixo.php | 165 + .../Netresearch/OPS/Helper/MobileDetect.php | 68 + .../Netresearch/OPS/Helper/Order.php | 170 + .../Netresearch/OPS/Helper/Order/Abstract.php | 84 + .../Netresearch/OPS/Helper/Order/Capture.php | 79 + .../Netresearch/OPS/Helper/Order/Refund.php | 142 + .../Netresearch/OPS/Helper/Payment.php | 969 +++++ .../OPS/Helper/Payment/DirectLink/Request.php | 333 ++ .../Payment/DirectLink/RequestInterface.php | 115 + .../OPS/Helper/Payment/Request.php | 738 ++++ .../Netresearch/OPS/Helper/Quote.php | 164 + .../Netresearch/OPS/Helper/Subscription.php | 148 + .../OPS/Helper/Validation/Checkout/Step.php | 98 + .../OPS/Helper/Validation/Result.php | 210 + .../Netresearch/OPS/Helper/Version.php | 66 + .../community/Netresearch/OPS/Model/Alias.php | 70 + .../Netresearch/OPS/Model/Alias/State.php | 34 + .../Netresearch/OPS/Model/Api/Directlink.php | 218 + .../Capture/Additional/OpenInvoiceNl.php | 15 + .../Backend/Operation/Capture/Parameter.php | 51 + .../OPS/Model/Backend/Operation/Parameter.php | 69 + .../Backend/Operation/Parameter/Abstract.php | 133 + .../Parameter/Additional/Interface.php | 11 + .../Additional/OpenInvoiceNlAbstract.php | 151 + .../Backend/Operation/Parameter/Interface.php | 17 + .../Refund/Additional/OpenInvoiceNl.php | 138 + .../Backend/Operation/Refund/Parameter.php | 70 + .../Netresearch/OPS/Model/Config.php | 976 +++++ .../Netresearch/OPS/Model/Eci/Values.php | 35 + .../Netresearch/OPS/Model/File/Download.php | 61 + .../OPS/Model/Kwixo/Category/Mapping.php | 57 + .../OPS/Model/Kwixo/Shipping/Setting.php | 24 + .../Netresearch/OPS/Model/Mysql4/Alias.php | 33 + .../OPS/Model/Mysql4/Alias/Collection.php | 29 + .../Model/Mysql4/Kwixo/Category/Mapping.php | 32 + .../Kwixo/Category/Mapping/Collection.php | 28 + .../Model/Mysql4/Kwixo/Shipping/Setting.php | 20 + .../Kwixo/Shipping/Setting/Collection.php | 21 + .../Netresearch/OPS/Model/Observer.php | 725 ++++ .../OPS/Model/Payment/Abstract.php | 1127 ++++++ .../OPS/Model/Payment/Backend/OpsId.php | 36 + .../OPS/Model/Payment/Bancontact.php | 102 + .../OPS/Model/Payment/BankTransfer.php | 49 + .../OPS/Model/Payment/BelfiusDirectNet.php | 25 + .../Netresearch/OPS/Model/Payment/CashU.php | 25 + .../OPS/Model/Payment/CbcOnline.php | 25 + .../Netresearch/OPS/Model/Payment/Cc.php | 302 ++ .../OPS/Model/Payment/ChinaUnionPay.php | 58 + .../OPS/Model/Payment/Debitcard.php | 54 + .../OPS/Model/Payment/DirectDebit.php | 61 + .../OPS/Model/Payment/DirectEbanking.php | 67 + .../OPS/Model/Payment/DirectLink.php | 350 ++ .../OPS/Model/Payment/EDankort.php | 26 + .../Netresearch/OPS/Model/Payment/Eps.php | 24 + .../Model/Payment/Features/PaymentEmail.php | 198 + .../Model/Payment/Features/ZeroAmountAuth.php | 41 + .../Netresearch/OPS/Model/Payment/Flex.php | 41 + .../OPS/Model/Payment/FortisPayButton.php | 25 + .../Netresearch/OPS/Model/Payment/GiroPay.php | 26 + .../Netresearch/OPS/Model/Payment/IDeal.php | 78 + .../OPS/Model/Payment/IngHomePay.php | 26 + .../OPS/Model/Payment/InterSolve.php | 51 + .../OPS/Model/Payment/KbcOnline.php | 26 + .../OPS/Model/Payment/Kwixo/Abstract.php | 551 +++ .../OPS/Model/Payment/KwixoApresReception.php | 25 + .../OPS/Model/Payment/KwixoComptant.php | 26 + .../OPS/Model/Payment/KwixoCredit.php | 27 + .../OPS/Model/Payment/Masterpass.php | 27 + .../Model/Payment/OpenInvoice/Abstract.php | 104 + .../OPS/Model/Payment/OpenInvoiceAt.php | 107 + .../OPS/Model/Payment/OpenInvoiceDe.php | 95 + .../OPS/Model/Payment/OpenInvoiceNl.php | 140 + .../OPS/Model/Payment/PayPerMail.php | 41 + .../Netresearch/OPS/Model/Payment/Paypal.php | 26 + .../OPS/Model/Payment/Paysafecard.php | 26 + .../OPS/Model/Payment/PingPing.php | 26 + .../OPS/Model/Payment/PostFinanceEFinance.php | 25 + .../OPS/Model/Payment/Recurring/Cc.php | 391 ++ .../Payment/Recurring/Cc/ParameterBag.php | 533 +++ .../OPS/Model/Response/Handler.php | 77 + .../OPS/Model/Response/Type/Abstract.php | 373 ++ .../OPS/Model/Response/Type/Authorize.php | 251 ++ .../OPS/Model/Response/Type/Capture.php | 151 + .../OPS/Model/Response/Type/Refund.php | 193 + .../OPS/Model/Response/Type/Special.php | 87 + .../OPS/Model/Response/Type/Void.php | 67 + .../OPS/Model/Response/TypeInterface.php | 51 + .../Model/Source/BankTransfer/Countries.php | 70 + .../Source/Cc/AliasInterfaceEnabledTypes.php | 62 + .../OPS/Model/Source/Cc/RecurringTypes.php | 50 + .../Netresearch/OPS/Model/Source/Cc/Types.php | 52 + .../DebitCard/AliasInterfaceEnabledTypes.php | 61 + .../OPS/Model/Source/DebitCard/Types.php | 52 + .../Model/Source/DirectDebit/Countries.php | 65 + .../Model/Source/DirectEbanking/Brands.php | 67 + .../Model/Source/Kwixo/ProductCategories.php | 127 + .../OPS/Model/Source/Kwixo/ShipMethodType.php | 94 + .../Netresearch/OPS/Model/Source/Mode.php | 31 + .../OPS/Model/Source/OrderReference.php | 50 + .../OPS/Model/Source/PaymentAction.php | 50 + .../Netresearch/OPS/Model/Source/Pmlist.php | 54 + .../Netresearch/OPS/Model/Source/Template.php | 58 + .../OPS/Model/Source/TemplateType.php | 23 + .../Netresearch/OPS/Model/Status.php | 301 ++ .../Netresearch/OPS/Model/Status/Feedback.php | 33 + .../Netresearch/OPS/Model/Status/Update.php | 314 ++ .../OPS/Model/Subscription/Manager.php | 306 ++ .../Model/System/Config/Backend/Encoding.php | 46 + .../System/Config/Backend/Flex/Methods.php | 67 + .../System/Config/Backend/HashMethods.php | 48 + .../Config/Backend/Intersolve/Brands.php | 60 + .../System/Config/Backend/PaymentLogo.php | 47 + .../OPS/Model/System/Config/Mode.php | 40 + .../OPS/Model/System/Config/Template.php | 63 + .../Model/Validator/CompositeInterface.php | 12 + .../Validator/Kwixo/Shipping/Setting.php | 95 + .../OPS/Model/Validator/Parameter/Factory.php | 63 + .../OPS/Model/Validator/Parameter/Length.php | 96 + .../Model/Validator/Parameter/Validator.php | 56 + .../Adminhtml/Customer/Renderer/StateTest.php | 23 + .../Adminhtml/Kwixo/Shipping/EditTest.php | 81 + .../Order/Creditmemo/Totals/CheckboxTest.php | 11 + .../OPS/Test/Block/Alias/ListTest.php | 77 + .../Checkout/DeviceFingerprintingTest.php | 75 + .../fixtures/fingerPrintingDisabled.yaml | 3 + .../fixtures/fingerPrintingEnabled.yaml | 3 + .../OPS/Test/Block/Form/CcTest.php | 459 +++ .../Block/Form/CcTest/fixtures/aliases.yaml | 92 + .../OPS/Test/Block/Form/DirectDebitTest.php | 51 + .../OPS/Test/Block/Form/Field/MethodTest.php | 42 + .../OPS/Test/Block/Form/FlexTest.php | 89 + .../Netresearch/OPS/Test/Block/Form/Ideal.php | 12 + .../OPS/Test/Block/Form/InterSolveTest.php | 52 + .../Block/Form/Kwixo/ApresReceptionTest.php | 29 + .../Test/Block/Form/Kwixo/ComptantTest.php | 30 + .../OPS/Test/Block/Form/Kwixo/CreditTest.php | 34 + .../Netresearch/OPS/Test/Block/FormTest.php | 106 + .../OPS/Test/Block/FormTest/fixtures/.gitkeep | 0 .../Block/FormTest/fixtures/FormTest.yaml | 22 + .../OPS/Test/Block/FrauddetectionTest.php | 65 + .../OPS/Test/Block/Info/FlexTest.php | 50 + .../OPS/Test/Block/PlaceFormShaTest.php | 81 + .../OPS/Test/Block/PlaceformTest.php | 92 + .../Test/Block/RetryPayment/MethodsTest.php | 86 + .../OPS/Test/Block/RetryPaymentTest.php | 94 + .../System/Config/Form/Field/ImageTest.php | 73 + .../System/Config/KwixoconfigurationTest.php | 24 + .../OPS/Test/Block/System/Config/ModeTest.php | 21 + .../Adminhtml/AdminControllerTest.php | 82 + .../Adminhtml/KwixocategoryControllerTest.php | 45 + .../fixtures/category_mapping.yaml | 11 + .../Adminhtml/KwixoshippingControllerTest.php | 133 + .../fixtures/shipping_settings.yaml | 7 + .../Adminhtml/OpsstatusControllerTest.php | 71 + .../Test/Controller/AliasControllerTest.php | 125 + .../AliasControllerTest/fixtures/orders.yaml | 595 +++ .../OPS/Test/Controller/ApiControllerTest.php | 165 + .../Test/Controller/DeviceControllerTest.php | 66 + .../Test/Controller/PaymentControllerTest.php | 413 ++ .../PaymentControllerTest/fixtures/.gitkeep | 0 .../fixtures/orders.yaml | 26 + .../OPS/Test/Helper/AddressTest.php | 22 + .../AddressTest/expectations/splitStreet.yaml | 209 + .../AddressTest/providers/splitStreet.yaml | 105 + .../Netresearch/OPS/Test/Helper/AliasTest.php | 763 ++++ .../Test/Helper/AliasTest/fixtures/.gitkeep | 0 .../Netresearch/OPS/Test/Helper/ApiTest.php | 52 + .../Netresearch/OPS/Test/Helper/DataTest.php | 170 + .../OPS/Test/Helper/DirectDebitTest.php | 144 + .../Helper/DirectDebitTest/fixtures/.gitkeep | 0 .../DirectDebitTest/fixtures/orders.yaml | 79 + .../OPS/Test/Helper/DirectLinkTest.php | 562 +++ .../Helper/DirectLinkTest/fixtures/.gitkeep | 0 .../Netresearch/OPS/Test/Helper/KwixoTest.php | 132 + .../KwixoTest/fixtures/category_mapping.yaml | 11 + .../OPS/Test/Helper/MobileDetectTest.php | 77 + .../OPS/Test/Helper/Order/CaptureTest.php | 82 + .../Order/CaptureTest/fixtures/.gitkeep | 0 .../OPS/Test/Helper/Order/RefundTest.php | 141 + .../Helper/Order/RefundTest/fixtures/.gitkeep | 0 .../Netresearch/OPS/Test/Helper/OrderTest.php | 137 + .../Test/Helper/OrderTest/fixtures/order.yaml | 42 + .../Helper/Payment/DirectLink/RequestTest.php | 89 + .../DirectLink/RequestTest/fixtures/.gitkeep | 0 .../OPS/Test/Helper/Payment/RequestTest.php | 323 ++ .../OPS/Test/Helper/PaymentTest.php | 708 ++++ .../Test/Helper/PaymentTest/fixtures/.gitkeep | 0 .../Netresearch/OPS/Test/Helper/QuoteTest.php | 176 + .../Test/Helper/QuoteTest/fixtures/.gitkeep | 1 + .../OPS/Test/Helper/SubscriptionTest.php | 116 + .../SubscriptionTest/fixtures/profile.yaml | 11 + .../Helper/Validation/Checkout/StepTest.php | 44 + .../OPS/Test/Helper/Validation/ResultTest.php | 94 + .../ResultTest/fixtures/quotes.yaml | 42 + .../OPS/Test/Helper/VersionTest.php | 92 + .../Test/Model/AliasTest/fixtures/.gitkeep | 0 .../OPS/Test/Model/Api/DirectLinkShaTest.php | 44 + .../OPS/Test/Model/Api/DirectLinkTest.php | 253 ++ .../Capture/Additional/OpenInvoiceNlTest.php | 291 ++ .../Operation/Capture/ParameterTest.php | 70 + .../Model/Backend/Operation/ParameterTest.php | 52 + .../Refund/Additional/OpenInvoiceNlTest.php | 248 ++ .../Operation/Refund/ParameterTest.php | 91 + .../Netresearch/OPS/Test/Model/ConfigTest.php | 611 +++ .../OPS/Test/Model/File/Download.php | 37 + .../Test/Model/Kwixo/Category/MappingTest.php | 28 + .../fixtures/category_mapping.yaml | 11 + .../OPS/Test/Model/Mysql4/Alias/AliasTest.php | 9 + .../OPS/Test/Model/Mysql4/AliasTest.php | 11 + .../OPS/Test/Model/ObserverTest.php | 1111 ++++++ .../Test/Model/ObserverTest/fixtures/.gitkeep | 0 .../Model/Payment/AbstractCaptureTest.php | 276 ++ .../Test/Model/Payment/AbstractRefundTest.php | 214 + .../OPS/Test/Model/Payment/AbstractTest.php | 890 +++++ .../expectations/paymentMethods.yaml | 279 ++ .../Payment/AbstractTest/fixtures/.gitkeep | 0 .../Payment/AbstractTest/fixtures/orders.yaml | 135 + .../OPS/Test/Model/Payment/BancontactTest.php | 81 + .../Payment/BancontactTest/fixtures/.gitkeep | 0 .../OPS/Test/Model/Payment/CcTest.php | 213 + .../Test/Model/Payment/ChinaUnionPayTest.php | 47 + .../OPS/Test/Model/Payment/DebitcardTest.php | 53 + .../Test/Model/Payment/DirectDebitTest.php | 23 + .../Test/Model/Payment/DirectEbankingTest.php | 19 + .../OPS/Test/Model/Payment/DirectLinkTest.php | 475 +++ .../Payment/Features/PaymentEmailTest.php | 120 + .../Payment/Features/ZeroAmountAuthTest.php | 58 + .../OPS/Test/Model/Payment/FlexTest.php | 77 + .../OPS/Test/Model/Payment/IDealTest.php | 145 + .../Payment/IDealTest/fixtures/orders.yaml | 58 + .../OPS/Test/Model/Payment/InterSolveTest.php | 78 + .../Test/Model/Payment/Kwixo/AbstractTest.php | 768 ++++ .../expectations/paymentMethods.yaml | 279 ++ .../Kwixo/AbstractTest/fixtures/.gitkeep | 0 .../Kwixo/AbstractTest/fixtures/orders.yaml | 633 +++ .../Model/Payment/KwixoApresReceptionTest.php | 55 + .../Test/Model/Payment/KwixoComptantTest.php | 56 + .../Test/Model/Payment/KwixoCreditTest.php | 59 + .../OPS/Test/Model/Payment/ObjectHandler.php | 14 + .../Test/Model/Payment/OpenInvoiceAtTest.php | 87 + .../Test/Model/Payment/OpenInvoiceDeTest.php | 60 + .../Test/Model/Payment/OpenInvoiceNlTest.php | 94 + .../OPS/Test/Model/Payment/PayPerMailTest.php | 77 + .../Payment/Recurring/Cc/ParameterBagTest.php | 396 ++ .../Test/Model/Payment/Recurring/CcTest.php | 582 +++ .../OPS/Test/Model/Payment/objects.xml | 4 + .../OPS/Test/Model/Response/TestCase.php | 62 + .../Model/Response/Type/AuthorizeTest.php | 267 ++ .../Type/AuthorizeTest/fixtures/orders.yaml | 239 ++ .../Test/Model/Response/Type/CaptureTest.php | 188 + .../Type/CaptureTest/fixtures/orders.yaml | 135 + .../Test/Model/Response/Type/RefundTest.php | 274 ++ .../Type/RefundTest/fixtures/orders.yaml | 157 + .../Cc/AliasInterfaceEnabledTypesTest.php | 20 + .../Model/Source/Cc/RecurringTypesTest.php | 41 + .../Source/DirectEbanking/BrandsTest.php | 19 + .../Source/Kwixo/ProductCategoriesTest.php | 51 + .../Model/Source/Kwixo/ShipMethodTypeTest.php | 25 + .../OPS/Test/Model/Source/ModeTest.php | 21 + .../Test/Model/Source/OrderReferenceTest.php | 22 + .../OPS/Test/Model/Status/UpdateTest.php | 311 ++ .../Status/UpdateTest/fixtures/orders.yaml | 135 + .../Netresearch/OPS/Test/Model/StatusTest.php | 122 + .../Test/Model/Subscription/ManagerTest.php | 176 + .../Config/Backend/Design/BrandsTest.php | 53 + .../System/Config/Backend/EncodingTest.php | 60 + .../Config/Backend/Flex/MethodsTest.php | 106 + .../System/Config/Backend/PaymentLogoTest.php | 53 + .../Validator/Kwixo/Shipping/Setting.php | 124 + .../Model/Validator/Parameter/FactoryTest.php | 34 + .../Model/Validator/Parameter/LengthTest.php | 47 + .../Validator/Parameter/ValidatorTest.php | 36 + .../OPS/Test/var/fixtures/aliases.yaml | 92 + .../OPS/Test/var/fixtures/orders.yaml | 703 ++++ .../OPS/Test/var/fixtures/quotes.yaml | 21 + .../Netresearch/OPS/Trait/AliasController.php | 259 ++ .../Netresearch/OPS/Trait/PaymentHelper.php | 60 + .../controllers/Adminhtml/AdminController.php | 80 + .../controllers/Adminhtml/AliasController.php | 42 + .../Adminhtml/KwixocategoryController.php | 228 ++ .../Adminhtml/KwixoshippingController.php | 93 + .../Adminhtml/OpsstatusController.php | 31 + .../OPS/controllers/AliasController.php | 13 + .../OPS/controllers/ApiController.php | 89 + .../OPS/controllers/CustomerController.php | 68 + .../OPS/controllers/DeviceController.php | 83 + .../OPS/controllers/PaymentController.php | 408 ++ .../data/ops_setup/data-install-14.02.05.php | 12 + .../data-upgrade-14.02.05-14.06.25.php | 12 + .../data-upgrade-14.06.25-14.07.16.php | 150 + .../data-upgrade-15.06.08-15.11.02.php | 44 + .../data-upgrade-15.11.09-16.03.09.php | 37 + .../community/Netresearch/OPS/etc/config.xml | 599 +++ .../Netresearch/OPS/etc/jstranslator.xml | 27 + .../community/Netresearch/OPS/etc/system.xml | 3542 +++++++++++++++++ .../sql/ops_setup/mysql4-install-1.0.1.php | 46 + .../mysql4-upgrade-1.0.1-12.12.03.php | 24 + .../mysql4-upgrade-13.04.10-13.04.16.php | 126 + .../mysql4-upgrade-13.05.30-13.06.07.php | 70 + .../mysql4-upgrade-13.06.07-13.07.04.php | 17 + .../mysql4-upgrade-13.07.10-13.07.23.php | 14 + .../mysql4-upgrade-13.11.04-13.11.05.php | 15 + .../mysql4-upgrade-14.01.27-14.02.05.php | 52 + .../mysql4-upgrade-16.05.26-27.06.16.php | 8 + .../adminhtml/default/default/layout/ops.xml | 89 + .../default/template/ops/categoriestree.phtml | 632 +++ .../default/template/ops/form/cc.phtml | 177 + .../template/ops/form/directDebit.phtml | 189 + .../template/ops/form/kwixo/category.phtml | 21 + .../template/ops/form/kwixo/shipping.phtml | 102 + .../default/template/ops/form/opsId.phtml | 37 + .../template/ops/form/payPerMail.phtml | 31 + .../default/default/template/ops/info.phtml | 64 + .../template/ops/info/bancontact.phtml | 81 + .../default/template/ops/info/cc.phtml | 73 + .../default/template/ops/info/flex.phtml | 84 + .../default/template/ops/info/opsId.phtml | 23 + .../template/ops/info/payPerMail.phtml | 23 + .../default/template/ops/info/redirect.phtml | 75 + .../closed-transaction/warning.phtml | 28 + .../order/creditmemo/totals/checkbox.phtml | 30 + .../ops/system/config/kwixoconfiglinks.phtml | 44 + .../template/ops/system/config/support.phtml | 100 + .../frontend/base/default/layout/ops.xml | 189 + .../ops/checkout/deviceFingerprinting.phtml | 30 + .../ops/checkout/subscriptionNotice.phtml | 45 + .../template/ops/customer/alias/list.phtml | 68 + .../base/default/template/ops/form.phtml | 38 + .../default/template/ops/form/alias.phtml | 42 + .../template/ops/form/bankTransfer.phtml | 51 + .../base/default/template/ops/form/cc.phtml | 168 + .../template/ops/form/directDebit.phtml | 145 + .../template/ops/form/directEbanking.phtml | 51 + .../base/default/template/ops/form/flex.phtml | 98 + .../default/template/ops/form/ideal.phtml | 37 + .../template/ops/form/intersolve.phtml | 56 + .../ops/form/kwixo/apres_reception.phtml | 11 + .../template/ops/form/kwixo/comptant.phtml | 11 + .../template/ops/form/kwixo/credit.phtml | 11 + .../template/ops/form/openInvoice.phtml | 42 + .../default/template/ops/form/other.phtml | 26 + .../default/template/ops/frauddetection.phtml | 48 + .../base/default/template/ops/info.phtml | 27 + .../default/template/ops/info/alias.phtml | 23 + .../template/ops/info/bancontact.phtml | 23 + .../base/default/template/ops/info/cc.phtml | 23 + .../base/default/template/ops/info/flex.phtml | 33 + .../default/template/ops/info/opsId.phtml | 23 + .../template/ops/info/payPerMail.phtml | 23 + .../default/template/ops/info/redirect.phtml | 23 + .../base/default/template/ops/paypage.phtml | 23 + .../base/default/template/ops/placeform.phtml | 128 + .../template/ops/placeform3dsecure.phtml | 23 + .../default/template/ops/retryPayment.phtml | 146 + .../template/ops/customer/alias/list.phtml | 80 + app/etc/modules/Netresearch_OPS.xml | 44 + app/locale/de_DE/Netresearch_OPS.csv | 485 +++ .../ops/ops_email_resend_payment_info.html | 49 + .../email/ops/ops_pay_per_mail_info.html | 49 + .../email/ops/suspend_subscription.html | 43 + app/locale/en_US/Netresearch_OPS.csv | 485 +++ .../ops/ops_email_resend_payment_info.html | 49 + .../email/ops/ops_pay_per_mail_info.html | 49 + .../email/ops/suspend_subscription.html | 43 + app/locale/fr_FR/Netresearch_OPS.csv | 189 + app/locale/it_IT/Netresearch_OPS.csv | 185 + app/locale/nl_NL/Netresearch_OPS.csv | 186 + composer.json | 8 + js/netresearch/ops/deviceFingerprinting.js | 48 + js/netresearch/ops/osc_payment.js | 101 + js/netresearch/ops/payment.js | 198 + js/netresearch/ops/tokenization.js | 230 ++ lib/MobileDetect/Mobile_Detect.php | 1387 +++++++ modman | 20 + skin/adminhtml/default/default/ops.css | 46 + skin/frontend/base/default/css/ops.css | 299 ++ .../ops/alias/brands/AmericanExpress.png | Bin 0 -> 119967 bytes .../images/ops/alias/brands/DinnerClub.png | Bin 0 -> 40457 bytes .../images/ops/alias/brands/Maestro.png | Bin 0 -> 29431 bytes .../images/ops/alias/brands/MasterCard.png | Bin 0 -> 96289 bytes .../default/images/ops/alias/brands/VISA.png | Bin 0 -> 49753 bytes .../images/ops/kwixo/apres_reception.jpg | Bin 0 -> 4950 bytes .../default/images/ops/kwixo/comptant.jpg | Bin 0 -> 4841 bytes .../base/default/images/ops/kwixo/credit.jpg | Bin 0 -> 4814 bytes .../default/images/ops/logos/ops_BCMC.png | Bin 0 -> 2306 bytes .../images/ops/logos/ops_Masterpass.png | Bin 0 -> 1529 bytes .../images/ops/logos/ops_bankTransfer.png | Bin 0 -> 2037 bytes .../images/ops/logos/ops_belfiusDirectNet.png | Bin 0 -> 2174 bytes .../default/images/ops/logos/ops_cashU.png | Bin 0 -> 2218 bytes .../images/ops/logos/ops_cbcOnline.png | Bin 0 -> 1198 bytes .../base/default/images/ops/logos/ops_cc.png | Bin 0 -> 2293 bytes .../images/ops/logos/ops_chinaUnionPay.png | Bin 0 -> 3325 bytes .../base/default/images/ops/logos/ops_dc.png | Bin 0 -> 2293 bytes .../images/ops/logos/ops_directDebit.png | Bin 0 -> 1863 bytes .../images/ops/logos/ops_directEbanking.png | Bin 0 -> 2277 bytes .../default/images/ops/logos/ops_eDankort.png | Bin 0 -> 1708 bytes .../base/default/images/ops/logos/ops_eps.png | Bin 0 -> 1848 bytes .../default/images/ops/logos/ops_flex.png | Bin 0 -> 3490 bytes .../images/ops/logos/ops_fortisPayButton.png | Bin 0 -> 3488 bytes .../default/images/ops/logos/ops_giroPay.png | Bin 0 -> 2463 bytes .../default/images/ops/logos/ops_iDeal.png | Bin 0 -> 2859 bytes .../images/ops/logos/ops_ingHomePay.png | Bin 0 -> 2187 bytes .../images/ops/logos/ops_interSolve.jpg | Bin 0 -> 39346 bytes .../images/ops/logos/ops_interSolve.png | Bin 0 -> 1241 bytes .../images/ops/logos/ops_kbcOnline.png | Bin 0 -> 1147 bytes .../ops/logos/ops_kwixoApresReception.png | Bin 0 -> 1822 bytes .../images/ops/logos/ops_kwixoComptant.png | Bin 0 -> 1822 bytes .../images/ops/logos/ops_kwixoCredit.png | Bin 0 -> 1822 bytes .../images/ops/logos/ops_openInvoiceAt.png | Bin 0 -> 1606 bytes .../images/ops/logos/ops_openInvoiceDe.png | Bin 0 -> 1606 bytes .../images/ops/logos/ops_openInvoiceNl.png | Bin 0 -> 1606 bytes .../default/images/ops/logos/ops_opsid.png | Bin 0 -> 2037 bytes .../default/images/ops/logos/ops_paypal.png | Bin 0 -> 1963 bytes .../images/ops/logos/ops_paysafecard.png | Bin 0 -> 1860 bytes .../default/images/ops/logos/ops_pingPing.png | Bin 0 -> 1884 bytes .../ops/logos/ops_postFinanceEFinance.png | Bin 0 -> 1319 bytes .../images/ops/logos/ops_recurring_cc.png | Bin 0 -> 2293 bytes skin/frontend/rwd/default/css/ops.css | 316 ++ 478 files changed, 56036 insertions(+) create mode 100644 README.md create mode 100644 app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Edit/Tab/Alias.php create mode 100644 app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Renderer/PaymentMethod.php create mode 100644 app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Renderer/State.php create mode 100644 app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixo/Shipping/Edit.php create mode 100644 app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/CategoryTree.php create mode 100644 app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/Edit.php create mode 100644 app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/Edit/Form.php create mode 100644 app/code/community/Netresearch/OPS/Block/Adminhtml/Sales/Order/Creditmemo/ClosedTransaction/Warning.php create mode 100644 app/code/community/Netresearch/OPS/Block/Adminhtml/Sales/Order/Creditmemo/Totals/Checkbox.php create mode 100644 app/code/community/Netresearch/OPS/Block/Alias/List.php create mode 100644 app/code/community/Netresearch/OPS/Block/Checkout/DeviceFingerprinting.php create mode 100644 app/code/community/Netresearch/OPS/Block/Checkout/SubscriptionNotice.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/Alias.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/BankTransfer.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/Cc.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/DirectDebit.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/DirectEbanking.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/Flex.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/Ideal.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/InterSolve.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/Kwixo/ApresReception.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/Kwixo/Comptant.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/Kwixo/Credit.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/OpenInvoice.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/OpsId.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/PayPerMail.php create mode 100644 app/code/community/Netresearch/OPS/Block/Form/RecurringCc.php create mode 100644 app/code/community/Netresearch/OPS/Block/Frauddetection.php create mode 100644 app/code/community/Netresearch/OPS/Block/Info/Alias.php create mode 100644 app/code/community/Netresearch/OPS/Block/Info/Bancontact.php create mode 100644 app/code/community/Netresearch/OPS/Block/Info/Cc.php create mode 100644 app/code/community/Netresearch/OPS/Block/Info/Flex.php create mode 100644 app/code/community/Netresearch/OPS/Block/Info/OpsId.php create mode 100644 app/code/community/Netresearch/OPS/Block/Info/PayPerMail.php create mode 100644 app/code/community/Netresearch/OPS/Block/Info/RecurringCc.php create mode 100644 app/code/community/Netresearch/OPS/Block/Info/Redirect.php create mode 100644 app/code/community/Netresearch/OPS/Block/Paypage.php create mode 100644 app/code/community/Netresearch/OPS/Block/Placeform.php create mode 100644 app/code/community/Netresearch/OPS/Block/Placeform3dsecure.php create mode 100644 app/code/community/Netresearch/OPS/Block/RetryPayment.php create mode 100644 app/code/community/Netresearch/OPS/Block/RetryPayment/Methods.php create mode 100644 app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Brand.php create mode 100644 app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Image.php create mode 100644 app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Method.php create mode 100644 app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/RecurringActive.php create mode 100644 app/code/community/Netresearch/OPS/Block/System/Config/Kwixoconfiguration.php create mode 100644 app/code/community/Netresearch/OPS/Block/System/Config/Mode.php create mode 100644 app/code/community/Netresearch/OPS/Block/System/Config/PaymentForm.php create mode 100644 app/code/community/Netresearch/OPS/Block/System/Config/Support.php create mode 100644 app/code/community/Netresearch/OPS/Controller/Abstract.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Address.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Alias.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Api.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Creditcard.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Data.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Debitcard.php create mode 100644 app/code/community/Netresearch/OPS/Helper/DirectDebit.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Directlink.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Kwixo.php create mode 100644 app/code/community/Netresearch/OPS/Helper/MobileDetect.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Order.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Order/Abstract.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Order/Capture.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Order/Refund.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Payment.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Payment/DirectLink/Request.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Payment/DirectLink/RequestInterface.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Payment/Request.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Quote.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Subscription.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Validation/Checkout/Step.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Validation/Result.php create mode 100644 app/code/community/Netresearch/OPS/Helper/Version.php create mode 100644 app/code/community/Netresearch/OPS/Model/Alias.php create mode 100644 app/code/community/Netresearch/OPS/Model/Alias/State.php create mode 100644 app/code/community/Netresearch/OPS/Model/Api/Directlink.php create mode 100644 app/code/community/Netresearch/OPS/Model/Backend/Operation/Capture/Additional/OpenInvoiceNl.php create mode 100644 app/code/community/Netresearch/OPS/Model/Backend/Operation/Capture/Parameter.php create mode 100644 app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter.php create mode 100644 app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Abstract.php create mode 100644 app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Additional/Interface.php create mode 100644 app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Additional/OpenInvoiceNlAbstract.php create mode 100644 app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Interface.php create mode 100644 app/code/community/Netresearch/OPS/Model/Backend/Operation/Refund/Additional/OpenInvoiceNl.php create mode 100644 app/code/community/Netresearch/OPS/Model/Backend/Operation/Refund/Parameter.php create mode 100644 app/code/community/Netresearch/OPS/Model/Config.php create mode 100644 app/code/community/Netresearch/OPS/Model/Eci/Values.php create mode 100644 app/code/community/Netresearch/OPS/Model/File/Download.php create mode 100644 app/code/community/Netresearch/OPS/Model/Kwixo/Category/Mapping.php create mode 100644 app/code/community/Netresearch/OPS/Model/Kwixo/Shipping/Setting.php create mode 100644 app/code/community/Netresearch/OPS/Model/Mysql4/Alias.php create mode 100644 app/code/community/Netresearch/OPS/Model/Mysql4/Alias/Collection.php create mode 100644 app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Category/Mapping.php create mode 100644 app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Category/Mapping/Collection.php create mode 100644 app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Shipping/Setting.php create mode 100644 app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Shipping/Setting/Collection.php create mode 100644 app/code/community/Netresearch/OPS/Model/Observer.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Abstract.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Backend/OpsId.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Bancontact.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/BankTransfer.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/BelfiusDirectNet.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/CashU.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/CbcOnline.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Cc.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/ChinaUnionPay.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Debitcard.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/DirectDebit.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/DirectEbanking.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/DirectLink.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/EDankort.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Eps.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Features/PaymentEmail.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Features/ZeroAmountAuth.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Flex.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/FortisPayButton.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/GiroPay.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/IDeal.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/IngHomePay.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/InterSolve.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/KbcOnline.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Kwixo/Abstract.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/KwixoApresReception.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/KwixoComptant.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/KwixoCredit.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Masterpass.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/OpenInvoice/Abstract.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceAt.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceDe.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceNl.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/PayPerMail.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Paypal.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Paysafecard.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/PingPing.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/PostFinanceEFinance.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Recurring/Cc.php create mode 100644 app/code/community/Netresearch/OPS/Model/Payment/Recurring/Cc/ParameterBag.php create mode 100644 app/code/community/Netresearch/OPS/Model/Response/Handler.php create mode 100644 app/code/community/Netresearch/OPS/Model/Response/Type/Abstract.php create mode 100644 app/code/community/Netresearch/OPS/Model/Response/Type/Authorize.php create mode 100644 app/code/community/Netresearch/OPS/Model/Response/Type/Capture.php create mode 100644 app/code/community/Netresearch/OPS/Model/Response/Type/Refund.php create mode 100644 app/code/community/Netresearch/OPS/Model/Response/Type/Special.php create mode 100644 app/code/community/Netresearch/OPS/Model/Response/Type/Void.php create mode 100644 app/code/community/Netresearch/OPS/Model/Response/TypeInterface.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/BankTransfer/Countries.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/Cc/AliasInterfaceEnabledTypes.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/Cc/RecurringTypes.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/Cc/Types.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/DebitCard/AliasInterfaceEnabledTypes.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/DebitCard/Types.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/DirectDebit/Countries.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/DirectEbanking/Brands.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/Kwixo/ProductCategories.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/Kwixo/ShipMethodType.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/Mode.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/OrderReference.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/PaymentAction.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/Pmlist.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/Template.php create mode 100644 app/code/community/Netresearch/OPS/Model/Source/TemplateType.php create mode 100644 app/code/community/Netresearch/OPS/Model/Status.php create mode 100644 app/code/community/Netresearch/OPS/Model/Status/Feedback.php create mode 100644 app/code/community/Netresearch/OPS/Model/Status/Update.php create mode 100644 app/code/community/Netresearch/OPS/Model/Subscription/Manager.php create mode 100644 app/code/community/Netresearch/OPS/Model/System/Config/Backend/Encoding.php create mode 100644 app/code/community/Netresearch/OPS/Model/System/Config/Backend/Flex/Methods.php create mode 100644 app/code/community/Netresearch/OPS/Model/System/Config/Backend/HashMethods.php create mode 100644 app/code/community/Netresearch/OPS/Model/System/Config/Backend/Intersolve/Brands.php create mode 100644 app/code/community/Netresearch/OPS/Model/System/Config/Backend/PaymentLogo.php create mode 100644 app/code/community/Netresearch/OPS/Model/System/Config/Mode.php create mode 100644 app/code/community/Netresearch/OPS/Model/System/Config/Template.php create mode 100644 app/code/community/Netresearch/OPS/Model/Validator/CompositeInterface.php create mode 100644 app/code/community/Netresearch/OPS/Model/Validator/Kwixo/Shipping/Setting.php create mode 100644 app/code/community/Netresearch/OPS/Model/Validator/Parameter/Factory.php create mode 100644 app/code/community/Netresearch/OPS/Model/Validator/Parameter/Length.php create mode 100644 app/code/community/Netresearch/OPS/Model/Validator/Parameter/Validator.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Customer/Renderer/StateTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Kwixo/Shipping/EditTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Sales/Order/Creditmemo/Totals/CheckboxTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Alias/ListTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest/fixtures/fingerPrintingDisabled.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest/fixtures/fingerPrintingEnabled.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/CcTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/CcTest/fixtures/aliases.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/DirectDebitTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/Field/MethodTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/FlexTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/Ideal.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/InterSolveTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/ApresReceptionTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/ComptantTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/CreditTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/FormTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/FormTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Block/FormTest/fixtures/FormTest.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Block/FrauddetectionTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/Info/FlexTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/PlaceFormShaTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/PlaceformTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/RetryPayment/MethodsTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/RetryPaymentTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/System/Config/Form/Field/ImageTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/System/Config/KwixoconfigurationTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Block/System/Config/ModeTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/Adminhtml/AdminControllerTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/Adminhtml/KwixocategoryControllerTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/Adminhtml/KwixocategoryControllerTest/fixtures/category_mapping.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/Adminhtml/KwixoshippingControllerTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/Adminhtml/KwixoshippingControllerTest/fixtures/shipping_settings.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/Adminhtml/OpsstatusControllerTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/AliasControllerTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/AliasControllerTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/ApiControllerTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/DeviceControllerTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/AddressTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/AddressTest/expectations/splitStreet.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/AddressTest/providers/splitStreet.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/AliasTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/AliasTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/ApiTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/DataTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/DirectLinkTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/DirectLinkTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/KwixoTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/KwixoTest/fixtures/category_mapping.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/MobileDetectTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Order/CaptureTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Order/CaptureTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Order/RefundTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Order/RefundTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/OrderTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/OrderTest/fixtures/order.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Payment/DirectLink/RequestTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Payment/DirectLink/RequestTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Payment/RequestTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/PaymentTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/PaymentTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/QuoteTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/QuoteTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/SubscriptionTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/SubscriptionTest/fixtures/profile.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Validation/Checkout/StepTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Validation/ResultTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/Validation/ResultTest/fixtures/quotes.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Helper/VersionTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/AliasTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Api/DirectLinkShaTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Api/DirectLinkTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Capture/Additional/OpenInvoiceNlTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Capture/ParameterTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/ParameterTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Refund/Additional/OpenInvoiceNlTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Refund/ParameterTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/ConfigTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/File/Download.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Kwixo/Category/MappingTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Kwixo/Category/MappingTest/fixtures/category_mapping.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Mysql4/Alias/AliasTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Mysql4/AliasTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/ObserverTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/ObserverTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractCaptureTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractRefundTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/expectations/paymentMethods.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/BancontactTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/BancontactTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/CcTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/ChinaUnionPayTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/DebitcardTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/DirectDebitTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/DirectEbankingTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/DirectLinkTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/Features/PaymentEmailTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/Features/ZeroAmountAuthTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/FlexTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/IDealTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/IDealTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/InterSolveTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/expectations/paymentMethods.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/fixtures/.gitkeep create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoApresReceptionTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoComptantTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoCreditTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/ObjectHandler.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceAtTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceDeTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceNlTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/PayPerMailTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/Recurring/Cc/ParameterBagTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/Recurring/CcTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Payment/objects.xml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Response/TestCase.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Response/Type/AuthorizeTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Response/Type/AuthorizeTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Response/Type/CaptureTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Response/Type/CaptureTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Response/Type/RefundTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Response/Type/RefundTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Source/Cc/AliasInterfaceEnabledTypesTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Source/Cc/RecurringTypesTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Source/DirectEbanking/BrandsTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Source/Kwixo/ProductCategoriesTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Source/Kwixo/ShipMethodTypeTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Source/ModeTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Source/OrderReferenceTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Status/UpdateTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Status/UpdateTest/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/Model/StatusTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Subscription/ManagerTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/Design/BrandsTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/EncodingTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/Flex/MethodsTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/PaymentLogoTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Validator/Kwixo/Shipping/Setting.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/FactoryTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/LengthTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/ValidatorTest.php create mode 100644 app/code/community/Netresearch/OPS/Test/var/fixtures/aliases.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/var/fixtures/orders.yaml create mode 100644 app/code/community/Netresearch/OPS/Test/var/fixtures/quotes.yaml create mode 100644 app/code/community/Netresearch/OPS/Trait/AliasController.php create mode 100644 app/code/community/Netresearch/OPS/Trait/PaymentHelper.php create mode 100644 app/code/community/Netresearch/OPS/controllers/Adminhtml/AdminController.php create mode 100644 app/code/community/Netresearch/OPS/controllers/Adminhtml/AliasController.php create mode 100644 app/code/community/Netresearch/OPS/controllers/Adminhtml/KwixocategoryController.php create mode 100644 app/code/community/Netresearch/OPS/controllers/Adminhtml/KwixoshippingController.php create mode 100644 app/code/community/Netresearch/OPS/controllers/Adminhtml/OpsstatusController.php create mode 100644 app/code/community/Netresearch/OPS/controllers/AliasController.php create mode 100644 app/code/community/Netresearch/OPS/controllers/ApiController.php create mode 100644 app/code/community/Netresearch/OPS/controllers/CustomerController.php create mode 100644 app/code/community/Netresearch/OPS/controllers/DeviceController.php create mode 100644 app/code/community/Netresearch/OPS/controllers/PaymentController.php create mode 100644 app/code/community/Netresearch/OPS/data/ops_setup/data-install-14.02.05.php create mode 100644 app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-14.02.05-14.06.25.php create mode 100644 app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-14.06.25-14.07.16.php create mode 100644 app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-15.06.08-15.11.02.php create mode 100644 app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-15.11.09-16.03.09.php create mode 100644 app/code/community/Netresearch/OPS/etc/config.xml create mode 100644 app/code/community/Netresearch/OPS/etc/jstranslator.xml create mode 100644 app/code/community/Netresearch/OPS/etc/system.xml create mode 100644 app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-install-1.0.1.php create mode 100644 app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-1.0.1-12.12.03.php create mode 100644 app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.04.10-13.04.16.php create mode 100644 app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.05.30-13.06.07.php create mode 100644 app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.06.07-13.07.04.php create mode 100644 app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.07.10-13.07.23.php create mode 100644 app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.11.04-13.11.05.php create mode 100644 app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-14.01.27-14.02.05.php create mode 100644 app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-16.05.26-27.06.16.php create mode 100644 app/design/adminhtml/default/default/layout/ops.xml create mode 100644 app/design/adminhtml/default/default/template/ops/categoriestree.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/form/cc.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/form/directDebit.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/form/kwixo/category.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/form/kwixo/shipping.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/form/opsId.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/form/payPerMail.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/info.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/info/bancontact.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/info/cc.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/info/flex.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/info/opsId.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/info/payPerMail.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/info/redirect.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/sales/order/creditmemo/closed-transaction/warning.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/sales/order/creditmemo/totals/checkbox.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/system/config/kwixoconfiglinks.phtml create mode 100644 app/design/adminhtml/default/default/template/ops/system/config/support.phtml create mode 100644 app/design/frontend/base/default/layout/ops.xml create mode 100644 app/design/frontend/base/default/template/ops/checkout/deviceFingerprinting.phtml create mode 100644 app/design/frontend/base/default/template/ops/checkout/subscriptionNotice.phtml create mode 100644 app/design/frontend/base/default/template/ops/customer/alias/list.phtml create mode 100644 app/design/frontend/base/default/template/ops/form.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/alias.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/bankTransfer.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/cc.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/directDebit.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/directEbanking.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/flex.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/ideal.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/intersolve.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/kwixo/apres_reception.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/kwixo/comptant.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/kwixo/credit.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/openInvoice.phtml create mode 100644 app/design/frontend/base/default/template/ops/form/other.phtml create mode 100644 app/design/frontend/base/default/template/ops/frauddetection.phtml create mode 100644 app/design/frontend/base/default/template/ops/info.phtml create mode 100644 app/design/frontend/base/default/template/ops/info/alias.phtml create mode 100644 app/design/frontend/base/default/template/ops/info/bancontact.phtml create mode 100644 app/design/frontend/base/default/template/ops/info/cc.phtml create mode 100644 app/design/frontend/base/default/template/ops/info/flex.phtml create mode 100644 app/design/frontend/base/default/template/ops/info/opsId.phtml create mode 100644 app/design/frontend/base/default/template/ops/info/payPerMail.phtml create mode 100644 app/design/frontend/base/default/template/ops/info/redirect.phtml create mode 100644 app/design/frontend/base/default/template/ops/paypage.phtml create mode 100644 app/design/frontend/base/default/template/ops/placeform.phtml create mode 100644 app/design/frontend/base/default/template/ops/placeform3dsecure.phtml create mode 100644 app/design/frontend/base/default/template/ops/retryPayment.phtml create mode 100644 app/design/frontend/rwd/default/template/ops/customer/alias/list.phtml create mode 100644 app/etc/modules/Netresearch_OPS.xml create mode 100644 app/locale/de_DE/Netresearch_OPS.csv create mode 100644 app/locale/de_DE/template/email/ops/ops_email_resend_payment_info.html create mode 100644 app/locale/de_DE/template/email/ops/ops_pay_per_mail_info.html create mode 100644 app/locale/de_DE/template/email/ops/suspend_subscription.html create mode 100644 app/locale/en_US/Netresearch_OPS.csv create mode 100644 app/locale/en_US/template/email/ops/ops_email_resend_payment_info.html create mode 100644 app/locale/en_US/template/email/ops/ops_pay_per_mail_info.html create mode 100644 app/locale/en_US/template/email/ops/suspend_subscription.html create mode 100644 app/locale/fr_FR/Netresearch_OPS.csv create mode 100644 app/locale/it_IT/Netresearch_OPS.csv create mode 100644 app/locale/nl_NL/Netresearch_OPS.csv create mode 100644 composer.json create mode 100644 js/netresearch/ops/deviceFingerprinting.js create mode 100644 js/netresearch/ops/osc_payment.js create mode 100644 js/netresearch/ops/payment.js create mode 100644 js/netresearch/ops/tokenization.js create mode 100644 lib/MobileDetect/Mobile_Detect.php create mode 100644 modman create mode 100644 skin/adminhtml/default/default/ops.css create mode 100644 skin/frontend/base/default/css/ops.css create mode 100644 skin/frontend/base/default/images/ops/alias/brands/AmericanExpress.png create mode 100644 skin/frontend/base/default/images/ops/alias/brands/DinnerClub.png create mode 100644 skin/frontend/base/default/images/ops/alias/brands/Maestro.png create mode 100644 skin/frontend/base/default/images/ops/alias/brands/MasterCard.png create mode 100644 skin/frontend/base/default/images/ops/alias/brands/VISA.png create mode 100644 skin/frontend/base/default/images/ops/kwixo/apres_reception.jpg create mode 100644 skin/frontend/base/default/images/ops/kwixo/comptant.jpg create mode 100644 skin/frontend/base/default/images/ops/kwixo/credit.jpg create mode 100644 skin/frontend/base/default/images/ops/logos/ops_BCMC.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_Masterpass.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_bankTransfer.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_belfiusDirectNet.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_cashU.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_cbcOnline.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_cc.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_chinaUnionPay.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_dc.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_directDebit.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_directEbanking.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_eDankort.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_eps.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_flex.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_fortisPayButton.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_giroPay.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_iDeal.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_ingHomePay.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_interSolve.jpg create mode 100644 skin/frontend/base/default/images/ops/logos/ops_interSolve.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_kbcOnline.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_kwixoApresReception.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_kwixoComptant.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_kwixoCredit.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_openInvoiceAt.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_openInvoiceDe.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_openInvoiceNl.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_opsid.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_paypal.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_paysafecard.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_pingPing.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_postFinanceEFinance.png create mode 100644 skin/frontend/base/default/images/ops/logos/ops_recurring_cc.png create mode 100644 skin/frontend/rwd/default/css/ops.css diff --git a/README.md b/README.md new file mode 100644 index 0000000..af14cd8 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Netresearch OPS + +**Extension for Magento 1** + +Tested for Magento 1.9.3.1 + +Module version 16.12.14 + +--- + +*Please note that we are not the developer of this extension. In this repository, we only added modman and composer support. We will not provide any support for this repository. If you have any problems on integration, please use the official link provided below.* + +## Overview + +This module is the official Ingenico ePayments extension. + +For more information, please visit https://www.magentocommerce.com/magento-connect/official-ingenico-epayments-extension-1.html. + +## Installation + +Add the `require` and `repositories` sections to your composer.json as shown below and run `composer update` + +``` +{ + ... + + "repositories": [ + + ... + + {"type": "git", "url": "https://github.com/kirchbergerknorr/magento1_netresearch_ops"}, + + ... + ], + + ... + + "require": { + + ... + + "kirchbergerknorr/magento1_netresearch_ops": "^16.12.14", + + ... + }, + + ... +} +``` \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Edit/Tab/Alias.php b/app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Edit/Tab/Alias.php new file mode 100644 index 0000000..15f0229 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Edit/Tab/Alias.php @@ -0,0 +1,172 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Block_Adminhtml_Customer_Edit_Tab_Alias + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Block_Adminhtml_Customer_Edit_Tab_Alias + extends Mage_Adminhtml_Block_Widget_Grid + implements Mage_Adminhtml_Block_Widget_Tab_Interface +{ + public function _construct() + { + parent::_construct(); + $this->setId('customer_edit_tab_ops_alias'); + $this->setUseAjax(true); + } + + /** + * Return Tab label + * + * @return string + */ + public function getTabLabel() + { + return $this->__('Payment Information'); + } + + /** + * Return Tab label + * + * @return string + */ + public function getTabTitle() + { + return $this->__('Payment Information'); + } + + /** + * Can show tab in tabs + * + * @return boolean + */ + public function canShowTab() + { + $customer = Mage::registry('current_customer'); + return (bool)$customer->getId(); + } + + /** + * Tab is hidden + * + * @return boolean + */ + public function isHidden() + { + return false; + } + + /** + * Defines after which tab, this tab should be rendered + * + * @return string + */ + public function getAfter() + { + return 'orders'; + } + + + protected function _prepareCollection() + { + $customer = Mage::registry('current_customer'); + + $collection = Mage::getModel('ops/alias') + ->getCollection() + ->addFieldToFilter('customer_id', $customer->getId()); + + $this->setCollection($collection); + return parent::_prepareCollection(); + } + + protected function _prepareColumns() + { + $this->addColumn( + 'alias', array( + 'header' => Mage::helper('ops')->__('Alias'), + 'align' => 'right', + 'index' => 'alias', + ) + ); + + $this->addColumn( + 'payment_method', array( + 'header' => Mage::helper('ops')->__('Payment method'), + 'index' => 'payment_method', + 'renderer' => 'Netresearch_OPS_Block_Adminhtml_Customer_Renderer_PaymentMethod' + ) + ); + + $this->addColumn( + 'brand', array( + 'header' => Mage::helper('ops')->__('Credit Card Type'), + 'index' => 'brand', + ) + ); + + $this->addColumn( + 'pseudo_account_or_cc_no', array( + 'header' => Mage::helper('ops')->__('Card Number/Account Number'), + 'index' => 'pseudo_account_or_cc_no', + ) + ); + + $this->addColumn( + 'expiration_date', array( + 'header' => Mage::helper('ops')->__('Expiration Date'), + 'index' => 'expiration_date', + ) + ); + + $this->addColumn( + 'card_holder', array( + 'header' => Mage::helper('ops')->__('Card Holder'), + 'index' => 'card_holder', + ) + ); + + $this->addColumn( + 'state', array( + 'header' => Mage::helper('ops')->__('State'), + 'index' => 'state', + 'renderer' => 'Netresearch_OPS_Block_Adminhtml_Customer_Renderer_State', + ) + ); + + $this->addColumn( + 'action', + array( + 'header' => Mage::helper('ops')->__('Action'), + 'width' => '100', + 'type' => 'action', + 'getter' => 'getId', + 'actions' => array( + array( + 'caption' => Mage::helper('ops')->__('Delete'), + 'url' => array('base' => 'adminhtml/alias/delete'), + 'field' => 'id' + ) + ), + 'filter' => false, + 'sortable' => false, + 'index' => 'stores', + 'is_system' => true, + ) + ); + + return parent::_prepareColumns(); + } + +} diff --git a/app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Renderer/PaymentMethod.php b/app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Renderer/PaymentMethod.php new file mode 100644 index 0000000..f18da52 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Renderer/PaymentMethod.php @@ -0,0 +1,27 @@ + + */ +class Netresearch_OPS_Block_Adminhtml_Customer_Renderer_PaymentMethod + extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract +{ + + /** + * @param Varien_Object $row + * @return string + */ + public function render(Varien_Object $row) + { + $title = ''; + $methodCode = $row->getData($this->getColumn()->getIndex()); + $instance = Mage::helper('payment')->getMethodInstance($methodCode); + if ($instance) { + $title = $instance->getTitle(); + } + + return $title; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Renderer/State.php b/app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Renderer/State.php new file mode 100644 index 0000000..08b8e92 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Adminhtml/Customer/Renderer/State.php @@ -0,0 +1,21 @@ +getData($this->getColumn()->getIndex()); + return Mage::helper('ops')->__($value); + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixo/Shipping/Edit.php b/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixo/Shipping/Edit.php new file mode 100644 index 0000000..c6e78b3 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixo/Shipping/Edit.php @@ -0,0 +1,86 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Block_Adminhtml_Kwixo_Shipping_Edit + extends Mage_Adminhtml_Block_Widget_Form_Container +{ + + + protected $kwixoShippingModel = null; + + /** + * gets the form action url + * + * @return string + */ + public function getFormActionUrl() + { + return $this->getUrl('adminhtml/kwixoshipping/save'); + } + + /** + * gets the shipping types + * + * @return array + */ + public function getShippingMethods() + { + $methods = Mage::getSingleton('shipping/config')->getAllCarriers(); + $options = array(); + + foreach ($methods as $carrierCode => $carrier) { + if (!$title = Mage::getStoreConfig("carriers/$carrierCode/title")) { + $title = $carrierCode; + } + $values = $this->getValues($carrierCode); + $options[] = array('code' => $carrierCode, 'label' => $title, 'values' => $values); + } + + return $options; + } + + /** + * returns the corresponding shipping method types + * + * @return array - the kwxixo Shipping method types + */ + public function getKwixoShippingTypes() + { + return Mage::getModel('ops/source_kwixo_shipMethodType')->toOptionArray(); + } + + public function getKwixoShippingSettingModel() + { + if (null === $this->kwxioShippingModel) { + $this->kwixoShippingModel = Mage::getModel('ops/kwixo_shipping_setting'); + } + return $this->kwixoShippingModel; + } + + /** + * @param $carrierCode + * @return array + */ + protected function getValues($carrierCode) + { + $values = array( + 'kwixo_shipping_type' => '', + 'kwixo_shipping_speed' => '', + 'kwixo_shipping_details' => '' + ); + if (null != ($this->getData('postData')) && array_key_exists($carrierCode, $this->getData('postData'))) { + $errorData = $this->getData('postData'); + $values = $errorData[$carrierCode]; + } else { + $values = $this->getKwixoShippingSettingModel()->load($carrierCode, 'shipping_code')->getData(); + } + return $values; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/CategoryTree.php b/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/CategoryTree.php new file mode 100644 index 0000000..b87399b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/CategoryTree.php @@ -0,0 +1,106 @@ +setTemplate('ops/categoriestree.phtml'); + $this->setUseAjax(true); + $this->_withProductCount = false; + } + + public function getSwitchTreeUrl() + { + return $this->getUrl( + "*/*/tree", + array( + '_current' => true, + 'store' => null, + '_query' => false, + 'id' => null, + 'parent' => null + ) + ); + } + + public function getNodesUrl() + { + return $this->getUrl('adminhtml/catalog_category/jsonTree'); + } + + public function getEditUrl() + { + return $this->getUrl('*/*/edit', array('_current' => true, '_query' => false, 'id' => null, 'parent' => null)); + } + + protected function _getNodeJson($node, $level = 0) + { + $item = array(); + $item['text'] = $this->buildNodeName($node); + $item['id'] = $node->getId(); + $item['cls'] = 'folder ' . ($node->getIsActive() ? 'active-category' : 'no-active-category'); + $item['store'] = (int) $this->getStore()->getId(); + $item['path'] = $node->getData('path'); + $item['allowDrop'] = false; + $item['allowDrag'] = false; + if ((int) $node->getChildrenCount() > 0) { + $item['children'] = array(); + } + $isParent = $this->_isParentSelectedCategory($node); + if ($node->hasChildren()) { + $item['children'] = array(); + if (!($this->getUseAjax() && $node->getLevel() > 1 && !$isParent)) { + foreach ($node->getChildren() as $child) { + $item['children'][] = $this->_getNodeJson($child, $level + 1); + } + } + } + + if ($isParent || $node->getLevel() < 2) { + $item['expanded'] = true; + } + return $item; + } + + protected function _getProductTypeLabel($productTypeId) + { + $res = ''; + $types = Mage::getModel('ops/source_kwixo_productCategories')->toOptionArray(); + foreach ($types as $data) { + if ($data['value'] == $productTypeId) { + $res = $data['label']; + break; + } + } + return $res; + } + + public function buildNodeName($node) + { + $result = $this->escapeHtml($node->getName()); + return $result; + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/Edit.php b/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/Edit.php new file mode 100644 index 0000000..d38d6e6 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/Edit.php @@ -0,0 +1,58 @@ +_objectId = 'id'; + $this->_controller = 'adminhtml_kwixocategory'; + $this->_blockGroup = 'ops'; + $this->_mode = 'edit'; + $this->_updateButton('save', 'label', $this->__('Save')); + $this->_updateButton('save', 'url', $this->getUrl('*/*/save')); + + } + + public function getHeaderText() + { + $categoryId = (int) $this->getRequest()->getParam('id'); + + if ($categoryId <= 0) { + return Mage::helper('ops/data')->__('Categories configuration'); + } + $category = Mage::getModel('catalog/category')->load($categoryId); + return Mage::helper('ops/data')->__("Categorie's %s configuration", $category->getName()); + } + + public function getFormActionUrl() + { + return $this->getUrl('*/*/save'); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/Edit/Form.php b/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/Edit/Form.php new file mode 100644 index 0000000..7bf3e59 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Adminhtml/Kwixocategory/Edit/Form.php @@ -0,0 +1,75 @@ + 'edit_form', + 'action' => $this->getUrl('*/*/save'), + 'method' => 'post', + 'enctype' => 'multipart/form-data' + ) + ); + + $categoryId = (int) $this->getRequest()->getParam('id'); + if ($categoryId <= 0) { + return parent::_prepareForm(); + } + $kwixoCategoryMapping = Mage::getModel('ops/kwixo_category_mapping')->loadByCategoryId($categoryId); + $storeId = (int) $this->getRequest()->getParam('store'); + + $fieldset = $form->addFieldset( + 'ops_form', + array( + 'legend' => Mage::helper('ops/data')->__('Categories configuration') + ) + ); + + $fieldset->addField( + 'storeId', 'hidden', array( + 'required' => true, + 'name' => 'storeId', + 'value' => $storeId, + ) + ); + + $fieldset->addField( + 'id', 'hidden', array( + 'required' => false, + 'name' => 'id', + 'value' => $kwixoCategoryMapping->getId(), + ) + ); + $fieldset->addField( + 'category_id', 'hidden', array( + 'required' => true, + 'name' => 'category_id', + 'value' => $categoryId, + ) + ); + + $fieldset->addField( + 'kwixoCategory_id', 'select', array( + 'label' => Mage::Helper('ops/data')->__('Kwixo category'), + 'class' => 'required-entry', + 'required' => true, + 'name' => 'kwixoCategory_id', + 'value' => $kwixoCategoryMapping->getKwixoCategoryId(), + 'values' => Mage::getModel('ops/source_kwixo_productCategories')->toOptionArray() + ) + ); + + $fieldset->addField( + 'applysubcat', 'checkbox', array( + 'label' => Mage::Helper('ops/data')->__('Apply to sub-categories'), + 'name' => 'applysubcat' + ) + ); + + $form->setUseContainer(true); + $this->setForm($form); + return parent::_prepareForm(); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Adminhtml/Sales/Order/Creditmemo/ClosedTransaction/Warning.php b/app/code/community/Netresearch/OPS/Block/Adminhtml/Sales/Order/Creditmemo/ClosedTransaction/Warning.php new file mode 100644 index 0000000..d3a48be --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Adminhtml/Sales/Order/Creditmemo/ClosedTransaction/Warning.php @@ -0,0 +1,33 @@ + + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Block_Adminhtml_Sales_Order_Creditmemo_Create_Adjustments_Checkbox + * + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +class Netresearch_OPS_Block_Adminhtml_Sales_Order_Creditmemo_ClosedTransaction_Warning + extends Mage_Core_Block_Template +{ + + /** + * Internal constructor, that is called from real constructor. + * + * + */ + protected function _construct() + { + parent::_construct(); + $this->setTemplate('ops/sales/order/creditmemo/closed-transaction/warning.phtml'); + } + +} diff --git a/app/code/community/Netresearch/OPS/Block/Adminhtml/Sales/Order/Creditmemo/Totals/Checkbox.php b/app/code/community/Netresearch/OPS/Block/Adminhtml/Sales/Order/Creditmemo/Totals/Checkbox.php new file mode 100644 index 0000000..a070d3a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Adminhtml/Sales/Order/Creditmemo/Totals/Checkbox.php @@ -0,0 +1,33 @@ + + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Block_Adminhtml_Sales_Order_Creditmemo_Create_Adjustments_Checkbox + * + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +class Netresearch_OPS_Block_Adminhtml_Sales_Order_Creditmemo_Totals_Checkbox + extends Mage_Core_Block_Template +{ + + /** + * Internal constructor, that is called from real constructor. + * + * + */ + protected function _construct() + { + parent::_construct(); + $this->setTemplate('ops/sales/order/creditmemo/totals/checkbox.phtml'); + } + +} diff --git a/app/code/community/Netresearch/OPS/Block/Alias/List.php b/app/code/community/Netresearch/OPS/Block/Alias/List.php new file mode 100644 index 0000000..806cb5b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Alias/List.php @@ -0,0 +1,69 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Block_Alias_List + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Block_Alias_List + extends Mage_Core_Block_Template +{ + public function getAliases() + { + $aliases = array(); + $customer = Mage::helper('customer')->getCustomer(); + if (0 < $customer->getId()) { + $aliasesCollection = Mage::helper('ops/alias')->getAliasesForCustomer($customer->getId()); + foreach ($aliasesCollection as $alias) { + $aliases[] = $alias; + } + } + return $aliases; + } + + /** + * get human readable name of payment method + * + * @param string $methodCode Code of payment method + * @return string Name of payment method + */ + public function getMethodName($methodCode) + { + $title = ''; + $instance = Mage::helper('payment')->getMethodInstance($methodCode); + if ($instance) { + $title = $instance->getTitle(); + } + + return $title; + } + + /** + * retrieves the url for deletion the alias + * + * @param $aliasId - the id of the alias + * + * @return string - the url for deleting the alias with the given id + */ + public function getAliasDeleteUrl($aliasId) + { + return Mage::getUrl( + 'ops/customer/deleteAlias/', + array( + 'id' => $aliasId, + '_secure' => Mage::app()->getFrontController()->getRequest()->isSecure() + ) + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Checkout/DeviceFingerprinting.php b/app/code/community/Netresearch/OPS/Block/Checkout/DeviceFingerprinting.php new file mode 100644 index 0000000..2c00ce2 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Checkout/DeviceFingerprinting.php @@ -0,0 +1,79 @@ + + * @copyright 2016 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ + +/** + * Netresearch_OPS_Block_Checkout_DeviceFingerprinting + * + * @category Netresearch + * @package Netresearch_OPS + * @author Christoph Aßmann + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ +class Netresearch_OPS_Block_Checkout_DeviceFingerprinting + extends Mage_Core_Block_Template +{ + /** + * @var string + */ + protected $_template = 'ops/checkout/deviceFingerprinting.phtml'; + + /** + * @return Mage_Core_Block_Abstract + */ + protected function _prepareLayout() + { + if (Mage::getModel('ops/config')->getDeviceFingerPrinting()) { + /** @var Mage_Page_Block_Html_Head $headBlock */ + $headBlock = $this->getLayout()->getBlock('head'); + if ($headBlock) { + $headBlock->addJs('netresearch/ops/deviceFingerprinting.js'); + } + } + + return parent::_prepareLayout(); + } + + /** + * @return string + */ + protected function _toHtml() + { + if (!Mage::getModel('ops/config')->getDeviceFingerPrinting()) { + return ''; + } + + return parent::_toHtml(); + } + + /** + * @return string + */ + public function getConsentUrl() + { + return $this->getUrl('ops/device'); + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Checkout/SubscriptionNotice.php b/app/code/community/Netresearch/OPS/Block/Checkout/SubscriptionNotice.php new file mode 100644 index 0000000..5820ba1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Checkout/SubscriptionNotice.php @@ -0,0 +1,202 @@ + + * + * + * @method Netresearch_OPS_Block_Checkout_SubscriptionNotice setProfile(Mage_Catalog_Model_Product $product) + * + */ +class Netresearch_OPS_Block_Checkout_SubscriptionNotice extends Mage_Core_Block_Template +{ + + /** + * Returns the quotes nominal items product + * + * @return Mage_Sales_Model_Recurring_Profile + */ + public function getProfile() + { + if (!$this->hasData('profile')) { + /** @var Mage_Sales_Model_Quote_Item $item */ + foreach ($this->getQuote()->getAllItems() as $item) { + $product = $item->getProduct(); + if (is_object($product) && $product->isRecurring() + && $profile = Mage::getModel('sales/recurring_profile')->importProduct($product) + ) { + $profile->importQuote($this->getQuote()); + $profile->importQuoteItem($item); + $this->setProfile($profile); + break; + } + } + } + + return $this->getData('profile'); + } + + /** + * Get checkout session quote + * + * @return Mage_Sales_Model_Quote + */ + public function getQuote() + { + return Mage::getSingleton('checkout/session')->getQuote(); + } + + /** + * Checks wether the recurring profile has a trial period specified + * + * @return bool + */ + public function hasTrial() + { + return (bool)$this->getProfile()->getTrialPeriodUnit(); + } + + /** + * Checks wether the recurring profile has an initial fee specified + * + * @return bool + */ + public function hasInitialFee() + { + return $this->getProfile()->getInitAmount() > 0; + } + + /** + * @return string + */ + public function getInitialFeeText() + { + return $this->__( + 'You will be charged an initial amount of %s.', + $this->helper('checkout')->formatPrice($this->getProfile()->getInitAmount()) + ); + } + + /** + * @return string + */ + public function getTrialSubscriptionText() + { + $profile = $this->getProfile(); + + return $this->__( + 'A trial subscription will be created. This will charge you %s every %s %s until %s.', + $this->helper('checkout')->formatPrice( + $profile->getTrialBillingAmount() + $profile->getShippingAmount() + ), + $profile->getTrialPeriodFrequency(), + $profile->getPeriodUnitLabel($profile->getTrialPeriodUnit()), + $this->localizeDate($this->getTrialSubscriptionEndDate()) + ); + } + + /** + * @return string + */ + public function getRegularSubscriptionText() + { + $profile = $this->getProfile(); + + $message = $this->__( + 'A subscription will be created. This will charge you %s every %s %s.', + $this->helper('checkout')->formatPrice( + $profile->getBillingAmount() + $profile->getTaxAmount() + $profile->getShippingAmount() + ), + $profile->getPeriodFrequency(), + $profile->getPeriodUnitLabel($profile->getPeriodUnit()) + ); + + if ($this->getRegularSubscriptionEndDate()) { + $message .= $this->__( + ' The subscription will end on %s.', + $this->localizeDate($this->getRegularSubscriptionEndDate()) + ); + } + return $message; + } + + public function getCancelInformationText() + { + return $this->__( + 'To cancel the subscription, please send an email to the shop owner' . + ' or request this by clicking the suspend button on the subscriptions detail view in your customer account.' + . ' A link to that page will be displayed on the checkout success page.' + ); + } + + public function displayNotice() + { + $result = true; + if (!$this->getProfile() + || $this->getQuote()->getPayment()->getMethod() != Netresearch_OPS_Model_Payment_Recurring_Cc::CODE + ) { + $result = false; + } + + return $result; + } + + protected function getTrialSubscriptionEndDate() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag $parameterModel */ + $parameterModel = Mage::getModel('ops/payment_recurring_cc_parameterBag'); + $enddate = null; + $startDate = new DateTime($this->getProfile()->getStartDatetime()); + $enddate = $parameterModel->calculateEndDate( + $startDate, $this->getProfile()->getTrialPeriodUnit(), + $this->getProfile()->getTrialPeriodFrequency(), $this->getProfile()->getTrialPeriodMaxCycles() + ); + + return $enddate; + } + + protected function getRegularSubscriptionEndDate() + { + $parameterModel = Mage::getModel('ops/payment_recurring_cc_parameterBag'); + $enddate = null; + if ($this->hasTrial()) { + $startDate = $this->getTrialSubscriptionEndDate(); + } else { + $startDate = new DateTime($this->getProfile()->getStartDatetime()); + } + $enddate = $parameterModel->calculateEndDate( + $startDate, $this->getProfile()->getPeriodUnit(), + $this->getProfile()->getPeriodFrequency(), $this->getProfile()->getPeriodMaxCycles() + ); + + return $enddate; + } + + protected function localizeDate(DateTime $date) + { + $date = new Zend_Date($date->getTimestamp()); + return Mage::helper('core')->formatDate($date); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Form.php b/app/code/community/Netresearch/OPS/Block/Form.php new file mode 100644 index 0000000..7779874 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form.php @@ -0,0 +1,363 @@ +setTemplate(self::FRONTEND_TEMPLATE); + } + + /** + * get OPS config + * + * @return Netresearch_Ops_Model_Config + */ + public function getConfig() + { + if (null === $this->config) { + $this->config = Mage::getSingleton('ops/config'); + } + + return $this->config; + } + + /** + * @param Netresearch_OPS_Model_Config $config + * @return $this + */ + public function setConfig(Netresearch_OPS_Model_Config $config) + { + $this->config = $config; + + return $this; + } + + /** + * @return Mage_Sales_Model_Quote + */ + public function getQuote() + { + $quote = Mage::getSingleton('checkout/session')->getQuote(); + if (Mage::app()->getStore()->isAdmin()) { + $quote = Mage::getSingleton('adminhtml/sales_order_create')->getQuote(); + } + + return $quote; + } + + /** + * @return array + */ + public function getDirectDebitCountryIds() + { + return explode(',', $this->getConfig()->getDirectDebitCountryIds()); + } + + public function getBankTransferCountryIds() + { + return explode(',', $this->getConfig()->getBankTransferCountryIds()); + } + + /** + * @param null $storeId + * @return string + */ + public function getPSPID($storeId = null) + { + return Mage::getModel('ops/config')->getPSPID($storeId); + } + + /** + * @param null $storeId + * @param bool $admin + * @return string + */ + public function getGenerateHashUrl($storeId = null, $admin = false) + { + return Mage::getModel('ops/config')->getGenerateHashUrl($storeId, $admin); + } + + /** + * @return string + */ + public function getValidationUrl() + { + return Mage::getModel('ops/config')->getValidationUrl(); + } + + /** + * @return array + */ + public function getDirectEbankingBrands() + { + return explode(',', $this->getConfig()->getDirectEbankingBrands()); + } + + + /** + * wrapper for Netresearch_OPS_Helper_Data::checkIfUserRegistering + * + * @return type bool + */ + public function isUserRegistering() + { + return Mage::Helper('ops/data')->checkIfUserIsRegistering(); + } + + /** + * wrapper for Netresearch_OPS_Helper_Data::checkIfUserRegistering + * + * @return type bool + */ + public function isUserNotRegistering() + { + return Mage::Helper('ops/data')->checkIfUserIsNotRegistering(); + } + + /** + * @return string + */ + public function getPmLogo() + { + return $this->pmLogo; + } + + /** + * @return array + */ + protected function getFieldMapping() + { + return $this->getConfig()->getFrontendFieldMapping(); + } + + /** + * @param $mappedFields + * @param $key + * @param $value + * @param $frontendFields + * + * @return mixed + */ + public function getFrontendValidationFields($mappedFields, $key, $value, $frontendFields) + { + if (!is_array($mappedFields[$key])) { + $frontendFields[$mappedFields[$key]] = $value; + } else { + foreach ($mappedFields[$key] as $mKey) { + $frontendFields[$mKey] = $value; + } + } + + return $frontendFields; + } + + public function getImageForBrand($brand) + { + $brandName = str_replace(' ', '', $brand); + return $this->getSkinUrl('images/ops/alias/brands/'. $brandName .'.png'); + } + + /** + * Function to add the Payment Logo to the according title + * + * @return string + */ + public function getMethodLabelAfterHtml() + { + $paymentCode = $this->getMethod()->getCode(); + $logoValue = Mage::getStoreConfig('payment/' . $paymentCode . '/image'); + $logoPosition = Mage::getStoreConfig('payment/' . $paymentCode . '/position'); + + if ($logoPosition != 'hidden') { + if (!empty($logoValue)) { + $url = Mage::getBaseUrl('media') . 'ops/paymentLogo/' . $logoValue; + } else { + $url = Mage::helper('ops/payment')->getPaymentDefaultLogo($paymentCode); + } + + return ""; + } + + return ''; + } + + /** + * Obtain redirect message. + * + * @return string + */ + public function getRedirectMessage() + { + return $this->__('You will be redirected to finalize your payment.'); + } + + /** + * retrieves the alias data for the logged in customer + * + * @return array | null - array the alias data or null if the customer + * is not logged in + */ + protected function getStoredAliasForCustomer() + { + if (Mage::helper('customer/data')->isLoggedIn() + && Mage::getModel('ops/config')->isAliasManagerEnabled($this->getMethodCode()) + ) { + $quote = $this->getQuote(); + $aliases = Mage::helper('ops/alias')->getAliasesForAddresses( + $quote->getCustomer()->getId(), $quote->getBillingAddress(), + $quote->getShippingAddress(), $quote->getStoreId() + ) + ->addFieldToFilter('state', Netresearch_OPS_Model_Alias_State::ACTIVE) + ->addFieldToFilter('payment_method', $this->getMethodCode()) + ->setOrder('created_at', Varien_Data_Collection::SORT_ORDER_DESC); + + + foreach ($aliases as $key => $alias) { + $this->_aliasDataForCustomer[$key] = $alias; + } + } + + return $this->_aliasDataForCustomer; + } + + /** + * @param null $storeId + * @param bool $admin + * + * @return string + */ + public function getAliasAcceptUrl($storeId = null, $admin = false) + { + return Mage::getModel('ops/config')->getAliasAcceptUrl($storeId, $admin); + } + + /** + * @param null $storeId + * @param bool $admin + * + * @return string + */ + public function getAliasExceptionUrl($storeId = null, $admin = false) + { + return Mage::getModel('ops/config')->getAliasExceptionUrl($storeId, $admin); + } + + + /** + * @param null $storeId + * + * @return string + */ + public function getAliasGatewayUrl($storeId = null) + { + return Mage::getModel('ops/config')->getAliasGatewayUrl($storeId); + } + + /** + * + * @param $aliasId + * + * @return null|string - the card holder either from alias data or + * the name from the the user who is logged in, null otherwise + */ + public function getCardHolderName($aliasId) + { + $cardHolderName = $this->getStoredAliasDataForCustomer($aliasId, 'card_holder'); + $customerHelper = Mage::helper('customer/data'); + if ((null === $cardHolderName || 0 === strlen(trim($cardHolderName))) + && $customerHelper->isLoggedIn() + && Mage::getModel('ops/config')->isAliasManagerEnabled($this->getMethodCode()) + ) { + $cardHolderName = $customerHelper->getCustomerName(); + } + + return $cardHolderName; + } + + /** + * @param $aliasId + * @return null|string + */ + public function getAliasCardNumber($aliasId) + { + $aliasCardNumber = $this->getStoredAliasDataForCustomer($aliasId, 'pseudo_account_or_cc_no'); + if (0 < strlen(trim($aliasCardNumber))) { + $aliasCardNumber = Mage::helper('ops/alias')->formatAliasCardNo( + $this->getStoredAliasDataForCustomer($aliasId, 'brand'), $aliasCardNumber + ); + } + + return $aliasCardNumber; + } + + /** + * retrieves single values to given keys from the alias data + * + * @param $aliasId + * @param $key - string the key for the alias data + * + * @return null|string - null if key is not set in the alias data, otherwise + * the value for the given key from the alias data + */ + protected function getStoredAliasDataForCustomer($aliasId, $key) + { + $returnValue = null; + $aliasData = array(); + + if (empty($this->_aliasDataForCustomer)) { + $aliasData = $this->getStoredAliasForCustomer(); + } else { + $aliasData = $this->_aliasDataForCustomer; + } + + if (array_key_exists($aliasId, $aliasData) && $aliasData[$aliasId]->hasData($key)) { + $returnValue = $aliasData[$aliasId]->getData($key); + } + + return $returnValue; + } + +} diff --git a/app/code/community/Netresearch/OPS/Block/Form/Alias.php b/app/code/community/Netresearch/OPS/Block/Form/Alias.php new file mode 100644 index 0000000..370e03a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/Alias.php @@ -0,0 +1,55 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Block_Form_Alias + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Block_Form_Alias extends Netresearch_OPS_Block_Form +{ + protected function _construct() + { + parent::_construct(); + $this->setTemplate('ops/form/alias.phtml'); + } + + /** + * get available aliases for current customer + * will return empty array if there is no current user + * + * @return array|Netresearch_OPS_Model_Mysql4_Alias_Collection + */ + public function getAvailableAliases() + { + $customer = Mage::helper('customer')->getCustomer(); + if (0 < $customer->getId()) { + $quote = Mage::helper('ops/payment')->getQuote(); + return Mage::helper('ops/payment')->getAliasesForCustomer($customer->getId(), $quote); + } + return array(); + } + + /** + * @param $alias + * @return string + */ + protected function getHumanReadableAlias($alias) + { + $helper = Mage::helper('ops'); + $aliasString = $helper->__('Credit Card Type') . ' ' . $helper->__($alias->getBrand()); + $aliasString .= ' ' . $helper->__('AccountNo') . ' ' . $helper->__($alias->getPseudoAccountOrCCNo()); + $aliasString .= ' ' . $helper->__('Expiration Date') . ' ' . $alias->getExpirationDate(); + return $aliasString; + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Form/BankTransfer.php b/app/code/community/Netresearch/OPS/Block/Form/BankTransfer.php new file mode 100644 index 0000000..a8adcc0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/BankTransfer.php @@ -0,0 +1,42 @@ + + */ +?> +setTemplate('ops/form/bankTransfer.phtml'); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Form/Cc.php b/app/code/community/Netresearch/OPS/Block/Form/Cc.php new file mode 100644 index 0000000..8597605 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/Cc.php @@ -0,0 +1,178 @@ + + * @author Thomas Birke + * @license OSL 3.0 + */ +class Netresearch_OPS_Block_Form_Cc extends Netresearch_OPS_Block_Form +{ + + protected $_aliasDataForCustomer = array(); + + /** + * CC Payment Template + */ + const FRONTEND_TEMPLATE = 'ops/form/cc.phtml'; + + protected function _construct() + { + parent::_construct(); + $this->setTemplate(self::FRONTEND_TEMPLATE); + } + + + /** + * gets all Alias CC brands + * + * @return array + */ + public function getAliasBrands() + { + return Mage::getModel('ops/source_cc_aliasInterfaceEnabledTypes') + ->getAliasInterfaceCompatibleTypes(); + } + + /** + * @return string + */ + public function getSaveCcBrandUrl() + { + return Mage::getModel('ops/config')->getSaveCcBrandUrl(); + } + + /** + * @param null $storeId + * @param bool $admin + * + * @return mixed + */ + public function getCcSaveAliasUrl($storeId = null, $admin = false) + { + return Mage::getModel('ops/config')->getCcSaveAliasUrl($storeId, $admin); + } + + /** + * checks if the 'alias' payment method (!) is available + * no check for customer has aliases here + * just a passthrough of the isAvailable of Netresearch_OPS_Model_Payment_Abstract::isAvailable + * + * @return boolean + */ + public function isAliasPMEnabled() + { + return Mage::getModel('ops/config')->isAliasManagerEnabled($this->getMethodCode()); + } + + + /** + * retrieves the alias data for the logged in customer + * + * @return array | null - array the alias data or null if the customer + * is not logged in + */ + protected function getStoredAliasForCustomer() + { + if (Mage::helper('customer/data')->isLoggedIn() + && Mage::getModel('ops/config')->isAliasManagerEnabled($this->getMethodCode()) + ) { + $quote = $this->getQuote(); + $aliases = Mage::helper('ops/alias')->getAliasesForAddresses( + $quote->getCustomer()->getId(), $quote->getBillingAddress(), + $quote->getShippingAddress(), $quote->getStoreId() + ) + ->addFieldToFilter('state', Netresearch_OPS_Model_Alias_State::ACTIVE) + ->addFieldToFilter('payment_method', $this->getMethodCode()) + ->setOrder('created_at', Varien_Data_Collection::SORT_ORDER_DESC); + + + foreach ($aliases as $key => $alias) { + $this->_aliasDataForCustomer[$key] = $alias; + } + } + + return $this->_aliasDataForCustomer; + } + + + + + /** + * @param $aliasId + * @param $key + * @return null|string + */ + public function getExpirationDatePart($aliasId, $key) + { + $returnValue = null; + $expirationDate = $this->getStoredAliasDataForCustomer($aliasId, 'expiration_date'); + // set expiration date to actual date if no stored Alias is used + if ($expirationDate === null) { + $expirationDate = date('my'); + } + + if (0 < strlen(trim($expirationDate)) + ) { + $expirationDateValues = str_split($expirationDate, 2); + + if ($key == 'month') { + $returnValue = $expirationDateValues[0]; + } + if ($key == 'year') { + $returnValue = $expirationDateValues[1]; + } + + if ($key == 'complete') { + $returnValue = implode('/', $expirationDateValues); + } + } + + return $returnValue; + + } + + + /** + * the brand of the stored card data + * + * @param $aliasId + * + * @return null|string - string if stored card data were found, null otherwise + */ + public function getStoredAliasBrand($aliasId) + { + return $this->getStoredAliasDataForCustomer($aliasId, 'brand'); + } + + /** + * determines whether the alias hint is shown to guests or not + * + * @return bool true if alias feature is enabled and display the hint to + * guests is enabled + */ + public function isAliasInfoBlockEnabled() + { + return ($this->isAliasPMEnabled() + && Mage::getModel('ops/config')->isAliasInfoBlockEnabled()); + } + + /** + * @return string[] + */ + public function getCcBrands() + { + return explode(',', $this->getConfig()->getAcceptedCcTypes($this->getMethodCode())); + } + + public function checkIfBrandHasAliasInterfaceSupport($alias) + { + $brand = $this->getStoredAliasBrand($alias); + $allowedBrands = $this->getMethod()->getBrandsForAliasInterface(); + + return in_array($brand, $allowedBrands); + } + +} diff --git a/app/code/community/Netresearch/OPS/Block/Form/DirectDebit.php b/app/code/community/Netresearch/OPS/Block/Form/DirectDebit.php new file mode 100644 index 0000000..bcaf296 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/DirectDebit.php @@ -0,0 +1,47 @@ + + * @author Thomas Birke + * @license OSL 3.0 + */ +class Netresearch_OPS_Block_Form_DirectDebit extends Netresearch_OPS_Block_Form +{ + + /** + * Backend Payment Template + */ + const TEMPLATE = 'ops/form/directDebit.phtml'; + + protected function _construct() + { + parent::_construct(); + $this->setTemplate(self::TEMPLATE); + } + + /** + * get ids of supported countries + * + * @return array + */ + public function getDirectDebitCountryIds() + { + return explode(',', $this->getConfig()->getDirectDebitCountryIds()); + } + + /** + * @return string + */ + public function getSelectedCountryId() + { + $countryId = $this->getQuote()->getPayment()->getAdditionalInformation('country_id'); + if (Mage::app()->getStore()->isAdmin()) { + $data = $this->getQuote()->getPayment()->getData('ops_directDebit_data'); + $countryId = $data && array_key_exists('country_id', $data) ? $data['country_id'] : ''; + } + return $countryId; + + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Form/DirectEbanking.php b/app/code/community/Netresearch/OPS/Block/Form/DirectEbanking.php new file mode 100644 index 0000000..c4929f0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/DirectEbanking.php @@ -0,0 +1,42 @@ + + */ +?> +setTemplate('ops/form/directEbanking.phtml'); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Form/Flex.php b/app/code/community/Netresearch/OPS/Block/Form/Flex.php new file mode 100644 index 0000000..6a26f05 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/Flex.php @@ -0,0 +1,70 @@ + + */ +?> +setTemplate('ops/form/flex.phtml'); + } + + /** + * get configurable payment methods + * + * @return string[][] + */ + public function getFlexMethods() + { + $methods = $this->getMethod()->getConfigData('methods'); + if (!is_array($methods)) { + $methods = unserialize($methods); + } + + return $methods; + } + + /** + * @return boolean + */ + public function isDefaultOptionActive() + { + return (bool) $this->getMethod()->getConfigData('default'); + } + + public function getDefaultOptionTitle() + { + return $this->getMethod()->getConfigData('default_title'); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Form/Ideal.php b/app/code/community/Netresearch/OPS/Block/Form/Ideal.php new file mode 100644 index 0000000..5d9ab7c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/Ideal.php @@ -0,0 +1,33 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Block_Form_Ideal + extends Netresearch_OPS_Block_Form +{ + + /** + * Init OPS payment form + * + */ + protected function _construct() + { + parent::_construct(); + $this->setTemplate('ops/form/ideal.phtml'); + } + + /** + * return the ideal issuers + * + * @return array + */ + public function getIssuers() + { + return Mage::getModel('ops/payment_iDeal')->getIDealIssuers(); + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Form/InterSolve.php b/app/code/community/Netresearch/OPS/Block/Form/InterSolve.php new file mode 100644 index 0000000..40f483e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/InterSolve.php @@ -0,0 +1,52 @@ + + */ +?> +setTemplate('ops/form/intersolve.phtml'); + } + + /** + * + * @return array empty or intersolve Vouchers + */ + public function getInterSolveBrands() + { + $brands = Mage::getModel('ops/config')->getIntersolveBrands(); + + return $brands; + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Form/Kwixo/ApresReception.php b/app/code/community/Netresearch/OPS/Block/Form/Kwixo/ApresReception.php new file mode 100644 index 0000000..e93d8dd --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/Kwixo/ApresReception.php @@ -0,0 +1,27 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Block_Form_Kwixo_ApresReception extends Netresearch_OPS_Block_Form +{ + const FRONTEND_TEMPLATE = 'ops/form/kwixo/apres_reception.phtml'; + + protected $pmLogo = 'images/ops/kwixo/apres_reception.jpg'; + + /** + * Init OPS payment form + * + */ + protected function _construct() + { + parent::_construct(); + /** @noinspection PhpUndefinedMethodInspection */ + $this->setTemplate(self::FRONTEND_TEMPLATE); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Form/Kwixo/Comptant.php b/app/code/community/Netresearch/OPS/Block/Form/Kwixo/Comptant.php new file mode 100644 index 0000000..d35de6e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/Kwixo/Comptant.php @@ -0,0 +1,30 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + + +class Netresearch_OPS_Block_Form_Kwixo_Comptant extends Netresearch_OPS_Block_Form +{ + const FRONTEND_TEMPLATE = 'ops/form/kwixo/comptant.phtml'; + + protected $pmLogo = 'images/ops/kwixo/comptant.jpg'; + + /** + * Init OPS payment form + * + */ + protected function _construct() + { + parent::_construct(); + /** @noinspection PhpUndefinedMethodInspection */ + $this->setTemplate(self::FRONTEND_TEMPLATE); + } + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Form/Kwixo/Credit.php b/app/code/community/Netresearch/OPS/Block/Form/Kwixo/Credit.php new file mode 100644 index 0000000..47a6735 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/Kwixo/Credit.php @@ -0,0 +1,28 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + + +class Netresearch_OPS_Block_Form_Kwixo_Credit extends Netresearch_OPS_Block_Form +{ + const FRONTEND_TEMPLATE = 'ops/form/kwixo/credit.phtml'; + + protected $pmLogo = 'images/ops/kwixo/credit.jpg'; + + /** + * Init OPS payment form + * + */ + protected function _construct() + { + parent::_construct(); + /** @noinspection PhpUndefinedMethodInspection */ + $this->setTemplate(self::FRONTEND_TEMPLATE); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Form/OpenInvoice.php b/app/code/community/Netresearch/OPS/Block/Form/OpenInvoice.php new file mode 100644 index 0000000..09f9c37 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/OpenInvoice.php @@ -0,0 +1,63 @@ + + */ +class Netresearch_OPS_Block_Form_OpenInvoice extends Netresearch_OPS_Block_Form +{ + protected function _construct() + { + parent::_construct(); + $this->setTemplate('ops/form/openInvoice.phtml'); + } + + /** + * @return string + * @see Netresearch_OPS_Model_Payment_OpenInvoice_Abstract::getInvoiceTermsTitle + */ + public function getInvoiceTermsTitle() + { + return $this->getMethod()->getInvoiceTermsTitle(); + } + + /** + * @return string + * @see Netresearch_OPS_Model_Payment_OpenInvoice_Abstract::getInvoiceTermsUrl + */ + public function getInvoiceTermsUrl() + { + return $this->getMethod()->getInvoiceTermsUrl(); + } + + /** + * @return bool + * @see Netresearch_OPS_Model_Payment_OpenInvoice_Abstract::showInvoiceTermsLink + */ + public function showInvoiceTermsLink() + { + return $this->getMethod()->showInvoiceTermsLink(); + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Form/OpsId.php b/app/code/community/Netresearch/OPS/Block/Form/OpsId.php new file mode 100644 index 0000000..1074da5 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/OpsId.php @@ -0,0 +1,17 @@ + + * @author Thomas Birke + * @license OSL 3.0 + */ +class Netresearch_OPS_Block_Form_OpsId extends Mage_Payment_Block_Form +{ + protected function _construct() + { + parent::_construct(); + $this->setTemplate('ops/form/opsId.phtml'); + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Form/PayPerMail.php b/app/code/community/Netresearch/OPS/Block/Form/PayPerMail.php new file mode 100644 index 0000000..41fa881 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/PayPerMail.php @@ -0,0 +1,43 @@ + + */ +?> +setTemplate('ops/form/payPerMail.phtml'); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Form/RecurringCc.php b/app/code/community/Netresearch/OPS/Block/Form/RecurringCc.php new file mode 100644 index 0000000..99a6c7c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Form/RecurringCc.php @@ -0,0 +1,37 @@ + + */ + +class Netresearch_OPS_Block_Form_RecurringCc extends Netresearch_OPS_Block_Form_Cc +{ + + public function getCcBrands() + { + return $this->getConfig()->getAcceptedRecurringCcTypes(); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Frauddetection.php b/app/code/community/Netresearch/OPS/Block/Frauddetection.php new file mode 100644 index 0000000..e560b92 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Frauddetection.php @@ -0,0 +1,74 @@ +getStoreId(); + if (true == Mage::getModel('ops/config')->getDeviceFingerPrinting($storeId) + && Mage::getSingleton('customer/session')->getData(Netresearch_OPS_Model_Payment_Abstract::FINGERPRINT_CONSENT_SESSION_KEY)) { + $html = parent::_toHtml(); + } + + return $html; + } + + /** + * get the tracking code application id from config + * + * @return string + */ + public function getTrackingCodeAid() + { + return self::TRACKING_CODE_APPLICATION_ID; + } + + + /** + * build md5 hash from customer session ID + * + * @return string + */ + public function getTrackingSid() + { + $quote = Mage::getSingleton('checkout/type_onepage')->getQuote(); + + return md5(Mage::getModel('ops/config')->getPSPID($quote->getStoreId()) . Mage::helper('ops/order')->getOpsOrderId($quote)); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Info/Alias.php b/app/code/community/Netresearch/OPS/Block/Info/Alias.php new file mode 100644 index 0000000..91500b0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Info/Alias.php @@ -0,0 +1,32 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Block_Info_Alias + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Block_Info_Alias extends Netresearch_OPS_Block_Info_Redirect +{ + /** + * Init ops payment information block + * + */ + protected function _construct() + { + parent::_construct(); + /** @noinspection PhpUndefinedMethodInspection */ + $this->setTemplate('ops/info/cc.phtml'); + } +} + diff --git a/app/code/community/Netresearch/OPS/Block/Info/Bancontact.php b/app/code/community/Netresearch/OPS/Block/Info/Bancontact.php new file mode 100644 index 0000000..ada7f24 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Info/Bancontact.php @@ -0,0 +1,54 @@ +setTemplate('ops/info/bancontact.phtml'); + } + + + public function getDeviceParameter() + { + return $this->getInfo()->getAdditionalInformation('DEVICE'); + } + + + public function getMobileModeParameter() + { + return $this->getInfo()->getAdditionalInformation('MOBILEMODE'); + } + +} diff --git a/app/code/community/Netresearch/OPS/Block/Info/Cc.php b/app/code/community/Netresearch/OPS/Block/Info/Cc.php new file mode 100644 index 0000000..6527764 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Info/Cc.php @@ -0,0 +1,43 @@ +setTemplate('ops/info/cc.phtml'); + } +} + diff --git a/app/code/community/Netresearch/OPS/Block/Info/Flex.php b/app/code/community/Netresearch/OPS/Block/Info/Flex.php new file mode 100644 index 0000000..7543ace --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Info/Flex.php @@ -0,0 +1,55 @@ + + */ +?> +setTemplate('ops/info/flex.phtml'); + } + + + /** + * @return string + */ + public function getFlexTitle() + { + /** @noinspection PhpUndefinedMethodInspection */ + return $this->getMethod()->getInfoInstance()->getAdditionalInformation( + Netresearch_OPS_Model_Payment_Flex::INFO_KEY_TITLE + ); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Info/OpsId.php b/app/code/community/Netresearch/OPS/Block/Info/OpsId.php new file mode 100644 index 0000000..2167e32 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Info/OpsId.php @@ -0,0 +1,43 @@ +setTemplate('ops/info/opsId.phtml'); + } +} + diff --git a/app/code/community/Netresearch/OPS/Block/Info/PayPerMail.php b/app/code/community/Netresearch/OPS/Block/Info/PayPerMail.php new file mode 100644 index 0000000..002c78e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Info/PayPerMail.php @@ -0,0 +1,44 @@ + + */ +?> +setTemplate('ops/info/payPerMail.phtml'); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Info/RecurringCc.php b/app/code/community/Netresearch/OPS/Block/Info/RecurringCc.php new file mode 100644 index 0000000..0406f1e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Info/RecurringCc.php @@ -0,0 +1,35 @@ + + */ + +class Netresearch_OPS_Block_Info_RecurringCc extends Netresearch_OPS_Block_Info_Cc +{ + + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/Info/Redirect.php b/app/code/community/Netresearch/OPS/Block/Info/Redirect.php new file mode 100644 index 0000000..690e5b9 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Info/Redirect.php @@ -0,0 +1,42 @@ +setTemplate('ops/info/redirect.phtml'); + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Paypage.php b/app/code/community/Netresearch/OPS/Block/Paypage.php new file mode 100644 index 0000000..fa2dafa --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Paypage.php @@ -0,0 +1,41 @@ +setTemplate('ops/paypage.phtml'); + return $this; + } +} diff --git a/app/code/community/Netresearch/OPS/Block/Placeform.php b/app/code/community/Netresearch/OPS/Block/Placeform.php new file mode 100644 index 0000000..04cee6a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Placeform.php @@ -0,0 +1,191 @@ +loadByIncrementId($this->getCheckout()->getLastRealOrderId()); + if ($order && null != $order->getId()) { + $methodInstance = $order->getPayment()->getMethodInstance(); + } + + return $methodInstance; + } + + /** + * Return order instance with loaded information by increment id + * + * @return Mage_Sales_Model_Order + */ + protected function _getOrder() + { + if ($this->getOrder()) { + $order = $this->getOrder(); + } else if ($this->getCheckout()->getLastRealOrderId()) { + $order = Mage::getModel('sales/order')->loadByIncrementId($this->getCheckout()->getLastRealOrderId()); + } else { + return null; + } + return $order; + } + + /** + * check if payment method is q kwixo one + * + * @return boolean + */ + public function isKwixoPaymentMethod() + { + $isKwixoPayment = false; + $methodInstance = $this->_getOrder()->getPayment()->getMethodInstance(); + if ($methodInstance instanceof Netresearch_OPS_Model_Payment_Kwixo_Abstract) { + $isKwixoPayment= true; + } + return $isKwixoPayment; + } + /** + * Get Form data by using ops payment api + * + * @return array + */ + public function getFormData() + { + if (null == $this->formFields && $this->_getOrder() + && null != $this->_getOrder()->getId()) { + $this->formFields = $this->_getApi()->getFormFields($this->_getOrder(), $this->getRequest()->getParams()); + } + return $this->formFields; + } + + /** + * Getting gateway url + * + * @return string + */ + public function getFormAction() + { + $formAction = ''; + + // extract variable to ensure php 5.4 compatibility + $question = $this->getQuestion(); + + if ($this->getRequest()->isPost() || empty($question)) { + /** @var Netresearch_OPS_Model_Payment_Abstract $method */ + $method = $this->_getOrder()->getPayment()->getMethodInstance(); + $formAction = $method->getFrontendGateWay(); + } else { + $formAction = Mage::getUrl( + '*/*/*', array('_secure' => Mage::app()->getFrontController()->getRequest()->isSecure()) + ); + } + + return $formAction; + } + + /** + * @return bool|null + */ + public function hasMissingParams() + { + if (null === $this->_getOrder()) { + return null; + } + if (null === $this->hasMissingParams) { + $this->hasMissingParams = $this->_getApi() + ->hasFormMissingParams( + $this->_getOrder(), + $this->getRequest()->getParams(), + $this->getFormData() + ); + } + return $this->hasMissingParams; + } + + /** + * @return string + */ + public function getQuestion() + { + if (null === $this->question && $this->_getOrder() && null != $this->_getOrder()->getId()) { + $this->question = $this->_getApi()->getQuestion(); + } + return $this->question; + } + + /** + * @return array + */ + public function getQuestionedFormFields() + { + if (is_null($this->missingFormFields) && $this->_getOrder() && !is_null($this->_getOrder()->getId())) { + $this->missingFormFields = $this->_getApi() + ->getQuestionedFormFields($this->_getOrder()); + } + + return $this->missingFormFields; + } + + /** + * @return bool + */ + public function isIframeTarget() + { + return $this->getConfig()->getConfigData('template') === Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_IFRAME; + } + + +} diff --git a/app/code/community/Netresearch/OPS/Block/Placeform3dsecure.php b/app/code/community/Netresearch/OPS/Block/Placeform3dsecure.php new file mode 100644 index 0000000..0f63b12 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/Placeform3dsecure.php @@ -0,0 +1,42 @@ +_getOrder()) { + return base64_decode($this->_getOrder()->getPayment()->getAdditionalInformation('HTML_ANSWER')); + } + return null; + } +} diff --git a/app/code/community/Netresearch/OPS/Block/RetryPayment.php b/app/code/community/Netresearch/OPS/Block/RetryPayment.php new file mode 100644 index 0000000..4d73353 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/RetryPayment.php @@ -0,0 +1,103 @@ + + */ +class Netresearch_OPS_Block_RetryPayment extends Netresearch_OPS_Block_Placeform +{ + + protected $_order = null; + + protected function _getApi() + { + return $this->_getOrder()->getPayment()->getMethodInstance(); + } + + protected function _getOrder() + { + if (null === $this->_order) { + $opsOrderId = $this->getOrderId(); + $this->_order = Mage::helper('ops/order')->getOrder($opsOrderId); + } + + return $this->_order; + } + + /** + * Getting placeform url + * + * @return string + */ + public function getFormAction() + { + $formAction = Mage::getUrl( + '*/*/updatePaymentAndPlaceForm', + array('_secure' => Mage::app()->getFrontController()->getRequest()->isSecure()) + ); + + return $formAction; + } + + /** + * Getting cancel url + * + * @return string + */ + public function getCancelUrl() + { + $formAction = Mage::getUrl( + '*/*/cancel', + array('_secure' => Mage::app()->getFrontController()->getRequest()->isSecure()) + ); + + return $formAction; + } + + public function getOrderId() + { + return $this->getRequest()->getParam('orderID'); + } + + /** + * Returns the orders billing address + * + * @return Mage_Sales_Model_Order_Address + */ + public function getBillingAddress() + { + return $this->_getOrder()->getBillingAddress(); + } + + /** + * Returns the orders shipping address or false in case of a virtual order + * + * @return Mage_Sales_Model_Order_Address|false + */ + public function getShippingAddress() + { + return $this->_getOrder()->getShippingAddress(); + } +} diff --git a/app/code/community/Netresearch/OPS/Block/RetryPayment/Methods.php b/app/code/community/Netresearch/OPS/Block/RetryPayment/Methods.php new file mode 100644 index 0000000..19d4e80 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/RetryPayment/Methods.php @@ -0,0 +1,80 @@ + + */ +class Netresearch_OPS_Block_RetryPayment_Methods extends Mage_Checkout_Block_Onepage_Payment_Methods +{ + protected $quote = null; + + /** + * Get Order ID from Url + * + * @return null + * @throws Exception + */ + public function getQuote() + { + if (null === $this->quote) { + $opsOrderId = $this->getRequest()->getParam('orderID'); + $order = Mage::helper('ops/order')-> getOrder($opsOrderId); + $quote = Mage::getModel('sales/quote')->load($order->getQuoteId()); + + if ($quote->getId()) { + $quote->setIsActive(1) + ->save(); + Mage::getSingleton('checkout/session') + ->replaceQuote($quote); + } + + $this->quote = $quote; + } + + return $this->quote; + } + + /** + * Retrieve available payment methods - filtered by OPS methods + * + * @return array + */ + public function getMethods() + { + $methods = $this->getData('methods'); + if ($methods === null) { + $methods = parent::getMethods(); + foreach ($methods as $key => $method) { + if (!$method instanceof Netresearch_OPS_Model_Payment_Abstract) { + $this->_assignMethod($method); + unset($methods[$key]); + } + } + $this->setData('methods', $methods); + } + + return $methods; + } +} diff --git a/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Brand.php b/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Brand.php new file mode 100644 index 0000000..b10ad10 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Brand.php @@ -0,0 +1,53 @@ + + */ + +class Netresearch_OPS_Block_System_Config_Form_Field_Brand + extends Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract +{ + public function __construct() + { + $this->addColumn( + 'brand', array( + 'label' => Mage::helper('ops')->__('Brand'), + 'style' => 'width:120px', + ) + ); + $this->addColumn( + 'value', array( + 'label' => Mage::helper('ops')->__('Title'), + 'style' => 'width:120px', + ) + ); + $this->_addAfter = false; + $this->_addButtonLabel = Mage::helper('ops')->__('Add Brand'); + parent::__construct(); + } +} diff --git a/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Image.php b/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Image.php new file mode 100644 index 0000000..cb022a1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Image.php @@ -0,0 +1,87 @@ + + */ +class Netresearch_OPS_Block_System_Config_Form_Field_Image + extends Mage_Adminhtml_Block_System_Config_Form_Field_Image +{ + + /** + * Return element html code + * + * @return string + */ + public function getElementHtml() + { + $html = ''; + $url = $this->_getUrl(); + + if (!empty($url)) { + $value = $this->getData('value'); + $html = '' + . '' . $value . '' + . ' '; + } + $this->setData('class', 'input-file'); + $html .= Varien_Data_Form_Element_Abstract::getElementHtml(); + $html .= $this->_getDeleteCheckbox(); + + return $html; + } + + /** + * Get image preview url + * + * @return string + */ + protected function _getUrl() + { + $url = $this->getData('value'); + $config = $this->getData('field_config'); + + if (!empty($url)) { + /* @var $config Varien_Simplexml_Element */ + if (!empty($config->base_url)) { + $el = $config->descend('base_url'); + $urlType = empty($el['type']) ? 'link' : (string)$el['type']; + $url = Mage::getBaseUrl($urlType) . (string)$config->base_url . '/' . $url; + } + } else { + preg_match("/groups\[([a-zA-Z_]*)/", $this->getData('name'), $imageName); + + if (isset($imageName[1])) { + $url = Mage::helper('ops/payment')->getPaymentDefaultLogo($imageName[1]); + } + } + + return $url; + } +} diff --git a/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Method.php b/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Method.php new file mode 100644 index 0000000..30a92f9 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/Method.php @@ -0,0 +1,59 @@ + + */ + +class Netresearch_OPS_Block_System_Config_Form_Field_Method extends Mage_Adminhtml_Block_System_Config_Form_Field_Array_Abstract +{ + public function __construct() + { + $this->addColumn( + 'title', array( + 'label' => Mage::helper('ops')->__('Title'), + 'style' => 'width:80px', + 'class' => 'required-entry' + ) + ); + $this->addColumn( + 'pm', array( + 'label' => 'PM', + 'style' => 'width:80px', + 'class' => 'required-entry' + ) + ); + $this->addColumn( + 'brand', array( + 'label' => 'BRAND', + 'style' => 'width:80px', + ) + ); + + $this->_addAfter = false; + $this->_addButtonLabel = Mage::helper('ops')->__('Add Method'); + parent::__construct(); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/RecurringActive.php b/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/RecurringActive.php new file mode 100644 index 0000000..49e1ac7 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/System/Config/Form/Field/RecurringActive.php @@ -0,0 +1,55 @@ + + */ +?> + + element = $('" . $element->getHtmlId() . "'); + Event.observe(element, 'change', function(){ + if(element.value == 1){ + $('ops_recurring_cc_active_comment').style.display = 'block'; + } else { + $('ops_recurring_cc_active_comment').style.display = 'none'; + } + }); + "; + + return $html . $javascript; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/System/Config/Kwixoconfiguration.php b/app/code/community/Netresearch/OPS/Block/System/Config/Kwixoconfiguration.php new file mode 100644 index 0000000..dd67f91 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/System/Config/Kwixoconfiguration.php @@ -0,0 +1,35 @@ +addData( + array( + 'fieldset_label' => $fieldset->getLegend(), + ) + ); + + return $this->toHtml(); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Block/System/Config/Mode.php b/app/code/community/Netresearch/OPS/Block/System/Config/Mode.php new file mode 100644 index 0000000..0e8e0d6 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/System/Config/Mode.php @@ -0,0 +1,32 @@ + + element = $('".$element->getHtmlId()."'); + Event.observe(element, 'change', function(){ + if(element.selectedOptions[0].value != '".$element->getValue()."'){ + $('ops_mode_comment').style.display = 'block'; + } else { + $('ops_mode_comment').style.display = 'none'; + } + }); + "; + + return $html.$javascript; + } + + +} diff --git a/app/code/community/Netresearch/OPS/Block/System/Config/PaymentForm.php b/app/code/community/Netresearch/OPS/Block/System/Config/PaymentForm.php new file mode 100644 index 0000000..7ec6b7d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/System/Config/PaymentForm.php @@ -0,0 +1,47 @@ + + */ +class Netresearch_Ops_Block_System_Config_PaymentForm + extends Mage_Adminhtml_Block_System_Config_Form +{ + + /** + * Add Payment Logo Image Renderer to renderer types + * + * @return array + */ + protected function _getAdditionalElementTypes() + { + $elementTypes = parent::_getAdditionalElementTypes(); + $elementTypes['paymentLogo'] = Mage::getConfig()->getBlockClassName('ops/system_config_form_field_image'); + + return $elementTypes; + } +} diff --git a/app/code/community/Netresearch/OPS/Block/System/Config/Support.php b/app/code/community/Netresearch/OPS/Block/System/Config/Support.php new file mode 100644 index 0000000..5872d8b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Block/System/Config/Support.php @@ -0,0 +1,170 @@ +getOriginalData(); + $this->addData( + array( + 'fieldset_label' => $fieldset->getLegend(), + ) + ); + return $this->toHtml(); + } + + /** + * get extension version + * + * @return string + */ + public function getExtensionVersion() + { + return (string)Mage::getConfig()->getNode('modules')->children()->Netresearch_OPS->version; + } + + /** + * get Magento version + * + * @return string + */ + public function getMageVersion() + { + $mageVersion = Mage::getVersion(); + if (is_callable('Mage::getEdition')) { + $mageVersion = Mage::getEdition() . ' ' . $mageVersion; + } + return $mageVersion; + } + + /** + * get support mail address + * + * @return string + */ + public function getSupportMail() + { + $mail = $this->getConfig()->getConfigData('support_mail'); + if (false !== strpos($mail, '@')) { + return $mail; + } + } + + /** + * if we have a link to documentation + * + * @return int + */ + public function hasDocumentation() + { + return strlen($this->getDocLinkDe() . $this->getDocLinkEn()); + } + + /** + * get URL of German documentation + * + * @return string + */ + public function getDocLinkDe() + { + $link = $this->getConfig()->getConfigData('doc_link_de'); + if (false !== strpos($link, '://')) { + return $link; + } + } + + /** + * get URL of English documentation + * + * @return string + */ + public function getDocLinkEn() + { + $link = $this->getConfig()->getConfigData('doc_link_en'); + if (false !== strpos($link, '://')) { + return $link; + } + } + + /** + * if we have link to a FAQ + * + * @return int + */ + public function hasFaq() + { + return strlen($this->getFaqLinkDe() . $this->getFaqLinkEn()); + } + /** + * get URL of German FAQ + * + * @return string + */ + public function getFaqLinkDe() + { + $link = $this->getConfig()->getConfigData('faq_link_de'); + if (false !== strpos($link, '://')) { + return $link; + } + + } + + /** + * get URL of English FAQ + * + * @return string + */ + public function getFaqLinkEn() + { + $link = $this->getConfig()->getConfigData('faq_link_en'); + if (false !== strpos($link, '://')) { + return $link; + } + } + + /** + * if we use a prefix for parameter ORDERID + * + * @return bool + */ + public function hasDevPrefix() + { + return 0 < strlen($this->getDevPrefix()); + } + + /** + * get prefix for parameter ORDERID + * + * @return string + */ + public function getDevPrefix() + { + return $this->getConfig()->getConfigData('devprefix'); + } + + /** + * get link for ops.log download action + * + * @return string + */ + public function getLogDownloadLink() + { + return $this->getUrl($this->_downloadLogPath); + } +} + diff --git a/app/code/community/Netresearch/OPS/Controller/Abstract.php b/app/code/community/Netresearch/OPS/Controller/Abstract.php new file mode 100644 index 0000000..b7e946e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Controller/Abstract.php @@ -0,0 +1,181 @@ + + * @author André Herrn + * @license OSL 3.0 + */ +class Netresearch_OPS_Controller_Abstract extends Mage_Core_Controller_Front_Action +{ + protected function getQuote() + { + return $this->_getCheckout()->getQuote(); + } + + /** + * Get checkout session namespace + * + * @return Mage_Checkout_Model_Session + */ + protected function _getCheckout() + { + return Mage::getSingleton('checkout/session'); + } + + protected function getConfig() + { + return Mage::getModel('ops/config'); + } + + /** + * Return order instance loaded by increment id' + * + * @return Mage_Sales_Model_Order + */ + + /** + * Return order instance loaded by increment id' + * + * @param $opsOrderId + * + * @return Mage_Sales_Model_Order + */ + + protected function _getOrder($opsOrderId = null) + { + if (empty($this->_order)) { + if (null === $opsOrderId) { + $opsOrderId = $this->getRequest()->getParam('orderID'); + } + $this->_order = Mage::helper('ops/order')->getOrder($opsOrderId); + } + + return $this->_order; + } + + /** + * Get singleton with Checkout by OPS Api + * + * @return Netresearch_OPS_Model_Payment_Abstract + */ + protected function _getApi() + { + $api = Mage::getSingleton('checkout/session')->getQuote()->getPayment()->getMethodInstance(); + if (null != $this->getRequest()->getParam('orderID')) { + $api = $this->_getOrder()->getPayment()->getMethodInstance(); + } + + return $api; + } + + /** + * get payment helper + * + * @return Netresearch_OPS_Helper_Payment + */ + protected function getPaymentHelper() + { + return Mage::helper('ops/payment'); + } + + /** + * get direct link helper + * + * @return Netresearch_OPS_Helper_Directlink + */ + protected function getDirectlinkHelper() + { + return Mage::helper('ops/directlink'); + } + + protected function getSubscriptionHelper() + { + return Mage::helper('ops/subscription'); + } + + /** + * Validation of incoming OPS data + * + * @param mixed[]|bool $paramOverwrite array of parameters with SHASIGN to validate instead of standard request + * parameters + * + * @return bool + */ + protected function _validateOPSData($paramOverwrite = false) + { + $helper = Mage::helper('ops'); + + $params = $paramOverwrite ? : $this->getRequest()->getParams(); + + if ($this->getSubscriptionHelper()->isSubscriptionFeedback($params)) { + $profile = $this->getSubscriptionHelper()->getProfileForSubscription($params['orderID']); + if (!$profile->getId()) { + $this->_getCheckout()->addError($this->__('Subscription is not valid')); + $helper->log( + $helper->__( + "Incoming Ingenico ePayments Feedback\n\nRequest Path: %s\nParams: %s\n\nSubscription not valid\n", + $this->getRequest()->getPathInfo(), + serialize($this->getRequest()->getParams()) + ) + ); + return false; + } + $storeId = $profile->getStoreId(); + } else { + $order = $this->_getOrder(); + if (!$order->getId()) { + $helper->log( + $helper->__( + "Incoming Ingenico ePayments Feedback\n\nRequest Path: %s\nParams: %s\n\nOrder not valid\n", + $this->getRequest()->getPathInfo(), + serialize($this->getRequest()->getParams()) + ) + ); + $this->_getCheckout()->addError($this->__('Order is not valid')); + return false; + } + $storeId = $order->getStoreId(); + } + + //remove custom responseparams, because they are not hashed by Ingenico ePayments + if ($this->getConfig()->getConfigData('template') == Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_IFRAME + && array_key_exists('IFRAME', $params) + ) { + unset($params['IFRAME']); + } + + $secureKey = $this->getConfig()->getShaInCode($storeId); + $secureSet = $this->getPaymentHelper()->getSHAInSet($params, $secureKey); + + $helper->log( + $helper->__( + "Incoming Ingenico ePayments Feedback\n\nRequest Path: %s\nParams: %s\n", + $this->getRequest()->getPathInfo(), + serialize($this->getRequest()->getParams()) + ) + ); + + if (Mage::helper('ops/payment')->shaCryptValidation($secureSet, $params['SHASIGN']) !== true) { + $this->_getCheckout()->addError($this->__('Hash is not valid')); + return false; + } + + return true; + } + + public function isJsonRequested($params) + { + if (array_key_exists('RESPONSEFORMAT', $params) && $params['RESPONSEFORMAT'] == 'JSON') { + return true; + } + return false; + } + + public function getSubscriptionManager() + { + return Mage::getModel('ops/subscription_manager'); + } +} diff --git a/app/code/community/Netresearch/OPS/Helper/Address.php b/app/code/community/Netresearch/OPS/Helper/Address.php new file mode 100644 index 0000000..7f4c2e0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Address.php @@ -0,0 +1,181 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Address extends Mage_Core_Helper_Abstract +{ + + const OPTION_A_ADDITION_1 = 'A_Addition_to_address_1'; + const OPTION_A_STREET_NAME = 'A_Street_name_1'; + const OPTION_A_HOUSE_NUMBER = 'A_House_number_1'; + const OPTION_A_ADDITION_2 = 'A_Addition_to_address_2'; + const OPTION_B_ADDITION_1 = 'B_Addition_to_address_1'; + const OPTION_B_STREET_NAME = 'B_Street_name'; + const OPTION_B_HOUSE_NUMBER = 'B_House_number'; + const OPTION_B_ADDITION_2 = 'B_Addition_to_address_2'; + + + /** + * split street into street name, number and additional street information + * + * @param string $street + * + * @return array + */ + public function splitStreet($street) + { + $result = array( + 'street_name' => $street, + 'street_number' => '', + 'supplement' => '' + ); + + if (preg_match($this->getStreetSplitter(), $street, $matches)) { + + // Pattern A + if (isset($matches[self::OPTION_A_STREET_NAME]) && !empty($matches[self::OPTION_A_STREET_NAME])) { + + $result['street_name'] = trim($matches[self::OPTION_A_STREET_NAME]); + + if (isset($matches[self::OPTION_A_HOUSE_NUMBER]) && !empty($matches[self::OPTION_A_HOUSE_NUMBER])) { + $result['street_number'] = trim($matches[self::OPTION_A_HOUSE_NUMBER]); + } + + if (isset($matches[self::OPTION_A_ADDITION_1]) && isset($matches[self::OPTION_A_ADDITION_2])) { + $result['supplement'] = trim($matches[self::OPTION_A_ADDITION_1] . ' ' + . $matches[self::OPTION_A_ADDITION_2] + ); + } + + return $result ; + + // Pattern B + } elseif (isset($matches[self::OPTION_B_STREET_NAME]) && !empty($matches[self::OPTION_B_STREET_NAME])) { + + $result['street_name'] = trim($matches[self::OPTION_B_STREET_NAME]); + + if (isset($matches[self::OPTION_B_HOUSE_NUMBER]) && !empty($matches[self::OPTION_B_HOUSE_NUMBER])) { + $result['street_number'] = trim($matches[self::OPTION_B_HOUSE_NUMBER]); + } + + if (isset($matches[self::OPTION_B_ADDITION_1]) && isset($matches[self::OPTION_B_ADDITION_2])) { + $result['supplement'] = trim($matches[self::OPTION_B_ADDITION_1] . ' ' + . $matches[self::OPTION_B_ADDITION_2] + ); + } + + + return $result; + } + } + + return $result; + } + + /** + * Regex to analyze addresses and split them into the groups Street Name, House Number and Additional Information + * Pattern A is addition number street addition + * Pattern B is addition street number addition + * + * @return string + */ + protected function getStreetSplitter() + { + return "/\\A\\s* + (?: + ######################################################################### + # Option A: [] # + # [] # + ######################################################################### + (?: + (?P.*?) + ,\\s* + )? + # Addition to address 1 + (?:No\\.\\s*)? + (?P + \\pN+[a-zA-Z]? + (?:\\s*[-\\/\\pP]\\s*\\pN+[a-zA-Z]?)* + ) + # House number + \\s*,?\\s* + (?P + (?:[a-zA-Z]\\s*|\\pN\\pL{2,}\\s\\pL) + \\S[^,#]*? + (? + (?!\\s) + .*? + ) + )? + # Addition to address 2 + | + ######################################################################### + # Option B: [] # + # [] # + ######################################################################### + (?: + (?P.*?) + ,\\s* + (?=.*[,\\/]) + )? + # Addition to address 1 + (?!\\s*No\\.) + (?P + \\S\\s*\\S + (?: + [^,#] + (?!\\b\\pN+\\s) + )*? + (? + \\pN+\\s*-?[a-zA-Z]? + (?: + \\s*[-\\/\\pP]?\\s*\\pN+ + (?:\\s*[\\-a-zA-Z])? + )*| + [IVXLCDM]+ + (?!.*\\b\\pN+\\b) + ) + (? + (?!\\s) + .*? + ) + )? + # Addition to address 2 + ) + \\s*\\Z/x"; + } +} diff --git a/app/code/community/Netresearch/OPS/Helper/Alias.php b/app/code/community/Netresearch/OPS/Helper/Alias.php new file mode 100644 index 0000000..3076451 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Alias.php @@ -0,0 +1,514 @@ +getAdminSession()->getUser()) { + return 0 < $this->getAdminSession()->getUser()->getUserId(); + } + return false; + } + + /** + * PM value is not used for payments with Alias Manager + * + * @param Mage_Sales_Model_Quote_Payment|null Payment + * + * @return null + */ + public function getOpsCode($payment = null) + { + return $payment; + } + + /** + * BRAND value is not used for payments with Alias Manager + * + * @param Mage_Sales_Model_Quote_Payment|null Payment + * + * @return null + */ + public function getOpsBrand($payment = null) + { + return $payment; + } + + /** + * saves the alias if customer is logged in (and want to create an alias) + * + * @param $params + * + * @return Netresearch_OPS_Model_Alias | null + */ + public function saveAlias($params) + { + $quote = null; + $aliasModel = null; + Mage::helper('ops')->log('aliasData ' . Zend_Json::encode(Mage::helper('ops/data')->clearMsg($params))); + if (array_key_exists('Alias_OrderId', $params) && is_numeric($params['Alias_OrderId'])) { + $quote = Mage::getModel('sales/quote')->load($params['Alias_OrderId']); + } + + if ($quote instanceof Mage_Sales_Model_Quote + && $quote->getPayment() + && Mage::getSingleton('checkout/type_onepage')->getCheckoutMethod() + != Mage_Checkout_Model_Type_Onepage::METHOD_GUEST + && (array_key_exists('Alias_StorePermanently', $params) && 'Y' == $params['Alias_StorePermanently']) + ) { + + // alias does not exist -> create a new one if requested + if (null != $quote && $quote->getPayment()) { + // create new alias + $aliasModel = $this->saveNewAliasFromQuote($quote, $params); + $quote->getPayment()->setAdditionalInformation( + 'opsAliasId', $aliasModel->getId() + ); + $quote->getPayment()->save(); + } + } elseif (array_key_exists('orderid', $params)) { + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::helper('ops/order')->getOrder($params['orderid']); + $aliasModel = $this->saveNewAliasFromOrder($order, $params); + $order->getPayment()->setAdditionalInformation('opsAliasId', $aliasModel->getId()); + } + + return $aliasModel; + } + + /** + * + * @param Mage_Sales_Model_Quote $quote + */ + protected function deleteAlias(Mage_Sales_Model_Quote $quote) + { + $customerId = $quote->getCustomer()->getId(); + $billingAddressHash = $this->generateAddressHash( + $quote->getBillingAddress() + ); + $shippingAddressHash = $this->generateAddressHash( + $quote->getShippingAddress() + ); + $aliasModel = Mage::getModel('ops/alias'); + $aliasCollection = $aliasModel->getCollection() + ->addFieldToFilter('customer_id', $customerId) + ->addFieldToFilter('billing_address_hash', $billingAddressHash) + ->addFieldToFilter('shipping_address_hash', $shippingAddressHash) + ->addFieldToFilter('state', Netresearch_OPS_Model_Alias_State::PENDING) + ->addFieldToFilter('store_id', array(array('eq' => $quote->getStoreId()), array('null' => true))) + ->setOrder('created_at', 'DESC') + ->setPageSize(1); + $aliasCollection->load(); + foreach ($aliasCollection as $alias) { + $alias->delete(); + } + } + + protected function saveNewAliasFromQuote(Mage_Sales_Model_Quote $quote, $params) + { + $customerId = $quote->getCustomer()->getId(); + $billingAddressHash = $this->generateAddressHash( + $quote->getBillingAddress() + ); + $shippingAddressHash = $this->generateAddressHash( + $quote->getShippingAddress() + ); + + + $aliasData = array(); + $aliasData['customer_id'] = $customerId; + $aliasData['alias'] = $params['Alias_AliasId']; + $aliasData['expiration_date'] = $params['Card_ExpiryDate']; + $aliasData['billing_address_hash'] = $billingAddressHash; + $aliasData['shipping_address_hash'] = $shippingAddressHash; + $aliasData['brand'] = $params['Card_Brand']; + $aliasData['payment_method'] = $quote->getPayment()->getMethod(); + $aliasData['pseudo_account_or_cc_no'] = $params['Card_CardNumber']; + $aliasData['state'] = Netresearch_OPS_Model_Alias_State::PENDING; + $aliasData['store_id'] = $quote->getStoreId(); + + if (array_key_exists('Card_CardHolderName', $params)) { + $aliasData['card_holder'] = $params['Card_CardHolderName']; + } + + $aliasModel = $this->persistAlias($aliasData); + + return $aliasModel; + } + + public function saveNewAliasFromOrder(Mage_Sales_Model_Order $order, $params) + { + $customerId = $order->getCustomerId(); + $billingAddressHash = $this->generateAddressHash( + $order->getBillingAddress() + ); + $shippingAddressHash = $this->generateAddressHash( + $order->getShippingAddress() + ); + + $aliasData = array(); + $aliasData['customer_id'] = $customerId; + $aliasData['alias'] = $params['alias']; + $aliasData['expiration_date'] = $params['ed']; + $aliasData['billing_address_hash'] = $billingAddressHash; + $aliasData['shipping_address_hash'] = $shippingAddressHash; + $aliasData['brand'] = $params['brand']; + $aliasData['payment_method'] = $order->getPayment()->getMethod(); + $aliasData['pseudo_account_or_cc_no'] = $params['cardno']; + $aliasData['state'] = Netresearch_OPS_Model_Alias_State::ACTIVE; + $aliasData['store_id'] = $order->getStoreId(); + $aliasData['card_holder'] = $params['cn']; + + $aliasModel = $this->persistAlias($aliasData); + + return $aliasModel; + } + + + public function persistAlias(array $aliasParams) + { + /** @var Netresearch_OPS_Model_Alias $aliasModel */ + $aliasModel = Mage::getModel('ops/alias')->load($aliasParams['alias'], 'alias'); + + Mage::helper('ops')->log( + 'saving alias' . Zend_Json::encode($aliasModel->getData()) + ); + + $aliasModel->addData($aliasParams); + $aliasModel->save(); + + return $aliasModel; + } + + /** + * generates hash from address data + * + * @param Mage_Customer_Model_Address_Abstract $address the address data to hash + * + * @returns string hash of address + */ + public function generateAddressHash(Mage_Customer_Model_Address_Abstract $address) { + /** @var Netresearch_OPS_Helper_Payment $opsHelper */ + $opsHelper = Mage::helper('ops/payment'); + $addressString = $address->getFirstname(); + $addressString .= $address->getMiddlename(); + $addressString .= $address->getLastname(); + $addressString .= $address->getCompany(); + $street = $address->getStreetFull(); + if (is_array($street)) { + $street = implode('', $street); + } + $addressString .= $street; + $addressString .= $address->getPostcode(); + $addressString .= $address->getCity(); + $addressString .= $address->getCountryId(); + + return hash($opsHelper->getCryptMethod(), $addressString); + } + + /** + * retrieves the aliases for a given customer + * + * @param int $customerId + * @param Mage_Sales_Model_Quote + * + * @return Netresearch_OPS_Model_Mysql4_Alias_Collection - collection + * of aliases for the given customer + */ + public function getAliasesForCustomer( + $customerId, Mage_Sales_Model_Quote $quote = null + ) + { + $billingAddressHash = null; + $shippingAddressHash = null; + $storeId = null; + if (null !=($quote)) { + $billingAddressHash = $this->generateAddressHash( + $quote->getBillingAddress() + ); + $shippingAddressHash = $this->generateAddressHash( + $quote->getShippingAddress() + ); + $storeId = $quote->getStoreId(); + } + return Mage::getModel('ops/alias') + ->getAliasesForCustomer( + $customerId, $billingAddressHash, $shippingAddressHash, $storeId + ); + } + + /** + * if alias is valid for address + * + * @param int $customerId + * @param string $alias + * @param Mage_Sales_Model_Quote_Address $billingAddress + * @param Mage_Sales_Model_Quote_Address $shippingAddress + * @param int $storeId + * + * @return boolean + */ + public function isAliasValidForAddresses( + $customerId, $alias, $billingAddress, $shippingAddress, $storeId = null + ) + { + $aliasCollection = $this->getAliasesForAddresses( + $customerId, $billingAddress, $shippingAddress, $storeId + ) + ->addFieldToFilter('alias', $alias) + ->setPageSize(1); + return (1 == $aliasCollection->getSize()); + } + + /** + * get aliases that are allowed for customer with given addresses + * + * @param int $customerId Id of customer + * @param Mage_Sales_Model_Quote_Address $billingAddress billing address + * @param Mage_Sales_Model_Quote_Address $shippingAddress shipping address + * @param int $storeId + * + * @return Netresearch_OPS_Model_Mysql4_Alias_Collection + */ + public function getAliasesForAddresses( + $customerId, $billingAddress, $shippingAddress, $storeId = null + ) + { + $billingAddressHash = $this->generateAddressHash($billingAddress); + $shippingAddressHash = $this->generateAddressHash($shippingAddress); + return Mage::getModel('ops/alias')->getCollection() + ->addFieldToFilter('customer_id', $customerId) + ->addFieldToFilter('billing_address_hash', $billingAddressHash) + ->addFieldToFilter('shipping_address_hash', $shippingAddressHash) + ->addFieldToFilter('store_id', array(array('eq' => $storeId), array('null' => true))); + } + + /** + * formats the pseudo cc number in a brand specific format + * supported brand (so far): + * - MasterCard + * - Visa + * - American Express + * - Diners Club + * + * @param $brand - the cc brand we need to format the pseudo cc number + * @param $aliasCcNo - the pseudo cc number itself + * + * @return string - the formatted pseudo cc number + */ + public function formatAliasCardNo($brand, $aliasCcNo) + { + + if (in_array(strtolower($brand), array('visa', 'mastercard'))) { + $aliasCcNo = implode(' ', str_split($aliasCcNo, 4)); + } + if (in_array(strtolower($brand), array('american express', 'diners club', 'maestrouk'))) { + $aliasCcNo = str_replace('-', ' ', $aliasCcNo); + } + + return strtoupper($aliasCcNo); + } + + /** + * saves the alias and if given the cvc to the payment information + * + * @param Mage_Payment_Model_Info $payment - the payment which should be updated + * @param array $aliasData - the data we will update + * @param boolean $userIsRegistering - is registering method in checkout + * @param boolean $paymentSave - is it necessary to save the payment afterwards + */ + public function setAliasToPayment( + Mage_Payment_Model_Info $payment, + array $aliasData, + $userIsRegistering = false, + $paymentSave = false + ) + { + if (array_key_exists('alias_aliasid', $aliasData) && 0 < strlen(trim($aliasData['alias_aliasid']))) { + $payment->setAdditionalInformation('alias', trim($aliasData['alias_aliasid'])); + $payment->setAdditionalInformation('userIsRegistering', $userIsRegistering); + if (array_key_exists('card_cvc', $aliasData)) { + $payment->setAdditionalInformation('cvc', $aliasData['card_cvc']); + $this->setCardHolderToAlias($payment->getQuote(), $aliasData); + } + + if ( array_key_exists('method', $aliasData)) { + $alias = Mage::getModel('ops/alias')->load($aliasData['alias_aliasid'], 'alias'); + $alias->setPaymentMethod($aliasData['method']); + $alias->save(); + } + + $payment->setDataChanges(true); + if ($paymentSave === true) { + $payment->save(); + } + } else { + Mage::helper('ops/data')->log('did not save alias due to empty alias'); + Mage::helper('ops/data')->log(serialize($aliasData)); + } + } + + protected function setCardHolderToAlias($quote, $aliasData) + { + $customerId = $quote->getCustomerId(); + $billingAddressHash = $this->generateAddressHash($quote->getBillingAddress()); + $shippingAddressHash = $this->generateAddressHash($quote->getShippingAddress()); + $oldAlias = Mage::getModel('ops/alias')->getCollection() + ->addFieldToFilter('customer_id', $customerId) + ->addFieldToFilter('billing_address_hash', $billingAddressHash) + ->addFieldToFilter('shipping_address_hash', $shippingAddressHash) + ->addFieldToFilter('state', Netresearch_OPS_Model_Alias_State::ACTIVE) + ->addFieldToFilter('store_id', array(array('eq' => $quote->getStoreId()), array('null' => true))) + ->getFirstItem(); + // and if so update this alias with alias data from alias gateway + if (is_numeric($oldAlias->getId()) && null === $oldAlias->getCardHolder() + && array_key_exists('Card_CardHolderName', $aliasData) + ) { + $oldAlias->setCardHolder($aliasData['Card_CardHolderName']); + $oldAlias->save(); + } + } + + /** + * set the last pending alias to active and remove other aliases for customer based on address + * + * + * @param Mage_Sales_Model_Quote $quote + * @param Mage_Sales_Model_Order|null $order + * @param bool $saveSalesObjects + */ + public function setAliasActive( + Mage_Sales_Model_Quote $quote, + Mage_Sales_Model_Order $order = null, + $saveSalesObjects = false + ) + { + if (null === $quote->getPayment()->getAdditionalInformation('userIsRegistering') + || false == $quote->getPayment()->getAdditionalInformation('userIsRegistering') + ) { + $aliasesToDelete = Mage::helper('ops/alias')->getAliasesForAddresses( + $quote->getCustomer()->getId(), $quote->getBillingAddress(), $quote->getShippingAddress() + ) + ->addFieldToFilter('state', Netresearch_OPS_Model_Alias_State::ACTIVE); + $lastPendingAlias = Mage::helper('ops/alias')->getAliasesForAddresses( + $quote->getCustomer()->getId(), + $quote->getBillingAddress(), + $quote->getShippingAddress(), + $quote->getStoreId() + ) + ->addFieldToFilter('alias', $quote->getPayment()->getAdditionalInformation('alias')) + ->addFieldToFilter('state', Netresearch_OPS_Model_Alias_State::PENDING) + ->setOrder('created_at', Varien_Data_Collection::SORT_ORDER_DESC) + ->getFirstItem(); + if (0 < $lastPendingAlias->getId()) { + foreach ($aliasesToDelete as $alias) { + $alias->delete(); + } + $lastPendingAlias->setState(Netresearch_OPS_Model_Alias_State::ACTIVE); + $lastPendingAlias->setPaymentMethod($order->getPayment()->getMethod()); + $lastPendingAlias->save(); + } + } else { + $this->setAliasToActiveAfterUserRegisters($order, $quote); + } + $this->cleanUpAdditionalInformation($order->getPayment(), false, $saveSalesObjects); + $this->cleanUpAdditionalInformation($quote->getPayment(), false, $saveSalesObjects); + } + + public function setAliasToActiveAfterUserRegisters( + Mage_Sales_Model_Order $order, Mage_Sales_Model_Quote $quote + ) + { + if (true == $quote->getPayment()->getAdditionalInformation('userIsRegistering') + ) { + $customerId = $order->getCustomerId(); + $billingAddressHash = $this->generateAddressHash( + $quote->getBillingAddress() + ); + $shippingAddressHash = $this->generateAddressHash( + $quote->getShippingAddress() + ); + $aliasId = $quote->getPayment()->getAdditionalInformation( + 'opsAliasId' + ); + if (is_numeric($aliasId) && 0 < $aliasId) { + $alias = Mage::getModel('ops/alias')->getCollection() + ->addFieldToFilter( + 'alias', $quote->getPayment()->getAdditionalInformation('alias') + ) + ->addFieldToFilter( + 'billing_address_hash', $billingAddressHash + ) + ->addFieldToFilter( + 'shipping_address_hash', $shippingAddressHash + ) + ->addFieldToFilter('store_id', array('eq' => $quote->getStoreId())) + ->getFirstItem(); + + $alias->setState(Netresearch_OPS_Model_Alias_State::ACTIVE); + $alias->setPaymentMethod($order->getPayment()->getMethod()); + $alias->setCustomerId($customerId); + $alias->save(); + } + } + } + + /** + * cleans up the stored cvc and storedOPSId + * + * @param Mage_Sales_Model_Quote_Payment || Mage_Sales_Model_Order_Payment $payment + * @param bool $cvcOnly + * @param bool $savePayment + * + */ + public function cleanUpAdditionalInformation($payment, $cvcOnly = false, $savePayment = false) + { + if (is_array($payment->getAdditionalInformation()) + && array_key_exists('cvc', $payment->getAdditionalInformation()) + ) { + $payment->unsAdditionalInformation('cvc'); + } + + if ($cvcOnly === false && is_array($payment->getAdditionalInformation()) + && array_key_exists('storedOPSId', $payment->getAdditionalInformation()) + ) { + $payment->unsAdditionalInformation('storedOPSId'); + } + + /* OGNH-7: seems not to needed anymore since payment and quote is saved after this call, + otherwise admin payments will fail */ + if ($savePayment) { + $payment->save(); + } + } +} diff --git a/app/code/community/Netresearch/OPS/Helper/Api.php b/app/code/community/Netresearch/OPS/Helper/Api.php new file mode 100644 index 0000000..6af1e42 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Api.php @@ -0,0 +1,126 @@ + + * @author Sebastian Ertner + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Api extends Mage_Core_Helper_Abstract +{ + + protected $configModel = null; + + + /** + * @param $status - one of the fedd back status + * + * @throws Mage_Core_Exception - in case the status is not known + * @return string - the route for redirect + */ + public function getRedirectRouteFromStatus($status) + { + $route = null; + $configModel = $this->getConfigModel(); + if ($this->isAcceptStatus($status)) { + $route = $configModel->getAcceptRedirectRoute(); + } + if ($this->isCancelStatus($status)) { + $route = $configModel->getCancelRedirectRoute(); + } + if ($this->isDeclineStatus($status)) { + $route = $configModel->getDeclineRedirectRoute(); + } + if ($this->isExceptionStatus($status)) { + $route = $configModel->getExceptionRedirectRoute(); + } + + // in case none of the cases above match then the status is not known + if (null === $route) { + Mage::throwException('invalid status provided'); + } + + return $route; + } + + + /** + * config getter + * + * @return Netresearch_OPS_Model_Config + */ + protected function getConfigModel() + { + if (null === $this->configModel) { + $this->configModel = Mage::getModel('ops/config'); + } + return $this->configModel; + } + + /** + * determine if the status is known as accepted status + * + * @param $status - the status + * + * @return bool - true if the status is known as accept status, false otherwise + */ + protected function isAcceptStatus($status) + { + return in_array( + $status, array( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT + ) + ); + } + + /** + * determine if the status is known as canceled status + * + * @param $status - the status + * + * @return bool - true if the status is known as canceled status, false otherwise + */ + protected function isCancelStatus($status) + { + return in_array( + $status, array( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_CANCEL + ) + ); + } + + /** + * determine if the status is known as declined status + * + * @param $status - the status + * + * @return bool - true if the status is known as declined status, false otherwise + */ + protected function isDeclineStatus($status) + { + return in_array( + $status, array( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_DECLINE + ) + ); + } + + /** + * determine if the status is known as exception status + * + * @param $status - the status + * + * @return bool - true if the status is known as exception status, false otherwise + */ + protected function isExceptionStatus($status) + { + return in_array( + $status, array( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_EXCEPTION + ) + ); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Creditcard.php b/app/code/community/Netresearch/OPS/Helper/Creditcard.php new file mode 100644 index 0000000..e726417 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Creditcard.php @@ -0,0 +1,79 @@ + + * @category Netresearch + * @package ${MODULENAME} + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Helper_Creditcard extends Netresearch_OPS_Helper_Payment_DirectLink_Request +{ + + protected $aliasHelper = null; + + /** + * @param Netresearch_OPS_Helper_Alias $aliasHelper + */ + public function setAliasHelper($aliasHelper) + { + $this->aliasHelper = $aliasHelper; + } + + /** + * @return Netresearch_OPS_Helper_Alias + */ + public function getAliasHelper() + { + if (null === $this->aliasHelper) { + $this->aliasHelper = Mage::helper('ops/alias'); + } + return $this->aliasHelper; + } + + + /** + * @param Mage_Sales_Model_Quote $quote + * @param array $requestParams + * @return $this + */ + public function handleAdminPayment(Mage_Sales_Model_Quote $quote, $requestParams) + { + return $this; + } + + protected function getPaymentSpecificParams(Mage_Sales_Model_Quote $quote) + { + $alias = $quote->getPayment()->getAdditionalInformation('alias'); + if (null === $alias && $this->getDataHelper()->isAdminSession()) { + $alias = $this->getAliasHelper()->getAlias($quote); + } + $saveAlias = Mage::getModel('ops/alias')->load($alias, 'alias')->getId(); + $params = array ( + 'ALIAS' => $alias, + 'ALIASPERSISTEDAFTERUSE' => $saveAlias ? 'Y' : 'N', + ); + if ($this->getConfig()->getCreditDebitSplit($quote->getStoreId())) { + $params['CREDITDEBIT'] = 'C'; + } + if (is_numeric($quote->getPayment()->getAdditionalInformation('cvc'))) { + $params['CVC'] = $quote->getPayment()->getAdditionalInformation('cvc'); + } + $requestParams3ds = array(); + if ($this->getConfig()->get3dSecureIsActive() && false == $this->getDataHelper()->isAdminSession()) { + $requestParams3ds = array( + 'FLAG3D' => 'Y', + 'WIN3DS' => Netresearch_OPS_Model_Payment_Abstract::OPS_DIRECTLINK_WIN3DS, + 'LANGUAGE' => Mage::app()->getLocale()->getLocaleCode(), + 'HTTP_ACCEPT' => '*/*', + 'HTTP_USER_AGENT' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)', + 'ACCEPTURL' => $this->getConfig()->getAcceptUrl(), + 'DECLINEURL' => $this->getConfig()->getDeclineUrl(), + 'EXCEPTIONURL' => $this->getConfig()->getExceptionUrl(), + ); + } + $params = array_merge($params, $requestParams3ds); + return $params; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Data.php b/app/code/community/Netresearch/OPS/Helper/Data.php new file mode 100644 index 0000000..cc28c50 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Data.php @@ -0,0 +1,193 @@ + + * @author André Herrn + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Data extends Mage_Core_Helper_Abstract +{ + const LOG_FILE_NAME = 'ops.log'; + + /** + * Returns config model + * + * @return Netresearch_OPS_Model_Config + */ + public function getConfig() + { + return Mage::getSingleton('ops/config'); + } + + /** + * Replace all dots or any content following and including plus ("+") and minus ("-") signs. + * @return string + */ + public function getModuleVersionString() + { + $version = Mage::getConfig()->getNode('modules/Netresearch_OPS/version'); + $plainversion = preg_replace('/\.|[+-].+$/', '', $version); + return 'IG1X' . $plainversion; + } + + /** + * Checks if logging is enabled and if yes, logs given message to logfile + * + * @param string $message + * @param int $level + */ + public function log($message, $level = null) + { + $separator = "\n"."==================================================================="; + $message = $this->clearMsg($message); + if ($this->getConfig()->shouldLogRequests()) { + Mage::log($message.$separator, $level, self::LOG_FILE_NAME); + } + } + + /** + * Returns full path to ops.log + */ + public function getLogPath() + { + return Mage::getBaseDir('log'). DIRECTORY_SEPARATOR . self::LOG_FILE_NAME; + } + + + /** + * deletes certain keys from the message which is going to logged + * + * @param $message - the message + * + * @return array - the cleared message + */ + public function clearMsg($message) + { + if (is_array($message)) { + $keysToBeDeleted = array('cvc', 'CVC'); + foreach ($keysToBeDeleted as $keyToDelete) { + if (array_key_exists($keyToDelete, $message)) { + unset($message[$keyToDelete]); + } + } + } + if (is_string($message)) { + $message = preg_replace('/"CVC":".*"(,)/i', '', $message); + $message = preg_replace('/"CVC":".*"/i', '', $message); + $message = preg_replace('/"CVC".*"[A-Z]*";/', '', $message); + $message = preg_replace('/"CVC":".*"(})/i', '}', $message); + } + return $message; + } + + public function redirect($url) + { + Mage::app()->getResponse()->setRedirect($url); + Mage::app()->getResponse()->sendResponse(); + } + + /** + * Redirects to the given order and prints some notice output + * + * @param int $orderId + * @param string $message + * @return void + */ + public function redirectNoticed($orderId, $message) + { + Mage::getSingleton('core/session')->addNotice($message); + $this->redirect( + Mage::getUrl('*/sales_order/view', array('order_id' => $orderId)) + ); + } + + public function getStatusText($statusCode) + { + $translationOrigin = "STATUS_".$statusCode; + $translationResult = $this->__($translationOrigin); + if ($translationOrigin != $translationResult): + return $translationResult. " ($statusCode)"; + else: + return $statusCode; + endif; + } + + public function getAmount($amount) + { + return round($amount * 100); + } + + public function getAdminSession() + { + return Mage::getSingleton('admin/session'); + } + + public function isAdminSession() + { + if ($this->getAdminSession()->getUser()) { + return 0 < $this->getAdminSession()->getUser()->getUserId() || $this->getAdminSession()->isLoggedIn(); + } + return false; + } + + /* + * check if user is registering or not + */ + public function checkIfUserIsRegistering() + { + $isRegistering = false; + $checkoutMethod = Mage::getSingleton('checkout/session')->getQuote()->getCheckoutMethod(); + if ($checkoutMethod === Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER + || $checkoutMethod === Mage_Sales_Model_Quote::CHECKOUT_METHOD_LOGIN_IN + ) { + $isRegistering = true; + } + return $isRegistering; + + } + + /* + * check if user is registering or not + */ + public function checkIfUserIsNotRegistering() + { + $isRegistering = false; + $checkoutMethod = Mage::getSingleton('checkout/session')->getQuote()->getCheckoutMethod(); + if ($checkoutMethod === Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER) { + $isRegistering = true; + } + return $isRegistering; + + } + + /** + * Trigger sending order confirmation and invoice emails when Magento does not: + * - "authorization" after return from gateway (order emails) + * - "authorization+capture" (order or invoice emails) + * + * @param Mage_Sales_Model_Abstract $document + * @return Mage_Sales_Model_Abstract + * @throws Exception + */ + public function sendTransactionalEmail(Mage_Sales_Model_Abstract $document) + { + if ($document instanceof Mage_Sales_Model_Order) { + + if (!$document->getEmailSent() && $document->getCanSendNewEmailFlag()) { + $document->sendNewOrderEmail(); + } + + } elseif ($document instanceof Mage_Sales_Model_Order_Invoice) { + + if (!$document->getEmailSent() && Mage::getModel('ops/config')->getSendInvoice()) { + $document->sendEmail(); + } + + } + + return $document; + } +} diff --git a/app/code/community/Netresearch/OPS/Helper/Debitcard.php b/app/code/community/Netresearch/OPS/Helper/Debitcard.php new file mode 100644 index 0000000..1c756ad --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Debitcard.php @@ -0,0 +1,21 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Helper_Debitcard extends Netresearch_OPS_Helper_Creditcard +{ + protected function getPaymentSpecificParams(Mage_Sales_Model_Quote $quote) + { + $params = parent::getPaymentSpecificParams($quote); + if ($this->getConfig()->getCreditDebitSplit($quote->getStoreId())) { + $params['CREDITDEBIT'] = 'D'; + } + return $params; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/DirectDebit.php b/app/code/community/Netresearch/OPS/Helper/DirectDebit.php new file mode 100644 index 0000000..9c85fe7 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/DirectDebit.php @@ -0,0 +1,150 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Helper_DirectDebit extends Netresearch_OPS_Helper_Payment_DirectLink_Request +{ + + protected $dataHelper = null; + + protected $quoteHelper = null; + + protected $orderHelper = null; + + protected $customerHelper = null; + + + /** + * sets the data helper + * + * @param Netresearch_OPS_Helper_Data $dataHelper + */ + public function setDataHelper(Netresearch_OPS_Helper_Data $dataHelper) + { + $this->dataHelper = Mage::helper('ops/data'); + } + + /** + * gets the data helper + * + * @return Netresearch_OPS_Helper_Data + */ + public function getDataHelper() + { + if (null === $this->dataHelper) { + $this->dataHelper = Mage::helper('ops/data'); + } + + return $this->dataHelper; + } + + /** + * sets the quote helper + * + * @param Netresearch_OPS_Helper_Quote $quoteHelper + */ + public function setQuoteHelper(Netresearch_OPS_Helper_Quote $quoteHelper) + { + $this->quoteHelper = $quoteHelper; + } + + /** + * gets the quote helper + * + * @return Mage_Core_Helper_Abstract + */ + public function getQuoteHelper() + { + if (null === $this->quoteHelper) { + $this->quoteHelper = Mage::helper('ops/quote'); + } + + return $this->quoteHelper; + } + + /** + * sets the order helper + * + * @param Netresearch_OPS_Helper_Order $orderHelper + */ + public function setOrderHelper(Netresearch_OPS_Helper_Order $orderHelper) + { + $this->orderHelper = $orderHelper; + } + + /** + * gets the order helper + * + * @return Mage_Core_Helper_Abstract + */ + public function getOrderHelper() + { + if (null === $this->orderHelper) { + $this->orderHelper = Mage::helper('ops/order'); + } + + return $this->orderHelper; + } + + /** + * sets the customer helper + * + * @param Mage_Core_Helper_Abstract $customerHelper + */ + public function setCustomerHelper(Mage_Core_Helper_Abstract $customerHelper) + { + $this->customerHelper = $customerHelper; + } + + /** + * gets the customer helper + * + * @return Mage_Core_Helper_Abstract + */ + public function getCustomerHelper() + { + if (null === $this->customerHelper) { + $this->customerHelper = Mage::helper('customer/data'); + } + + return $this->customerHelper; + } + + /** + * @param Mage_Sales_Model_Quote $quote + * @param array $requestParams + * @return Netresearch_OPS_Helper_DirectDebit + */ + public function handleAdminPayment(Mage_Sales_Model_Quote $quote, $requestParams) + { + return $this; + } + + + /** + * @param Mage_Sales_Model_Quote $quote + * @return array + */ + protected function getPaymentSpecificParams(Mage_Sales_Model_Quote $quote) + { + $alias = $quote->getPayment()->getAdditionalInformation('alias'); + $saveAlias = Mage::getModel('ops/alias')->load($alias, 'alias')->getId(); + + $paymentMethod = 'Direct Debits ' . $quote->getPayment()->getAdditionalInformation('country_id'); + $params = array ( + 'ALIAS' => $alias, + 'ALIASPERSISTEDAFTERUSE' => $saveAlias ? 'Y' : 'N', + 'PM' => $paymentMethod, + 'BRAND' => $paymentMethod + ); + + return $params; + } +} + \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Directlink.php b/app/code/community/Netresearch/OPS/Helper/Directlink.php new file mode 100644 index 0000000..24886df --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Directlink.php @@ -0,0 +1,262 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Directlink extends Mage_Core_Helper_Abstract +{ + /** + * Creates Transactions for directlink activities + * + * @param Mage_Sales_Model_Order $order + * @param int $transactionID - persistent transaction id + * @param int $subPayID - identifier for each transaction + * @param array $arrInformation - add dynamic data + * @param string $typename - name for the transaction exp.: refund + * @param string $comment - order comment + * + * @return Netresearch_OPS_Helper_Directlink $this + */ + public function directLinkTransact($order,$transactionID, $subPayID, + $arrInformation = array(), $typename, $comment, $closed = 0) + { + $payment = $order->getPayment(); + $payment->setTransactionId($transactionID."/".$subPayID); + $payment->setParentTransactionId($transactionID); + $payment->setIsTransactionClosed($closed); + $payment->setTransactionAdditionalInfo($arrInformation, null); + return $this; + } + + /** + * Checks if there is an active transaction for a special order for special + * type + * + * @param string $type - refund, capture etc. + * @param int $orderID + * @return bol success + */ + public function checkExistingTransact($type, $orderID) + { + $transaction = Mage::getModel('sales/order_payment_transaction') + ->getCollection() + ->addAttributeToFilter('order_id', $orderID) + ->addAttributeToFilter('txn_type', $type) + ->addAttributeToFilter('is_closed', 0) + ->getLastItem(); + + return ($transaction->getTxnId()) ? true : false; + } + + /** + * get transaction type for given OPS status + * + * @param string $status + * + * @return string + */ + public function getTypeForStatus($status) + { + switch ($status) { + case Netresearch_OPS_Model_Status::REFUNDED : + case Netresearch_OPS_Model_Status::REFUND_PENDING: + case Netresearch_OPS_Model_Status::REFUND_UNCERTAIN : + case Netresearch_OPS_Model_Status::REFUND_REFUSED : + case Netresearch_OPS_Model_Status::REFUNDED_OK : + return Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_TRANSACTION_TYPE; + case Netresearch_OPS_Model_Status::PAYMENT_REQUESTED : + case Netresearch_OPS_Model_Status::PAYMENT_PROCESSED_BY_MERCHANT : + case Netresearch_OPS_Model_Status::PAYMENT_PROCESSING: + case Netresearch_OPS_Model_Status::PAYMENT_UNCERTAIN: + case Netresearch_OPS_Model_Status::PAYMENT_IN_PROGRESS: + case Netresearch_OPS_Model_Status::PAYMENT_REFUSED: + case Netresearch_OPS_Model_Status::PAYMENT_DECLINED_BY_ACQUIRER: + return Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_TRANSACTION_TYPE; + case Netresearch_OPS_Model_Status::AUTHORIZED_AND_CANCELLED: //Void finished + case Netresearch_OPS_Model_Status::AUTHORIZED_AND_CANCELLED_OK: + case Netresearch_OPS_Model_Status::DELETION_WAITING: + case Netresearch_OPS_Model_Status::DELETION_UNCERTAIN: + case Netresearch_OPS_Model_Status::DELETION_REFUSED: + return Netresearch_OPS_Model_Payment_Abstract::OPS_VOID_TRANSACTION_TYPE; + case Netresearch_OPS_Model_Status::PAYMENT_DELETED: + case Netresearch_OPS_Model_Status::PAYMENT_DELETION_PENDING: + case Netresearch_OPS_Model_Status::PAYMENT_DELETION_UNCERTAIN: + case Netresearch_OPS_Model_Status::PAYMENT_DELETION_REFUSED: + case Netresearch_OPS_Model_Status::PAYMENT_DELETION_OK: + case Netresearch_OPS_Model_Status::DELETION_HANDLED_BY_MERCHANT: + return Netresearch_OPS_Model_Payment_Abstract::OPS_DELETE_TRANSACTION_TYPE; + } + } + + /** + * Process Direct Link Feedback to do: Capture, De-Capture and Refund + * + * @param Mage_Sales_Model_Order $order Order + * @param array $params Request params + * + * @return void + */ + public function processFeedback($order, $params) + { + Mage::getModel('ops/response_handler')->processResponse($params, $order->getPayment()->getMethodInstance()); + $order->getPayment()->save(); + } + + /** + * Get the payment transaction by PAYID and Operation + * + * @param Mage_Sales_Model_Order $order + * @param int $payId + * @param string $operation + * + * @return Mage_Sales_Model_Order_Payment_Transaction + * + * @throws Mage_Core_Exception + */ + public function getPaymentTransaction($order, $payId, $operation) + { + $helper = Mage::helper('ops'); + $transactionCollection = Mage::getModel('sales/order_payment_transaction') + ->getCollection() + ->addAttributeToFilter('txn_type', $operation) + ->addAttributeToFilter('is_closed', 0) + ->addAttributeToFilter('order_id', $order->getId()); + if ($payId != '') { + $transactionCollection->addAttributeToFilter('parent_txn_id', $payId); + } + + if ($transactionCollection->getSize()>1 || $transactionCollection->getSize() == 0) { + $errorMsq = $helper->__( + "Warning, transaction count is %s instead of 1 for the Payid '%s', order '%s' and Operation '%s'.", + $transactionCollection->getSize(), + $payId, + $order->getId(), + $operation + ); + $helper->log($errorMsq); + Mage::throwException($errorMsq); + } + + if ($transactionCollection->getSize() == 1) { + $transaction = $transactionCollection->getLastItem(); + $transaction->setOrderPaymentObject($order->getPayment()); + return $transaction; + } + } + + + /** + * Check if there are payment transactions for an order and an operation + * + * @param Mage_Sales_Model_Order $order + * @param string $authorization + * + * @return boolean + */ + public function hasPaymentTransactions($order, $operation) + { + $transactionCollection = Mage::getModel('sales/order_payment_transaction') + ->getCollection() + ->addAttributeToFilter('txn_type', $operation) + ->addAttributeToFilter('is_closed', 0) + ->addAttributeToFilter('order_id', $order->getId()); + + return (0 < $transactionCollection->getSize()); + } + + /** + * validate incoming and internal amount value format and convert it to float + * + * @param string + * @return float + */ + public function formatAmount($amount) + { + // Avoid quotes added somewhere unknown + if (preg_match("/^[\"']([0-9-\..,-]+)[\"']$/i", $amount, $matches)) { + Mage::helper('ops')->log( + "Warning in formatAmount: Found quotes around amount in '" . var_export($amount, true) . "'" + ); + $amount = $matches[1]; + } + + return number_format($amount, 2); + } + + /** + * determine if the current OPS request is valid + * + * @param array $transactions Iteratable of Mage_Sales_Model_Order_Payment_Transaction + * @param Mage_Sales_Model_Order $order + * @param array $opsRequestParams + * + * @return boolean + */ + public function isValidOpsRequest( + $openTransaction, + Mage_Sales_Model_Order $order, + $opsRequestParams + ) + { + if ($this->getTypeForStatus($opsRequestParams['STATUS']) == Netresearch_OPS_Model_Payment_Abstract::OPS_DELETE_TRANSACTION_TYPE) { + return false; + } + + $requestedAmount = null; + if (array_key_exists('amount', $opsRequestParams)) { + $requestedAmount = $this->formatAmount($opsRequestParams['amount']); + } + + /* find expected amount */ + $expectedAmount = null; + if (null !== $openTransaction) { + $transactionInfo = unserialize($openTransaction->getAdditionalInformation('arrInfo')); + if (array_key_exists('amount', $transactionInfo)) { + if (null === $expectedAmount || $transactionInfo['amount'] == $requestedAmount) { + $expectedAmount = $this->formatAmount($transactionInfo['amount']); + } + } + } + + if ($this->getTypeForStatus($opsRequestParams['STATUS']) == Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_TRANSACTION_TYPE + || $this->getTypeForStatus($opsRequestParams['STATUS']) == Netresearch_OPS_Model_Payment_Abstract::OPS_VOID_TRANSACTION_TYPE + ) { + if (null === $requestedAmount || 0 == count($openTransaction) || $requestedAmount != $expectedAmount) { + return false; + } + } + + if ($this->getTypeForStatus($opsRequestParams['STATUS']) == Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_TRANSACTION_TYPE) { + if (null === $requestedAmount) { + Mage::helper('ops')->log('Please configure Ingenico ePayments to submit amount'); + return false; + } + $grandTotal = $this->formatAmount(Mage::helper('ops/payment')->getBaseGrandTotalFromSalesObject($order)); + if ($grandTotal != $requestedAmount) { + if (null === $openTransaction || $expectedAmount != $requestedAmount) { + return false; + } + } + } + return true; + } + + public function performDirectLinkRequest($quote, $params, $storeId = null) + { + $url = Mage::getModel('ops/config')->getDirectLinkGatewayOrderPath($storeId); + $response = Mage::getSingleton('ops/api_directlink')->performRequest($params, $url, $storeId); + /** + * allow null as valid state for creating the order with status 'pending' + */ + if (null != $response['STATUS'] && Mage::helper('ops/payment')->isPaymentFailed($response['STATUS'])) { + Mage::getSingleton('checkout/type_onepage')->getCheckout()->setGotoSection('payment'); + Mage::throwException(Mage::helper('ops/data')->__('Ingenico ePayments Payment failed')); + } + + return $response; + } +} diff --git a/app/code/community/Netresearch/OPS/Helper/Kwixo.php b/app/code/community/Netresearch/OPS/Helper/Kwixo.php new file mode 100644 index 0000000..b8f8e4b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Kwixo.php @@ -0,0 +1,165 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Kwixo extends Mage_Core_Helper_Abstract +{ + + protected $helper = null; + + protected function getHelper() + { + if (null === $this->helper) { + $this->helper = Mage::helper('ops/data'); + } + + return $this->helper; + } + + /** + * validates the kwixoConfiguration data + * + * @param array $postData the data to validate + * + * @throws Mage_Core_Exception on errors + * + */ + public function validateKwixoconfigurationMapping(array $postData) + { + $this->validateKwixoConfigurationData($postData); + $this->validateKwixoMappingExist($postData); + $this->validateCategoryExist($postData); + } + + /** + * saves the KwixoConfigurationMapping + * + * @param array $postData + */ + public function saveKwixoconfigurationMapping(array $postData) + { + $this->validateKwixoconfigurationMapping($postData); + $kwixoCatMapModel = Mage::getModel( + 'ops/kwixo_category_mapping' + )->load($postData['id']); + $kwixoCatMapModel->setCategoryId($postData['category_id']); + $kwixoCatMapModel->setKwixoCategoryId( + $postData['kwixoCategory_id'] + ); + $kwixoCatMapModel->save(); + if (array_key_exists('applysubcat', $postData)) { + $category = Mage::getModel('catalog/category')->load( + $postData['category_id'] + ); + $subcategories = $category->getAllChildren(true); + foreach ($subcategories as $subcategory) { + $kwixoCatMapModel = Mage::getModel('ops/kwixo_category_mapping')->loadByCategoryId($subcategory); + $kwixoCatMapModel->setCategoryId($subcategory); + $kwixoCatMapModel->setKwixoCategoryId($postData['kwixoCategory_id']); + $kwixoCatMapModel->save(); + } + } + Mage::getSingleton('adminhtml/session')->addSuccess( + Mage::helper('ops/data')->__( + 'Successfully added Kwixo category mapping' + ) + ); + } + + + /** + * validates if the structure of a given array does match the expected kwixo + * setting configuration + * + * @param array $postData - the array to inspect + * + * @throws Mage_Core_Exception - if the structure does not match + */ + protected function validateKwixoConfigurationData(array $postData) + { + $helper = $this->getHelper(); + $isValid = true; + $message = ''; + if (0 === count($postData)) { + $message = $helper->__('Invalid form data provided'); + $isValid = false; + } + + if ($isValid && !array_key_exists('id', $postData)) { + $message = $helper->__('Invalid form data provided'); + $isValid = false; + } + + if ($isValid && 0 < strlen($postData['id']) + && (!is_numeric($postData['id']) + || $postData['id'] < 0) + ) { + $message = $helper->__('Invalid id provided'); + $isValid = false; + } + if (false === $isValid) { + Mage::throwException($message); + } + } + + /** + * validates if the given array contains the neccessary information for + * a proper kwixo category setting + * + * @param array $postData - the array to inspect + * + * @throws Mage_Core_Exception - if the array does not contain the needed + * information + * + */ + protected function validateKwixoMappingExist(array $postData) + { + $helper = $this->getHelper(); + $kwixoCategories = Mage::getModel('ops/source_kwixo_productCategories') + ->getValidKwixoCategoryIds(); + if (!array_key_exists('kwixoCategory_id', $postData) + || !in_array( + $postData['kwixoCategory_id'], $kwixoCategories + ) + ) { + Mage::throwException( + $helper->__('Invalid kwixo category provided') + ); + } + } + + /** + * validates if the given array contains a proper category setting + * + * @param array $postData - the array to inspect + * + * @throws Mage_Core_Exception - if an invalid setting is given + */ + protected function validateCategoryExist(array $postData) + { + $helper = $this->getHelper(); + $isValid = true; + $message = ''; + if (!array_key_exists('category_id', $postData)) { + $isValid = false; + $message = $helper->__('Invalid category provided'); + + } + if ($isValid + && (!is_numeric($postData['category_id']) + || null === Mage::getModel('catalog/category')->load($postData['category_id'])->getId()) + ) { + $isValid = false; + $message = $helper->__('Invalid category provided'); + } + if (false === $isValid) { + Mage::throwException($message); + } + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/MobileDetect.php b/app/code/community/Netresearch/OPS/Helper/MobileDetect.php new file mode 100644 index 0000000..bf8ba70 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/MobileDetect.php @@ -0,0 +1,68 @@ + + * @license OSL 3.0 + */ +require_once Mage::getBaseDir('lib') . DS . 'MobileDetect' .DS. 'Mobile_Detect.php'; + +class Netresearch_OPS_Helper_MobileDetect extends Mage_Core_Helper_Abstract +{ + /** + * Computer device type string + */ + const DEVICE_TYPE_COMPUTER = 'Computer'; + /** + * mobile device type string + */ + const DEVICE_TYPE_MOBILE = 'Mobile'; + /** + * tablet device type string + */ + const DEVICE_TYPE_TABLET = 'Tablet'; + + /** + * @var Mobile_Detect + */ + private $_detector = null; + + /** + * create class instance + * + * Netresearch_OPS_Helper_MobileDetect constructor. + * @param null $headers + * @param null $userAgent + * @param null $detector + */ + public function __construct() + { + $this->_detector = new Mobile_Detect(); + } + + public function setDetector($detector) + { + $this->_detector = $detector; + } + + /** + * determine device type with help of mobile_detect lib and return it + * + * @return string + */ + public function getDeviceType() + { + $deviceType = self::DEVICE_TYPE_COMPUTER; + if ($this->_detector->isMobile()) { + $deviceType = self::DEVICE_TYPE_MOBILE; + } + + if ($this->_detector->isTablet()) { + $deviceType = self::DEVICE_TYPE_TABLET; + } + + return $deviceType; + } +} diff --git a/app/code/community/Netresearch/OPS/Helper/Order.php b/app/code/community/Netresearch/OPS/Helper/Order.php new file mode 100644 index 0000000..eeb7cfd --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Order.php @@ -0,0 +1,170 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Order extends Mage_Core_Helper_Abstract +{ + + const DELIMITER = '#'; + + /** @var $config Netresearch_OPS_Model_Config */ + protected $config = null; + + protected $statusMappingModel = null; + + /** + * @param Netresearch_OPS_Helper_Data $dataHelper + */ + public function setDataHelper(Netresearch_OPS_Helper_Data $dataHelper) + { + $this->dataHelper = $dataHelper; + } + + /** + * @return Netresearch_OPS_Helper_Data + */ + public function getDataHelper() + { + if (null === $this->dataHelper) { + $this->dataHelper = Mage::helper('ops/data'); + } + + return $this->dataHelper; + } + + protected $dataHelper = null; + + + /** + * return the config model + * + * @return Netresearch_OPS_Model_Config + */ + protected function getConfig() + { + if (null === $this->config) { + $this->config = Mage::getModel('ops/config'); + } + + return $this->config; + } + + /** + * generates the OPS order id in dependency to the config + * + * @param mixed $salesObject + * @param bool $useOrderIdIfPossible if false forces the usage of quoteid (for Kwixo pm etc.) + * + * @return string + */ + public function getOpsOrderId($salesObject, $useOrderIdIfPossible = true) + { + $config = $this->getConfig(); + $devPrefix = $config->getConfigData('devprefix'); + if ($salesObject instanceof Mage_Sales_Model_Order) { + /** @var $salesObject Mage_Sales_Model_Order */ + $orderRef = $salesObject->getQuoteId(); + } elseif ($salesObject instanceof Mage_Sales_Model_Quote) { + /** @var $salesObject Mage_Sales_Model_Quote */ + $orderRef = $salesObject->getId(); + } + + if ($config->getOrderReference($salesObject->getStoreId()) + == Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID + && $useOrderIdIfPossible === true + ) { + if ($salesObject instanceof Mage_Sales_Model_Quote) { + $salesObject->reserveOrderId(); + $orderRef = self::DELIMITER . $salesObject->getReservedOrderId(); + } elseif ($salesObject instanceof Mage_Sales_Model_Order) { + $orderRef = self::DELIMITER . $salesObject->getIncrementId(); + } + + } + + return $devPrefix . $orderRef; + } + + /** + * getting the order from opsOrderId which can either the quote id or the order increment id + * in both cases the dev prefix is stripped, if neccessary + * + * @param $opsOrderId + * + * @return Mage_Sales_Model_Order + */ + public function getOrder($opsOrderId) + { + $order = null; + $fieldToFilter = 'quote_id'; + $devPrefix = $this->getConfig()->getConfigData('devprefix'); + if ($devPrefix == substr($opsOrderId, 0, strlen($devPrefix))) { + $opsOrderId = substr($opsOrderId, strlen($devPrefix)); + } + // opsOrderId was created from order increment id, use increment id for filtering + if (0 === strpos($opsOrderId, self::DELIMITER)) { + $opsOrderId = substr($opsOrderId, strlen(self::DELIMITER)); + $fieldToFilter = 'increment_id'; + } + + /* @var $order Mage_Sales_Model_Resource_Order_Collection */ + $order = Mage::getModel('sales/order')->getCollection() + ->addFieldToFilter($fieldToFilter, $opsOrderId) + // filter for OPS payment methods + ->join(array('payment' => 'sales/order_payment'), 'main_table.entity_id=parent_id', 'method') + ->addFieldToFilter('method', array(array('like' => 'ops_%'))) + // sort by increment_id of order to get only the latest (relevant for quote id search) + ->addOrder('main_table.increment_id'); + + return $order->getFirstItem(); + } + + /** + * load and return the quote via the quoteId + * + * @param string $quoteId + * + * @return Mage_Model_Sales_Quote + */ + public function getQuote($quoteId) + { + return Mage::getModel('sales/quote')->load($quoteId); + } + + /** + * check if billing is same as shipping address + * + * @param Mage_Model_Sales_Order $order + * + * @return int + */ + public function checkIfAddressesAreSame(Mage_Sales_Model_Order $order) + { + $addMatch = 0; + $billingAddressHash = null; + $shippingAddressHash = null; + if ($order->getBillingAddress() instanceof Mage_Customer_Model_Address_Abstract) { + $billingAddressHash = Mage::helper('ops/alias')->generateAddressHash( + $order->getBillingAddress() + ); + } + if ($order->getShippingAddress() instanceof Mage_Customer_Model_Address_Abstract) { + $shippingAddressHash = Mage::helper('ops/alias')->generateAddressHash( + $order->getShippingAddress() + ); + } + + if ($billingAddressHash === $shippingAddressHash) { + $addMatch = 1; + } + + return $addMatch; + } + +} diff --git a/app/code/community/Netresearch/OPS/Helper/Order/Abstract.php b/app/code/community/Netresearch/OPS/Helper/Order/Abstract.php new file mode 100644 index 0000000..6387a82 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Order/Abstract.php @@ -0,0 +1,84 @@ +getBaseGrandTotalFromSalesObject($payment->getOrder())) * 100, + 0 + ); + $amount = round(($amount * 100), 0); + + if (abs($orderTotalAmount - $amount) <= 1) { + return 'full'; + } else { + return 'partial'; + } + } + + + /** + * checks if the amount captured/refunded is equal to the amount of the full order + * and returns the operation code accordingly + * + * @param Mage_Sales_Order_Payment $payment + * @param float $amount + * @return string operation code for the requested amount + * @see getPartialOperationCode() and getFullOperationCode() * + */ + public function determineOperationCode($payment, $amount) + { + $orderTotalAmount = round( + (Mage::helper('ops/payment')->getBaseGrandTotalFromSalesObject($payment->getOrder())) * 100, + 0 + ); + $totalProcessedAmount = round((($this->getPreviouslyProcessedAmount($payment) + $amount) * 100), 0); + + if (abs($orderTotalAmount - $totalProcessedAmount) <= 1 ) { + return $this->getFullOperationCode(); + } else { + return $this->getPartialOperationCode(); + } + + } + + /** + * Returns the Amount already processed for this kind of operation + * eg. getBaseAmountPaidOnline and getRefundedAmount + * + * @param Mage_Sales_Order_Payment $payment + * @return float amount already processed for this kind of operation + */ + protected abstract function getPreviouslyProcessedAmount($payment); + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Order/Capture.php b/app/code/community/Netresearch/OPS/Helper/Order/Capture.php new file mode 100644 index 0000000..afef199 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Order/Capture.php @@ -0,0 +1,79 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Order_Capture extends Netresearch_OPS_Helper_Order_Abstract +{ + protected function getFullOperationCode() + { + return Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_FULL; + } + + protected function getPartialOperationCode() + { + return Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_PARTIAL; + } + + protected function getPreviouslyProcessedAmount($payment) + { + return $payment->getBaseAmountPaidOnline(); + } + + + /** + * Prepare capture informations + * + * @param Mage_Sales_Order_Payment $payment + * @param float $amount + * @return array + */ + public function prepareOperation($payment, $amount) + { + $params = Mage::app()->getRequest()->getParams(); + if (array_key_exists('invoice', $params)) { + $arrInfo = $params['invoice']; + $arrInfo['amount'] = $amount; + } + $arrInfo['type'] = $this->determineType($payment, $amount); + $arrInfo['operation'] = $this->determineOperationCode($payment, $amount); + + return $arrInfo; + } + + /** + * Prepare shipment + * + * @param Mage_Sales_Model_Order_Invoice $invoice New invoice + * @param array $additionalData Array containing additional transaction data + * + * @return Mage_Sales_Model_Order_Shipment + */ + protected function _prepareShipment($invoice, $additionalData) + { + $savedQtys = $additionalData['items']; + $shipment = Mage::getModel('sales/service_order', $invoice->getOrder()) + ->prepareShipment($savedQtys); + if (!$shipment->getTotalQty()) { + return false; + } + + $shipment->register(); + if (array_key_exists('tracking', $additionalData) + && $additionalData['tracking'] + ) { + foreach ($additionalData['tracking'] as $data) { + $track = Mage::getModel('sales/order_shipment_track') + ->addData($data); + $shipment->addTrack($track); + } + } + + return $shipment; + } +} diff --git a/app/code/community/Netresearch/OPS/Helper/Order/Refund.php b/app/code/community/Netresearch/OPS/Helper/Order/Refund.php new file mode 100644 index 0000000..d417358 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Order/Refund.php @@ -0,0 +1,142 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Order_Refund extends Netresearch_OPS_Helper_Order_Abstract +{ + protected $payment; + protected $amount; + protected $params; + + protected function getFullOperationCode() + { + return Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL; + } + + protected function getPartialOperationCode() + { + return Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL; + } + + protected function getPreviouslyProcessedAmount($payment) + { + return $payment->getBaseAmountRefundedOnline(); + } + + + /** + * @param Varien_Object $payment + * @return $this + */ + public function setPayment(Varien_Object $payment) + { + $this->payment = $payment; + + return $this; + } + + /** + * @param $amount + * @return $this + */ + public function setAmount($amount) + { + $this->amount = $amount; + + return $this; + } + + /** + * @param array $params + * @return $this + */ + public function setCreditMemoRequestParams($params) + { + $this->params = $params; + + return $this; + } + + /** + * @return array params + */ + public function getCreditMemoRequestParams() + { + if (!is_array($this->params)) { + $this->setCreditMemoRequestParams(Mage::app()->getRequest()->getParams()); + } + + return $this->params; + } + + public function getInvoiceFromCreditMemoRequest() + { + $params = $this->getCreditMemoRequestParams(); + if (array_key_exists('invoice_id', $params)) { + return Mage::getModel('sales/order_invoice')->load($params['invoice_id']); + } + + return null; + } + + public function getCreditMemoFromRequest() + { + $params = $this->getCreditMemoRequestParams(); + if (array_key_exists('creditmemo', $params)) { + return $params['creditmemo']; + } + + return array(); + } + + /** + * @param $payment + * @param $amount + * @return mixed + */ + public function prepareOperation($payment, $amount) + { + $params = $this->getCreditMemoRequestParams(); + + if (array_key_exists('creditmemo', $params)) { + $arrInfo = $params['creditmemo']; + $arrInfo['amount'] = $amount; + } + $arrInfo['type'] = $this->determineType($payment, $amount); + $arrInfo['operation'] = $this->determineOperationCode($payment, $amount); + + if($arrInfo['type'] == 'full'){ + // hard overwrite operation code for last transaction + $arrInfo['operation'] = $this->getFullOperationCode(); + } + + + return $arrInfo; + } + + /** + * Checks for open refund transaction + * + * @param Mage_Sales_Model_Order_Payment $payment + * + * @return Mage_Sales_Model_Order_Payment_Transaction|null + */ + public function getOpenRefundTransaction($payment) + { + /** @var Mage_Sales_Model_Resource_Order_Payment_Transaction_Collection $refundTransactions */ + $refundTransactions = Mage::getModel('sales/order_payment_transaction')->getCollection(); + $transaction = $refundTransactions->addPaymentIdFilter($payment->getId()) + ->addTxnTypeFilter(Mage_Sales_Model_Order_Payment_Transaction::TYPE_REFUND) + ->setOrderFilter($payment->getOrder()) + ->addFieldToFilter('is_closed', 0) + ->getFirstItem(); + + return $transaction; + } +} diff --git a/app/code/community/Netresearch/OPS/Helper/Payment.php b/app/code/community/Netresearch/OPS/Helper/Payment.php new file mode 100644 index 0000000..3d57bce --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Payment.php @@ -0,0 +1,969 @@ + + * @author André Herrn + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Payment extends Mage_Core_Helper_Abstract +{ + protected $shaAlgorithm = null; + + /** + * Get checkout session namespace + * + * @return Mage_Checkout_Model_Session + */ + protected function _getCheckout() + { + return Mage::getSingleton('checkout/session'); + } + + /** + * Get checkout session namespace + * + * @return Netresearch_OPS_Model_Config + */ + protected function getConfig() + { + return Mage::getSingleton('ops/config'); + } + + /** + * Get encrypt / decrypt algorithm + * + * @return string + */ + public function getCryptMethod() + { + if (null === $this->shaAlgorithm) { + $this->shaAlgorithm = $this->getConfig()->getConfigData('secret_key_type'); + } + + return $this->shaAlgorithm; + } + + /** + * Crypt Data by SHA1 ctypting algorithm by secret key + * + * @param array $data + * @param string $key + * + * @return string hash + */ + public function shaCrypt($data, $key = '') + { + if (is_array($data)) { + return hash($this->getCryptMethod(), implode("", $data)); + } + if (is_string($data)) { + return hash($this->getCryptMethod(), $data); + } else { + return ""; + } + } + + /** + * Check hash crypted by SHA1 with existing data + * + * @param array $data + * @param string $hashFromOPS + * @param string $key + * + * @return bool + */ + public function shaCryptValidation($data, $hashFromOPS, $key = '') + { + if (is_array($data)) { + $data = implode("", $data); + } + + $hashUtf8 = strtoupper(hash($this->getCryptMethod(), $data)); + $hashNonUtf8 = strtoupper(hash($this->getCryptMethod(), utf8_encode($data))); + + $helper = Mage::helper('ops'); + $helper->log($helper->__("Module Secureset: %s", $data)); + + if ($this->compareHashes($hashFromOPS, $hashUtf8)) { + return true; + } else { + $helper->log($helper->__("Trying again with non-utf8 secureset")); + + return $this->compareHashes($hashFromOPS, $hashNonUtf8); + } + } + + protected function compareHashes($hashFromOPS, $actual) + { + $helper = Mage::helper('ops'); + $helper->log( + $helper->__( + "Checking hashes\nHashed String by Magento: %s\nHashed String by Ingenico ePayments: %s", + $actual, + $hashFromOPS + ) + ); + + if ($hashFromOPS == $actual) { + Mage::helper('ops')->log("Successful validation"); + + return true; + } + + return false; + } + + /** + * Return set of data which is ready for SHA crypt + * + * @param array $params + * @param string $SHAkey + * + * @return string + */ + public function getSHAInSet($params, $SHAkey) + { + $params = $this->prepareParamsAndSort($params); + $plainHashString = ""; + foreach ($params as $paramSet): + if ($paramSet['value'] == '' || $paramSet['key'] == 'SHASIGN' || is_array($paramSet['value'])) { + continue; + } + $plainHashString .= strtoupper($paramSet['key']) . "=" . $paramSet['value'] . $SHAkey; + endforeach; + + return $plainHashString; + } + + /** + * Return prepared and sorted array for SHA Signature Validation + * + * @param array $params + * + * @return string + */ + public function prepareParamsAndSort($params) + { + unset($params['CardNo']); + unset($params['Brand']); + unset($params['SHASign']); + + $params = array_change_key_case($params, CASE_UPPER); + + //PHP ksort takes care about "_", OPS not + $sortedParams = array(); + foreach ($params as $key => $value): + $sortedParams[str_replace("_", "", $key)] = array('key' => $key, 'value' => $value); + endforeach; + ksort($sortedParams); + + return $sortedParams; + } + + /* + * Get SHA-1-IN hash for ops-authentification + * + * All Parameters have to be alphabetically, UPPERCASE + * Empty Parameters shouldn't appear in the secure string + * + * @param array $formFields + * @param string $shaCode + * + * @return string + */ + public function getSHASign($formFields, $shaCode = null, $storeId = null) + { + if (null === $shaCode) { + $shaCode = Mage::getModel('ops/config')->getShaOutCode($storeId); + } + $formFields = array_change_key_case($formFields, CASE_UPPER); + uksort($formFields, 'strnatcasecmp'); + $plainHashString = ''; + foreach ($formFields as $formKey => $formVal) { + if (null === $formVal || '' === $formVal || $formKey == 'SHASIGN') { + continue; + } + $plainHashString .= strtoupper($formKey) . '=' . $formVal . $shaCode; + } + + return $plainHashString; + } + + /** + * @param int $opsOrderId + * @param int $storeId + * + * @return array + */ + public function validateOrderForReuse($opsOrderId, $storeId) + { + + return array( + 'orderID' => $opsOrderId, + 'SHASIGN' => strtoupper( + $this->shaCrypt( + $this->getSHAInSet( + array('orderId' => $opsOrderId), + $this->getConfig()->getShaOutCode($storeId) + ) + ) + ), + ); + } + + /** + * We get some CC info from ops, so we must save it + * + * @param Mage_Sales_Model_Order $order + * @param array $ccInfo + * + * @return $this + */ + public function _prepareCCInfo($order, $ccInfo) + { + if (isset($ccInfo['CN'])) { + $order->getPayment()->setCcOwner($ccInfo['CN']); + } + + if (isset($ccInfo['CARDNO'])) { + $order->getPayment()->setCcNumberEnc($ccInfo['CARDNO']); + $order->getPayment()->setCcLast4(substr($ccInfo['CARDNO'], -4)); + } + + if (isset($ccInfo['ED'])) { + $order->getPayment()->setCcExpMonth(substr($ccInfo['ED'], 0, 2)); + $order->getPayment()->setCcExpYear(substr($ccInfo['ED'], 2, 2)); + } + + return $this; + } + + public function isPaymentAccepted($status) + { + return in_array( + $status, array( + Netresearch_OPS_Model_Status::AUTHORIZED, + Netresearch_OPS_Model_Status::AUTHORIZATION_WAITING, + Netresearch_OPS_Model_Status::AUTHORIZED_UNKNOWN, + Netresearch_OPS_Model_Status::WAITING_CLIENT_PAYMENT, + Netresearch_OPS_Model_Status::PAYMENT_REQUESTED, + Netresearch_OPS_Model_Status::PAYMENT_PROCESSING, + Netresearch_OPS_Model_Status::PAYMENT_UNCERTAIN, + Netresearch_OPS_Model_Status::WAITING_FOR_IDENTIFICATION + ) + ); + } + + public function isPaymentAuthorizeType($status) + { + return in_array( + $status, array( + Netresearch_OPS_Model_Status::AUTHORIZED, + Netresearch_OPS_Model_Status::AUTHORIZATION_WAITING, + Netresearch_OPS_Model_Status::AUTHORIZED_UNKNOWN, + Netresearch_OPS_Model_Status::WAITING_CLIENT_PAYMENT + ) + ); + } + + public function isPaymentCaptureType($status) + { + return in_array( + $status, array( + Netresearch_OPS_Model_Status::PAYMENT_REQUESTED, + Netresearch_OPS_Model_Status::PAYMENT_PROCESSING, + Netresearch_OPS_Model_Status::PAYMENT_UNCERTAIN + ) + ); + } + + public function isPaymentFailed($status) + { + return false == $this->isPaymentAccepted($status); + } + + /** + * apply ops state for order + * + * @param Mage_Sales_Model_Order $order Order + * @param array $params Request params + * + * @return string + */ + public function applyStateForOrder($order, $params) + { + + Mage::getModel('ops/response_handler')->processResponse($params, $order->getPayment()->getMethodInstance()); + $order->getPayment()->save(); + + $feedbackStatus = ''; + + switch ($params['STATUS']) { + case Netresearch_OPS_Model_Status::WAITING_FOR_IDENTIFICATION : //3D-Secure + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT; + break; + case Netresearch_OPS_Model_Status::AUTHORIZED: + case Netresearch_OPS_Model_Status::AUTHORIZED_WAITING_EXTERNAL_RESULT: + case Netresearch_OPS_Model_Status::AUTHORIZATION_WAITING: + case Netresearch_OPS_Model_Status::WAITING_CLIENT_PAYMENT: + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT; + break; + case Netresearch_OPS_Model_Status::PAYMENT_REQUESTED: + case Netresearch_OPS_Model_Status::PAYMENT_PROCESSING: + case Netresearch_OPS_Model_Status::PAYMENT_PROCESSED_BY_MERCHANT: + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT; + break; + case Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED: + case Netresearch_OPS_Model_Status::PAYMENT_REFUSED: + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_DECLINE; + break; + case Netresearch_OPS_Model_Status::CANCELED_BY_CUSTOMER: + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_CANCEL; + break; + default: + //all unknown transaction will accept as exceptional + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_EXCEPTION; + } + + return $feedbackStatus; + } + + /** + * Process success action by accept url + * + * + * @param $order + * @param $params + * @param int $instantCapture + * @throws Exception + */ + public function acceptOrder($order, $params, $instantCapture = 0) + { + $this->_getCheckout()->setLastSuccessQuoteId($order->getQuoteId()); + $this->_prepareCCInfo($order, $params); + $this->setPaymentTransactionInformation($order->getPayment(), $params, 'accept'); + $this->setFraudDetectionParameters($order->getPayment(), $params); + + if ($transaction = Mage::helper('ops/payment')->getTransactionByTransactionId($order->getQuoteId())) { + $transaction->setTxnId($params['PAYID'])->save(); + } + + try { + if (false === $this->forceAuthorize($order) + && ($this->getConfig()->getConfigData('payment_action') + == Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE + || $instantCapture) + && $params['STATUS'] != Netresearch_OPS_Model_Status::WAITING_CLIENT_PAYMENT + ) { + $this->_processDirectSale($order, $params, $instantCapture); + } else { + $this->_processAuthorize($order, $params); + } + } catch (Exception $e) { + $this->_getCheckout()->addError(Mage::helper('ops')->__('Order can not be saved.')); + throw $e; + } + } + + /** + * Set Payment Transaction Information + * + * @param Mage_Sales_Model_Order_Payment $payment Sales Payment Model + * @param array $params Request params + * @param string $action Action (accept|cancel|decline|wait|exception) + */ + protected function setPaymentTransactionInformation(Mage_Sales_Model_Order_Payment $payment, $params, $action) + { + $payment->setTransactionId($params['PAYID']); + $code = $payment->getMethodInstance()->getCode(); + + $isInline = false; + + /* In authorize mode we still have no authorization transaction for CC and DirectDebit payments, + * so capture or cancel won't work. So we need to create a new authorization transaction for them + * when a payment was accepted by Ingenico ePayments + * + * In exception-case we create the authorization-transaction too + * because some exception-cases can turn into accepted + */ + if (('accept' === $action || 'exception' === $action) + && in_array($code, array('ops_cc', 'ops_directDebit')) + ) { + $payment->setIsTransactionClosed(false); + $isInline = $this->isInlinePayment($payment); + /* create authorization transaction for non-inline pms */ + if (false === $isInline + || (array_key_exists('HTML_ANSWER', $params) + || 0 < strlen( + $payment->getAdditionalInformation('HTML_ANSWER') + )) + ) { + $payment->addTransaction("authorization", null, true, $this->__("Process outgoing transaction")); + } + $payment->setLastTransId($params['PAYID']); + } + + /* Ingenico ePayments sends parameter HTML_ANSWER to trigger 3D secure redirection */ + if (isset($params['HTML_ANSWER']) && ('ops_cc' == $code)) { + $payment->setAdditionalInformation('HTML_ANSWER', $params['HTML_ANSWER']); + $payment->setIsTransactionPending(true); + } + + $payment->setAdditionalInformation('paymentId', $params['PAYID']); + $payment->setAdditionalInformation('status', $params['STATUS']); + if (array_key_exists('ACCEPTANCE', $params) && 0 < strlen(trim($params['ACCEPTANCE']))) { + $payment->setAdditionalInformation('acceptance', $params['ACCEPTANCE']); + } + if (array_key_exists('BRAND', $params) && ('ops_cc' == $code) && 0 < strlen(trim($params['BRAND']))) { + $payment->setAdditionalInformation('CC_BRAND', $params['BRAND']); + } + if (false === $isInline || array_key_exists('HTML_ANSWER', $params)) { + $payment->setIsTransactionClosed(true); + } + $payment->setDataChanges(true); + $payment->save(); + } + + /** + * add fraud detection of Ingenico ePayments to additional payment data + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param array $params + */ + protected function setFraudDetectionParameters($payment, $params) + { + $params = array_change_key_case($params, CASE_UPPER); + if (array_key_exists('SCORING', $params)) { + $payment->setAdditionalInformation('scoring', $params['SCORING']); + } + if (array_key_exists('SCO_CATEGORY', $params)) { + $payment->setAdditionalInformation('scoringCategory', $params['SCO_CATEGORY']); + } + $additionalScoringData = array(); + foreach ($this->getConfig()->getAdditionalScoringKeys() as $key) { + if (array_key_exists($key, $params)) { + if (false === mb_detect_encoding($params[$key], 'UTF-8', true)) { + $additionalScoringData[$key] = utf8_encode($params[$key]); + } else { + $additionalScoringData[$key] = $params[$key]; + } + } + } + $payment->setAdditionalInformation('additionalScoringData', $additionalScoringData); + } + + + + + + /** + * Get Payment Exception Message + * + * @param $ops_status + * @return string + */ + protected function getPaymentExceptionMessage($ops_status) + { + $exceptionMessage = ''; + switch ($ops_status) { + case Netresearch_OPS_Model_Status::PAYMENT_UNCERTAIN : + $exceptionMessage = Mage::helper('ops')->__( + 'A technical problem arose during payment process, giving unpredictable result. Ingenico ePayments status: %s.', + Mage::helper('ops')->getStatusText($ops_status) + ); + break; + default: + $exceptionMessage = Mage::helper('ops')->__( + 'An unknown exception was thrown in the payment process. Ingenico ePayments status: %s.', + Mage::helper('ops')->getStatusText($ops_status) + ); + } + + return $exceptionMessage; + } + + + /** + * send invoice to customer if that was configured by the merchant + * + * @param Mage_Sales_Model_Order_Invoice $invoice Invoice to be sent + * + * @return void + */ + public function sendInvoiceToCustomer(Mage_Sales_Model_Order_Invoice $invoice) + { + if (false == $invoice->getEmailSent() + && $this->getConfig()->getSendInvoice() + ) { + $invoice->sendEmail(true); + } + } + + /** + * Process Configured Payment Actions: Authorized, Default operation + * just place order + * + * @param Mage_Sales_Model_Order $order Order + * @param array $params Request params + */ + protected function _processAuthorize($order, $params) + { + $status = $params['STATUS']; + if ($status == Netresearch_OPS_Model_Status::WAITING_CLIENT_PAYMENT) { + $order->setState( + Mage_Sales_Model_Order::STATE_PROCESSING, + Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, + Mage::helper('ops')->__( + 'Waiting for payment. Ingenico ePayments status: %s.', Mage::helper('ops')->getStatusText($status) + ) + ); + + // send new order mail for bank transfer, since it is 'successfully' authorized at this point + if ($order->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_BankTransfer + && $order->getEmailSent() != 1 + ) { + $order->sendNewOrderEmail(); + } + } elseif ($status == Netresearch_OPS_Model_Status::AUTHORIZATION_WAITING) { + $order->setState( + Mage_Sales_Model_Order::STATE_PROCESSING, + Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, + Mage::helper('ops')->__( + 'Authorization uncertain. Ingenico ePayments status: %s.', Mage::helper('ops')->getStatusText($status) + ) + ); + } else { + // for 3DS payments the order has to be retrieved from the payment review step + if ($this->isInlinePayment($order->getPayment()) + && 0 < strlen(trim($order->getPayment()->getAdditionalInformation('HTML_ANSWER'))) + && $order->getPayment()->getAdditionalInformation('status') == Netresearch_OPS_Model_Status::AUTHORIZED + ) { + + $order->getPayment()->setIsTransactionApproved(true)->registerPaymentReviewAction( + Mage_Sales_Model_Order_Payment::REVIEW_ACTION_UPDATE, true + )->save(); + } + if ($this->isRedirectPaymentMethod($order) === true + && $order->getEmailSent() != 1 + ) { + $order->sendNewOrderEmail(); + } + + if (!$this->isPaypalSpecialStatus($order->getPayment()->getMethodInstance(), $status)) { + + $payId = $params['PAYID']; + $order->setState( + Mage_Sales_Model_Order::STATE_PROCESSING, + Mage_Sales_Model_Order::STATE_PROCESSING, + Mage::helper('ops')->__( + 'Processed by Ingenico ePayments. Payment ID: %s. Ingenico ePayments status: %s.', $payId, + Mage::helper('ops')->getStatusText($status) + ) + ); + } + } + $order->save(); + } + + /** + * Special status handling for Paypal and status 91 + * + * @param $pm + * @param $status + * + * @return bool + */ + protected function isPaypalSpecialStatus($pm, $status) + { + return $pm instanceof Netresearch_OPS_Model_Payment_Paypal + && $status == Netresearch_OPS_Model_Status::PAYMENT_PROCESSING; + } + + /** + * Fetches transaction with given transaction id + * + * @param string $transactionId + * + * @return mixed Mage_Sales_Model_Order_Payment_Transaction | boolean + */ + public function getTransactionByTransactionId($transactionId) + { + if (!$transactionId) { + return false; + } + $transaction = Mage::getModel('sales/order_payment_transaction') + ->getCollection() + ->addAttributeToFilter('txn_id', $transactionId) + ->getLastItem(); + if (null === $transaction->getId()) { + return false; + } + $transaction->getOrderPaymentObject(); + + return $transaction; + } + + /** + * refill cart + * + * @param Mage_Sales_Model_Order $order + * + * @return void + */ + public function refillCart($order) + { + // add items + $cart = Mage::getSingleton('checkout/cart'); + + if (0 < $cart->getQuote()->getItemsCollection()->getSize()) { + //cart is not empty, so refilling it is not required + return; + } + foreach ($order->getItemsCollection() as $item) { + try { + $cart->addOrderItem($item); + } catch (Exception $e) { + Mage::logException($e); + } + } + $cart->save(); + + // add coupon code + $coupon = $order->getCouponCode(); + $session = Mage::getSingleton('checkout/session'); + if (null != $coupon) { + $session->getQuote()->setCouponCode($coupon)->save(); + } + } + + /** + * Save OPS Status to Payment + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param array $params OPS-Response + * + * @return void + */ + public function saveOpsStatusToPayment(Mage_Sales_Model_Order_Payment $payment, $params) + { + $payment + ->setAdditionalInformation('status', $params['STATUS']) + ->save(); + } + + /** + * Check is payment method is a redirect method + * + * @param $order + * @return bool + */ + protected function isRedirectPaymentMethod($order) + { + $result = false; + $method = $order->getPayment()->getMethodInstance(); + if ($method + && $method->getOrderPlaceRedirectUrl() != '' // Magento returns '' + && $method->getOrderPlaceRedirectUrl() !== false // Ops returns false + ) { + $result = true; + } + + return $result; + } + + public function getQuote() + { + return $this->_getCheckout()->getQuote(); + } + + /** + * sets the state to pending payment if neccessary (order is in state new) + * and adds a comment to status history + * + * @param $order - the order + */ + public function handleUnknownStatus($order) + { + if ($order instanceof Mage_Sales_Model_Order) { + $message = Mage::helper('ops')->__( + 'Unknown Ingenico ePayments state for this order. Please check Ingenico ePayments backend for this order.' + ); + if ($order->getState() == Mage_Sales_Model_Order::STATE_NEW) { + $order->setState( + Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, + Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, + $message + ); + } else { + $order->addStatusHistoryComment($message); + } + $order->save(); + } + } + + /** + * returns the base grand total from either a quote or an order + * + * @param $salesObject + * + * @return double the base amount of the order + * @throws Exception if $salesObject is not a quote or an order + */ + public function getBaseGrandTotalFromSalesObject($salesObject) + { + if (!($salesObject instanceof Mage_Sales_Model_Order || $salesObject instanceof Mage_Sales_Model_Quote)) { + Mage::throwException('$salesObject is not a quote or an order instance'); + } + + return $salesObject->getBaseGrandTotal(); + } + + + /** + * Save the last used refund operation code to payment + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param string $operationCode + * + * @return void + */ + public function saveOpsRefundOperationCodeToPayment(Mage_Sales_Model_Order_Payment $payment, $operationCode) + { + if (in_array( + strtoupper(trim($operationCode)), + array( + Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, + Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL + ) + ) + ) { + Mage::helper('ops/data')->log( + sprintf( + "set last refund operation '%s' code to payment for order '%s'", + $operationCode, + $payment->getOrder()->getIncrementId() + ) + ); + $payment + ->setAdditionalInformation('lastRefundOperationCode', $operationCode) + ->save(); + } + } + + /** + * sets the canRefund information depending on the last refund operation code + * + * @param Mage_Sales_Model_Order_Payment $payment + */ + public function setCanRefundToPayment(Mage_Sales_Model_Order_Payment $payment) + { + $refundOperationCode = $payment->getAdditionalInformation('lastRefundOperationCode'); + if (in_array( + strtoupper(trim($refundOperationCode)), + array( + Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, + Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL + ) + ) + ) { + /* + * a further refund is possible if the transaction remains open, that means either the merchant + * did not close the transaction or the refunded amount is less than the orders amount + */ + $canRefund = ($refundOperationCode == Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL); + Mage::helper('ops/data')->log( + sprintf( + "set canRefund to '%s' for payment of order '%s'", + var_export($canRefund, true), + $payment->getOrder()->getIncrementId() + ) + ); + $payment + ->setAdditionalInformation('canRefund', $canRefund) + ->save(); + } + } + + /** + * determine whether the payment supports only authorize or not + * + * @param Mage_Sales_Model_Order $order + * + * @return true . if so, false otherwise + */ + protected function forceAuthorize(Mage_Sales_Model_Order $order) + { + return ($order->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_Kwixo_Abstract); + } + + + /** + * add ops_cc payment to checkout methods if quote total is zero and zero amount checkout is activated + * + * @param Mage_Payment_Block_Form_Container $block + * + * @return $this + */ + public function addCCForZeroAmountCheckout(Mage_Payment_Block_Form_Container $block) + { + $methods = $block->getMethods(); + if (false === $this->checkIfCCisInCheckoutMethods($methods)) { + $ccPayment = Mage::getModel('ops/payment_cc'); + if ($ccPayment->getFeatureModel()->isCCAndZeroAmountAuthAllowed($ccPayment, $block->getQuote())) { + $ccPayment->setInfoInstance($block->getQuote()->getPayment()); + $methods[] = $ccPayment; + $block->setData('methods', $methods); + } + } + + return $this; + } + + + /** + * check if ops_cc is in payment methods array + * + * @param $methods + * + * @return array + */ + protected function checkIfCCisInCheckoutMethods($methods) + { + $result = false; + foreach ($methods as $method) { + if ($method->getCode() == 'ops_cc') { + $result = true; + break; + } + } + + return $result; + } + + /** + * checks if the payment method can use order's increment id as merchant's reference + * + * @param Mage_Payment_Model_Info $payment + * + * @return bool + */ + public function isInlinePaymentWithOrderId(Mage_Payment_Model_Info $payment) + { + return $this->isInlinePayment($payment) && $this->getConfig()->getInlineOrderReference() == Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID; + } + + /** + * checks if the payment method can pbe processed via direct link + * + * @param Mage_Payment_Model_Info $payment + * + * @return bool + */ + public function isInlinePayment(Mage_Payment_Model_Info $payment) + { + $result = false; + $methodInstance = $payment->getMethodInstance(); + if ($methodInstance instanceof Netresearch_OPS_Model_Payment_DirectDebit + || ($methodInstance instanceof Netresearch_OPS_Model_Payment_Cc + && ($methodInstance->hasBrandAliasInterfaceSupport($payment) + || Mage::helper('ops/data')->isAdminSession())) + ) { + $result = true; + } + + return $result; + } + + /** + * checks if the inline payment can use quote id as merchant's reference + * + * @param Mage_Payment_Model_Info $payment + * + * @return bool + */ + public function isInlinePaymentWithQuoteId(Mage_Payment_Model_Info $payment) + { + return $this->isInlinePayment($payment) + && (0 === strlen( + trim($payment->getMethodInstance()->getConfigPaymentAction()) + )); + } + + /** + * sets the invoices of an order to paid + * + * @param Mage_Sales_Model_Order $order + * + * @return Netresearch_OPS_Helper_Payment + */ + public function setInvoicesToPaid($order) + { + /** @var $invoice Mage_Sales_Model_Order_Invoice */ + foreach ($order->getInvoiceCollection() as $invoice) { + $invoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE); + $invoice->setState(Mage_Sales_Model_Order_Invoice::STATE_PAID); + } + $order->getInvoiceCollection()->save(); + + return $this; + } + + /** + * cancel all invoices for a given order + * + * @param $order + * + * @return Netresearch_OPS_Helper_Payment + * @throws Exception + */ + public function cancelInvoices($order) + { + /** @var $invoice Mage_Sales_Model_Order_Invoice */ + foreach ($order->getInvoiceCollection() as $invoice) { + $invoice->cancel(); + $invoice->save(); + } + + return $this; + } + + /** + * Returns if the current payment status is an invalid one, namely if it is one of the following: + * Netresearch_OPS_Model_Payment_Abstract::INVALID_INCOMPLETE, + * Netresearch_OPS_Model_Payment_Abstract::CANCELED_BY_CUSTOMER, + * Netresearch_OPS_Model_Payment_Abstract::AUTHORISATION_DECLINED, + * + * @param $status + * + * @return bool + */ + public function isPaymentInvalid($status) + { + return in_array( + $status, + array( + Netresearch_OPS_Model_Status::INVALID_INCOMPLETE, + Netresearch_OPS_Model_Status::CANCELED_BY_CUSTOMER, + Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED, + ) + ); + } + + /** + * @param $paymentCode + * + * @return string + */ + public function getPaymentDefaultLogo($paymentCode) + { + return Mage::getSingleton('core/design_package')->getSkinUrl( + 'images/ops/logos/' . $paymentCode . '.png', + array('_area' => 'frontend') + ); + } + +} diff --git a/app/code/community/Netresearch/OPS/Helper/Payment/DirectLink/Request.php b/app/code/community/Netresearch/OPS/Helper/Payment/DirectLink/Request.php new file mode 100644 index 0000000..8928186 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Payment/DirectLink/Request.php @@ -0,0 +1,333 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + + +abstract class Netresearch_OPS_Helper_Payment_DirectLink_Request + implements Netresearch_OPS_Helper_Payment_DirectLink_RequestInterface +{ + + protected $dataHelper = null; + + protected $quoteHelper = null; + + protected $orderHelper = null; + + protected $customerHelper = null; + + protected $validator = null; + + protected $requestHelper = null; + + protected $config = null; + + /** + * @param null $config + */ + public function setConfig($config) + { + $this->config = $config; + } + + /** + * @return Netresearch_OPS_Model_Config + */ + public function getConfig() + { + if (null === $this->config) { + $this->config = Mage::getModel('ops/config'); + } + return $this->config; + } + + public function setRequestHelper(Netresearch_OPS_Helper_Payment_Request $requestHelper) + { + $this->requestHelper = $requestHelper; + } + + /** + * @return Netresearch_OPS_Helper_Payment_Request + */ + public function getRequestHelper() + { + if (null === $this->requestHelper) { + $this->requestHelper = Mage::helper('ops/payment_request'); + $this->requestHelper->setConfig($this->getConfig()); + } + + return $this->requestHelper; + } + + /** + * sets the data helper + * + * @param Netresearch_OPS_Helper_Data $dataHelper + */ + public function setDataHelper(Netresearch_OPS_Helper_Data $dataHelper) + { + $this->dataHelper = $dataHelper; + } + + /** + * gets the data helper + * + * @return Mage_Core_Helper_Abstract + */ + public function getDataHelper() + { + if (null === $this->dataHelper) { + $this->dataHelper = Mage::helper('ops/data'); + } + + return $this->dataHelper; + } + + /** + * sets the quote helper + * + * @param Netresearch_OPS_Helper_Quote $quoteHelper + */ + public function setQuoteHelper(Netresearch_OPS_Helper_Quote $quoteHelper) + { + $this->quoteHelper = $quoteHelper; + } + + /** + * gets the quote helper + * + * @return Mage_Core_Helper_Abstract + */ + public function getQuoteHelper() + { + if (null === $this->quoteHelper) { + $this->quoteHelper = Mage::helper('ops/quote'); + } + + return $this->quoteHelper; + } + + /** + * sets the order helper + * + * @param Netresearch_OPS_Helper_Order $orderHelper + */ + public function setOrderHelper(Netresearch_OPS_Helper_Order $orderHelper) + { + $this->orderHelper = $orderHelper; + } + + /** + * gets the order helper + * + * @return Netresearch_OPS_Helper_Order + */ + public function getOrderHelper() + { + if (null === $this->orderHelper) { + $this->orderHelper = Mage::helper('ops/order'); + } + + return $this->orderHelper; + } + + /** + * sets the customer helper + * + * @param Mage_Customer_Helper_Data $customerHelper + */ + public function setCustomerHelper(Mage_Core_Helper_Abstract $customerHelper) + { + $this->customerHelper = $customerHelper; + } + + /** + * gets the customer helper + * + * @return Mage_Customer_Helper_Data + */ + public function getCustomerHelper() + { + if (null === $this->customerHelper) { + $this->customerHelper = Mage::helper('customer/data'); + } + + return $this->customerHelper; + } + + + /** + * extracts the parameter for the direct link request from the quote, + * order and, optionally from existing request params + * + * @param Mage_Sales_Model_Quote $quote + * @param Mage_Sales_Model_Order $order + * @param array $requestParams + * + * @return array - the parameters for the direct link request + */ + public function getDirectLinkRequestParams( + Mage_Sales_Model_Quote $quote, Mage_Sales_Model_Order $order, $requestParams = array()) + { + $billingAddress = $order->getBillingAddress(); + $shippingAddress = $this->getShippingAddress($order, $billingAddress); + $requestParams = $this->getBaseRequestParams($quote, $order, $billingAddress); + $requestParams = array_merge($requestParams, $this->getPaymentSpecificParams($quote)); + + if ($this->getConfig()->canSubmitExtraParameter($quote->getStoreId())) { + $shipToParams = $this->getRequestHelper()->extractShipToParameters($shippingAddress, $quote); + $shipToParams = $this->decodeParamsForDirectLinkCall($shipToParams); + $requestParams = array_merge($requestParams, $shipToParams); + } + + $requestParams = $this->addCustomerSpecificParams($requestParams); + $requestParams = $this->addParamsForAdminPayments($requestParams); + + return $requestParams; + } + + /** + * specail handling like validation and so on for admin payments + * + * @param Mage_Sales_Model_Quote $quote + * @param array $requestParams + * + * @return mixed + */ + abstract public function handleAdminPayment(Mage_Sales_Model_Quote $quote, $requestParams); + + /** + * extracts payment specific payment parameters + * + * @param Mage_Sales_Model_Quote $quote + * + * @return array + */ + abstract protected function getPaymentSpecificParams(Mage_Sales_Model_Quote $quote); + + /** + * gets the shipping address if there is one, otherwise the billing address is used as shipping address + * + * @param $order + * @param $billingAddress + * + * @return mixed + */ + protected function getShippingAddress(Mage_Sales_Model_Order $order, $billingAddress) + { + $shippingAddress = $order->getShippingAddress(); + if (null === $shippingAddress || false === $shippingAddress) { + $shippingAddress = $billingAddress; + } + return $shippingAddress; + } + + /** + * utf8 decode for direct link calls + * + * @param array $requestParams + * + * @return array - the decoded array + */ + protected function decodeParamsForDirectLinkCall(array $requestParams) + { + foreach ($requestParams as $key => $value) { + $requestParams[$key] = utf8_decode($value); + } + return $requestParams; + } + + /** + * @param $requestParams + * + * @return mixed + */ + protected function addCustomerSpecificParams($requestParams) + { + if ($this->getCustomerHelper()->isLoggedIn()) { + $requestParams['CUID'] = $this->getCustomerHelper()->getCustomer()->getId(); + } + return $requestParams; + } + + /** + * @param $requestParams + * + * @return mixed + */ + protected function addParamsForAdminPayments($requestParams) + { + if ($this->getDataHelper()->isAdminSession()) { + $requestParams['ECI'] = Netresearch_OPS_Model_Eci_Values::MANUALLY_KEYED_FROM_MOTO; + $requestParams['REMOTE_ADDR'] = 'NONE'; + } + + return $requestParams; + } + + /** + * @param $quote + * @param $order + * @param $billingAddress + * + * @return array + */ + protected function getBaseRequestParams($quote, $order, $billingAddress) + { + $merchantRef = $this->getOrderHelper()->getOpsOrderId($order, $this->canUseOrderId($quote->getPayment())); + $requestParams = array( + 'AMOUNT' => $this->getDataHelper()->getAmount($quote->getBaseGrandTotal()), + 'CURRENCY' => $this->getQuoteHelper()->getQuoteCurrency($quote), + 'OPERATION' => $this->getQuoteHelper()->getPaymentAction($quote), + 'ORDERID' => $merchantRef, + 'ORIG' => $this->getDataHelper()->getModuleVersionString(), + 'EMAIL' => $order->getCustomerEmail(), + 'REMOTE_ADDR' => $quote->getRemoteIp(), + 'RTIMEOUT' => $this->getConfig()->getTransActionTimeout() + ); + + $ownerParams = $this->getOwnerParams($quote, $billingAddress, $requestParams); + $requestParams = array_merge($requestParams, $ownerParams); + $requestParams['ADDMATCH'] = $this->getOrderHelper()->checkIfAddressesAreSame($order); + + return $requestParams; + } + + /** + * @param $quote + * @param $billingAddress + * @param $requestParams + * + * @return array + */ + protected function getOwnerParams($quote, $billingAddress, $requestParams) + { + $ownerParams = $this->getRequestHelper()->getOwnerParams($billingAddress, $quote); + if (array_key_exists('OWNERADDRESS', $ownerParams) && array_key_exists('OWNERTOWN', $ownerParams)) { + $ownerAddrParams = $this->decodeParamsForDirectLinkCall( + array('OWNERADDRESS' => $ownerParams['OWNERADDRESS'], 'OWNERTOWN' => $ownerParams['OWNERTOWN']) + ); + $ownerParams = array_merge($ownerParams, $ownerAddrParams); + } + + return $ownerParams; + } + + /** + * @return bool + */ + public function canUseOrderId(Varien_Object $payment) + { + $methodInstance = $payment->getMethodInstance(); + return + $this->getConfig()->getInlineOrderReference() == Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID + && $methodInstance instanceof Netresearch_OPS_Model_Payment_DirectLink; + } + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Payment/DirectLink/RequestInterface.php b/app/code/community/Netresearch/OPS/Helper/Payment/DirectLink/RequestInterface.php new file mode 100644 index 0000000..ab196b7 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Payment/DirectLink/RequestInterface.php @@ -0,0 +1,115 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * @author Michael Lühr + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +interface Netresearch_OPS_Helper_Payment_DirectLink_RequestInterface +{ + /** + * sets the data helper + * + * @param Netresearch_OPS_Helper_Data $dataHelper + */ + public function setDataHelper(Netresearch_OPS_Helper_Data $dataHelper); + + /** + * @return Netresearch_OPS_Model_Config + */ + public function getConfig(); + + /** + * sets the quote helper + * + * @param Netresearch_OPS_Helper_Quote $quoteHelper + */ + public function setQuoteHelper(Netresearch_OPS_Helper_Quote $quoteHelper); + + /** + * extracts the parameter for the direct link request from the quote, order and, optionally from existing request params + * + * @param Mage_Sales_Model_Quote $quote + * @param Mage_Sales_Model_Order $order + * @param array $requestParams + * + * @return array - the parameters for the direct link request + */ + public function getDirectLinkRequestParams( + Mage_Sales_Model_Quote $quote, + Mage_Sales_Model_Order $order, + $requestParams = array() + ); + + /** + * @param null $config + */ + public function setConfig($config); + + /** + * sets the order helper + * + * @param Netresearch_OPS_Helper_Order $orderHelper + */ + public function setOrderHelper(Netresearch_OPS_Helper_Order $orderHelper); + + /** + * sets the customer helper + * + * @param Mage_Core_Helper_Abstract $customerHelper + */ + public function setCustomerHelper(Mage_Core_Helper_Abstract $customerHelper); + + public function setRequestHelper(Netresearch_OPS_Helper_Payment_Request $requestHelper); + + /** + * gets the customer helper + * + * @return Mage_Core_Helper_Abstract + */ + public function getCustomerHelper(); + + /** + * gets the order helper + * + * @return Mage_Core_Helper_Abstract + */ + public function getOrderHelper(); + + /** + * gets the data helper + * + * @return Mage_Core_Helper_Abstract + */ + public function getDataHelper(); + + /** + * @return Netresearch_OPS_Helper_Payment_Request + */ + public function getRequestHelper(); + + /** + * special handling like validation and so on for admin payments + * + * @param Mage_Sales_Model_Quote $quote + * @param $requestParams + * + * @return mixed + */ + public function handleAdminPayment(Mage_Sales_Model_Quote $quote, $requestParams); + + /** + * gets the quote helper + * + * @return Mage_Core_Helper_Abstract + */ + public function getQuoteHelper(); +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Payment/Request.php b/app/code/community/Netresearch/OPS/Helper/Payment/Request.php new file mode 100644 index 0000000..a0b6a79 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Payment/Request.php @@ -0,0 +1,738 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Helper_Payment_Request +{ + protected $config = null; + + /** + * @param Netresearch_OPS_Model_Config $config + */ + public function setConfig($config) + { + $this->config = $config; + } + + /** + * @return Netresearch_OPS_Model_Config + */ + public function getConfig() + { + if ($this->config === null) { + $this->config = Mage::getModel('ops/config'); + } + + return $this->config; + } + + /** + * extracts the ship to information from a given address + * + * @param Mage_Customer_Model_Address_Abstract $address + * @param Mage_Sales_Model_Quote || Mage_Sales_Model_Order $salesObject + * + * @return array - the parameters containing the ship to data + */ + public function extractShipToParameters($address, $salesObject = null) + { + $paramValues = array(); + + if (!$address instanceof Mage_Customer_Model_Address_Abstract) { + // virtual carts may not have a shipping address - so fall back to billing + if (!is_null($salesObject) && $salesObject->getShippingAddress()) { + $address = $salesObject->getShippingAddress(); + } else { + $address = $salesObject->getBillingAddress(); + } + + if (!$address) { + return $paramValues; + } + } + + $shippingStreet = str_replace("\n", ' ', $address->getStreet(-1)); + $splittedShippingStreet = Mage::Helper('ops/address')->splitStreet($shippingStreet); + + $paramValues['ECOM_SHIPTO_POSTAL_CITY'] = $address->getCity(); + $paramValues['ECOM_SHIPTO_POSTAL_POSTALCODE'] = $address->getPostcode(); + $paramValues['ECOM_SHIPTO_POSTAL_STATE'] = $this->getIsoRegionCode($address); + $paramValues['ECOM_SHIPTO_POSTAL_COUNTRYCODE'] = $address->getCountry(); + $paramValues['ECOM_SHIPTO_POSTAL_NAME_FIRST'] = $address->getFirstname(); + $paramValues['ECOM_SHIPTO_POSTAL_NAME_LAST'] = $address->getLastname(); + $paramValues['ECOM_SHIPTO_POSTAL_STREET_LINE1'] = $splittedShippingStreet['street_name']; + $paramValues['ECOM_SHIPTO_POSTAL_STREET_NUMBER'] = $splittedShippingStreet['street_number']; + $paramValues['ECOM_SHIPTO_POSTAL_STREET_LINE2'] = $splittedShippingStreet['supplement']; + + return $paramValues; + } + + /** + * Extracts the billing address parameters for the ECOM_BILLTO fields + * + * @param Mage_Customer_Model_Address_Abstract $address + * @param Mage_Sales_Model_Quote || Mage_Sales_Model_Order $salesObject + * + * @return string[] - array containing the ECOM_BILLTO parameters + */ + public function extractBillToParameters($address, $salesObject = null) + { + $paramValues = array(); + + if (!$address instanceof Mage_Customer_Model_Address_Abstract) { + if (!is_null($salesObject)) { + $address = $salesObject->getBillingAddress(); + } + if (!$address) { + return $paramValues; + } + } + + $billingStreet = str_replace("\n", ' ', $address->getStreet(-1)); + $splittedBillingStreet = Mage::Helper('ops/address')->splitStreet($billingStreet); + + $paramValues['ECOM_BILLTO_POSTAL_CITY'] = $address->getCity(); + $paramValues['ECOM_BILLTO_POSTAL_POSTALCODE'] = $address->getPostcode(); + $paramValues['ECOM_BILLTO_POSTAL_COUNTY'] = $this->getIsoRegionCode($address); + $paramValues['ECOM_BILLTO_POSTAL_COUNTRYCODE'] = $address->getCountry(); + $paramValues['ECOM_BILLTO_POSTAL_NAME_FIRST'] = $address->getFirstname(); + $paramValues['ECOM_BILLTO_POSTAL_NAME_LAST'] = $address->getLastname(); + $paramValues['ECOM_BILLTO_POSTAL_POSTALCODE'] = $address->getPostcode(); + $paramValues['ECOM_BILLTO_POSTAL_STREET_LINE1'] = $splittedBillingStreet['street_name']; + $paramValues['ECOM_BILLTO_POSTAL_STREET_NUMBER'] = $splittedBillingStreet['street_number']; + $paramValues['ECOM_BILLTO_POSTAL_STREET_LINE2'] = $splittedBillingStreet['supplement']; + $paramValues['ECOM_BILLTO_POSTAL_STREET_LINE3'] = $address->getStreet(3); + + return $paramValues; + } + + /** + * extraxcts the according Ingenico ePayments owner* parameter + * + * @param Mage_Customer_Model_Address_Abstract $billingAddress + * + * @param Mage_Sales_Model_Quote|Mage_Sales_Model_Order $salesObject + * + * @return string[] + */ + public function getOwnerParams(Mage_Customer_Model_Address_Abstract $billingAddress, $salesObject) + { + $ownerParams = array(); + if ($this->getConfig()->canSubmitExtraParameter($salesObject->getStoreId())) { + $ownerParams = array( + 'OWNERADDRESS' => str_replace("\n", ' ', $billingAddress->getStreet(1)), + 'OWNERTOWN' => $billingAddress->getCity(), + 'OWNERZIP' => $billingAddress->getPostcode(), + 'OWNERTELNO' => $billingAddress->getTelephone(), + 'OWNERCTY' => $billingAddress->getCountry(), + + 'ECOM_BILLTO_POSTAL_POSTALCODE' => $billingAddress->getPostcode(), + ); + } + + return $ownerParams; + } + + /** + * Returns the template parameters and their dependencies + * + * @return array + */ + + public function getTemplateParams($storeId = null) + { + $formFields = array(); + switch ($this->getConfig()->getConfigData('template')) { + case Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_MAGENTO_INTERNAL: + $formFields['TP'] = $this->getConfig()->getPayPageTemplate(); + break; + case Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_TEMPLATE: + $formFields['TP'] = $this->getConfig()->getTemplateIdentifier($storeId); + break; + case Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_IFRAME: + $formFields['PARAMPLUS'] = 'IFRAME=1'; + case Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_REDIRECT: + $formFields['PMLISTTYPE'] = $this->getConfig()->getConfigData('pmlist', $storeId); + $formFields['TITLE'] = $this->getConfig()->getConfigData('html_title', $storeId); + $formFields['BGCOLOR'] = $this->getConfig()->getConfigData('bgcolor', $storeId); + $formFields['TXTCOLOR'] = $this->getConfig()->getConfigData('txtcolor', $storeId); + $formFields['TBLBGCOLOR'] = $this->getConfig()->getConfigData('tblbgcolor', $storeId); + $formFields['TBLTXTCOLOR'] = $this->getConfig()->getConfigData('tbltxtcolor', $storeId); + $formFields['BUTTONBGCOLOR'] = $this->getConfig()->getConfigData('buttonbgcolor', $storeId); + $formFields['BUTTONTXTCOLOR'] = $this->getConfig()->getConfigData('buttontxtcolor', $storeId); + $formFields['FONTTYPE'] = $this->getConfig()->getConfigData('fonttype', $storeId); + $formFields['LOGO'] = $this->getConfig()->getConfigData('logo', $storeId); + $formFields['HOMEURL'] = $this->getConfig()->hasHomeUrl() ? $this->getConfig()->getContinueUrl( + array('redirect' => 'home') + ) : 'NONE'; + $formFields['CATALOGURL'] = $this->getConfig()->hasCatalogUrl() ? $this->getConfig()->getContinueUrl( + array('redirect' => 'catalog') + ) : ''; + break; + default: + break; + }; + + return $formFields; + + } + + /** + * extracts the region code in iso format (if possible) + * + * @param Mage_Customer_Model_Address_Abstract $address + * + * @return string - the region code in iso format + */ + public function getIsoRegionCode(Mage_Customer_Model_Address_Abstract $address) + { + $regionCode = trim($address->getRegionCode()); + $countryCode = $address->getCountry(); + if ($this->isAlreadyIsoCode($regionCode, $countryCode)) { + return $regionCode; + } + if (0 === strpos($regionCode, $countryCode . '-')) { + return str_replace($countryCode . '-', '', $regionCode); + } + + return $this->getRegionCodeFromMapping($countryCode, $regionCode); + } + + /** + * checks if the given region code is already in iso format + * + * @param string $regionCode + * @param string $countryCode + * + * @return bool + */ + protected function isAlreadyIsoCode($regionCode, $countryCode) + { + return ((strlen($regionCode) < 3 && !in_array($countryCode, array('AT'))) + || (strlen($regionCode) === 3 && !in_array($countryCode, array('DE')))); + } + + protected function getRegionCodeFromMapping($countryCode, $regionCode) + { + $countryRegionMapping = $this->getCountryRegionMapping($countryCode); + if (array_key_exists($regionCode, $countryRegionMapping)) { + return $countryRegionMapping[$regionCode]; + } + + return $countryCode; + } + + /** + * retrieves country specific region mapping + * + * @param $countryCode + * + * @return array - the country specific region mapping or empty array if mapping could not be found + */ + protected function getCountryRegionMapping($countryCode) + { + if (strtoupper($countryCode) === 'DE') { + return $this->getRegionMappingForGermany(); + } + if (strtoupper($countryCode) === 'AT') { + return $this->getRegionMappingForAustria(); + } + if (strtoupper($countryCode) === 'ES') { + return $this->getRegionMappingForSpain(); + } + if (strtoupper($countryCode) === 'FI') { + return $this->getRegionsMappingForFinland(); + } + if (strtoupper($countryCode) === 'LV') { + return $this->getRegionsMappingForLatvia(); + } + + return array(); + } + + /** + * translates the Magento's region code for germany into ISO format + * + * @return array + */ + protected function getRegionMappingForGermany() + { + return array( + 'NDS' => 'NI', + 'BAW' => 'BW', + 'BAY' => 'BY', + 'BER' => 'BE', + 'BRG' => 'BB', + 'BRE' => 'HB', + 'HAM' => 'HH', + 'HES' => 'HE', + 'MEC' => 'MV', + 'NRW' => 'NW', + 'RHE' => 'RP', + 'SAR' => 'SL', + 'SAS' => 'SN', + 'SAC' => 'ST', + 'SCN' => 'SH', + 'THE' => 'TH' + ); + } + + /** + * translates the Magento's region code for austria into ISO format + * + * @return array + */ + protected function getRegionMappingForAustria() + { + return array( + 'WI' => '9', + 'NO' => '3', + 'OO' => '4', + 'SB' => '5', + 'KN' => '2', + 'ST' => '6', + 'TI' => '7', + 'BL' => '1', + 'VB' => '8' + ); + } + + /** + * translates the Magento's region code for spain into ISO format + * + * @return array + */ + protected function getRegionMappingForSpain() + { + return array( + 'A Coruсa' => 'C', + 'Alava' => 'VI', + 'Albacete' => 'AB', + 'Alicante' => 'A', + 'Almeria' => 'AL', + 'Asturias' => 'O', + 'Avila' => 'AV', + 'Badajoz' => 'BA', + 'Baleares' => 'PM', + 'Barcelona' => 'B', + 'Caceres' => 'CC', + 'Cadiz' => 'CA', + 'Cantabria' => 'S', + 'Castellon' => 'CS', + 'Ceuta' => 'CE', + 'Ciudad Real' => 'CR', + 'Cordoba' => 'CO', + 'Cuenca' => 'CU', + 'Girona' => 'GI', + 'Granada' => 'GR', + 'Guadalajara' => 'GU', + 'Guipuzcoa' => 'SS', + 'Huelva' => 'H', + 'Huesca' => 'HU', + 'Jaen' => 'J', + 'La Rioja' => 'LO', + 'Las Palmas' => 'GC', + 'Leon' => 'LE', + 'Lleida' => 'L', + 'Lugo' => 'LU', + 'Madrid' => 'M', + 'Malaga' => 'MA', + 'Melilla' => 'ML', + 'Murcia' => 'MU', + 'Navarra' => 'NA', + 'Ourense' => 'OR', + 'Palencia' => 'P', + 'Pontevedra' => 'PO', + 'Salamanca' => 'SA', + 'Santa Cruz de Tenerife' => 'TF', + 'Segovia' => 'Z', + 'Sevilla' => 'SG', + 'Soria' => 'SE', + 'Tarragona' => 'SO', + 'Teruel' => 'T', + 'Toledo' => 'TE', + 'Valencia' => 'TO', + 'Valladolid' => 'V', + 'Vizcaya' => 'VA', + 'Zamora' => 'BI', + 'Zaragoza' => 'ZA', + ); + } + + /** + * translates the Magento's region code for finland into ISO format + * + * @return array + */ + protected function getRegionsMappingForFinland() + { + return array( + 'Lappi' => '10', + 'Pohjois-Pohjanmaa' => '14', + 'Kainuu' => '05', + 'Pohjois-Karjala' => '13', + 'Pohjois-Savo' => '15', + 'Etelä-Savo' => '04', + 'Etelä-Pohjanmaa' => '03', + 'Pohjanmaa' => '12', + 'Pirkanmaa' => '11', + 'Satakunta' => '17', + 'Keski-Pohjanmaa' => '07', + 'Keski-Suomi' => '08', + 'Varsinais-Suomi' => '19', + 'Etelä-Karjala' => '02', + 'Päijät-Häme' => '16', + 'Kanta-Häme' => '06', + 'Uusimaa' => '18', + 'Itä-Uusimaa' => '19', + 'Kymenlaakso' => '09', + 'Ahvenanmaa' => '01' + ); + } + + /** + * translates the Magento's region code for latvia into ISO format + * + * @return array + */ + protected function getRegionsMappingForLatvia() + { + return array( + 'Ādažu novads' => 'LV', + 'Aglonas novads' => '001', + 'Aizputes novads' => '003', + 'Aknīstes novads' => '004', + 'Alojas novads' => '005', + 'Alsungas novads' => '006', + 'Amatas novads' => '008', + 'Apes novads' => '009', + 'Auces novads' => '010', + 'Babītes novads' => '012', + 'Baldones novads' => '013', + 'Baltinavas novads' => '014', + 'Beverīnas novads' => '017', + 'Brocēnu novads' => '018', + 'Burtnieku novads' => '019', + 'Carnikavas novads' => '020', + 'Cesvaines novads' => '021', + 'Ciblas novads' => '023', + 'Dagdas novads' => '024', + 'Dundagas novads' => '027', + 'Durbes novads' => '028', + 'Engures novads' => '029', + 'Ērgļu novads' => 'LV', + 'Garkalnes novads' => '031', + 'Grobiņas novads' => '032', + 'Iecavas novads' => '034', + 'Ikšķiles novads' => '035', + 'Ilūkstes novads' => '036', + 'Inčukalna novads' => '037', + 'Jaunjelgavas novads' => '038', + 'Jaunpiebalgas novads' => '039', + 'Jaunpils novads' => '040', + 'Jēkabpils' => '042', + 'Kandavas novads' => '043', + 'Kārsavas novads' => 'LV', + 'Ķeguma novads' => 'LV', + 'Ķekavas novads' => 'LV', + 'Kokneses novads' => '046', + 'Krimuldas novads' => '048', + 'Krustpils novads' => '049', + 'Lielvārdes novads' => '053', + 'Līgatnes novads' => 'LV', + 'Līvānu novads' => '056', + 'Lubānas novads' => '057', + 'Mālpils novads' => '061', + 'Mārupes novads' => '062', + 'Mazsalacas novads' => '060', + 'Naukšēnu novads' => '064', + 'Neretas novads' => '065', + 'Nīcas novads' => '066', + 'Olaines novads' => '068', + 'Ozolnieku novads' => '069', + 'Pārgaujas novads' => 'LV', + 'Pāvilostas novads' => '070', + 'Pļaviņu novads' => '072', + 'Priekules novads' => '074', + 'Priekuļu novads' => '075', + 'Raunas novads' => '076', + 'Riebiņu novads' => '078', + 'Rojas novads' => '079', + 'Ropažu novads' => '080', + 'Rucavas novads' => '081', + 'Rugāju novads' => '082', + 'Rūjienas novads' => '084', + 'Rundāles novads' => '083', + 'Salacgrīvas novads' => '085', + 'Salas novads' => '086', + 'Salaspils novads' => '087', + 'Saulkrastu novads' => '089', + 'Sējas novads' => 'LV', + 'Siguldas novads' => '091', + 'Skrīveru novads' => '092', + 'Skrundas novads' => '093', + 'Smiltenes novads' => '094', + 'Stopiņu novads' => '095', + 'Strenču novads' => '096', + 'Tērvetes novads' => '098', + 'Vaiņodes novads' => '100', + 'Valmiera' => 'LV', + 'Varakļānu novads' => '102', + 'Vārkavas novads' => 'LV', + 'Vecpiebalgas novads' => '104', + 'Vecumnieku novads' => '105', + 'Viesītes novads' => '107', + 'Viļakas novads' => '108', + 'Viļānu novads' => '109', + 'Zilupes novads' => '110' + ); + } + + /** + * Returns the mandatory fields for requests to Ingenico ePayments + * + * @param Mage_Sales_Model_Order $order + * + * @return array + */ + + public function getMandatoryRequestFields(Mage_Sales_Model_Order $order) + { + $payment = $order->getPayment()->getMethodInstance(); + $opsOrderId = Mage::helper('ops/order')->getOpsOrderId($order); + $formFields = array(); + $formFields['PSPID'] = $this->getConfig()->getPSPID($order->getStoreId()); + $formFields['AMOUNT'] = Mage::helper('ops')->getAmount($order->getBaseGrandTotal()); + $formFields['CURRENCY'] = Mage::app()->getStore()->getBaseCurrencyCode(); + $formFields['ORDERID'] = $opsOrderId; + $formFields['LANGUAGE'] = Mage::app()->getLocale()->getLocaleCode(); + $formFields['PM'] = $payment->getOpsCode($order->getPayment()); + $formFields['EMAIL'] = $order->getCustomerEmail(); + + $formFields['ACCEPTURL'] = $this->getConfig()->getAcceptUrl(); + $formFields['DECLINEURL'] = $this->getConfig()->getDeclineUrl(); + $formFields['EXCEPTIONURL'] = $this->getConfig()->getExceptionUrl(); + $formFields['CANCELURL'] = $this->getConfig()->getCancelUrl(); + + $formFields['BACKURL'] = $this->getConfig()->getPaymentRetryUrl( + Mage::helper('ops/payment')->validateOrderForReuse($opsOrderId, $order->getStoreId()) + ); + + $formFields['FP_ACTIV'] = $this->isFingerPrintingActive($order) ? '1' : '0'; + + return $formFields; + } + + /** + * Will return the combination of activiation via config and the state of consent of the customer + * + * @param $order + * + * @return bool + */ + protected function isFingerPrintingActive($order) + { + return $this->getConfig()->getDeviceFingerPrinting($order->getStoreId()) + && Mage::getSingleton('customer/session')->getData( + Netresearch_OPS_Model_Payment_Abstract::FINGERPRINT_CONSENT_SESSION_KEY + ); + } + + /** + * Extracts the order item parameters and puts them in a array like + * + * @param Mage_Sales_Model_Order|Mage_Sales_Model_Order_Invoice $salesObject + * + * @return mixed[] + */ + public function extractOrderItemParameters($salesObject) + { + $formFields = array(); + $formatAmounts = $salesObject instanceof Mage_Sales_Model_Order_Invoice; + // add order items + $count = 1; + $addItemCount = function (&$value, $key, $count) use (&$formFields) { + $formFields[$key . $count] = $value; + }; + foreach ($salesObject->getAllItems() as $item) { + if ($this->isNonDataItem($item)) { + continue; + } + + $itemFields = $this->getItemFormFields($item, $formatAmounts); + array_walk($itemFields, $addItemCount, $count); + $count++; + } + + // add discount item + /** @var mixed|false $discountItemFormFields */ + $discountItemFormFields = $this->getDiscountItemFormFields($salesObject, $formatAmounts); + if ($discountItemFormFields) { + array_walk($discountItemFormFields, $addItemCount, $count); + $count++; + } + + // add shipping item + /** @var mixed|false $shippingItemFields */ + $shippingItemFields = $this->getShippingItemFormFields($salesObject, $formatAmounts); + if ($shippingItemFields) { + array_walk($shippingItemFields, $addItemCount, $count); + } + + return $formFields; + } + + /** + * @param Mage_Sales_Model_Order_Item|Mage_Sales_Model_Order_Invoice_Item $item + * + * @return bool + */ + protected function isNonDataItem($item) + { + if ($item instanceof Mage_Sales_Model_Order_Invoice_Item) { + $item = $item->getOrderItem(); + } + + return $item->getParentItemId() + && $item->getParentItem()->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE + || $item->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_BUNDLE; + } + + /** + * @param Mage_Sales_Model_Order $order + * + * @return float + */ + + public function getShippingTaxRate($order) + { + $store = $order->getStore(); + $taxCalculation = Mage::getModel('tax/calculation'); + $request = $taxCalculation->getRateRequest(null, null, null, $store); + $taxRateId = Mage::getStoreConfig('tax/classes/shipping_tax_class', $store); + //taxRateId is the same model id as product tax classes, so you can do this: + $percent = $taxCalculation->getRate($request->setProductClassId($taxRateId)); + + return $percent; + } + + /** + * Genereates item array for shipping, returns false if order is virtual + * + * @param Mage_Sales_Model_Order|Mage_Sales_Model_Order_Invoice $salesObject + * @param bool $formatAmount + * + * @return mixed[] | false + */ + protected function getShippingItemFormFields($salesObject, $formatAmount = false) + { + + if ($salesObject instanceof Mage_Sales_Model_Order_Invoice) { + /** @var Mage_Sales_Model_Order_Invoice $salesObject */ + $order = $salesObject->getOrder(); + } else { + /** @var Mage_Sales_Model_Order $salesObject */ + $order = $salesObject; + } + /** @var string $taxRate */ + $taxRate = str_replace(',', '.', (string)(float)$this->getShippingTaxRate($order)) . '%'; + + if ($order->getIsNotVirtual() && 0 < $salesObject->getBaseShippingInclTax()) { + $formFields = array(); + + if ($formatAmount) { + $amount = Mage::helper('ops')->getAmount($salesObject->getBaseShippingInclTax()); + } else { + $amount = number_format($salesObject->getBaseShippingInclTax(), 2, '.', ''); + } + + /* add shipping item */ + $formFields['ITEMID'] = 'SHIPPING'; + $formFields['ITEMNAME'] = substr($order->getShippingDescription(), 0, 25); + $formFields['ITEMPRICE'] = $amount; + $formFields['TAXINCLUDED'] = 1; + $formFields['ITEMQUANT'] = 1; + $formFields['ITEMVATCODE'] = $taxRate; + + return $formFields; + } + + return false; + } + + /** + * Returns item array for Ingenico ePayments request for the specified item + * + * @param Mage_Sales_Model_Order_Invoice_Item|Mage_Sales_Model_Order_Item $item + * @param bool $formatAmount + * + * @return array + */ + protected function getItemFormFields($item, $formatAmount = false) + { + $formFields = array(); + $formFields['ITEMID'] = $item->getItemId() ?: $item->getOrderItemId(); + $formFields['ITEMNAME'] = substr($item->getName(), 0, 40); + if ($formatAmount) { + $amount = Mage::helper('ops')->getAmount($item->getBasePriceInclTax()); + } else { + $amount = number_format($item->getBasePriceInclTax(), 2, '.', ''); + } + $formFields['ITEMPRICE'] = $amount; + $formFields['ITEMQUANT'] = (int)$item->getQtyOrdered() ?: $item->getQty(); + $formFields['ITEMVATCODE'] = str_replace(',', '.', (string)(float)$item->getTaxPercent()) . '%'; + $formFields['TAXINCLUDED'] = 1; + + return $formFields; + } + + /** + * Creates array + * + * @param Mage_Sales_Model_Abstract $salesObject + * @param bool $formatAmount + * + * @return bool|mixed[] + */ + protected function getDiscountItemFormFields(Mage_Sales_Model_Abstract $salesObject, $formatAmount = false) + { + $formFields = array(); + if ($salesObject instanceof Mage_Sales_Model_Order_Invoice) { + $order = $salesObject->getOrder(); + } else { + $order = $salesObject; + } + /** @var Mage_Sales_Model_Order $order */ + /* add coupon item */ + if ($salesObject->getBaseDiscountAmount() != 0.00) { + $couponRuleName = 'DISCOUNT'; + if ($order->getCouponRuleName() && strlen(trim($order->getCouponRuleName())) > 0) { + $couponRuleName = substr(trim($order->getCouponRuleName()), 0, 30); + } + + if ($formatAmount) { + $couponAmount = Mage::helper('ops')->getAmount($salesObject->getBaseDiscountAmount()); + } else { + $couponAmount = number_format($salesObject->getBaseDiscountAmount(), 2, '.', ''); + } + + $formFields['ITEMID'] = 'DISCOUNT'; + $formFields['ITEMNAME'] = $couponRuleName; + $formFields['ITEMPRICE'] = $couponAmount; + $formFields['ITEMQUANT'] = 1; + $formFields['ITEMVATCODE'] + = str_replace(',', '.', (string)(float)$this->getShippingTaxRate($order)) . '%'; + $formFields['TAXINCLUDED'] = 1; + + return $formFields; + + } + + return false; + } +} diff --git a/app/code/community/Netresearch/OPS/Helper/Quote.php b/app/code/community/Netresearch/OPS/Helper/Quote.php new file mode 100644 index 0000000..51c4370 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Quote.php @@ -0,0 +1,164 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Helper_Quote extends Mage_Core_Helper_Abstract +{ + + const PAGE_SIZE = 100; + + const MINUTES_IN_PAST = 15; + + protected $dataHelper = null; + + /** + * cleans up old payment information (deletes cvc etc. from additional data) + */ + public function cleanUpOldPaymentInformation() + { + $allowedTimestamp = new Zend_Db_Expr( + sprintf( + 'NOW() - INTERVAL %d MINUTE', self::MINUTES_IN_PAST + ) + ); + /* + * fetching possible affected information from the sales_quote_payment table + * criteria are: + * - ops_cc was used + * - the last update is more than 15 minutes ago + * - and CVC is included in the additional information + */ + $paymentInformation = Mage::getModel('sales/quote_payment') + ->getCollection() + ->addFieldToFilter('method', array('eq' => 'ops_cc')) + ->addFieldToFilter('updated_at', array('lt' => $allowedTimestamp)) + ->addFieldToFilter( + 'additional_information', array('like' => '%"cvc"%') + ) + ->setOrder('created_at', 'DESC') + ->setPageSize(self::PAGE_SIZE); + foreach ($paymentInformation as $payment) { + if (null != $payment->getAdditionalInformation('cvc')) { + // quote needs to be loaded, because saving the payment information would fail otherwise + $payment->setQuote( + Mage::getModel('sales/quote')->load($payment->getQuoteId()) + ); + Mage::helper('ops/alias')->cleanUpAdditionalInformation( + $payment, true + ); + $payment->save(); + } + + } + + + } + + /** + * returns the quote currency + * + * @param $quote + * + * @return string - the quotes currency + */ + public function getQuoteCurrency(Mage_Sales_Model_Quote $quote) + { + if ($quote->hasForcedCurrency()) { + return $quote->getForcedCurrency()->getCode(); + } else { + return Mage::app()->getStore($quote->getStoreId()) + ->getBaseCurrencyCode(); + } + } + + /** + * get payment operation code + * + * @param Mage_Sales_Model_Order $order + * + * @return string + */ + public function getPaymentAction($order) + { + $operation + = Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_ACTION; + + // different capture operation name for direct debits + if ('Direct Debits DE' == $order->getPayment() + ->getAdditionalInformation('PM') + || 'Direct Debits AT' == $order->getPayment() + ->getAdditionalInformation('PM') + ) { + if ('authorize_capture' == Mage::getModel('ops/config') + ->getPaymentAction($order->getStoreId()) + ) { + return Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION; + } + + return Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_ACTION; + } + // no RES for Direct Debits NL, so we'll do the final sale + if ('Direct Debits NL' == $order->getPayment() + ->getAdditionalInformation('PM') + ) { + if ('authorize_capture' == Mage::getModel('ops/config') + ->getPaymentAction($order->getStoreId()) + ) { + return Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_DIRECTDEBIT_NL; + } + + return Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_ACTION; + } + + if ('authorize_capture' == Mage::getModel('ops/config') + ->getPaymentAction($order->getStoreId()) + ) { + $operation + = Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION; + } + + return $operation; + } + + /** + * @return Mage_Sales_Model_Quote + */ + public function getQuote() + { + if (Mage::app()->getStore()->isAdmin()) { + return Mage::getSingleton('adminhtml/session_quote')->getQuote(); + } + + return Mage::getSingleton('checkout/session')->getQuote(); + } + + /** + * gets the data helper + * + * @return Netresearch_OPS_Helper_Data + */ + public function getDataHelper() + { + if (null === $this->dataHelper) { + $this->dataHelper = Mage::helper('ops/data'); + } + + return $this->dataHelper; + } + + /** + * sets the data helper + * + * @param Netresearch_OPS_Helper_Data $dataHelper + */ + public function setDataHelper(Netresearch_OPS_Helper_Data $dataHelper) + { + $this->dataHelper = $dataHelper; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Subscription.php b/app/code/community/Netresearch/OPS/Helper/Subscription.php new file mode 100644 index 0000000..82aef59 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Subscription.php @@ -0,0 +1,148 @@ + + */ +class Netresearch_OPS_Helper_Subscription extends Mage_Core_Helper_Abstract +{ + const SUBSCRIPTION_PREFIX = 'SUB-'; + const TRIAL_SUFFIX = '-TRIAL'; + + /** + * Generates subscription id in the following pattern: + * + * SUB-subscriptionId-TRIAL + * + * @param Mage_Payment_Model_Recurring_Profile $profile - the profile itself + * @param bool $withTrial - if you wish the trial suffix appended in case the profile has a trial subscription + * + * @return string + */ + public function generateSubscriptionId(Mage_Payment_Model_Recurring_Profile $profile, $withTrial = false) + { + $config = Mage::getModel('ops/config'); + $devPrefix = $config->getConfigData('devprefix'); + + $subscriptionId = self::SUBSCRIPTION_PREFIX . $profile->getId(); + if ($profile->getTrialPeriodUnit() && $withTrial) { + $subscriptionId .= self::TRIAL_SUFFIX; + } + + return $devPrefix . $subscriptionId; + } + + /** + * Determine if the given request parameters belong to a subscription + * + * @param $requestParams + * + * @return bool + */ + public function isSubscriptionFeedback($requestParams) + { + $result = false; + if (is_array($requestParams) + && array_key_exists('orderID', $requestParams) + && false != strstr($this->removeDevPrefix($requestParams['orderID']), self::SUBSCRIPTION_PREFIX) + ) { + $result = true; + } + + return $result; + } + + /** + * Loads the recurring profile for the given ORDERID + * + * @param $orderId + * + * @throws Mage_Core_Exception if no profile could be found + * + * @return Mage_Sales_Model_Order + * @throws Mage_Core_Exception + */ + public function getProfileForSubscription($orderId) + { + $orderId = $this->removeDevPrefix($orderId); + + if ($this->isTrialFeedback($orderId)) { + $orderId = substr($orderId, -0, strlen(self::TRIAL_SUFFIX)); + } + $orderId = substr($orderId, strlen(self::SUBSCRIPTION_PREFIX)); + $profile = Mage::getModel('sales/recurring_profile')->load($orderId); + if (!$profile->getId()) { + Mage::throwException('Could find no subscription for id ' . $orderId); + } + + return $profile; + } + + /** + * Determines via the given orderId if the feedback request was a from a trial subscription + * + * @param string $orderId + * + * @return bool + */ + public function isTrialFeedback($orderId) + { + return (bool)strstr($orderId, self::TRIAL_SUFFIX); + } + + /** + * Determine day of billing according to the period unit of the subscription + * + * @param string $periodUnit @see Mage_Payment_Model_Recurring_Profile + * @param int $storeId + * + * @return int + */ + public function getBillingDayForPeriodUnit($periodUnit, $storeId = null) + { + $config = Mage::getModel('ops/config'); + $day = 1; + + switch ($periodUnit) { + case Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_MONTH: + $day = $config->getMonthlyBillingDay($storeId); + break; + case Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_WEEK: + // offset the day by one, since Magento counts from 0, but Ingenico ePayments from 1 + $day = $config->getWeeklyBillingDay($storeId) + 1; + break; + } + + return $day; + } + + protected function removeDevPrefix($orderId) + { + $config = Mage::getModel('ops/config'); + $devPrefix = $config->getConfigData('devprefix'); + $orderId = substr($orderId, strlen($devPrefix)); + return $orderId; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Validation/Checkout/Step.php b/app/code/community/Netresearch/OPS/Helper/Validation/Checkout/Step.php new file mode 100644 index 0000000..b4ecb56 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Validation/Checkout/Step.php @@ -0,0 +1,98 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Helper_Validation_Checkout_Step +{ + + const BILLING_STEP = 'billing'; + + const SHIPPING_STEP = 'shipping'; + + const BILLING_PARAMETER_STRING_VALUE = 'billing'; + + const SHIPPING_PARAMETER_STRING_VALUE = 'shipping'; + + /** + * retrieves the params for pushing back to the billing step + * + * @return array + */ + protected function determineStep() + { + $result = array(); + $mappedParams = $this->getMappedParams(); + + foreach ($mappedParams as $paramName => $value) { + if (strpos($value, self::BILLING_PARAMETER_STRING_VALUE) === 0) { + $result[self::BILLING_PARAMETER_STRING_VALUE][] = $paramName; + } + if (strpos($value, self::SHIPPING_PARAMETER_STRING_VALUE) === 0) { + $result[self::SHIPPING_STEP][] = $paramName; + } + } + + return $result; + } + + + protected function getMappedParams() + { + $result = array(); + $paramLengthFields = $this->getConfig()->getParameterLengths(); + $frontendFieldMapping = $this->getConfig()->getFrontendFieldMapping(); + + foreach (array_keys($paramLengthFields) as $key) { + if (isset($frontendFieldMapping[$key])) { + $frontendField = !is_array($frontendFieldMapping[$key]) + ? $frontendFieldMapping[$key] + : implode( + ',', $frontendFieldMapping[$key] + ); + $result[$key] = $frontendField; + } + } + + return $result; + } + + /** + * gets the corresponding checkout step for the erroneous fields + * + * @param array $erroneousFields + * + * @return string - the checkout step + */ + public function getStep(array $erroneousFields) + { + $checkoutStep = ''; + $stepParams = $this->determineStep(); + foreach ($erroneousFields as $erroneousField) { + if (isset($stepParams[self::BILLING_STEP]) + && in_array($erroneousField, $stepParams[self::BILLING_STEP]) + ) { + $checkoutStep = self::BILLING_STEP; + break; + } + if (isset($stepParams[self::SHIPPING_STEP]) + && in_array($erroneousField, $stepParams[self::SHIPPING_STEP]) + ) { + $checkoutStep = self::SHIPPING_STEP; + } + } + + return $checkoutStep; + } + + /** + * @return Netresearch_OPS_Model_Config + */ + protected function getConfig() + { + return Mage::getModel('ops/config'); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Validation/Result.php b/app/code/community/Netresearch/OPS/Helper/Validation/Result.php new file mode 100644 index 0000000..992c57d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Validation/Result.php @@ -0,0 +1,210 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + + + +class Netresearch_OPS_Helper_Validation_Result +{ + + protected $checkoutStepHelper = null; + + protected $config = null; + + protected $formBlock = null; + + protected $dataHelper = null; + + protected $result = array(); + + /** + * @param array $result + */ + public function setResult(array $result) + { + $this->result = $result; + } + + /** + * @return array + */ + public function getResult() + { + return $this->result; + } + + /** + * @param null $checkOutStepHelper + */ + public function setCheckoutStepHelper($checkOutStepHelper) + { + $this->checkoutStepHelper = $checkOutStepHelper; + } + + /** + * @return null + */ + public function getCheckoutStepHelper() + { + if (null === $this->checkoutStepHelper) { + $this->checkoutStepHelper = Mage::helper('ops/validation_checkout_step'); + } + + return $this->checkoutStepHelper; + } + + /** + * @param null $config + */ + public function setConfig($config) + { + $this->config = $config; + } + + /** + * @return null + */ + public function getConfig() + { + if (null === $this->config) { + $this->config = Mage::getModel('ops/config'); + } + + return $this->config; + } + + /** + * @param null $dataHelper + */ + public function setDataHelper($dataHelper) + { + $this->dataHelper = $dataHelper; + } + + /** + * @return null + */ + public function getDataHelper() + { + if (null === $this->dataHelper) { + $this->dataHelper = Mage::helper('ops/data'); + } + + return $this->dataHelper; + } + + /** + * @param null $formBlock + */ + public function setFormBlock($formBlock) + { + $this->formBlock = $formBlock; + } + + /** + * @return Netresearch_OPS_Block_Form + */ + public function getFormBlock() + { + if (null === $this->formBlock) { + $this->formBlock = Mage::getBlockSingleton('ops/form'); + } + + return $this->formBlock; + } + + + + /** + * @param array $messages + * @param Mage_Sales_Model_Quote $quote + * + * @return array + */ + public function getValidationFailedResult($messages, $quote) + { + $gotoSection = $this->getCheckoutStepHelper()->getStep(array_keys($messages)); + $this->setBaseErroneousFields($messages, $gotoSection); + $this->getFields($messages); + $this->addErrorToExistingAddress($quote, $gotoSection); + $this->cleanResult(); + + return $this->getResult(); + } + + /** + * @param $messages + * + * @return mixed + */ + protected function getFields($messages) + { + $mappedFields = $this->getConfig()->getFrontendFieldMapping(); + $frontendFields = array(); + foreach ($messages as $key => $value) { + if (array_key_exists($key, $mappedFields)) { + $frontendFields = $this->getFormBlock()->getFrontendValidationFields( + $mappedFields, + $key, + $value, + $frontendFields + ); + } + } + $this->result['fields'] = $frontendFields; + + return $this; + } + + /** + * @param Mage_Sales_Model_Quote $quote + * @param string $gotoSection + * + * @return $this + */ + protected function addErrorToExistingAddress($quote, $gotoSection) + { + if ($gotoSection == 'billing' && 0 < $quote->getBillingAddress()->getId()) { + $this->result['fields']['billing-address-select'] = $this->getDataHelper()->__( + 'Billing address contains invalid data' + ); + } + if ($gotoSection == 'shipping' && 0 < $quote->getShippingAddress()->getId()) { + $this->result['fields']['shipping-address-select'] = $this->getDataHelper()->__( + 'Shipping address contains invalid data' + ); + } + + return $this; + } + + /** + * @param string[] $messages + * @param string $gotoSection + * + * @return mixed + */ + protected function setBaseErroneousFields($messages, $gotoSection) + { + $this->result['error'] = implode(',', array_values($messages)); + $this->result['goto_section'] = $gotoSection; + $this->result['opsError'] = true; + + return $this; + } + + protected function cleanResult() + { + if (array_key_exists('update_section', $this->result)) { + unset($this->result['update_section']); + } + + return $this; + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Helper/Version.php b/app/code/community/Netresearch/OPS/Helper/Version.php new file mode 100644 index 0000000..d6844ea --- /dev/null +++ b/app/code/community/Netresearch/OPS/Helper/Version.php @@ -0,0 +1,66 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Helper_Version extends Mage_Core_Helper_Abstract +{ + const CAN_USE_APPLICABLE_FOR_QUOTE_EE_MINOR = 14; + + const CAN_USE_APPLICABLE_FOR_QUOTE_CE_MINOR = 8; + + + /** + * checks if Version is above EE 1.13 or CE 1.7.0.2 + * useApplicableForQuote was implemented in CE 1.8 / EE 1.14 + * + * @param $edition + * + * @return bool + */ + public function canUseApplicableForQuote($edition) + { + $result = false; + $versionInfo = $this->getVersionInfo(); + if (array_key_exists('minor', $versionInfo) + && $versionInfo['minor'] >= $this->getVersionForEdition($edition) + ) { + $result = true; + } + + return $result; + } + + /** + * return allowed version + * + * @param $edition + * + * @return int + */ + protected function getVersionForEdition($edition) + { + if (Mage::EDITION_ENTERPRISE == $edition) { + return self::CAN_USE_APPLICABLE_FOR_QUOTE_EE_MINOR; + } + + return self::CAN_USE_APPLICABLE_FOR_QUOTE_CE_MINOR; + } + + /** + * wraps Mage::getVersionInfo since Mage class is final and not testable + * + * @return array + */ + protected function getVersionInfo() + { + return Mage::getVersionInfo(); + } + + +} diff --git a/app/code/community/Netresearch/OPS/Model/Alias.php b/app/code/community/Netresearch/OPS/Model/Alias.php new file mode 100644 index 0000000..e2cae52 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Alias.php @@ -0,0 +1,70 @@ +_init('ops/alias'); + parent::_construct(); + } + + /** + * @param $customerId - the id of the customer + * @param $billingAddressHash - optional billing address hash + * @param $shippingAddressHash - optional shipping address hash + * @param $storeId + * + * @return Netresearch_OPS_Model_Mysql4_Alias_Collection - collection of aliases for given customer + */ + public function getAliasesForCustomer( + $customerId, + $billingAddressHash = null, + $shippingAddressHash = null, + $storeId = null + ) + { + $collection = $this->getCollection() + ->addFieldToFilter('customer_id', $customerId); + if (null != $billingAddressHash && null != $shippingAddressHash) { + $collection + ->addFieldToFilter('billing_address_hash', $billingAddressHash) + ->addFieldToFilter('shipping_address_hash', $shippingAddressHash) + ->addFieldToFilter('store_id', array('eq' => $storeId, 'null' => 'null')); + } + return $collection->load(); + } +} + diff --git a/app/code/community/Netresearch/OPS/Model/Alias/State.php b/app/code/community/Netresearch/OPS/Model/Alias/State.php new file mode 100644 index 0000000..4dfd83d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Alias/State.php @@ -0,0 +1,34 @@ + 30); + $http->setConfig($config); + $http->write(Zend_Http_Client::POST, $url, '1.1', array(), http_build_query($params)); + $response = $http->read(); + $response = substr($response, strpos($response, "__('Ingenico ePayments server is temporarily not available, please try again later.') + ); + } + + return $response; + } + + /** + * Performs a POST request to the Direct Link Gateway with the given + * parameters and returns the result parameters as array + * + * @param array $requestParams + * @param string $url + * @param int $storeId + * + * @return array + */ + public function performRequest($requestParams, $url, $storeId = 0) + { + $helper = Mage::helper('ops'); + $params = $this->getEncodedParametersWithHash( + array_merge( + $requestParams, + $this->buildAuthenticationParams($storeId) + ),//Merge Logic Operation Data with Authentication Data + null, + $storeId + ); + $responseParams = $this->getResponseParams($params, $url); + $helper->log( + $helper->__( + "Direct Link Request/Response in Ingenico ePayments \n\nRequest: %s\nResponse: %s\nMagento-URL: %s\nAPI-URL: %s", + serialize($params), + serialize($responseParams), + Mage::helper('core/url')->getCurrentUrl(), + $url + ) + ); + + $this->checkResponse($responseParams); + + return $responseParams; + + } + + public function getEncodedParametersWithHash($params, $shaCode=null, $storeId) + { + $hash = Mage::helper('ops/payment')->getSHASign($params, $shaCode, $storeId); + $params['SHASIGN'] = Mage::helper('ops/payment')->shaCrypt(iconv('iso-8859-1', 'utf-8', $hash)); + + return $params; + } + + + /** + * + * wraps the request and response handling and repeats request/response + * if there are errors + * + * @param array $params - request params + * @param string $url - the url for the request + * @param int $retryCount - current request count + * @return array | null - null if requests were not successful, array containing Ingenico ePayments payment data otherwise + * + */ + protected function getResponseParams($params, $url, $retryCount = 0) + { + $responseParams = null; + $responseXml = null; + if ($retryCount < self::MAX_RETRY_COUNT) { + try { + $responseXml = $this->call($params, $url); + $responseParams = $this->getParamArrFromXmlString($responseXml); + } catch (Exception $e) { + try { + $responseParams = $this->getParamArrFromXmlString(utf8_encode($responseXml)); + } catch (Exception $e) { + $ref = ''; + if (array_key_exists('ORDERID', $params)) { + $ref = $params['ORDERID']; + } elseif (array_key_exists('PAYID', $params)) { + $ref = $params['PAYID']; + } + Mage::helper('ops')->log( + 'DirectLink::getResponseParams failed: ' . + $e->getMessage() . ' current retry count: ' . $retryCount . ' for quote ' . $ref + ); + $responseParams = $this->getResponseParams($params, $url, ++$retryCount); + } + } + } else { + Mage::throwException(Mage::helper('ops')->__('An error occured during the Ingenico ePayments request. Your action could not be executed.')); + } + return $responseParams; + } + + /** + * Return Authentication Params for OPS Call + * + * @param int $storeId + * + * @return array + */ + protected function buildAuthenticationParams($storeId = 0) + { + return array( + 'PSPID' => Mage::getModel('ops/config')->getPSPID($storeId), + 'USERID' => Mage::getModel('ops/config')->getApiUserId($storeId), + 'PSWD' => Mage::getModel('ops/config')->getApiPswd($storeId), + ); + } + + /** + * Parses the XML-String to an array with the result data + * + * @param $xmlString + * @return mixed + * @throws Exception + */ + public function getParamArrFromXmlString($xmlString) + { + try { + $xml = new SimpleXMLElement($xmlString); + foreach ($xml->attributes() as $key => $value) { + $arrAttr[$key] = (string)$value; + } + foreach ($xml->children() as $child) { + $arrAttr[$child->getName()] = (string) $child; + } + return $arrAttr; + } catch (Exception $e) { + Mage::log('Could not convert string to xml in ' . __FILE__ . '::' . __METHOD__ . ': ' . $xmlString); + Mage::logException($e); + throw $e; + } + } + + /** + * Check if the Response from OPS reports Errors + * + * @param $responseParams + */ + public function checkResponse($responseParams) + { + if (false === is_array($responseParams) + || false === array_key_exists('NCERROR', $responseParams) + || $responseParams['NCERROR'] > 0 + ) + { + if (empty($responseParams['NCERRORPLUS'])) { + $responseParams['NCERRORPLUS'] = Mage::helper('ops')->__('Invalid payment information')." Errorcode:".$responseParams['NCERROR']; + } + + //avoid exception if STATUS is set with special values + if (isset($responseParams['STATUS']) && is_numeric($responseParams['STATUS'])) { + return; + } + + Mage::throwException( + Mage::helper('ops')->__("An error occured during the Ingenico ePayments request. Your action could not be executed. Message: '%s.'", $responseParams['NCERRORPLUS']) + ); + } + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Backend/Operation/Capture/Additional/OpenInvoiceNl.php b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Capture/Additional/OpenInvoiceNl.php new file mode 100644 index 0000000..b1c69ce --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Capture/Additional/OpenInvoiceNl.php @@ -0,0 +1,15 @@ + + * @category Netresearch + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Model_Backend_Operation_Capture_Additional_OpenInvoiceNl + extends Netresearch_OPS_Model_Backend_Operation_Parameter_Additional_OpenInvoiceNlAbstract +{ + /** + * @see Netresearch_OPS_Model_Backend_Operation_Parameter_Additional_OpenInvoiceNlAbstract + */ +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Backend/Operation/Capture/Parameter.php b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Capture/Parameter.php new file mode 100644 index 0000000..be05201 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Capture/Parameter.php @@ -0,0 +1,51 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Model_Backend_Operation_Capture_Parameter + extends Netresearch_OPS_Model_Backend_Operation_Parameter_Abstract +{ + /** + * checks whether we need to retrieve additional parameter for the capture request or not + * + * @param Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod + * + * @return bool - true if we need to retrieve any additional parameters, false otherwise + */ + protected function isPmRequiringAdditionalParams(Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod) + { + $opsPaymentMethodClass = get_class($opsPaymentMethod); + $opsPmsRequiringSpecialParams = $this->getOpsConfig() + ->getMethodsRequiringAdditionalParametersFor( + Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_TRANSACTION_TYPE + ); + + return (in_array($opsPaymentMethodClass, array_values($opsPmsRequiringSpecialParams))); + } + + /** + * sets the model which retrieves the additional params + * + * @param Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod + */ + protected function setAdditionalParamsModelFor(Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod) + { + if ($opsPaymentMethod instanceof Netresearch_OPS_Model_Payment_OpenInvoice_Abstract) { + $this->additionalParamsModel = Mage::getModel('ops/backend_operation_capture_additional_openInvoiceNl'); + } + } + + /** + * Returns the order helper for the corresponding transaction type + * + * @return Netresearch_OPS_Helper_Order_Abstract + */ + public function getOrderHelper() + { + return Mage::helper('ops/order_capture'); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter.php b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter.php new file mode 100644 index 0000000..dd406f2 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter.php @@ -0,0 +1,69 @@ + + * @category Netresearch + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Model_Backend_Operation_Parameter +{ + protected $parameterModel = null; + + protected $dataHelper = null; + + /** + * retrieves the neccessary parameter for the given operation + * + * @param $operation + * @param Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod + * @param Varien_Object $payment + * @param $amount + * + * @return array + */ + public function getParameterFor( + $operation, + Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod, + Varien_Object $payment, + $amount + ) + { + return $this->getParameterModel($operation)->getRequestParams($opsPaymentMethod, $payment, $amount); + } + + /** + * retrieves the parameter model for the given operation + * + * @param $operation - the operation we need the parameters for + * + * @throws Mage_Core_Exception - in case the operation is not supported + * @return Netresearch_OPS_Model_Backend_Operation_Parameter_Interface - the model for the parameters of the + * operation + */ + protected function getParameterModel($operation) + { + if ($operation === Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_TRANSACTION_TYPE) { + return Mage::getModel('ops/backend_operation_capture_parameter'); + } + if ($operation === Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_TRANSACTION_TYPE) { + return Mage::getModel('ops/backend_operation_refund_parameter'); + } + + Mage::throwException($this->getDataHelper()->__('operation %s not supported', $operation)); + } + + /** + * retrieves the data helper + * + * @return Netresearch_OPS_Helper_Data|null + */ + protected function getDataHelper() + { + if (null == $this->dataHelper) { + $this->dataHelper = Mage::helper('ops/data'); + } + + return $this->dataHelper; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Abstract.php b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Abstract.php new file mode 100644 index 0000000..b94cfb6 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Abstract.php @@ -0,0 +1,133 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +abstract class Netresearch_OPS_Model_Backend_Operation_Parameter_Abstract + implements Netresearch_OPS_Model_Backend_Operation_Parameter_Interface +{ + protected $requestParams = array(); + + protected $opsConfig = null; + protected $dataHelper = null; + + protected $additionalParamsModel = null; + + /** + * @param Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod + * @param $payment + * @param $amount + * + * @return array + */ + public function getRequestParams( + Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod, + Varien_Object $payment, + $amount + ) + { + $this->getBaseParams($opsPaymentMethod, $payment, $amount); + $this->addPmSpecificParams($opsPaymentMethod, $payment, $amount); + + return $this->requestParams; + } + + /** + * retrieves the basic parameters for a capture call + * + * @param Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod + * @param Varien_Object $payment + * @param $amount + * + * @return $this + */ + protected function getBaseParams( + Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod, + Varien_Object $payment, + $amount + ) + { + $this->requestParams['AMOUNT'] = $this->getDataHelper()->getAmount($amount); + $this->requestParams['PAYID'] = $payment->getAdditionalInformation('paymentId'); + $this->requestParams['OPERATION'] = $this->getOrderHelper()->determineOperationCode($payment, $amount); + $this->requestParams['CURRENCY'] = Mage::app()->getStore($payment->getOrder()->getStoreId()) + ->getBaseCurrencyCode(); + + return $this; + } + + /** + * retrieves ops config model + * + * @return Netresearch_OPS_Model_Config + */ + protected function getOpsConfig() + { + if (null === $this->opsConfig) { + $this->opsConfig = Mage::getModel('ops/config'); + } + + return $this->opsConfig; + } + + /** + * if we have to add payment specific paramters to our request, we'll set them here + * + * @param $opsPaymentMethod + * @param $payment + * @param $amount + * + * @return $this + */ + protected function addPmSpecificParams(Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod, + Varien_Object $payment, $amount + ) + { + if ($this->isPmRequiringAdditionalParams($opsPaymentMethod)) { + $this->setAdditionalParamsModelFor($opsPaymentMethod); + if ($this->additionalParamsModel instanceof + Netresearch_OPS_Model_Backend_Operation_Parameter_Additional_Interface + ) { + $params = $this->additionalParamsModel->extractAdditionalParams($payment); + $this->requestParams = array_merge($this->requestParams, $params); + } + } + + return $this; + } + + protected function isPmRequiringAdditionalParams(Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod) + { + return false; + } + + protected function setAdditionalParamsModelFor(Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod) + { + $this->additionalParamsModel = null; + } + + /** + * retrieves the data helper + * + * @return Netresearch_OPS_Helper_Data|null + */ + protected function getDataHelper() + { + if (null == $this->dataHelper) { + $this->dataHelper = Mage::helper('ops/data'); + } + + return $this->dataHelper; + } + + /** + * Returns the order helper for the corresponding transaction type + * + * @return Netresearch_OPS_Helper_Order_Abstract + */ + public abstract function getOrderHelper(); + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Additional/Interface.php b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Additional/Interface.php new file mode 100644 index 0000000..c4daeef --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Additional/Interface.php @@ -0,0 +1,11 @@ + +* @category Netresearch +* @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +*/ +interface Netresearch_OPS_Model_Backend_Operation_Parameter_Additional_Interface +{ + public function extractAdditionalParams(Mage_Sales_Model_Order_Payment $payment); +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Additional/OpenInvoiceNlAbstract.php b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Additional/OpenInvoiceNlAbstract.php new file mode 100644 index 0000000..864ef13 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Additional/OpenInvoiceNlAbstract.php @@ -0,0 +1,151 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +abstract class Netresearch_OPS_Model_Backend_Operation_Parameter_Additional_OpenInvoiceNlAbstract + implements Netresearch_OPS_Model_Backend_Operation_Parameter_Additional_Interface +{ + protected $_additionalParams = array(); + protected $_opsDataHelper = null; + protected $_itemIdx = 1; + + + /** + * @param Mage_Sales_Model_Order_Payment $payment + * + * @return array + */ + public function extractAdditionalParams(Mage_Sales_Model_Order_Payment $payment) + { + $itemContainer = $payment->getInvoice(); + if ($itemContainer instanceof Mage_Sales_Model_Order_Invoice) { + $requestHelper = Mage::helper('ops/payment_request'); + $this->_additionalParams = $requestHelper->extractOrderItemParameters($itemContainer); + } + + return $this->_additionalParams; + } + + /** + * @deprecated Netresearch_OPS_Helper_Payment_Request should be used instead + * + * extracts all necessary data from the invoice items + * + * @param Mage_Sales_Model_Order_Invoice $invoice + * + * @return Netresearch_OPS_Model_Backend_Operation_Parameter_Additional_Interface + */ + protected function extractFromInvoiceItems(Mage_Sales_Model_Order_Invoice $invoice) + { + foreach ($invoice->getItemsCollection() as $item) { + /** @var $item Mage_Sales_Model_Order_Invoice_Item */ + // filter out configurable products + if (!$this->isDataItem($item)) { + continue; + } + $this->_additionalParams['ITEMID' . $this->_itemIdx] = substr($item->getOrderItemId(), 0, 15); + $this->_additionalParams['ITEMNAME' . $this->_itemIdx] = substr($item->getName(), 0, 30); + $this->_additionalParams['ITEMPRICE' . $this->_itemIdx] = $this->getOpsDataHelper()->getAmount( + $item->getBasePriceInclTax() + ); + $this->_additionalParams['ITEMQUANT' . $this->_itemIdx] = $item->getQty(); + $this->_additionalParams['ITEMVATCODE' . $this->_itemIdx] + = str_replace(',', '.', (string)(float)$item->getTaxPercent()) . '%'; + $this->_additionalParams['TAXINCLUDED' . $this->_itemIdx] = 1; + ++$this->_itemIdx; + } + + return $this; + } + + + /** + * extract the necessary data from the shipping data of the invoice + * + * @deprecated Netresearch_OPS_Helper_Payment_Request should be used instead + * + * @param Mage_Sales_Model_Order_Invoice $invoice + * + * @return $this + */ + protected function extractFromInvoicedShippingMethod(Mage_Sales_Model_Order_Invoice $invoice) + { + $amount = $invoice->getBaseShippingInclTax(); + if (0 < $amount) { + $this->_additionalParams['ITEMID' . $this->_itemIdx] = 'SHIPPING'; + $this->_additionalParams['ITEMNAME' . $this->_itemIdx] + = substr($invoice->getOrder()->getShippingDescription(), 0, 30); + $this->_additionalParams['ITEMPRICE' . $this->_itemIdx] = $this->getOpsDataHelper()->getAmount($amount); + $this->_additionalParams['ITEMQUANT' . $this->_itemIdx] = 1; + $this->_additionalParams['ITEMVATCODE' . $this->_itemIdx] = $this->getShippingTaxRate($invoice) . '%'; + $this->_additionalParams['TAXINCLUDED' . $this->_itemIdx] = 1; + ++$this->_itemIdx; + } + + + return $this; + } + + /** + * @deprecated Netresearch_OPS_Helper_Payment_Request should be used instead + * + * retrieves used shipping tax rate + * + * @param Mage_Sales_Model_Order_Invoice $invoice + * + * @return float + */ + protected function getShippingTaxRate(Mage_Sales_Model_Order_Invoice $invoice) + { + $taxRate = 0.0; + $order = $invoice->getOrder(); + + $taxRate = (floatval(Mage::helper('ops/payment_request')->getShippingTaxRate($order))); + + return $taxRate; + } + + + /** + * gets the ops data helper + * + * @return Netresearch_OPS_Helper_Data + */ + protected function getOpsDataHelper() + { + if (null === $this->_opsDataHelper) { + $this->_opsDataHelper = Mage::helper('ops/data'); + } + + return $this->_opsDataHelper; + } + + + /** + * @deprecated Netresearch_OPS_Helper_Payment_Request should be used instead + * + * @param $invoice + */ + protected function extractFromDiscountData($invoice) + { + $amount = $invoice->getBaseDiscountAmount(); + if (0 > $amount) { + $couponRuleName = 'DISCOUNT'; + $order = $invoice->getOrder(); + if ($order->getCouponRuleName() && strlen(trim($order->getCouponRuleName())) > 0) { + $couponRuleName = substr(trim($order->getCouponRuleName()), 0, 30); + } + $this->_additionalParams['ITEMID' . $this->_itemIdx] = 'DISCOUNT'; + $this->_additionalParams['ITEMNAME' . $this->_itemIdx] = $couponRuleName; + $this->_additionalParams['ITEMPRICE' . $this->_itemIdx] = $this->getOpsDataHelper()->getAmount($amount); + $this->_additionalParams['ITEMQUANT' . $this->_itemIdx] = 1; + $this->_additionalParams['ITEMVATCODE' . $this->_itemIdx] = $this->getShippingTaxRate($invoice) . '%'; + $this->_additionalParams['TAXINCLUDED' . $this->_itemIdx] = 1; + ++$this->_itemIdx; + } + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Interface.php b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Interface.php new file mode 100644 index 0000000..fc80ff7 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Parameter/Interface.php @@ -0,0 +1,17 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +interface Netresearch_OPS_Model_Backend_Operation_Parameter_Interface +{ + public function getRequestParams( + Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod, + Varien_Object $payment, + $amount + ); + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Backend/Operation/Refund/Additional/OpenInvoiceNl.php b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Refund/Additional/OpenInvoiceNl.php new file mode 100644 index 0000000..ed24113 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Refund/Additional/OpenInvoiceNl.php @@ -0,0 +1,138 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +class Netresearch_OPS_Model_Backend_Operation_Refund_Additional_OpenInvoiceNl + extends Netresearch_OPS_Model_Backend_Operation_Parameter_Additional_OpenInvoiceNlAbstract +{ + /** @var $creditmemo Mage_Sales_Model_Order_Creditmemo */ + protected $creditmemo = null; + protected $amount = 0; + protected $refundHelper = null; + + /** + * @param Mage_Sales_Model_Order_Payment $itemContainer + * @return array + */ + public function extractAdditionalParams(Mage_Sales_Model_Order_Payment $payment = null) + { + $invoice = $payment->getInvoice(); + + if ($invoice == null) { + // if invoice is not set we load id hard from the request params + $invoice = $this->getRefundHelper()->getInvoiceFromCreditMemoRequest(); + } + $this->creditmemo = $payment->getCreditmemo(); + + if ($invoice instanceof Mage_Sales_Model_Order_Invoice) { + $this->extractFromCreditMemoItems($invoice); + // We dont extract from discount data for the moment, because partial refunds are a problem + $this->extractFromInvoicedShippingMethod($invoice); + $this->extractFromAdjustments($invoice); + // Overwrite amount to fix Magentos rounding problems (eg +1ct) + $this->_additionalParams['AMOUNT'] = $this->amount; + } + + return $this->_additionalParams; + } + + /** + * extracts all data from the invoice according to the credit memo items + * + */ + protected function extractFromCreditMemoItems() + { + /** @var Mage_Sales_Model_Order_Creditmemo_Item $item */ + foreach ($this->creditmemo->getAllItems() as $item) { + if ($item->getOrderItem()->getParentItemId() + && $item->getOrderItem()->getParentItem()->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE + || $item->getOrderItem()->getProductType() == Mage_Catalog_Model_Product_Type::TYPE_BUNDLE + ) { + continue; + } + $this->_additionalParams['ITEMID' . $this->_itemIdx] = substr($item->getOrderItemId(), 0, 15); + $this->_additionalParams['ITEMNAME' . $this->_itemIdx] = substr($item->getName(), 0, 30); + $this->_additionalParams['ITEMPRICE' . $this->_itemIdx] = $this->getOpsDataHelper()->getAmount( + $item->getBasePriceInclTax() + ); + $this->amount += $this->getOpsDataHelper() + ->getAmount($item->getBasePriceInclTax()) * $item->getQty(); + $this->_additionalParams['ITEMQUANT' . $this->_itemIdx] = $item->getQty(); + $this->_additionalParams['ITEMVATCODE' . $this->_itemIdx] + = str_replace(',', '.', (string)(float)$item->getOrderItem()->getTaxPercent()) . '%'; + $this->_additionalParams['TAXINCLUDED' . $this->_itemIdx] = 1; + ++$this->_itemIdx; + } + + } + + + protected function extractFromInvoicedShippingMethod(Mage_Sales_Model_Order_Invoice $invoice) + { + if ($this->creditmemo->getBaseShippingInclTax() > 0) { + $this->_additionalParams['ITEMID' . $this->_itemIdx] = 'SHIPPING'; + $this->_additionalParams['ITEMNAME' . $this->_itemIdx] = + substr($invoice->getOrder()->getShippingDescription(), 0, 30); + $this->_additionalParams['ITEMPRICE' . $this->_itemIdx] = $this->getOpsDataHelper() + ->getAmount($this->creditmemo->getBaseShippingInclTax()); + $this->amount += $this->getOpsDataHelper()->getAmount($this->creditmemo->getBaseShippingInclTax()); + $this->_additionalParams['ITEMQUANT' . $this->_itemIdx] = 1; + $this->_additionalParams['ITEMVATCODE' . $this->_itemIdx] = $this->getShippingTaxRate($invoice) . '%'; + $this->_additionalParams['TAXINCLUDED' . $this->_itemIdx] = 1; + ++$this->_itemIdx; + } + + } + + /** + * extracts all data from the adjustment fee/refund + * + * @param $invoice + */ + protected function extractFromAdjustments(Mage_Sales_Model_Order_Invoice $invoice) + { + + if ($this->creditmemo->getBaseAdjustmentPositive() > 0) { + $this->_additionalParams['ITEMID' . $this->_itemIdx] = 'ADJUSTREFUND'; + $this->_additionalParams['ITEMNAME' . $this->_itemIdx] = 'Adjustment Refund'; + $this->_additionalParams['ITEMPRICE' . $this->_itemIdx] = $this->getOpsDataHelper() + ->getAmount($this->creditmemo->getBaseAdjustmentPositive()); + $this->amount += $this->getOpsDataHelper()->getAmount($this->creditmemo->getBaseAdjustmentPositive()); + $this->_additionalParams['ITEMQUANT' . $this->_itemIdx] = 1; + $this->_additionalParams['ITEMVATCODE' . $this->_itemIdx] = $this->getShippingTaxRate($invoice) . '%'; + $this->_additionalParams['TAXINCLUDED' . $this->_itemIdx] = 1; + ++$this->_itemIdx; + + } + if ($this->creditmemo->getBaseAdjustmentNegative() > 0) { + $this->_additionalParams['ITEMID' . $this->_itemIdx] = 'ADJUSTFEE'; + $this->_additionalParams['ITEMNAME' . $this->_itemIdx] = 'Adjustment Fee'; + $this->_additionalParams['ITEMPRICE' . $this->_itemIdx] = $this->getOpsDataHelper() + ->getAmount(-$this->creditmemo->getBaseAdjustmentNegative()); + $this->amount += $this->getOpsDataHelper()->getAmount(-$this->creditmemo->getBaseAdjustmentNegative()); + $this->_additionalParams['ITEMQUANT' . $this->_itemIdx] = 1; + $this->_additionalParams['ITEMVATCODE' . $this->_itemIdx] = $this->getShippingTaxRate($invoice) . '%'; + $this->_additionalParams['TAXINCLUDED' . $this->_itemIdx] = 1; + ++$this->_itemIdx; + } + } + + /** + * gets the refund helper + * + * @return Netresearch_OPS_Helper_Order_Refund|null + */ + protected function getRefundHelper() + { + if (null === $this->refundHelper) { + $this->refundHelper = Mage::helper('ops/order_refund'); + } + + return $this->refundHelper; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Backend/Operation/Refund/Parameter.php b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Refund/Parameter.php new file mode 100644 index 0000000..edb6266 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Backend/Operation/Refund/Parameter.php @@ -0,0 +1,70 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Model_Backend_Operation_Refund_Parameter + extends Netresearch_OPS_Model_Backend_Operation_Parameter_Abstract +{ + /** + * checks whether we need to retrieve additional parameter for the refund request or not + * + * @param Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod + * + * @return bool - true if we need to retrieve any additional parameters, false otherwise + */ + protected function isPmRequiringAdditionalParams(Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod) + { + $opsPaymentMethodClass = get_class($opsPaymentMethod); + $opsPmsRequiringSpecialParams = $this->getOpsConfig() + ->getMethodsRequiringAdditionalParametersFor( + Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_TRANSACTION_TYPE + ); + + return (in_array($opsPaymentMethodClass, array_values($opsPmsRequiringSpecialParams))); + } + + /** + * sets the model which retrieves the additional params for the refund request + * + * @param Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod + */ + protected function setAdditionalParamsModelFor(Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod) + { + if ($opsPaymentMethod instanceof Netresearch_OPS_Model_Payment_OpenInvoice_Abstract) { + $this->additionalParamsModel = Mage::getModel('ops/backend_operation_refund_additional_openInvoiceNl'); + } + } + + protected function addPmSpecificParams(Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod, + Varien_Object $payment, $amount + ) + { + if ($this->isPmRequiringAdditionalParams($opsPaymentMethod)) { + $this->setAdditionalParamsModelFor($opsPaymentMethod); + if ($this->additionalParamsModel instanceof + Netresearch_OPS_Model_Backend_Operation_Parameter_Additional_Interface + ) { + $params = $this->additionalParamsModel->extractAdditionalParams($payment); + $this->requestParams = array_merge($this->requestParams, $params); + } + } + + return $this; + } + + /** + * Returns the order helper for the corresponding transaction type + * + * @return Netresearch_OPS_Helper_Order_Abstract + */ + public function getOrderHelper() + { + return Mage::helper('ops/order_refund'); + } + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Config.php b/app/code/community/Netresearch/OPS/Model/Config.php new file mode 100644 index 0000000..4030912 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Config.php @@ -0,0 +1,976 @@ +decrypt($this->getConfigData('secret_key_in', $storeId)); + } + + /** + * Return SHA1-out crypt key from config. Setup on admin place. + * + * @param int $storeId + * + * @return string + */ + public function getShaOutCode($storeId = null) + { + return Mage::helper('core')->decrypt($this->getConfigData('secret_key_out', $storeId)); + } + + /** + * Return frontend gateway path, get from config. Setup on admin place. + * + * @param string $path + * @param int $storeId + * + * @return string + */ + public function getFrontendGatewayPath($storeId = null) + { + return $this->determineOpsUrl('frontend_gateway', $storeId); + } + + /** + * Return Direct Link Gateway path, get from config. Setup on admin place. + * + * @param int $storeId + * + * @return string + */ + public function getDirectLinkGatewayPath($storeId = null) + { + return $this->determineOpsUrl('directlink_gateway', $storeId); + } + + public function getDirectLinkGatewayOrderPath($storeId = null) + { + return $this->determineOpsUrl('directlink_gateway_order', $storeId); + } + + /** + * Return API User, get from config. Setup on admin place. + * + * @param int $storeId + * + * @return string + */ + public function getApiUserId($storeId = null) + { + return $this->getConfigData('api_userid', $storeId); + } + + /** + * Return API Passwd, get from config. Setup on admin place. + * + * @param int $storeId + * + * @return string + */ + public function getApiPswd($storeId = null) + { + return Mage::helper('core')->decrypt($this->getConfigData('api_pswd', $storeId)); + } + + /** + * Get PSPID, affiliation name in ops system + * + * @param int $storeId + * + * @return string + */ + public function getPSPID($storeId = null) + { + return $this->getConfigData('pspid', $storeId); + } + + public function getPaymentAction($storeId = null) + { + return $this->getConfigData('payment_action', $storeId); + } + + /** + * Get paypage template for magento style templates using + * + * @return string + */ + public function getPayPageTemplate() + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'paypage', + array('_nosid' => true, '_secure' => $this->isCurrentlySecure()) + ); + } + + /** + * Return url which ops system will use as accept + * + * @return string + */ + public function getAcceptUrl() + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'accept', + array('_nosid' => true, '_secure' => $this->isCurrentlySecure()) + ); + } + + /** + * Return url which ops system will use as accept for alias generation + * + * @param int $storeId + * @param bool $admin + * + * @return string + */ + public function getAliasAcceptUrl($storeId = null, $admin = false) + { + $params = array( + '_secure' => $this->isCurrentlySecure(), + '_nosid' => true + ); + if (null != $storeId) { + $params['_store'] = $storeId; + } + + if ($admin) { + $params['_nosecret'] = true; + + return Mage::getModel('adminhtml/url')->getUrl('adminhtml/alias/accept', $params); + } else { + return Mage::getModel('core/url')->getUrl(self::OPS_CONTROLLER_ROUTE_ALIAS . 'accept', $params); + } + } + + /** + * Return url which ops system will use as decline url + * + * @return string + */ + public function getDeclineUrl() + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'decline', + array('_nosid' => true, '_secure' => $this->isCurrentlySecure()) + ); + } + + /** + * Return url which ops system will use as exception url + * + * @return string + */ + public function getExceptionUrl() + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'exception', + array('_nosid' => true, '_secure' => $this->isCurrentlySecure()) + ); + } + + /** + * Return url which ops system will use as exception url for alias generation + * + * @param int $storeId + * @param bool $admin + * + * @return string + */ + public function getAliasExceptionUrl($storeId = null, $admin = false) + { + $params = array( + '_secure' => $this->isCurrentlySecure(), + '_nosid' => true + ); + if (null !=$storeId) { + $params['_store'] = $storeId; + } + if ($admin) { + $params['_nosecret'] = true; + + return Mage::getModel('adminhtml/url')->getUrl('adminhtml/alias/exception', $params); + } else { + return Mage::getModel('core/url')->getUrl(self::OPS_CONTROLLER_ROUTE_ALIAS . 'exception', $params); + } + } + + /** + * Return url which ops system will use as cancel url + * + * @return string + */ + public function getCancelUrl() + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'cancel', + array('_nosid' => true, '_secure' => $this->isCurrentlySecure()) + ); + } + + /** + * Return url which ops system will use as continue shopping url + * + * @param array $redirect + * + * @return string + */ + public function getContinueUrl($redirect = array()) + { + $urlParams = array('_nosid' => true, '_secure' => $this->isCurrentlySecure()); + if (!empty($redirect)) { + $urlParams = array_merge($redirect, $urlParams); + } + + return Mage::getUrl(self::OPS_CONTROLLER_ROUTE_PAYMENT . 'continue', $urlParams); + } + + /** + * Return url to redirect after confirming the order + * + * @return string + */ + public function getPaymentRedirectUrl() + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'placeform', + array('_secure' => true, '_nosid' => true) + ); + } + + /** + * Return 3D Secure url to redirect after confirming the order + * + * @return string + */ + public function get3dSecureRedirectUrl() + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'placeform3dsecure', + array('_secure' => true, '_nosid' => true) + ); + } + + public function getSaveCcBrandUrl() + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'saveCcBrand', + array('_secure' => $this->isCurrentlySecure(), '_nosid' => true) + ); + } + + public function getGenerateHashUrl($storeId = null, $admin = false) + { + $params = array( + '_secure' => $this->isCurrentlySecure(), + '_nosid' => true, + ); + if (null != $storeId) { + $params['_store'] = $storeId; + } + if ($admin) { + $params['_nosecret'] = true; + + return Mage::getModel('adminhtml/url')->getUrl('adminhtml/alias/generatehash', $params); + } else { + return Mage::getModel('core/url')->getUrl(self::OPS_CONTROLLER_ROUTE_ALIAS . 'generatehash', $params); + } + + } + + /** + * Checks if requests should be logged or not regarding configuration + * + * @param null $storeId + * @return Simple_Xml + */ + public function shouldLogRequests($storeId = null) + { + return $this->getConfigData('debug_flag', $storeId); + } + + /** + * @return mixed + */ + public function hasCatalogUrl() + { + return Mage::getStoreConfig('payment_services/ops/showcatalogbutton'); + } + + /** + * @return mixed + */ + public function hasHomeUrl() + { + return Mage::getStoreConfig('payment_services/ops/showhomebutton'); + } + + /** + * @param $code + * @return mixed + */ + public function getAcceptedCcTypes($code) + { + return Mage::getStoreConfig('payment/' . $code . '/types'); + } + + /** + * Returns the cc types for which inline payments are activated + * + * @param string $code + * + * @return string[] + */ + public function getInlinePaymentCcTypes($code) + { + $redirectAll = (bool)(int)Mage::getStoreConfig('payment/' . $code . '/redirect_all'); + if ($redirectAll) { + return array(); + } + + $inlineTypes = Mage::getStoreConfig('payment/' . $code . '/inline_types'); + if (false == is_array($inlineTypes)) { + $inlineTypes = explode(',', $inlineTypes); + } + + return $inlineTypes; + } + + /** + * @return mixed + */ + public function get3dSecureIsActive() + { + return Mage::getStoreConfig('payment/ops_cc/enabled_3dsecure'); + } + + /** + * @return mixed + */ + public function getDirectDebitCountryIds() + { + return Mage::getStoreConfig('payment/ops_directDebit/countryIds'); + } + + /** + * @return mixed + */ + public function getBankTransferCountryIds() + { + return Mage::getStoreConfig('payment/ops_bankTransfer/countryIds'); + } + + /** + * @return mixed + */ + public function getDirectEbankingBrands() + { + return Mage::getStoreConfig('payment/ops_directEbanking/brands'); + } + + /** + * Returns the generated alias (hosted tokenization) url or the special url if needed by vendor + * + * @param null $storeId + * + * @return mixed|Simple_Xml|string + */ + public function getAliasGatewayUrl($storeId = null) + { + $url = $this->determineOpsUrl('ops_alias_gateway', $storeId); + + if ($this->getConfigData('ops_alias_gateway_test') != '') { + if ($this->getMode($storeId) == Netresearch_OPS_Model_Source_Mode::TEST) { + return $this->getConfigData('ops_alias_gateway_test'); + } elseif ($this->getMode($storeId) == Netresearch_OPS_Model_Source_Mode::PROD) { + $url = str_replace('ncol/prod/', '', $url); + } + } + + return $url; + } + + /** + * @param null $storeId + * @param bool $admin + * @return mixed + */ + public function getCcSaveAliasUrl($storeId = null, $admin = false) + { + $params = array( + '_secure' => $this->isCurrentlySecure() + ); + if (false === is_null($storeId)) { + $params['_store'] = $storeId; + } + if ($admin) { + return Mage::getModel('adminhtml/url')->getUrl('ops/admin/saveAlias', $params); + } else { + return Mage::getUrl('ops/alias/save', $params); + } + } + + /** + * get deeplink to transaction view at Ingenico ePayments + * + * @param Mage_Sales_Model_Order_Payment $payment + * + * @return string + */ + public function getOpsAdminPaymentUrl($payment) + { + return ''; + } + + public function isCurrentlySecure() + { + return Mage::app()->getStore()->isCurrentlySecure(); + } + + public function getIntersolveBrands($storeId = null) + { + $result = array(); + $brands = Mage::getStoreConfig('payment/ops_interSolve/brands', $storeId); + if (null !=$brands) { + $result = unserialize($brands); + } + + return $result; + } + + /** + * @param int $storeId + * + * @return string[][] + */ + public function getFlexMethods($storeId = null) + { + $result = array(); + $methods = Mage::getStoreConfig('payment/ops_flex/methods', $storeId); + if (null !=$methods) { + $result = unserialize($methods); + } + + return $result; + } + + /** + * @return array + */ + public function getAllCcTypes() + { + return explode(',', Mage::getStoreConfig('payment/ops_cc/availableTypes')); + } + + + /** + * @return array + */ + public function getAllDcTypes() + { + return explode(',', Mage::getStoreConfig('payment/ops_dc/availableTypes')); + } + + /** + * get keys of parameters to be shown in scoring information block + * + * @return array + */ + public function getAdditionalScoringKeys() + { + return array( + 'AAVCHECK', + 'ACCEPTANCE', + 'CVCCHECK', + 'CCCTY', + 'IPCTY', + 'NBREMAILUSAGE', + 'NBRIPUSAGE', + 'NBRIPUSAGE_ALLTX', + 'NBRUSAGE', + 'VC', + 'CARDNO', + 'ED', + 'CN' + ); + } + + /** + * @return bool + */ + public function getSendInvoice() + { + return (bool)(int)Mage::getStoreConfig('payment_services/ops/send_invoice'); + } + + /** + * if payment method with given code is enabled for backend payments + * + * @param string $code Payment method code + * + * @return bool + */ + public function isEnabledForBackend($code, $storeId = 0) + { + return (bool)(int)Mage::getStoreConfig('payment/' . $code . '/backend_enabled', $storeId); + } + + /** + * @return bool + */ + public function isAliasInfoBlockEnabled() + { + return (bool)(int)Mage::getStoreConfig('payment/ops_cc/show_alias_manager_info_for_guests'); + } + + /** + * return config value for Alias Manager enabled + * + * @param $code + * @param $storeId + * + * @return bool + */ + public function isAliasManagerEnabled($code, $storeId = null) + { + return (bool)Mage::getStoreConfig('payment/' . $code . '/active_alias', $storeId); + } + + /** + * return configured text for alias usage parameter for new alias creation + * + * @param $code + * @param null $storeId + * + * @return string + */ + public function getAliasUsageForNewAlias($code, $storeId = null) + { + return Mage::getStoreConfig('payment/' . $code . '/alias_usage_for_new_alias', $storeId); + } + + /** + * return configured text for alias usage parameter when using a existing alias + * + * @param $code + * @param null $storeId + * + * @return string + */ + public function getAliasUsageForExistingAlias($code, $storeId = null) + { + return Mage::getStoreConfig('payment/' . $code . '/alias_usage_for_existing_alias', $storeId); + } + + + /** + * getter for usage of order reference + */ + public function getOrderReference($storeId = null) + { + return $this->getConfigData('redirectOrderReference', $storeId); + } + + /** + * @param int $storeId - the store id to use + * + * @return int whether the QuoteId should be shown in + * the order grid (1) or not (0) + */ + public function getShowQuoteIdInOrderGrid($storeId = null) + { + return $this->getConfigData('showQuoteIdInOrderGrid', $storeId); + } + + /** + * Check if the current environment is frontend or backend + * + * @return boolean + */ + public function isFrontendEnvironment() + { + return (false === Mage::app()->getStore()->isAdmin()); + } + + /** + * getter for the accept route for payments + * + * @return string + */ + public function getAcceptRedirectRoute() + { + return self::OPS_CONTROLLER_ROUTE_PAYMENT . 'accept'; + } + + /** + * getter for the cancel route for payments + * + * @return string + */ + public function getCancelRedirectRoute() + { + return self::OPS_CONTROLLER_ROUTE_PAYMENT . 'cancel'; + } + + /** + * getter for the decline route for payments + * + * @return string + */ + public function getDeclineRedirectRoute() + { + return self::OPS_CONTROLLER_ROUTE_PAYMENT . 'decline'; + } + + /** + * getter for the decline route for payments + * + * @return string + */ + public function getExceptionRedirectRoute() + { + return self::OPS_CONTROLLER_ROUTE_PAYMENT . 'exception'; + } + + + /** + * @param $operation + * @return mixed + */ + public function getMethodsRequiringAdditionalParametersFor($operation) + { + return Mage::getStoreConfig('payment/additional_params_required/' . $operation); + } + + + /** + * returns the url for the maintenance api calls + * + * @param null $storeId + * + * @return string - the url for the maintenance api calls + */ + public function getDirectLinkMaintenanceApiPath($storeId = null) + { + return $this->determineOpsUrl('directlink_maintenance_api', $storeId); + } + + /** + * getter for the iDeal issuers + * + * @return array + */ + public function getIDealIssuers() + { + return Mage::getStoreConfig('payment/ops_iDeal/issuer'); + } + + /** + * whether extra parameters needs to be passed to Ingenico ePayments or not + * + * @param null $storeId + * + * @return bool - true if it's enabled, false otherwise + */ + public function canSubmitExtraParameter($storeId = null) + { + return (bool)$this->getConfigData('submitExtraParameters', $storeId); + } + + public function getParameterLengths() + { + return $this->getConfigData('paramLength'); + } + + /** + * @return Simple_Xml + */ + public function getFrontendFieldMapping() + { + return $this->getConfigData('frontendFieldMapping'); + + } + + /** + * @return mixed + */ + public function getValidationUrl() + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'validate', + array('_secure' => $this->isCurrentlySecure(), '_nosid' => true) + ); + } + + /** + * @param null $storeId + * @return Simple_Xml + */ + public function getInlineOrderReference($storeId = null) + { + return $this->getConfigData('inlineOrderReference', $storeId); + } + + /** + * Returns the mode of the store + * + * @param null $storeId + * + * @return string | mode (custom, prod, test) for the store + * @see Netresearch_OPS_Model_Source_Mode + */ + public function getMode($storeId = null) + { + return $this->getConfigData('mode', $storeId); + } + + protected function getOpsUrl($path) + { + return $this->getConfigData('url/' . $path); + } + + /** + * Will always return the base url (https://secure.domain.tld/ncol/[test, prod]) for the mode of the store + * + * @return string Url depending of the mode - will be empty for custom mode + */ + public function getOpsBaseUrl($storeId = null) + { + return $this->getOpsUrl('base_' . $this->getMode($storeId)); + } + + /** + * Returns the default url for the given gateway, depending on the mode, that is set for the the given store + * + * @param string $path + * @param int $storeId + * + * @return string + */ + public function getDefaultOpsUrl($path, $storeId = null) + { + return $this->getOpsBaseUrl($storeId) . $this->getOpsUrl($path); + } + + /** + * Returns the url for the given gateway depending on the set mode for the given store + * + * @param $path + * @param null $storeId + * + * @return string + */ + public function determineOpsUrl($path, $storeId = null) + { + if ($this->getMode($storeId) === Netresearch_OPS_Model_Source_Mode::CUSTOM) { + return $this->getConfigData($path, $storeId); + } else { + return $this->getDefaultOpsUrl($path, $storeId); + } + } + + /** + * @param null $storeId + * @return Simple_Xml + */ + public function getTemplateIdentifier($storeId = null) + { + return $this->getConfigData('template_identifier', $storeId); + } + + /** + * @param null $storeId + * @return Simple_Xml + */ + public function getResendPaymentInfoIdentity($storeId = null) + { + return $this->getConfigData('resendPaymentInfo_identity', $storeId); + } + + /** + * @param null $storeId + * @return Simple_Xml + */ + public function getResendPaymentInfoTemplate($storeId = null) + { + return $this->getConfigData('resendPaymentInfo_template', $storeId); + } + + + /** + * @param null $storeId + * @return Simple_Xml + */ + public function getPayPerMailTemplate($storeId = null) + { + return $this->getConfigData('payPerMail_template', $storeId); + } + + /** + * @return Simple_Xml + */ + public function getStateRestriction() + { + return $this->getConfigData('ops_state_restriction'); + } + + /** + * @param $params + * @param null $storeId + * @return mixed + */ + public function getPaymentRetryUrl($params, $storeId = null) + { + return Mage::getUrl( + self::OPS_CONTROLLER_ROUTE_PAYMENT . 'retry', + array('_secure' => true, '_nosid' => true, '_query' => $params, '_store' => $storeId) + ); + } + + /** + * Will return the state of the deviceFingerPrinting: + * - true if activated in config + * - false if deactivated in config + * + * @param int $storeId + * + * @return bool + */ + public function getDeviceFingerPrinting($storeId = null) + { + return (bool)$this->getConfigData('device_fingerprinting', $storeId); + } + + /** + * @param null $storeId + * @return int + */ + public function getTransActionTimeout($storeId = null) + { + return (int)$this->getConfigData('ops_rtimeout', $storeId); + } + + /** + * + * @param int|null $storeId + * + * @return bool + */ + public function getCreditDebitSplit($storeId = null) + { + return (bool)$this->getConfigData('creditdebit_split', $storeId); + } + + /** + * @return array + */ + public function getAllRecurringCcTypes() + { + return explode(',', Mage::getStoreConfig('payment/ops_recurring_cc/availableTypes')); + } + + /** + * @return array + */ + public function getAcceptedRecurringCcTypes() + { + return explode(',', Mage::getStoreConfig('payment/ops_recurring_cc/acceptedTypes')); + } + + /** + * @param null $storeId + * @return mixed + */ + public function getMonthlyBillingDay($storeId = null) + { + return Mage::getStoreConfig(self::OPS_PAYMENT_PATH . 'billing_day_month', $storeId); + } + + /** + * @param null $storeId + * @return mixed + */ + public function getWeeklyBillingDay($storeId = null) + { + return Mage::getStoreConfig(self::OPS_PAYMENT_PATH . 'billing_day_week', $storeId); + } + + /** + * @param null $storeId + * @return Simple_Xml + */ + public function getSuspendSubscriptionTemplate($storeId = null) + { + return $this->getConfigData('suspendSubscription_template', $storeId); + } + + /** + * @param null $storeId + * @return Simple_Xml + */ + public function getSuspendSubscriptionIdentity($storeId = null) + { + return $this->getConfigData('suspendSubscription_identity', $storeId); + } + + + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Eci/Values.php b/app/code/community/Netresearch/OPS/Model/Eci/Values.php new file mode 100644 index 0000000..a3067a1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Eci/Values.php @@ -0,0 +1,35 @@ +_filePath = $path; + if (!file_exists($this->_filePath) || !is_readable($this->_filePath)) { + Mage::throwException('File '.$this->_filePath.' does not exist or is not readable.'); + return ''; + } + if (filesize($path) > $threshold) { + return $this->_trimFileToThreshold($threshold); + } else { + return $this->_filePath; + } + } + + /** + * Trims the current file to the given threshold and creates a temporary file + * + * @param $threshold Max file size + * @return string Path to the temporary file + * @throws Mage_Core_Exception + */ + protected function _trimFileToThreshold($threshold) + { + $this->_fileHandler = fopen($this->_filePath, 'r'); + if (0 > fseek($this->_fileHandler, filesize($this->_filePath)-$threshold, SEEK_SET)) { + Mage::throwException('Function fseek on file '. $this->_filePath.' failed.'); + } + $content = fread($this->_fileHandler, $threshold); + fclose($this->_fileHandler); + $tempFileName = tempnam(sys_get_temp_dir(), 'tempFile'); + file_put_contents($tempFileName, $content); + + return $tempFileName; + } + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Kwixo/Category/Mapping.php b/app/code/community/Netresearch/OPS/Model/Kwixo/Category/Mapping.php new file mode 100644 index 0000000..485670e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Kwixo/Category/Mapping.php @@ -0,0 +1,57 @@ +_init('ops/kwixo_category_mapping'); + parent::_construct(); + } + + + public function loadByCategoryId($categoryId) + { + $collection = $this->getCollection() + ->addFieldToFilter('category_id', $categoryId) + ->load() + ->getFirstItem(); + + return $collection; + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Kwixo/Shipping/Setting.php b/app/code/community/Netresearch/OPS/Model/Kwixo/Shipping/Setting.php new file mode 100644 index 0000000..edf28e5 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Kwixo/Shipping/Setting.php @@ -0,0 +1,24 @@ + + * @category Netresearch + * @package ${MODULENAME} + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Model_Kwixo_Shipping_Setting + extends Mage_Core_Model_Abstract +{ + /** + * Constructor + * + * @see lib/Varien/Varien_Object#_construct() + */ + public function _construct() + { + $this->_init('ops/kwixo_shipping_setting'); + parent::_construct(); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Mysql4/Alias.php b/app/code/community/Netresearch/OPS/Model/Mysql4/Alias.php new file mode 100644 index 0000000..726895b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Mysql4/Alias.php @@ -0,0 +1,33 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Model_Mysql4_Alias + * Netresearch_OPS_Model_Mysql4_Alias + * @author Michael Lühr + * @author Thomas Birke + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +class Netresearch_OPS_Model_Mysql4_Alias + extends Mage_Core_Model_Mysql4_Abstract +{ + /** + * Constructor + * + * @see lib/Varien/Varien_Object#_construct() + * @return Netresearch_OPS_Model_Mysql4_Alias + */ + public function _construct() + { + $this->_init('ops/alias', 'id'); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Mysql4/Alias/Collection.php b/app/code/community/Netresearch/OPS/Model/Mysql4/Alias/Collection.php new file mode 100644 index 0000000..1a0c5ec --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Mysql4/Alias/Collection.php @@ -0,0 +1,29 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Model_Mysql4_Alias_Collection + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Model_Mysql4_Alias_Collection extends + Mage_Core_Model_Mysql4_Collection_Abstract +{ + + protected function _construct() + { + parent::_construct(); + $this->_init('ops/alias'); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Category/Mapping.php b/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Category/Mapping.php new file mode 100644 index 0000000..454cf44 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Category/Mapping.php @@ -0,0 +1,32 @@ + + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Model_Mysql4_Alias + * Netresearch_OPS_Model_Mysql4_Alias + * @author Michael Lühr + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Model_Mysql4_Kwixo_Category_Mapping + extends Mage_Core_Model_Mysql4_Abstract +{ + + /** + * Constructor + * + * @see lib/Varien/Varien_Object#_construct() + * @return Netresearch_OPS_Model_Mysql4_Kwixo_Category_Mapping + */ + public function _construct() + { + $this->_init('ops/kwixo_category_mapping', 'id'); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Category/Mapping/Collection.php b/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Category/Mapping/Collection.php new file mode 100644 index 0000000..8a3e6d2 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Category/Mapping/Collection.php @@ -0,0 +1,28 @@ + + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Model_Mysql4_Kwixo_Category_Mapping_Collection + * @author Michael Lühr + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +class Netresearch_OPS_Model_Mysql4_Kwixo_Category_Mapping_Collection + extends Mage_Core_Model_Mysql4_Collection_Abstract +{ + + protected function _construct() + { + parent::_construct(); + $this->_init('ops/kwixo_category_mapping'); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Shipping/Setting.php b/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Shipping/Setting.php new file mode 100644 index 0000000..ff09fc8 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Shipping/Setting.php @@ -0,0 +1,20 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Model_Mysql4_Kwixo_Shipping_Setting + extends Mage_Core_Model_Mysql4_Abstract +{ + + public function _construct() + { + $this->_init('ops/kwixo_shipping_setting', 'id'); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Shipping/Setting/Collection.php b/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Shipping/Setting/Collection.php new file mode 100644 index 0000000..585c8ce --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Mysql4/Kwixo/Shipping/Setting/Collection.php @@ -0,0 +1,21 @@ + + * @category Netresearch + * @package ${MODULENAME} + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Model_Mysql4_Kwixo_Shipping_Setting_Collection + extends Mage_Core_Model_Mysql4_Collection_Abstract +{ + + protected function _construct() + { + parent::_construct(); + $this->_init('ops/kwixo_shipping_setting'); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Observer.php b/app/code/community/Netresearch/OPS/Model/Observer.php new file mode 100644 index 0000000..7e69de1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Observer.php @@ -0,0 +1,725 @@ + + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Model_Observer + * + * @author André Herrn + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Model_Observer +{ + + /** + * Get one page checkout model + * + * @return Mage_Checkout_Model_Type_Onepage + */ + public function getOnepage() + { + return Mage::getSingleton('checkout/type_onepage'); + } + + public function getAdminSession() + { + return Mage::getSingleton('admin/session'); + } + + public function isAdminSession() + { + + if ($this->getAdminSession()->getUser()) { + return 0 < $this->getAdminSession()->getUser()->getUserId(); + } + + return false; + } + + public function getHelper($name = null) + { + if (null === $name) { + return Mage::helper('ops'); + } + + return Mage::helper('ops/' . $name); + } + + /** + * @return Netresearch_OPS_Model_Config + */ + public function getConfig() + { + return Mage::getModel('ops/config'); + } + + /** + * trigger ops payment + */ + public function checkoutTypeOnepageSaveOrderBefore($observer) + { + $quote = $observer->getQuote(); + $order = $observer->getOrder(); + $code = $quote->getPayment()->getMethodInstance()->getCode(); + + try { + if ('ops_directDebit' == $code + && Mage::helper('ops/payment')->isInlinePaymentWithQuoteId( + $quote->getPayment() + ) + ) { + $this->confirmDdPayment($order, $quote ); + } elseif ($quote->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_Abstract) { + $requestParams = $quote->getPayment()->getMethodInstance()->getFormFields($order, array(), false); + $this->invokeRequestParamValidation($requestParams); + } + } catch (Exception $e) { + $quote->setIsActive(true); + $this->getOnepage()->getCheckout()->setGotoSection('payment'); + Mage::throwException($e->getMessage()); + } + } + + public function salesModelServiceQuoteSubmitFailure($observer) + { + $quote = $observer->getQuote(); + if (Mage::helper('ops/payment')->isInlinePaymentWithQuoteId($quote->getPayment())) { + $this->handleFailedCheckout( + $observer->getQuote(), + $observer->getOrder() + ); + } + } + + protected function getQuoteCurrency($quote) + { + if ($quote->hasForcedCurrency()) { + return $quote->getForcedCurrency()->getCode(); + } else { + return Mage::app()->getStore($quote->getStoreId())->getBaseCurrencyCode(); + } + } + + public function confirmAliasPayment($order, $quote) + { + $requestParams = Mage::helper('ops/creditcard')->getDirectLinkRequestParams($quote, $order); + $this->invokeRequestParamValidation($requestParams); + Mage::helper('ops/alias')->cleanUpAdditionalInformation($quote->getPayment(), true); + + return $this->performDirectLinkRequest($quote, $requestParams, $quote->getStoreId()); + + } + + public function confirmDdPayment($order, $quote) + { + /** @var Netresearch_OPS_Helper_DirectDebit $directDebitHelper */ + $directDebitHelper = Mage::helper('ops/directDebit'); + $requestParams = Mage::app()->getRequest()->getParam('ops_directDebit'); + $directDebitHelper->handleAdminPayment($quote, $requestParams); + $requestParams = $directDebitHelper->getDirectLinkRequestParams($quote, $order, $requestParams); + $this->invokeRequestParamValidation($requestParams); + + return $this->performDirectLinkRequest($quote, $requestParams, $quote->getStoreId()); + } + + public function performDirectLinkRequest($quote, $params, $storeId = null) + { + $url = Mage::getModel('ops/config')->getDirectLinkGatewayOrderPath($storeId); + $response = Mage::getSingleton('ops/api_directlink')->performRequest($params, $url, $storeId); + /** + * allow null as valid state for creating the order with status 'pending' + */ + if (null != $response['STATUS'] && Mage::helper('ops/payment')->isPaymentFailed($response['STATUS'])) { + Mage::throwException($this->getHelper()->__('Ingenico ePayments Payment failed')); + } + $quote->getPayment()->setAdditionalInformation('ops_response', $response)->save(); + + } + + + /** + * Check if checkout was made with OPS CreditCart or DirectDebit + * + * @return boolean + */ + protected function isCheckoutWithExistingTxId($code) + { + if ('ops_opsid' == $code) { + return true; + } else { + return false; + } + } + + /** + * Replace order cancel comfirm message of Magento by a custom message from Ingenico ePayments + * + * @event adminhtml_block_html_before + * + * @param Varien_Event_Observer $observer + * + * @return Netresearch_OPS_Model_Observer + */ + public function updateOrderCancelButton(Varien_Event_Observer $observer) + { + /* @var $block Mage_Adminhtml_Block_Template */ + $block = $observer->getEvent()->getBlock(); + + //Stop if block is not sales order view + if ($block->getType() != 'adminhtml/sales_order_view') { + return $this; + } + + //If payment method is one of the Ingenico ePayments-ones and order can be cancelled manually + if ($block->getOrder()->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_Abstract + && true === $block->getOrder()->getPayment()->getMethodInstance()->canCancelManually($block->getOrder()) + ) { + //Build message and update cancel button + $message = Mage::helper('ops')->__( + "Are you sure you want to cancel this order? Warning:" . + " Please check the payment status in the back-office of Ingenico ePayments before." . + " By cancelling this order you won\\'t be able to update the status in Magento anymore." + ); + $block->updateButton( + 'order_cancel', + 'onclick', + 'deleteConfirm(\'' . $message . '\', \'' . $block->getCancelUrl() . '\')' + ); + } + + return $this; + } + + /** + * + * appends a checkbox for closing the transaction if it's a Ingenico ePayments payment + * + * @event core_block_abstract_to_html_after + * + * @param Varien_Event_Observer $observer + * + * @return string + */ + public function appendCheckBoxToRefundForm($observer) + { + $html = ''; + /* + * show the checkbox only if the credit memo create page is displayed and + * the refund can be done online and the payment is done via Ingenico ePayments + */ + if ($observer->getBlock() instanceof Mage_Adminhtml_Block_Sales_Order_Creditmemo_Totals + && $observer->getBlock()->getParentBlock() + instanceof Mage_Adminhtml_Block_Sales_Order_Creditmemo_Create_Items + && $observer->getBlock()->getParentBlock()->getCreditmemo()->getOrder()->getPayment() + && $observer->getBlock()->getParentBlock()->getCreditmemo()->getOrder()->getPayment()->getMethodInstance() + instanceof Netresearch_OPS_Model_Payment_Abstract + && $observer->getBlock()->getParentBlock()->getCreditmemo()->canRefund() + && $observer->getBlock()->getParentBlock()->getCreditmemo()->getInvoice() + && $observer->getBlock()->getParentBlock()->getCreditmemo()->getInvoice()->getTransactionId() + ) { + $transport = $observer->getTransport(); + $block = $observer->getBlock(); + $layout = $block->getLayout(); + $html = $transport->getHtml(); + $checkBoxHtml = $layout->createBlock( + 'ops/adminhtml_sales_order_creditmemo_totals_checkbox', + 'ops_refund_checkbox' + ) + ->setTemplate('ops/sales/order/creditmemo/totals/checkbox.phtml') + ->renderView(); + $html = $html . $checkBoxHtml; + $transport->setHtml($html); + } + + return $html; + } + + /** + * + * fetch the creation of credit memo event and display warning message when + * - credit memo could be done online + * - payment is a Ingenico ePayments payment + * - Ingenico ePayments transaction is closed + * + * @event core_block_abstract_to_html_after + * + * @param Varien_Event_Observer $observer + * + * @return string + */ + public function showWarningForClosedTransactions($observer) + { + $html = ''; + /** + * - credit memo could be done online + * - payment is a Ingenico ePayments payment + * - Ingenico ePayments transaction is closed + */ + if ($observer->getBlock() instanceof Mage_Adminhtml_Block_Sales_Order_Creditmemo_Create + && $observer->getBlock()->getCreditmemo()->getOrder()->getPayment() + && $observer->getBlock()->getCreditmemo()->getOrder()->getPayment()->getMethodInstance() + instanceof Netresearch_OPS_Model_Payment_Abstract + && $observer->getBlock()->getCreditmemo()->getInvoice() + && $observer->getBlock()->getCreditmemo()->getInvoice()->getTransactionId() + && false === $observer->getBlock()->getCreditmemo()->canRefund() + ) { + $transport = $observer->getTransport(); + $block = $observer->getBlock(); + $layout = $block->getLayout(); + $html = $transport->getHtml(); + $warningHtml = $layout->createBlock( + 'ops/adminhtml_sales_order_creditmemo_closedTransaction_warning', + 'ops_closed-transaction-warning' + )->renderView(); + $html = $warningHtml . $html; + $transport->setHtml($html); + } + + return $html; + } + + + /** + * triggered by cron for deleting old payment data from the additional payment information + * + * @param $observer + */ + public function cleanUpOldPaymentData() + { + Mage::helper('ops/quote')->cleanUpOldPaymentInformation(); + } + + /** + * in some cases the payment method is not set properly by Magento so we need to reset the + * payment method in the quote's payment before importing the data + * + * @event sales_quote_payment_import_data_before + * + * @param $observer + * + * @return $this + */ + public function clearPaymentMethodFromQuote(Varien_Event_Observer $observer) + { + if ($observer->getEventName() == 'sales_quote_payment_import_data_before' + && $observer->getEvent()->getPayment() instanceof Mage_Sales_Model_Quote_Payment + ) { + $observer->getEvent()->getPayment()->setMethod(null); + } + + return $this; + } + + /** + * appends the status update button to the order's button in case it's an Ingenico ePayments payment + * + * @event core_block_abstract_prepare_layout_before + * + * @param Varien_Event_Observer $observer + * + * @return $this + */ + public function addStatusUpdateButtonToOrderView(Varien_Event_Observer $observer) + { + $block = $observer->getBlock(); + if ($block instanceof Mage_Adminhtml_Block_Sales_Order_View) { + $paymentMethod = $block->getOrder()->getPayment()->getMethodInstance(); + if ($paymentMethod instanceof Netresearch_OPS_Model_Payment_Abstract + && Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/invoice') + ) { + + $block->addButton( + 'ops_refresh', array( + 'label' => Mage::helper('ops/data')->__('Refresh payment status'), + 'onclick' => 'setLocation(\'' . $block->getUrl('adminhtml/opsstatus/update') . '\')') + ); + } + } + + return $this; + } + + /** + * @event core_block_abstract_prepare_layout_before + * + * @param Varien_Event_Observer $observer + * + * @return $this + */ + public function addCcPaymentMethod(Varien_Event_Observer $observer) + { + /** @var $block Mage_Payment_Block_Form_Container */ + $block = $observer->getEvent()->getBlock(); + if ($block instanceof Mage_Payment_Block_Form_Container + && !Mage::helper('ops/version')->canUseApplicableForQuote(Mage::getEdition()) + ) { + Mage::helper('ops/payment')->addCCForZeroAmountCheckout($block); + } + + return $this; + } + + /** + * @event core_block_abstract_prepare_layout_before + * + * @param Varien_Event_Observer $observer + * + * @return $this + */ + public function disableCaptureForZeroAmountInvoice(Varien_Event_Observer $observer) + { + $block = $observer->getEvent()->getBlock(); + if ($block instanceof Mage_Adminhtml_Block_Sales_Order_Invoice_Create_Items) { + $invoice = $block->getInvoice(); + if ($invoice->getBaseGrandTotal() <= 0.01 + && $invoice->getOrder()->getPayment()->getMethodInstance() instanceof + Netresearch_OPS_Model_Payment_Abstract + ) { + $invoice->getOrder()->getPayment()->getMethodInstance()->setCanCapture(false); + } + } + + return $this; + } + + + /** + * @param $requestParams + * + * @throws Mage_Core_Exception + * @return Netresearch_OPS_Model_Observer + */ + protected function invokeRequestParamValidation($requestParams) + { + $validator = Mage::getModel('ops/validator_parameter_factory')->getValidatorFor( + Netresearch_OPS_Model_Validator_Parameter_Factory::TYPE_REQUEST_PARAMS_VALIDATION + ); + if (false == $validator->isValid($requestParams)) { + $this->getOnepage()->getCheckout()->setGotoSection('payment'); + Mage::throwException( + $this->getHelper()->__('The data you have provided can not be processed by Ingenico ePayments') + ); + } + + return $this; + } + + + /** + * validates the input fields after the payment step in OPC + * + * @event controller_action_postdispatch_checkout_onepage_savePayment + * + * @param Varien_Event_Observer $event + * + * @return $this + */ + public function controllerActionCheckoutOnepagePostdispatch(Varien_Event_Observer $event) + { + $controller = $event->getControllerAction(); + $quote = $this->getOnepage()->getQuote(); + if ($quote->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_Abstract) { + /** @var Netresearch_OPS_Helper_Payment_Request $paramHelper */ + $paramHelper = Mage::helper('ops/payment_request'); + $shippingParams = array(); + $ownerParams = $paramHelper->getOwnerParams($quote->getBillingAddress(), $quote); + $billingParams = $paramHelper->extractBillToParameters($quote->getBillingAddress(), $quote); + if ($quote->getShippingAddress()) { + $shippingParams = $paramHelper->extractShipToParameters($quote->getShippingAddress(), $quote); + } + $params = array_merge($ownerParams, $shippingParams, $billingParams); + $validator = Mage::getModel('ops/validator_parameter_factory')->getValidatorFor( + Netresearch_OPS_Model_Validator_Parameter_Factory::TYPE_REQUEST_PARAMS_VALIDATION + ); + if (false == $validator->isValid($params)) { + $result = Mage::helper('ops/validation_result')->getValidationFailedResult( + $validator->getMessages(), + $quote + ); + $controller->getResponse()->setBody(Mage::helper('core/data')->jsonEncode($result)); + } + } + + return $this; + } + + + public function salesOrderPaymentCapture(Varien_Event_Observer $event) + { + /** @var $payment Mage_Sales_Model_Order_Payment */ + $payment = $event->getPayment(); + $invoice = $event->getInvoice(); + if ($payment->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_Abstract) { + $payment->setInvoice($invoice); + } + + + return $this; + } + + /** + * resets the order status back to pending payment in case of directlink payments in Ingenico ePayments authorize status + * + * @event sales_order_payment_place_end + * + * @param Varien_Event_Observer $event + */ + public function setOrderStateDirectLink(Varien_Event_Observer $event) + { + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $event->getPayment(); + + if ($payment->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_DirectLink + && Mage::helper('ops/payment')->isInlinePayment($payment) + && Netresearch_OPS_Model_Status::AUTHORIZED == $payment->getAdditionalInformation('status') + && $payment->getOrder()->getState() != Mage_Sales_Model_Order::STATE_PENDING_PAYMENT + ) { + $payment->getOrder()->setState( + Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, true, + $this->getHelper()->__('Payment has been authorized by Ingenico ePayments, but not yet captured.') + ); + } + } + + + /** + * appends the resend payment info button to the order's button in case it's an Ingenico ePayments payment + * and the payment status is an authorize status + * + * @event core_block_abstract_prepare_layout_before + * + * @param Varien_Event_Observer $observer + * + * @return $this + */ + public function addResendPaymentInfoButtonToOrderView(Varien_Event_Observer $observer) + { + $block = $observer->getBlock(); + if ($block instanceof Mage_Adminhtml_Block_Sales_Order_View) { + $payment = $block->getOrder()->getPayment(); + $paymentMethod = $payment->getMethodInstance(); + if ($paymentMethod instanceof Netresearch_OPS_Model_Payment_Abstract + && Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/invoice') + && Netresearch_OPS_Model_Status::canResendPaymentInfo($payment->getAdditionalInformation('status')) + && !in_array( + $block->getOrder()->getState(), + array( + Mage_Sales_Model_Order::STATE_CANCELED, + Mage_Sales_Model_Order::STATE_CLOSED, + Mage_Sales_Model_Order::STATE_COMPLETE + ) + ) + ) { + $block->addButton( + 'ops_resend_info', array( + 'label' => Mage::helper('ops/data')->__('Resend payment information'), + 'onclick' => 'setLocation(\'' . $block->getUrl('adminhtml/admin/resendInfo') . '\')') + ); + } + } + } + + /** + * Adjusts the confirmation message text of the recurring profiles cancel and suspend button to inform the merchant + * that no call to Ingenico ePayments will happen + * + * @event adminhtml_block_html_before + * + * @param Varien_Event_Observer $observer + */ + public function updateRecurringProfileButtons(Varien_Event_Observer $observer) + { + /** @var $block Mage_Sales_Block_Adminhtml_Recurring_Profile_View */ + $block = $observer->getEvent()->getBlock(); + + if ($block->getType() == 'sales/adminhtml_recurring_profile_view') { + $profile = Mage::registry('current_recurring_profile'); + if ($profile->getMethodCode() == Netresearch_OPS_Model_Payment_Recurring_Cc::CODE) { + $cancelMessage = Mage::helper('ops') + ->__( + 'Are you sure you want to perform this action?' . + ' Canceling the subscription here will not actually cancel the subscription on Ingenico ePayments side.' . + ' To stop charging the customer you will have to deactivate the subscription there.' + ); + $cancelUrl = $block->getUrl( + '*/*/updateState', + array('profile' => $profile->getId(), 'action' => 'cancel') + ); + + $block->updateButton( + 'cancel', + 'onclick', + "confirmSetLocation('{$cancelMessage}', '{$cancelUrl}')" + ); + + $suspendMessage = Mage::helper('ops') + ->__( + 'Are you sure you want to perform this action?' . + 'Suspending the subscription here will not actually cancel the subscription on Ingenico ePayments side.' . + 'To stop charging the customer you will have to deactivate the subscription there.' + ); + $suspendUrl = $block->getUrl( + '*/*/updateState', + array('profile' => $profile->getId(), 'action' => 'suspend') + ); + + $block->updateButton( + 'suspend', + 'onclick', + "confirmSetLocation('{$suspendMessage}', '{$suspendUrl}')" + ); + } + } + } + + /** + * Overwrites the state of the recurring profile if necessary + * + * @event model_save_before - due to lack of event prefix for recurring profile models + * + * @param Varien_Event_Observer $observer + * + * @return $this + */ + public function overrideRecurringProfileState(Varien_Event_Observer $observer) + { + $object = $observer->getObject(); + + /** @var $object Mage_Payment_Model_Recurring_Profile */ + if ($object instanceof Mage_Payment_Model_Recurring_Profile + && $object->getMethodCode() === Netresearch_OPS_Model_Payment_Recurring_Cc::CODE + && $object->getState() != $object->getNewState() + && $object->getOverrideState() === true + ) { + $object->setState($object->getNewState()); + } + + return $this; + } + + + /** + * @event adminhtml_block_html_before + * + * @param Varien_Event_Observer $observer + * + * @return $this + */ + public function updateRecurringProfileEditForm(Varien_Event_Observer $observer) + { + if ($observer->getBlock() instanceof Mage_Sales_Block_Adminhtml_Recurring_Profile_Edit_Form + && Mage::getModel('ops/payment_recurring_cc')->getConfigData('active') + ) { + /** @var Mage_Sales_Block_Adminhtml_Recurring_Profile_Edit_Form $form */ + $html = $observer->getTransport()->getHtml(); + + $method = Mage::getModel('ops/payment_recurring_cc'); + + $message = Mage::helper('ops') + ->__( + "When using %s as payment method the settings for '%s' and '%s' will not be processed.", + $method->getTitle(), + Mage::helper('payment')->__('Allow Initial Fee Failure'), + Mage::helper('payment')->__('Maximum Payment Failures') + ); + + $message = '

' . $message + . '

'; + $observer->getTransport()->setHtml($html . $message); + + + } + + return $this; + } + + /** + * Since there is no other way for inline payments to change the order state, we enforce the pending_payment state + * for only authorized, not yet payed orders + * + * @param Varien_Event_Observer $observer + * + * @event sales_order_payment_place_end + * @return $this + */ + public function enforcePaymentPendingForAuthorizedOrders(Varien_Event_Observer $observer) + { + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $observer->getData('payment'); + /** @var Mage_Sales_Model_Order $order */ + $order = $payment->getOrder(); + + $status = $payment->getAdditionalInformation('status'); + if ($this->getConfig()->getPaymentAction($order->getStoreId()) + == Netresearch_OPS_Model_Payment_Abstract::ACTION_AUTHORIZE + && Netresearch_OPS_Model_Status::isAuthorize($status) + && $order->getState() != Mage_Sales_Model_Order::STATE_PENDING_PAYMENT + ) { + $message = $this->getHelper()->__('Order has been authorized, but not captured/paid yet.'); + $order->setState(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, true, $message); + + } + + return $this; + } + + /** + * Magento does not send order confirmation emails when + * - payment action "authorization" is processed in frontend via gateway. + * Magento does not send invoice emails when + * - payment action "authorization+capture" is processed in frontend or admin + * + * event: checkout_submit_all_after + * + * @param Varien_Event_Observer $observer + */ + public function sendTransactionalEmails(Varien_Event_Observer $observer) + { + /** @var Mage_Sales_Model_Order $order */ + $order = $observer->getOrder(); + if ($order == null || !$order->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_Abstract) { + // ignore third-party payment methods + return; + } + + /** @var Mage_Sales_Model_Quote $quote */ + $quote = $observer->getQuote(); + if ($quote && $quote->getPayment()->getOrderPlaceRedirectUrl()) { + // redirect payments require special treatment, may still get cancelled or declined + return; + } + + try { + Mage::helper('ops/data')->sendTransactionalEmail($order); + Mage::helper('ops/data')->sendTransactionalEmail($order->getPayment()->getCreatedInvoice()); + } catch (Exception $e) { + Mage::logException($e); + } + } + + /** + * triggers the email sending for payment method payPerMail + * + * event: sales_order_place_after + * + * @param Varien_Event_Observer $observer + */ + public function sendPayPerMailInfo(Varien_Event_Observer $observer) + { + /** @var Mage_Sales_Model_Order $order */ + $order = $observer->getOrder(); + if (!$order->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_PayPerMail) { + return; + } + + /** @var Netresearch_OPS_Model_Payment_Features_PaymentEmail $sendEmailModel */ + $sendEmailModel = Mage::getModel('ops/payment_features_paymentEmail'); + $sendEmailModel->resendPaymentInfo($order); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Abstract.php b/app/code/community/Netresearch/OPS/Model/Payment/Abstract.php new file mode 100644 index 0000000..1ef1316 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Abstract.php @@ -0,0 +1,1127 @@ +encoding; + } + + /** + * @param string $encoding + */ + public function setEncoding($encoding) + { + $this->encoding = $encoding; + } + + /** + * @param null $backendOperationParameterModel + */ + public function setBackendOperationParameterModel($backendOperationParameterModel) + { + $this->backendOperationParameterModel = $backendOperationParameterModel; + } + + /** + * @return Netresearch_OPS_Model_Backend_Operation_Parameter + */ + public function getBackendOperationParameterModel() + { + if (null === $this->backendOperationParameterModel) { + $this->backendOperationParameterModel = Mage::getModel('ops/backend_operation_parameter'); + } + + return $this->backendOperationParameterModel; + } + + /** + * Return OPS Config + * + * @return Netresearch_OPS_Model_Config + */ + public function getConfig() + { + if (null === $this->_config) { + $this->_config = Mage::getSingleton('ops/config'); + } + + return $this->_config; + } + + public function getConfigPaymentAction() + { + return $this->getPaymentAction(); + } + + /** + * get the frontend gateway path based on encoding property + */ + public function getFrontendGateWay() + { + $gateway = $this->getConfig()->getFrontendGatewayPath(); + + return $gateway; + } + + /** + * return if shipment params are needed for request + * + * @return bool + */ + public function getNeedsShipToParams() + { + return $this->_needsShipToParams; + } + + /** + * return if billing params are needed for request + * + * @return bool + */ + public function getNeedsBillToParams() + { + return $this->_needsBillToParams; + } + + /** + * returns the request helper + * + * @return Netresearch_OPS_Helper_Payment_Request + */ + public function getRequestHelper() + { + if (null === $this->requestHelper) { + $this->requestHelper = Mage::helper('ops/payment_request'); + } + + return $this->requestHelper; + } + + /** + * if payment method is available + * + * @param Mage_Sales_Model_Quote $quote + * + * @return boolean + */ + public function isAvailable($quote = null) + { + + $storeId = 0; + // allow multi store/site for backend orders with disabled backend payment methods in default store + if (null != $quote && null != $quote->getId()) { + $storeId = $quote->getStoreId(); + } + if (Mage_Core_Model_App::ADMIN_STORE_ID == Mage::app()->getStore()->getId() + && false == $this->isEnabledForBackend($storeId) + ) { + return false; + } + + return parent::isAvailable($quote); + } + + /** + * if method is enabled for backend payments + * + * @param int $storeId + * @return bool + */ + public function isEnabledForBackend($storeId = 0) + { + return $this->getConfig()->isEnabledForBackend($this->_code, $storeId); + } + + /** + * Redirect url to ops submit form + * + * @return string + */ + public function getOrderPlaceRedirectUrl() + { + return $this->getConfig()->getPaymentRedirectUrl(); + } + + public function getOpsBrand($payment = null) + { + if (null === $payment) { + $payment = $this->getInfoInstance(); + } + $brand = trim($payment->getAdditionalInformation('BRAND')); + if (!strlen($brand)) { + $brand = $this->brand; + } + + return $brand; + } + + public function getOpsCode($payment = null) + { + if (null === $payment) { + $payment = $this->getInfoInstance(); + } + $pm = trim($payment->getAdditionalInformation('PM')); + if (!strlen($pm)) { + $pm = $this->pm; + } + + return $pm; + } + + /** + * Return payment_action value from config area + * + * @return string + */ + public function getPaymentAction() + { + return $this->getConfig()->getPaymentAction($this->getStoreId()); + } + + /** + * @param Mage_Sales_Model_Order $order + * @param string[]|null $requestParams + * + * @return string[] + */ + public function getMethodDependendFormFields($order, $requestParams = null) + { + $billingAddress = $order->getBillingAddress(); + $shippingAddress = $order->getShippingAddress(); + if (null === $shippingAddress || false === $shippingAddress) { + $shippingAddress = $billingAddress; + } + $payment = $order->getPayment()->getMethodInstance(); + $quote = Mage::helper('ops/order')->getQuote($order->getQuoteId()); + + $formFields = array(); + $formFields['ORIG'] = Mage::helper("ops")->getModuleVersionString(); + $formFields['BRAND'] = $payment->getOpsBrand($order->getPayment()); + if ($this->getConfig()->canSubmitExtraParameter($order->getStoreId())) { + $formFields['CN'] = $billingAddress->getFirstname() . ' ' . $billingAddress->getLastname(); + $formFields['COM'] = $this->_getOrderDescription($order); + $formFields['ADDMATCH'] = Mage::helper('ops/order')->checkIfAddressesAreSame($order); + $ownerParams = $this->getRequestHelper()->getOwnerParams($billingAddress, $quote); + $formFields['ECOM_BILLTO_POSTAL_POSTALCODE'] = $billingAddress->getPostcode(); + $formFields = array_merge($formFields, $ownerParams); + } + + if (Mage::helper('customer/data')->isLoggedIn()) { + $formFields['CUID'] = Mage::helper('customer')->getCustomer()->getId(); + } + + return $formFields; + } + + /** + * return ship to params if needed otherwise false + * + * @param $shippingAddress + * + * @return array|bool + */ + public function getShipToParams($shippingAddress) + { + $shipToParams = false; + if ($this->getNeedsShipToParams() + && $this->getConfig()->canSubmitExtraParameter() + && $shippingAddress + ) { + $shipToParams = $this->getRequestHelper()->extractShipToParameters($shippingAddress); + } + + return $shipToParams; + } + + /** + * return ship to params if needed otherwise false + * + * @param $billingAddress + * + * @return array|bool + */ + public function getBillToParams($billingAddress) + { + $billToParams = false; + if ($this->getNeedsBillToParams() + && $this->getConfig()->canSubmitExtraParameter() + && $billingAddress + ) { + $billToParams = $this->getRequestHelper()->extractBillToParameters($billingAddress); + } + + return $billToParams; + } + + /** + * Prepare params array to send it to gateway page via POST + * + * @param $order + * @param $requestParams + * @param bool $isRequest + * @return array + */ + public function getFormFields($order, $requestParams, $isRequest = true) + { + $requestHelper = Mage::helper('ops/payment_request'); + + if (empty($order)) { + if (!($order = $this->getOrder())) { + return array(); + } + } + + // get mandatory parameters + $formFields = $this->getMandatoryFormFields($order); + + $paymentAction = $this->_getOPSPaymentOperation(); + if ($paymentAction ) { + $formFields['OPERATION'] = $paymentAction; + } + + $formFields = array_merge($formFields, $requestHelper->getTemplateParams($order->getStoreId())); + + $opsOrderId = Mage::helper('ops/order')->getOpsOrderId($order); + $formFields['ACCEPTURL'] = $this->getConfig()->getAcceptUrl(); + $formFields['DECLINEURL'] = $this->getConfig()->getDeclineUrl(); + $formFields['EXCEPTIONURL'] = $this->getConfig()->getExceptionUrl(); + $formFields['CANCELURL'] = $this->getConfig()->getCancelUrl(); + $formFields['BACKURL'] = $this->getConfig()->getPaymentRetryUrl( + Mage::helper('ops/payment')->validateOrderForReuse($opsOrderId, $order->getStoreId()) + ); + + + /** @var $order Mage_Sales_Model_Order */ + $shipToFormFields = $this->getShipToParams($order->getShippingAddress()); + if (is_array($shipToFormFields)) { + $formFields = array_merge($formFields, $shipToFormFields); + } + + $billToFormFields = $this->getBillToParams($order->getBillingAddress()); + if (is_array($billToFormFields)) { + $formFields = array_merge($formFields, $billToFormFields); + } + + $cartDataFormFields = $this->getOrderItemData($order); + + if (is_array($cartDataFormFields)) { + $formFields = array_merge($formFields, $cartDataFormFields); + } + + // get method specific parameters + $methodDependendFields = $this->getMethodDependendFormFields($order, $requestParams); + if (is_array($methodDependendFields)) { + $formFields = array_merge($formFields, $methodDependendFields); + } + + $formFields = $this->transliterateParams($formFields); + + $shaSign = Mage::helper('ops/payment')->shaCrypt( + Mage::helper('ops/payment') + ->getSHASign($formFields, null, $order->getStoreId()) + ); + + if ($isRequest) { + $helper = Mage::helper('ops'); + $helper->log( + $helper->__( + "Register Order %s in Ingenico ePayments \n\nAll form fields: %s\nIngenico ePayments String to hash: %s\nHash: %s", + $order->getIncrementId(), + serialize($formFields), + Mage::helper('ops/payment')->getSHASign($formFields, null, $order->getStoreId()), + $shaSign + ) + ); + } + + $formFields['SHASIGN'] = $shaSign; + + return $formFields; + } + + /** + * Get OPS Payment Action value + * + * @param string + * + * @return string + */ + protected function _getOPSPaymentOperation() + { + $value = $this->getPaymentAction(); + if ($value == Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE) { + $value = self::OPS_AUTHORIZE_ACTION; + } elseif ($value == Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE) { + $value = self::OPS_AUTHORIZE_CAPTURE_ACTION; + } + + return $value; + } + + /** + * get formated order description + * + * @param Mage_Sales_Model_Order + * + * @return string + */ + public function _getOrderDescription($order) + { + /** @var Mage_Sales_Model_Order_Item[] $items */ + $items = $order->getAllItems(); + $description = array_reduce( + $items, + function ($acc, $item) { + /** @var Mage_Sales_Model_Order_Item $item */ + if (!$item->getParentItem()) { + $acc .= ($acc != '' ? ', ' : '') . $item->getName(); + } + return $acc; + }, '' + ); + + return $description; + } + + /** + * Get Main OPS Helper + * + * @return Netresearch_OPS_Helper_Data + */ + public function getHelper() + { + return Mage::helper('ops/data'); + } + + /** + * Method that will be executed instead of authorize or capture + * if flag isInitializeNeeded set to true + * + * @param string $paymentAction + * @param object $stateObject + * + * @return Mage_Payment_Model_Abstract + */ + public function initialize($paymentAction, $stateObject) + { + $stateObject->setState(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT) + ->setStatus(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT); + + if (!$paymentAction) { + $paymentAction = $this->getConfigPaymentAction(); + } + + $message = $this->getHelper() + ->__('Customer got redirected to Ingenico ePayments for %s. Awaiting feedback.', $paymentAction); + + /** @var Mage_Sales_Model_Order $order */ + $order = $this->getInfoInstance()->getOrder(); + + $order->addStatusHistoryComment($message); + + return $this; + } + + /** + * accept payment + * + * @see \Mage_Sales_Model_Order_Payment::registerPaymentReviewAction + * + * @param Mage_Payment_Model_Info $payment + * + * @return boolean + * @throws Mage_Core_Exception + */ + public function acceptPayment(Mage_Payment_Model_Info $payment) + { + parent::acceptPayment($payment); + $status = $payment->getAdditionalInformation('status'); + + if ($status == Netresearch_OPS_Model_Status::AUTHORIZED + || $status == Netresearch_OPS_Model_Status::PAYMENT_REQUESTED) + { + return true; + } + + Mage::throwException( + $this->getHelper()->__( + 'The order can not be accepted via Magento. For the actual status of the payment check the Ingenico ePayments backend.' + ) + ); + } + + /** + * cancel order if in payment review state + * + * @see \Mage_Sales_Model_Order_Payment::registerPaymentReviewAction + * + * @param Mage_Payment_Model_Info $payment + * + * @return boolean + * @throws Mage_Core_Exception + */ + public function denyPayment(Mage_Payment_Model_Info $payment) + { + parent::denyPayment($payment); + + Mage::getSingleton('admin/session')->addNotice( + $this->getHelper()->__( + 'Order has been canceled permanently in Magento. Changes in Ingenico ePayments platform will no longer be considered.') + ); + + return true; + } + + /** + * check if payment is in payment review state + * + * @param Mage_Payment_Model_Info $payment + * + * @return bool + */ + public function canReviewPayment(Mage_Payment_Model_Info $payment) + { + $status = $payment->getAdditionalInformation('status'); + return Netresearch_OPS_Model_Status::canResendPaymentInfo($status); + } + + /** + * Determines if a capture will be processed + * + * @param Varien_Object $payment + * @param float $amount + * + * @throws Mage_Core_Exception + * @return \Mage_Payment_Model_Abstract|void + */ + public function capture(Varien_Object $payment, $amount) + { + // disallow Ingenico ePayments online capture if amount is zero + if ($amount < 0.01) { + return parent::capture($payment, $amount); + } + + if (true === Mage::registry('ops_auto_capture')) { + Mage::unregister('ops_auto_capture'); + + return parent::capture($payment, $amount); + } + + $orderId = $payment->getOrder()->getId(); + $arrInfo = Mage::helper('ops/order_capture')->prepareOperation($payment, $amount); + $storeId = $payment->getOrder()->getStoreId(); + + if ($this->isRedirectNoticed($orderId)) { + return $this; + } + try { + $requestParams = $this->getBackendOperationParameterModel()->getParameterFor( + self::OPS_CAPTURE_TRANSACTION_TYPE, + $this, + $payment, + $amount + ); + $requestParams = $this->transliterateParams($requestParams); + $response = Mage::getSingleton('ops/api_directlink')->performRequest( + $requestParams, + Mage::getModel('ops/config')->getDirectLinkGatewayPath($storeId), + $storeId + ); + + Mage::getModel('ops/response_handler')->processResponse($response, $this, false); + + } catch (Exception $e) { + Mage::getModel('ops/status_update')->updateStatusFor($payment->getOrder()); + Mage::helper('ops')->log("Exception in capture request:" . $e->getMessage()); + Mage::throwException($e->getMessage()); + } + + return $this; + } + + /** + * Refund + * + * @param Varien_Object $payment + * @param float $amount + * + * @return \Mage_Payment_Model_Abstract|void + */ + public function refund(Varien_Object $payment, $amount) + { + /** @var Netresearch_OPS_Helper_Order_Refund $refundHelper */ + $refundHelper = Mage::helper('ops/order_refund'); + + if ($refundHelper->getOpenRefundTransaction($payment)->getId()) { + Mage::throwException( + $this->getHelper()->__( + "There is already one creditmemo in the queue." . + "The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement." + ) + ); + } + + $refundHelper->setAmount($amount)->setPayment($payment); + $storeId = $payment->getOrder()->getStoreId(); + + try { + $requestParams = $this->getBackendOperationParameterModel()->getParameterFor( + self::OPS_REFUND_TRANSACTION_TYPE, + $this, + $payment, + $amount + ); + $requestParams = $this->transliterateParams($requestParams); + $response = Mage::getSingleton('ops/api_directlink')->performRequest( + $requestParams, + Mage::getModel('ops/config')->getDirectLinkGatewayPath($storeId), + $storeId + ); + Mage::getModel('ops/response_handler')->processResponse($response, $this, false); + } catch (Exception $e) { + Mage::logException($e); + Mage::getModel('ops/status_update')->updateStatusFor($payment->getOrder()); + Mage::throwException($e->getMessage()); + } + + return $this; + } + + /** + * Returns the mandatory fields for requests to Ingenico ePayments + * + * @param Mage_Sales_Model_Order $order + * + * @return array + */ + public function getMandatoryFormFields($order) + { + $formFields = Mage::helper('ops/payment_request')->getMandatoryRequestFields($order); + $paymentAction = $this->_getOPSPaymentOperation(); + if ($paymentAction) { + $formFields['OPERATION'] = $paymentAction; + } + + return $formFields; + } + + /** + * determines if the close transaction parameter is set in the credit memo data + * + * @param array $creditMemoData + * + * @return bool + */ + protected function getCloseTransactionFromCreditMemoData($creditMemoData) + { + $closeTransaction = false; + if (array_key_exists('ops_close_transaction', $creditMemoData) + && strtolower(trim($creditMemoData['ops_close_transaction'])) == 'on' + ) { + $closeTransaction = true; + } + + return $closeTransaction; + } + + /** + * Custom cancel behavior, deny cancel and force custom to use void instead + * + * @param Varien_Object $payment + * + * @return void + * @throws Mage_Core_Exception + */ + public function cancel(Varien_Object $payment) + { + /* + * Important: If an order was voided successfully and the user clicks on cancel in order-view + * this method is not triggered anymore + */ + + //Proceed parent cancel method in case that regirstry value ops_auto_void is set + if (true === Mage::registry('ops_auto_void')) { + Mage::unregister('ops_auto_void'); + return parent::cancel($payment); + } + + //If order has state 'pending_payment' and the payment has Ingenico ePayments-status 0 or null (unknown) then cancel the order + if (true === $this->canCancelManually($payment->getOrder())) { + $payment->getOrder()->addStatusHistoryComment( + $this->getHelper()->__("The order was cancelled manually. The Ingenico ePayments-state is 0 or null.") + ); + + return parent::cancel($payment); + } + + //Abort cancel method by throwing a Mage_Core_Exception + Mage::throwException($this->getHelper()->__('Please use void to cancel the operation.')); + } + + /** + * Custom void behavior, trigger Ingenico ePayments cancel request + * + * @param Varien_Object $payment + * @return $this|void + */ + public function void(Varien_Object $payment) + { + + $status = $payment->getAdditionalInformation('status'); + + if (!Netresearch_OPS_Model_Status::canVoidTransaction($status)) { + Mage::throwException($this->getHelper()->__('Status %s can not be voided.', $status)); + } + + //Set initital params + $orderID = $payment->getOrder()->getId(); + $order = Mage::getModel("sales/order")->load($orderID); + + //Calculate amount which has to be captured + $alreadyCaptured = $payment->getBaseAmountPaidOnline(); + + $grandTotal = Mage::helper('ops/payment') + ->getBaseGrandTotalFromSalesObject($order); + $voidAmount = $grandTotal - $alreadyCaptured; + $storeId = $order->getStoreId(); + //Build void directLink-Request-Params + $requestParams = array( + 'AMOUNT' => $this->getHelper()->getAmount($voidAmount), + 'PAYID' => $payment->getAdditionalInformation('paymentId'), + 'OPERATION' => self::OPS_DELETE_AUTHORIZE, + 'CURRENCY' => Mage::app()->getStore($storeId)->getBaseCurrencyCode() + ); + + //Check if there is already a waiting void transaction, if yes: redirect to order view + if (Mage::helper('ops/directlink')->checkExistingTransact( + self::OPS_VOID_TRANSACTION_TYPE, $orderID + ) + ) { + $this->getHelper()->getAdminSession()->addError( + $this->getHelper()->__( + 'You already sent a void request. Please wait until the void request will be acknowledged.' + ) + ); + + return; + } + + try { + //perform ops cancel request + $response = Mage::getSingleton('ops/api_directlink') + ->performRequest( + $requestParams, + Mage::getModel('ops/config')->getDirectLinkGatewayPath($storeId), + $order->getStoreId() + ); + + if ($response['STATUS'] == Netresearch_OPS_Model_Status::INVALID_INCOMPLETE + && $payment->getAdditionalInformation('status') == Netresearch_OPS_Model_Status::PAYMENT_REQUESTED + ) { + Mage::throwException( + $this->getHelper()->__('Order can no longer be voided. You have to refund the order online.') + ); + } + + + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $this, false); + + + return $this; + + } catch (Exception $e) { + Mage::getModel('ops/status_update')->updateStatusFor($payment->getOrder()); + Mage::helper('ops')->log( + "Exception in void request:" . $e->getMessage() + ); + Mage::throwException($e->getMessage()); + } + } + + /** + * get question for fields with disputable value + * users are asked to correct the values before redirect to Ingenico ePayments + * + * + * @return string + */ + public function getQuestion() + { + return ''; + } + + /** + * get an array of fields with disputable value + * users are asked to correct the values before redirect to Ingenico ePayments + * + * @param Mage_Sales_Model_Order $order Current order + * + * @return array + */ + public function getQuestionedFormFields($order) + { + return array(); + } + + /** + * if we need some missing form params + * users are asked to correct the values before redirect to Ingenico ePayments + * + * @param Mage_Sales_Model_Order $order + * @param array $requestParams Parameters sent in current request + * @param array $formFields Parameters to be sent to Ingenico ePayments + * + * @return bool + */ + public function hasFormMissingParams($order, $requestParams, $formFields = null) + { + if (false == is_array($requestParams)) { + $requestParams = array(); + } + if (null === $formFields) { + $formFields = $this->getFormFields($order, $requestParams, false); + } + $availableParams = array_merge($requestParams, $formFields); + $requiredParams = $this->getQuestionedFormFields($order); + foreach ($requiredParams as $requiredParam) { + if (false == array_key_exists($requiredParam, $availableParams) + || 0 == strlen($availableParams[$requiredParam]) + ) { + return true; + } + } + + return false; + } + + /** + * Check if order can be cancelled manually + * + * @param Mage_Sales_Model_Order $order + * + * @return bool + */ + public function canCancelManually($order) + { + $payment = $order->getPayment(); + + //If payment has Ingenico ePayments-status 0 or null (unknown) or another offline cancelable status + $status = $payment->getAdditionalInformation('status'); + + return (null === $status) + || in_array( + $status, array( + Netresearch_OPS_Model_Status::INVALID_INCOMPLETE, + Netresearch_OPS_Model_Status::CANCELED_BY_CUSTOMER, + Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED, + Netresearch_OPS_Model_Status::PAYMENT_DELETED + ) + ); + } + + public function getOpsHtmlAnswer($payment = null) + { + $returnValue = ''; + if (null === $payment) { + $quoteId = Mage::getSingleton('checkout/session')->getQuote()->getId(); + if (null === $quoteId) { + $orderIncrementId = Mage::getSingleton('checkout/session')->getLastRealOrderId(); + $order = Mage::getModel('sales/order')->loadByAttribute('increment_id', $orderIncrementId); + } else { + $order = Mage::getModel('sales/order')->loadByAttribute('quote_id', $quoteId); + } + if ($order instanceof Mage_Sales_Model_Order && 0 < $order->getId()) { + $payment = $order->getPayment(); + $returnValue = $payment->getAdditionalInformation('HTML_ANSWER'); + } + } elseif ($payment instanceof Mage_Payment_Model_Info) { + $returnValue = $payment->getAdditionalInformation('HTML_ANSWER'); + } + + return $returnValue; + } + + public function getShippingTaxRate($order) + { + return $this->getRequestHelper()->getShippingTaxRate($order); + } + + protected function isRedirectNoticed($orderId) + { + if (Mage::helper('ops/directlink')->checkExistingTransact(self::OPS_CAPTURE_TRANSACTION_TYPE, $orderId)) { + $this->getHelper()->redirectNoticed( + $orderId, + $this->getHelper()->__( + 'You already sent a capture request. Please wait until the capture request is acknowledged.' + ) + ); + + return true; + } + if (Mage::helper('ops/directlink')->checkExistingTransact(self::OPS_VOID_TRANSACTION_TYPE, $orderId)) { + $this->getHelper()->redirectNoticed( + $orderId, + $this->getHelper()->__( + 'There is one void request waiting. Please wait until this request is acknowledged.' + ) + ); + + return true; + } + + return false; + } + + public function setConfig(Netresearch_OPS_Model_Config $config) + { + $this->_config = $config; + } + + /** + * If cart Item information has to be transmitted to Ingenico ePayments + * + * @return bool + */ + + public function needsOrderItemDataForRequest() + { + return $this->_needsCartDataForRequest; + } + + /** + * Returns array with the order item data formatted in Ingenico ePayments fashion if payment method implementation + * needs the data otherwise just returns false. + * + * @param Mage_Sales_Model_Order $order + * + * @return array|false + */ + public function getOrderItemData(Mage_Sales_Model_Order $order) + { + if (!$this->needsOrderItemDataForRequest()) { + return false; + } + + return $this->getRequestHelper()->extractOrderItemParameters($order); + } + + /** + * @inheritDoc + */ + public function canVoid(Varien_Object $payment) + { + if (Netresearch_OPS_Model_Status::canVoidTransaction($payment->getAdditionalInformation('status'))) { + return parent::canVoid($payment); + } else { + return false; + } + + } + + /** + * @inheritDoc + */ + public function assignData($data) + { + parent::assignData($data); + + $paymentInfo = $this->getInfoInstance(); + if ($data instanceof Varien_Object && $data->getData($this->getCode().'_data')) { + foreach ($data->getData($this->getCode().'_data') as $key => $value) { + $paymentInfo->setAdditionalInformation($key, $value); + } + } + + return $this; + } + + + /** + * @inheritDoc + */ + public function validate() + { + + $paymentInfo = $this->getInfoInstance(); + if ($paymentInfo instanceof Mage_Sales_Model_Order_Payment) { + $billingAddress = $paymentInfo->getOrder()->getBillingAddress(); + $shippingAddress = $paymentInfo->getOrder()->getShippingAddress(); + $salesObject = $paymentInfo->getOrder(); + } else { + /** @var Mage_Sales_Model_Quote $salesObject */ + $salesObject = $paymentInfo->getQuote(); + $billingAddress = $salesObject->getBillingAddress(); + $shippingAddress = $salesObject->getShippingAddress(); + } + + /** @var Netresearch_OPS_Model_Validator_Parameter_Length $validator */ + $validator = Mage::getModel('ops/validator_parameter_factory')->getValidatorFor( + Netresearch_OPS_Model_Validator_Parameter_Factory::TYPE_REQUEST_PARAMS_VALIDATION + ); + + $params = $this->getRequestHelper()->getOwnerParams($billingAddress, $salesObject); + $billingParams = $this->getBillToParams($billingAddress); + $shippingParams = $this->getShipToParams($shippingAddress); + if ($shippingParams) { + $params = array_merge($params, $shippingParams); + } + if ($billingParams) { + $params = array_merge($params, $billingParams); + } + $params['CN'] = $billingAddress->getFirstname() . ' ' . $billingAddress->getLastname(); + + if (false === $validator->isValid($params)) { + $result = Mage::helper('ops/validation_result')->getValidationFailedResult( + $validator->getMessages(), $salesObject + ); + throw Mage::exception('Mage_Payment', implode(', ', $result['fields'])); + } + + return parent::validate(); + } + + /** + * Transliterates params if necessary by configuration + * + * @param string[] $formFields formfields to transliterate + * + * @return string[] + */ + protected function transliterateParams($formFields) + { + if (strtoupper($this->getEncoding()) != 'UTF-8') { + setlocale(LC_CTYPE, Mage::app()->getLocale()->getLocaleCode()); + array_walk( + $formFields, + function (&$value) { + $value = iconv('UTF-8', 'ASCII//TRANSLIT', $value); + } + ); + } + + return $formFields; + } + + +} diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Backend/OpsId.php b/app/code/community/Netresearch/OPS/Model/Payment/Backend/OpsId.php new file mode 100644 index 0000000..c8781b1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Backend/OpsId.php @@ -0,0 +1,36 @@ + + * @author Thomas Birke + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_Backend_OpsId + extends Mage_Payment_Model_Method_Abstract +{ + /* allow usage in Magento backend */ + protected $_canUseInternal = true; + + /* deny usage in Magento frontend */ + protected $_canUseCheckout = false; + + protected $_canBackendDirectCapture = true; + + protected $_isGateway = false; + protected $_canAuthorize = false; + protected $_canCapture = true; + protected $_canCapturePartial = true; + protected $_canRefund = true; + protected $_canRefundInvoicePartial = true; + protected $_canVoid = true; + + /** payment code */ + protected $_code = 'ops_opsid'; + + protected $_formBlockType = 'ops/form_opsId'; + + protected $_infoBlockType = 'ops/info_opsId'; +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Bancontact.php b/app/code/community/Netresearch/OPS/Model/Payment/Bancontact.php new file mode 100644 index 0000000..5b4afce --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Bancontact.php @@ -0,0 +1,102 @@ + + */ +class Netresearch_OPS_Model_Payment_Bancontact + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'CreditCard'; + protected $brand = 'BCMC'; + const CODE = 'ops_BCMC'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_bancontact'; + + /** payment code */ + protected $_code = self::CODE; + + protected $_mobileDetectHelper = null; + + /** + * add needed params to dependend formfields + * + * @param Mage_Sales_Model_Order $order + * @param null $requestParams + * @return string[] + */ + public function getMethodDependendFormFields($order, $requestParams = null) + { + $formFields = parent::getMethodDependendFormFields($order, $requestParams); + $formFields['DEVICE'] = $this->getInfoInstance()->getAdditionalInformation('DEVICE'); + + return $formFields; + } + + /** + * @param mixed $data + * @return $this + */ + public function assignData($data) + { + parent::assignData($data); + $payment = $this->getInfoInstance(); + $payment->setAdditionalInformation('DEVICE', $this->getMobileDetectHelper()->getDeviceType()); + + return $this; + } + + /** + * Get Mobile Detect Helper + * + * @return Netresearch_OPS_Helper_MobileDetect + */ + public function getMobileDetectHelper() + { + if ($this->_mobileDetectHelper === null) { + $this->_mobileDetectHelper = Mage::helper('ops/mobileDetect'); + } + return $this->_mobileDetectHelper; + } + + /** + * @param Netresearch_OPS_Helper_MobileDetect $mobileHelper + * + * @returns $this + */ + public function setMobileDetectHelper($mobileHelper) + { + $this->_mobileDetectHelper = $mobileHelper; + + return $this; + } + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/BankTransfer.php b/app/code/community/Netresearch/OPS/Model/Payment/BankTransfer.php new file mode 100644 index 0000000..45f14f9 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/BankTransfer.php @@ -0,0 +1,49 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_BankTransfer + extends Netresearch_OPS_Model_Payment_Abstract +{ + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** form block type */ + protected $_formBlockType = 'ops/form_bankTransfer'; + + /** payment code */ + protected $_code = 'ops_bankTransfer'; + + + /** + * Assign data to info model instance + * + * @param mixed $data + * @return Mage_Payment_Model_Info + */ + public function assignData($data) + { + $countryId = ''; + if (is_object($data) && $data instanceof Varien_Object) { + $countryId = $data->getCountryId(); + } elseif (is_array($data) && isset($data['country_id'])) { + $countryId = $data['country_id']; + } + $pm = $brand = trim('Bank transfer' . (('*' == $countryId) ? '' : ' ' . $countryId)); + + $payment = Mage::getSingleton('checkout/session')->getQuote()->getPayment(); + $payment->setAdditionalInformation('PM', $pm); + $payment->setAdditionalInformation('BRAND', $brand); + + parent::assignData($data); + return $this; + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Payment/BelfiusDirectNet.php b/app/code/community/Netresearch/OPS/Model/Payment/BelfiusDirectNet.php new file mode 100644 index 0000000..c3f84c7 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/BelfiusDirectNet.php @@ -0,0 +1,25 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_BelfiusDirectNet + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'Belfius Direct Net'; + protected $brand = 'Belfius Direct Net'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_belfiusDirectNet'; +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/CashU.php b/app/code/community/Netresearch/OPS/Model/Payment/CashU.php new file mode 100644 index 0000000..16c44dd --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/CashU.php @@ -0,0 +1,25 @@ + + * @author Thomas Kappel + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_CashU + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'cashU'; + protected $brand = 'cashU'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_cashU'; +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/CbcOnline.php b/app/code/community/Netresearch/OPS/Model/Payment/CbcOnline.php new file mode 100644 index 0000000..b7f6d56 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/CbcOnline.php @@ -0,0 +1,25 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_CbcOnline + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'CBC Online'; + protected $brand = 'CBC Online'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_cbcOnline'; +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Cc.php b/app/code/community/Netresearch/OPS/Model/Payment/Cc.php new file mode 100644 index 0000000..e107f0d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Cc.php @@ -0,0 +1,302 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_Cc extends Netresearch_OPS_Model_Payment_DirectLink +{ + const CODE = 'ops_cc'; + + /** info source path */ + protected $_infoBlockType = 'ops/info_cc'; + + /** @var string $_formBlockType define a specific form block */ + protected $_formBlockType = 'ops/form_cc'; + + /** payment code */ + protected $_code = self::CODE; + + protected $featureModel = null; + + + /** + * @param null $payment + * @return string + */ + public function getOpsCode($payment = null) + { + $opsBrand = $this->getOpsBrand($payment); + if ('PostFinance card' == $opsBrand) { + return 'PostFinance Card'; + } + if ('UNEUROCOM' == $this->getOpsBrand($payment)) { + return 'UNEUROCOM'; + } + + return 'CreditCard'; + } + + /** + * @param null $payment + * + * @return array|mixed|null + */ + public function getOpsBrand($payment = null) + { + if (null === $payment) { + $payment = Mage::getSingleton('checkout/session')->getQuote()->getPayment(); + } + + return $payment->getAdditionalInformation('CC_BRAND'); + } + + /** + * If payment is inline there should be no orderPlaceRedirectUrl except for 3d secure cards - if order was placed + * through admin it is definitely an inline payment. + * + * @param Mage_Sales_Model_Order_Payment $payment + * + * @return bool|string + */ + public function getOrderPlaceRedirectUrl($payment = null) + { + $salesObject = $this->getInfoInstance()->getOrder() ? : $this->getInfoInstance()->getQuote(); + if ($this->hasBrandAliasInterfaceSupport($payment) || null === $salesObject->getRemoteIp()) { + if ('' == $this->getOpsHtmlAnswer($payment)) { + return false; + } // Prevent redirect on cc payment + else { + return $this->getConfig()->get3dSecureRedirectUrl(); + } + } + + return parent::getOrderPlaceRedirectUrl(); + } + + /** + * only some brands are supported to be integrated into onepage checkout + * + * @return array + */ + public function getBrandsForAliasInterface() + { + $brands = $this->getConfig()->getInlinePaymentCcTypes($this->getCode()); + + return $brands; + } + + /** + * if cc brand supports ops alias interface + * + * @param Mage_Payment_Model_Info $payment + * + * @return bool + */ + public function hasBrandAliasInterfaceSupport($payment = null) + { + return in_array( + $this->getOpsBrand($payment), + $this->getBrandsForAliasInterface() + ); + } + + /** + * Validates alias for in quote provided addresses + * + * @param Mage_Sales_Model_Quote $quote + * @param Varien_Object $payment + * + * @throws Mage_Core_Exception + */ + protected function validateAlias($quote, $payment) + { + $alias = $payment->getAdditionalInformation('alias'); + if (0 < strlen(trim($alias)) + && is_numeric($payment->getAdditionalInformation('cvc')) + && false === Mage::helper('ops/alias')->isAliasValidForAddresses( + $quote->getCustomerId(), + $alias, + $quote->getBillingAddress(), + $quote->getShippingAddress(), + $quote->getStoreId() + ) + ) { + $this->getOnepage()->getCheckout()->setGotoSection('payment'); + Mage::throwException( + $this->getHelper()->__('Invalid payment information provided!') + ); + } + } + + /** + * @return Netresearch_OPS_Helper_Creditcard + */ + public function getRequestParamsHelper() + { + if (null === $this->requestParamsHelper) { + $this->requestParamsHelper = Mage::helper('ops/creditcard'); + } + + return $this->requestParamsHelper; + } + + + protected function performPreDirectLinkCallActions( + Mage_Sales_Model_Quote $quote, Varien_Object $payment, + $requestParams = array() + ) + { + Mage::helper('ops/alias')->cleanUpAdditionalInformation($payment, true); + if (true === Mage::getModel('ops/config')->isAliasManagerEnabled($this->getCode())) { + $this->validateAlias($quote, $payment); + } + + return $this; + } + + protected function performPostDirectLinkCallAction(Mage_Sales_Model_Quote $quote, Mage_Sales_Model_Order $order) + { + Mage::helper('ops/alias')->setAliasActive($quote, $order); + + return $this; + } + + protected function handleAdminPayment(Mage_Sales_Model_Quote $quote) + { + return $this; + } + + + /** + * returns allow zero amount authorization + * only TRUE if configured payment action for the store is authorize + * + * @param mixed null|int $storeId + * + * @return bool + */ + public function isZeroAmountAuthorizationAllowed($storeId = null) + { + $result = false; + if ( + $this->getConfig()->getPaymentAction($storeId) == Netresearch_OPS_Model_Payment_Abstract::ACTION_AUTHORIZE + && true == Mage::getStoreConfig('payment/ops_cc/zero_amount_checkout', $storeId) + ) { + $result = true; + } + + return $result; + } + + + /** + * method was implemented in CE 1.8 / EE 1.14 + * if Version is CE 1.8 / EE 1.14 use parent method otherwise use our implementation + * + * @param Mage_Sales_Model_Quote $quote + * @param $checksBitMask + * + * @return bool + */ + public function isApplicableToQuote($quote, $checksBitMask) + { + $result = true; + if (Mage::helper('ops/version')->canUseApplicableForQuote(Mage::getEdition())) { + $result = parent::isApplicableToQuote($quote, $checksBitMask); + } + + if ($quote->getBaseGrandTotal() < 0.01 && $result === false) { + $result = $this->getFeatureModel()->isCCAndZeroAmountAuthAllowed($this, $quote); + } + + return $result; + } + + /** + * @return Netresearch_OPS_Model_Payment_Features_ZeroAmountAuth + */ + public function getFeatureModel() + { + if (null === $this->featureModel) { + $this->featureModel = Mage::getModel('ops/payment_features_zeroAmountAuth'); + } + + return $this->featureModel; + } + + /** + * setter for canCapture from outside, needed for zero amount order since we need to disable online capture + * but still need to be able to create a invoice + * + * @param $canCapture + */ + public function setCanCapture($canCapture) + { + if ($this->_canCapture != $canCapture) { + $this->_canCapture = $canCapture; + } + } + + /** + * Check wether payment method is available for quote + * + * @param Mage_Sales_Model_Quote $quote + * + * @return bool + */ + public function isAvailable($quote = null) + { + if (null != $quote && !$quote->getItemsCount() > 0 && $this->getDataHelper()->isAdminSession()) { + /* Disable payment method in backend as long as there are no items in quote to + * avoid problems with alias creation in EE1.12 & EE1.13 + */ + return false; + } + + return parent::isAvailable($quote); + } + + public function isInitializeNeeded() + { + return !$this->getPaymentHelper()->isInlinePayment($this->getInfoInstance()); + } + + /** + * @inheritdoc + */ + public function getMethodDependendFormFields($order, $requestParams = null) + { + $formFields = parent::getMethodDependendFormFields($order, $requestParams); + if ($this->getConfig()->getCreditDebitSplit($order->getStoreId())) { + $formFields['CREDITDEBIT'] = "C"; + } + + $alias = $order->getPayment()->getAdditionalInformation('alias'); + + if ($alias) { + $formFields['ALIAS'] = $alias; + $formFields['ALIASOPERATION'] = "BYPSP"; + $formFields['ECI'] = 9; + $formFields['ALIASUSAGE'] = $this->getConfig()->getAliasUsageForExistingAlias( + $order->getPayment()->getMethodInstance()->getCode(), + $order->getStoreId() + ); + } else { + $formFields['ALIAS'] = ""; + $formFields['ALIASOPERATION'] = "BYPSP"; + $formFields['ALIASUSAGE'] = $this->getConfig()->getAliasUsageForNewAlias( + $order->getPayment()->getMethodInstance()->getCode(), + $order->getStoreId() + ); + + } + + return $formFields; + } +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/ChinaUnionPay.php b/app/code/community/Netresearch/OPS/Model/Payment/ChinaUnionPay.php new file mode 100644 index 0000000..758cae0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/ChinaUnionPay.php @@ -0,0 +1,58 @@ + + */ +class Netresearch_OPS_Model_Payment_ChinaUnionPay + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'PAYDOL_UPOP'; + protected $brand = 'UnionPay'; + const CODE = 'ops_chinaUnionPay'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** disable partial refund */ + protected $_canRefundInvoicePartial = false; + + /** disable partial capture */ + protected $_canCapturePartial = false; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = self::CODE; + + public function getPaymentAction() + { + return Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE; + } + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Debitcard.php b/app/code/community/Netresearch/OPS/Model/Payment/Debitcard.php new file mode 100644 index 0000000..04f89ff --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Debitcard.php @@ -0,0 +1,54 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_Debitcard extends Netresearch_OPS_Model_Payment_Cc +{ + + /** payment code */ + protected $_code = 'ops_dc'; + + + /** + * @param null $payment + * @return string + */ + public function getOpsCode($payment = null) + { + return 'CreditCard'; + } + + /** + * @inheritdoc + */ + public function getMethodDependendFormFields($order, $requestParams = null) + { + $formFields = parent::getMethodDependendFormFields($order, $requestParams); + if ($this->getConfig()->getCreditDebitSplit($order->getStoreId())) { + $formFields['CREDITDEBIT'] = "D"; + } + + return $formFields; + } + + /** + * @return Netresearch_OPS_Helper_Debitcard + */ + public function getRequestParamsHelper() + { + if (null === $this->requestParamsHelper) { + $this->requestParamsHelper = Mage::helper('ops/debitcard'); + } + + return $this->requestParamsHelper; + } + + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/DirectDebit.php b/app/code/community/Netresearch/OPS/Model/Payment/DirectDebit.php new file mode 100644 index 0000000..e999967 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/DirectDebit.php @@ -0,0 +1,61 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_DirectDebit + extends Netresearch_OPS_Model_Payment_DirectLink +{ + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /* define a specific form block */ + protected $_formBlockType = 'ops/form_directDebit'; + + /** payment code */ + protected $_code = 'ops_directDebit'; + + public function getOrderPlaceRedirectUrl() + { + // Prevent redirect on direct debit payment + return false; + } + + + /** + * @return Netresearch_OPS_Helper_DirectDebit + */ + public function getRequestParamsHelper() + { + if (null === $this->requestParamsHelper) { + $this->requestParamsHelper = Mage::helper('ops/directDebit'); + } + + return $this->requestParamsHelper; + } + + protected function performPreDirectLinkCallActions( + Mage_Sales_Model_Quote $quote, + Varien_Object $payment, $requestParams = array() + ) + { + return $this; + } + + protected function performPostDirectLinkCallAction(Mage_Sales_Model_Quote $quote, Mage_Sales_Model_Order $order) + { + Mage::helper('ops/alias')->setAliasActive($quote, $order); + + return $this; + } + + protected function handleAdminPayment(Mage_Sales_Model_Quote $quote) + { + return $this; + } +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/DirectEbanking.php b/app/code/community/Netresearch/OPS/Model/Payment/DirectEbanking.php new file mode 100644 index 0000000..9f316f9 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/DirectEbanking.php @@ -0,0 +1,67 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_DirectEbanking + extends Netresearch_OPS_Model_Payment_Abstract +{ + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** form block type */ + protected $_formBlockType = 'ops/form_directEbanking'; + + /** payment code */ + protected $_code = 'ops_directEbanking'; + + /** + * Assign data to info model instance + * + * @param mixed $data + * @return Mage_Payment_Model_Info + */ + public function assignData($data) + { + $brand = ''; + + if (is_object($data) && $data instanceof Varien_Object) { + $brand = $data['directEbanking_brand']; + } elseif (is_array($data) && isset($data['directEbanking_brand'])) { + + $brand = $data['directEbanking_brand']; + } + + $brand = $this->fixSofortUberweisungBrand($brand); + + $payment = $this->getInfoInstance(); + // brand == pm for all DirectEbanking methods + $payment->setAdditionalInformation('PM', $brand); + $payment->setAdditionalInformation('BRAND', $brand); + parent::assignData($data); + return $this; + } + + + /** + * Fixes legacy brand value of Sofort Uberweisung for DirectEbanking + * + * @param string $value + * @return string + */ + protected function fixSofortUberweisungBrand($value) + { + if ($value === 'Sofort Uberweisung') { + return 'DirectEbanking'; + } + return $value; + } +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/DirectLink.php b/app/code/community/Netresearch/OPS/Model/Payment/DirectLink.php new file mode 100644 index 0000000..8c8c9c8 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/DirectLink.php @@ -0,0 +1,350 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +abstract class Netresearch_OPS_Model_Payment_DirectLink extends Netresearch_OPS_Model_Payment_Abstract +{ + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** Check if we can capture directly from the backend */ + protected $_canUseInternal = true; + + protected $config = null; + + protected $directLinkHelper = null; + + protected $paymentHelper = null; + + protected $quoteHelper = null; + + protected $requestParamsHelper = null; + + protected $validationFactory = null; + + protected $dataHelper = null; + + protected $_isInitializeNeeded = false; + + /** + * @param Netresearch_OPS_Helper_Payment_DirectLink_RequestInterface $requestParamsHelper + */ + public function setRequestParamsHelper($requestParamsHelper) + { + $this->requestParamsHelper = $requestParamsHelper; + } + + + /** + * sets the quote helper + * + * @param Netresearch_OPS_Helper_Quote $quoteHelper + */ + public function setQuoteHelper(Netresearch_OPS_Helper_Quote $quoteHelper) + { + $this->quoteHelper = $quoteHelper; + } + + /** + * gets the quote helper + * + * @return Netresearch_OPS_Helper_Quote + */ + public function getQuoteHelper() + { + if (null === $this->quoteHelper) { + $this->quoteHelper = Mage::helper('ops/quote'); + } + + return $this->quoteHelper; + } + + + /** + * @param Netresearch_OPS_Helper_Directlink $directLinkHelper + */ + public function setDirectLinkHelper($directLinkHelper) + { + $this->directLinkHelper = $directLinkHelper; + } + + /** + * @return Netresearch_OPS_Helper_Directlink + */ + public function getDirectLinkHelper() + { + if (null === $this->directLinkHelper) { + $this->directLinkHelper = MAge::helper('ops/directlink'); + } + + return $this->directLinkHelper; + } + + /** + * @param Netresearch_OPS_Helper_Payment $paymentHelper + */ + public function setPaymentHelper($paymentHelper) + { + $this->paymentHelper = $paymentHelper; + } + + /** + * @return Netresearch_OPS_Helper_Payment + */ + public function getPaymentHelper() + { + if (null === $this->paymentHelper) { + $this->paymentHelper = Mage::helper('ops/payment'); + } + + return $this->paymentHelper; + } + + + /** + * @param null $config + */ + public function setConfig(Netresearch_OPS_Model_Config $config) + { + $this->config = $config; + } + + /** + * @return Netresearch_OPS_Model_Config + */ + public function getConfig() + { + if (null === $this->config) { + $this->config = Mage::getModel('ops/config'); + } + + return $this->config; + } + + /** + * @param Varien_Object $payment + * @param float $amount + * + * @return Mage_Payment_Model_Abstract|void + */ + + public function authorize(Varien_Object $payment, $amount) + { + if ($this->isInlinePayment($payment) + && Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE == $this->getConfigPaymentAction() + ) { + $order = $payment->getOrder(); + $quote = $this->getQuoteHelper()->getQuote(); + $this->confirmPayment($order, $quote, $payment); + } + } + + /** + * Saves the payment model and runs the request to Ingenico ePaymentss webservice + * + * @param Mage_Sales_Model_Order $order + * @param Mage_Sales_Model_Quote $quote + * @param Varien_Object $payment + * + * @throws Mage_Core_Exception + */ + + protected function confirmPayment(Mage_Sales_Model_Order $order, Mage_Sales_Model_Quote $quote, + Varien_Object $payment + ) + { + $this->handleAdminPayment($quote); + $requestParams = $this->getRequestParamsHelper()->getDirectLinkRequestParams($quote, $order, $payment); + $this->invokeRequestParamValidation($requestParams); + $this->performPreDirectLinkCallActions($quote, $order); + $response = $this->getDirectLinkHelper()->performDirectLinkRequest( + $quote, $requestParams, $quote->getStoreId() + ); + if ($response) { + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $this, false); + $this->performPostDirectLinkCallAction($quote, $order); + + } else { + $this->getPaymentHelper()->handleUnknownStatus($order); + } + } + + /** + * Handles backend payments on Magento side + * + * @param Mage_Sales_Model_Quote $quote + * + * @return Netresearch_OPS_Model_Payment_DirectLink + */ + abstract protected function handleAdminPayment(Mage_Sales_Model_Quote $quote); + + /** + * @return Netresearch_OPS_Helper_Payment_DirectLink_RequestInterface + */ + abstract protected function getRequestParamsHelper(); + + + /** + * Perform necessary preparation before request to Ingenico ePayments is sent + * + * @param Mage_Sales_Model_Quote $quote + * @param Varien_Object $payment + * @param array $requestParams + * + * @return Netresearch_OPS_Model_Payment_DirectLink + */ + abstract protected function performPreDirectLinkCallActions(Mage_Sales_Model_Quote $quote, Varien_Object $payment, + $requestParams = array() + ); + + /** + * Perform necessary work after the Directlink Request was sent and an response was received and processed + * + * @param Mage_Sales_Model_Quote $quote + * @param Mage_Sales_Model_Order $order + * + * @return Netresearch_OPS_Model_Payment_DirectLink + */ + abstract protected function performPostDirectLinkCallAction(Mage_Sales_Model_Quote $quote, + Mage_Sales_Model_Order $order + ); + + + /** + * performs direct link request either for inline payments and + * direct sale mode or the normal maintenance call (invoice) + * + * @override + * + * @param Varien_Object $payment + * @param float $amount + * + * @return Mage_Payment_Model_Abstract|void + */ + public function capture(Varien_Object $payment, $amount) + { + /** + * process direct sale inline payments (initial request) + */ + if (Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE == $this->getConfigPaymentAction() + && $this->getPaymentHelper()->isInlinePayment($payment) + ) { + $order = $payment->getOrder(); + $quote = $this->getQuoteHelper()->getQuote(); + $this->confirmPayment($order, $quote, $payment); + } /** + * invoice request authorize mode if the payment was placed on Ingenico ePayments side + */ + elseif (0 < strlen(trim($payment->getAdditionalInformation('paymentId')))) { + parent::capture($payment, $amount); + } + } + + + /** + * checks if the selected payment supports inline mode + * + * @param $payment - the payment to check + * + * @return bool - true if it's support inline mode, false otherwise + */ + protected function isInlinePayment($payment) + { + $result = false; + + $methodInstance = $payment->getMethodInstance(); + if (( + $methodInstance instanceof Netresearch_OPS_Model_Payment_Cc + && $methodInstance->hasBrandAliasInterfaceSupport($payment) + || $this->getDataHelper()->isAdminSession() + ) + || $methodInstance instanceof Netresearch_OPS_Model_Payment_DirectDebit + ) { + $result = true; + } + + return $result; + } + + + /** + * Get one page checkout model + * + * @return Mage_Checkout_Model_Type_Onepage + */ + public function getOnepage() + { + return Mage::getSingleton('checkout/type_onepage'); + } + + /** + * Validate checkout request parameters + * + * @param $requestParams + * + * @throws Mage_Core_Exception + * @return Netresearch_OPS_Model_Payment_DirectLink + */ + protected function invokeRequestParamValidation($requestParams) + { + $validator = $this->getValidationFactory()->getValidatorFor( + Netresearch_OPS_Model_Validator_Parameter_Factory::TYPE_REQUEST_PARAMS_VALIDATION + ); + if (false == $validator->isValid($requestParams)) { + $this->getOnepage()->getCheckout()->setGotoSection('payment'); + Mage::throwException( + $this->getHelper()->__('The data you have provided can not be processed by Ingenico ePayments') + ); + } + + return $this; + } + + /** + * @return Netresearch_OPS_Model_Validator_Parameter_Factory + */ + public function getValidationFactory() + { + if (null === $this->validationFactory) { + $this->validationFactory = Mage::getModel('ops/validator_parameter_factory'); + } + + return $this->validationFactory; + } + + /** + * sets the used validation factory + * + * @param Netresearch_OPS_Model_Validator_Parameter_Factory $validationFactory + */ + public function setValidationFactory(Netresearch_OPS_Model_Validator_Parameter_Factory $validationFactory) + { + $this->validationFactory = $validationFactory; + } + + /** + * @param Netresearch_OPS_Helper_Data $dataHelper + */ + public function setDataHelper(Netresearch_OPS_Helper_Data $dataHelper) + { + $this->dataHelper = $dataHelper; + } + + /** + * @return Netresearch_OPS_Helper_Data + */ + public function getDataHelper() + { + if (null === $this->dataHelper) { + $this->dataHelper = Mage::helper('ops/data'); + } + + return $this->dataHelper; + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Payment/EDankort.php b/app/code/community/Netresearch/OPS/Model/Payment/EDankort.php new file mode 100644 index 0000000..fc8cfae --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/EDankort.php @@ -0,0 +1,26 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_EDankort + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'eDankort'; + protected $brand = 'eDankort'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_eDankort'; + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Eps.php b/app/code/community/Netresearch/OPS/Model/Payment/Eps.php new file mode 100644 index 0000000..4567ef2 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Eps.php @@ -0,0 +1,24 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_Eps + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'EPS'; + protected $brand = 'EPS'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_eps'; +} diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Features/PaymentEmail.php b/app/code/community/Netresearch/OPS/Model/Payment/Features/PaymentEmail.php new file mode 100644 index 0000000..912f795 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Features/PaymentEmail.php @@ -0,0 +1,198 @@ + + */ +class Netresearch_OPS_Model_Payment_Features_PaymentEmail +{ + + protected function getConfig() + { + return Mage::getModel('ops/config'); + } + + /** + * Check if payment email is available for order + * + * @param $order + * + * @return bool + */ + public function isAvailableForOrder($order) + { + if ($order instanceof Mage_Sales_Model_Order) { + $status = $order->getPayment()->getAdditionalInformation('status'); + + return Netresearch_OPS_Model_Status::canResendPaymentInfo($status); + } + + return false; + } + + + /** + * Resends the payment information and returns true/false, depending if succeeded or not + * + * @param Mage_Sales_Model_Order $order + * + * @return boolean success state + */ + public function resendPaymentInfo(Mage_Sales_Model_Order $order) + { + + // reset payment method so the customer can choose freely from all available methods + $this->setPaymentMethodToGeneric($order); + + $identity = $this->getIdentity($this->getConfig()->getResendPaymentInfoIdentity($order->getStoreId())); + + if ($order->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_PayPerMail) { + $template = $this->getConfig()->getPayPerMailTemplate($order->getStoreId()); + } else { + $template = $this->getConfig()->getResendPaymentInfoTemplate($order->getStoreId()); + } + + $emailTemplate = $this->prepareTemplate( + $template, + $identity->getEmail(), + $identity->getName() + ); + + $parameters = array( + "order" => $order, + "paymentLink" => $this->generatePaymentLink($order), + "store" => Mage::app()->getStore($order->getStoreId()) + ); + + return $emailTemplate->send($order->getCustomerEmail(), $order->getCustomerName(), $parameters); + + } + + /** + * Generates the payment url + * + * @param Mage_Sales_Model_Order $order + * + * @return string + */ + protected function generatePaymentLink(Mage_Sales_Model_Order $order) + { + $opsOrderId = Mage::helper('ops/order')->getOpsOrderId($order); + + $url = Mage::getModel('ops/config')->getPaymentRetryUrl( + Mage::helper('ops/payment')->validateOrderForReuse($opsOrderId, $order->getStoreId()), + $order->getStoreId() + ); + + return $url; + } + + /** + * Set payment method to Netresearch_OPS_Model_Payment_Flex + * + * @param Mage_Sales_Model_Order $order + * + * @throws Exception + */ + protected function setPaymentMethodToGeneric(Mage_Sales_Model_Order $order) + { + if (!$order->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_PayPerMail) { + $order->getPayment()->setMethod(Netresearch_OPS_Model_Payment_Flex::CODE)->save(); + } + } + + /** + * Sends suspend subscription mail to configured store contact via configured mail template + * + * @param Mage_Sales_Model_Recurring_Profile $profile + * @param Mage_Customer_Model_Customer $customer + * + * @return bool + */ + public function sendSuspendSubscriptionMail($profile, $customer) + { + if (null === $profile || null === $customer) { + Mage::throwException('Could not send mail due to internal error!'); + } + + $emailTemplate = $this->prepareTemplate( + $this->getConfig()->getSuspendSubscriptionTemplate($profile->getStoreId()), + $customer->getEmail(), + $customer->getName() + ); + + $emailTemplate->addBcc($customer->getEmail()); + + $parameters = array( + "profile" => $profile, + "customer" => $customer, + "store" => Mage::app()->getStore($profile->getStoreId()) + ); + + $identity = $this->getIdentity($this->getConfig()->getSuspendSubscriptionIdentity($profile->getStoreId())); + + return $emailTemplate->send($identity->getEmail(), $identity->getName(), $parameters); + + } + + + /** + * Loads email and name of the given store identity + * + * @param string $key - identity to load, defaults to sales + * + * @return Varien_Object with data name and email + */ + protected function getIdentity($key = 'sales') + { + $identity = new Varien_Object(); + $identity->setName(Mage::getStoreConfig('trans_email/ident_' . $key . '/name')) + ->setEmail(Mage::getStoreConfig('trans_email/ident_' . $key . '/email')); + + return $identity; + } + + /** + * Loads the given template by identifier, sets sender mail and name + * + * @param string $template + * @param string $senderMail + * @param string $senderName + * + * @return Mage_Core_Model_Email_Template + */ + protected function prepareTemplate($template, $senderMail, $senderName) + { + $emailTemplate = Mage::getModel('core/email_template')->load($template); + if (null === $emailTemplate->getTemplateSubject()) { + $emailTemplate = $emailTemplate->loadDefault($template); + } + $emailTemplate->setSenderName($senderName) + ->setSenderEmail($senderMail); + + return $emailTemplate; + } + +} diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Features/ZeroAmountAuth.php b/app/code/community/Netresearch/OPS/Model/Payment/Features/ZeroAmountAuth.php new file mode 100644 index 0000000..c85d8f2 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Features/ZeroAmountAuth.php @@ -0,0 +1,41 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_Features_ZeroAmountAuth +{ + + /** + * check if payment method is cc and zero amount authorization is enabled + * + * @param Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod + * + * @return bool + */ + public function isCCAndZeroAmountAuthAllowed( + Netresearch_OPS_Model_Payment_Abstract $opsPaymentMethod, + Mage_Sales_Model_Quote $quote + ) + { + $result = false; + $storeId = $quote->getStoreId(); + if ($quote->getBaseGrandTotal() < 0.01 + && $opsPaymentMethod instanceof Netresearch_OPS_Model_Payment_Cc + && $opsPaymentMethod->isZeroAmountAuthorizationAllowed($storeId) + && 0 < $quote->getItemsCount() + && !$quote->isNominal() + + ) { + $result = true; + } + + return $result; + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Flex.php b/app/code/community/Netresearch/OPS/Model/Payment/Flex.php new file mode 100644 index 0000000..94320da --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Flex.php @@ -0,0 +1,41 @@ +getInfoInstance()->getAdditionalInformation(self::INFO_KEY_PM); + } + + public function getOpsBrand($payment = null) + { + return $this->getInfoInstance()->getAdditionalInformation(self::INFO_KEY_BRAND); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Payment/FortisPayButton.php b/app/code/community/Netresearch/OPS/Model/Payment/FortisPayButton.php new file mode 100644 index 0000000..a18f382 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/FortisPayButton.php @@ -0,0 +1,25 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_FortisPayButton + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'Fortis Pay Button'; + protected $brand = 'Fortis Pay Button'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_fortisPayButton'; +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/GiroPay.php b/app/code/community/Netresearch/OPS/Model/Payment/GiroPay.php new file mode 100644 index 0000000..eb9e07d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/GiroPay.php @@ -0,0 +1,26 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_GiroPay + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'giropay'; + protected $brand = 'giropay'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_giroPay'; + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/IDeal.php b/app/code/community/Netresearch/OPS/Model/Payment/IDeal.php new file mode 100644 index 0000000..e677ca6 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/IDeal.php @@ -0,0 +1,78 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_IDeal + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'iDEAL'; + protected $brand = 'iDEAL'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + protected $_formBlockType = 'ops/form_ideal'; + + /** payment code */ + protected $_code = 'ops_iDeal'; + + /** + * adds payment specific information to the payment + * + * @param mixed $data - data containing the issuer id which should be used + * + * @return Netresearch_OPS_Model_Payment_IDeal + */ + public function assignData($data) + { + if ($data instanceof Varien_Object) { + $data = $data->getData(); + } + if (array_key_exists('iDeal_issuer_id', $data)) { + $this->getInfoInstance()->setAdditionalInformation('iDeal_issuer_id', $data['iDeal_issuer_id']); + } + parent::assignData($data); + + return $this; + } + + /** + * getter for the iDeal issuers + * + * @return array + */ + public function getIDealIssuers() + { + return Mage::getStoreConfig('payment/ops_iDeal/issuer'); + } + + /** + * add iDeal issuer id to form fields + * + * @override Netresearch_OPS_Model_Payment_Abstract + * + * @param $order + * @param null $requestParams + * + * @return array + */ + public function getMethodDependendFormFields($order, $requestParams = null) + { + $formFields = parent::getMethodDependendFormFields($order, $requestParams); + if ($order->getPayment()->getAdditionalInformation('iDeal_issuer_id')) { + $formFields['ISSUERID'] = $order->getPayment()->getAdditionalInformation('iDeal_issuer_id'); + } + + return $formFields; + } +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/IngHomePay.php b/app/code/community/Netresearch/OPS/Model/Payment/IngHomePay.php new file mode 100644 index 0000000..280d839 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/IngHomePay.php @@ -0,0 +1,26 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_IngHomePay + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'ING HomePay'; + protected $brand = 'ING HomePay'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_ingHomePay'; + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/InterSolve.php b/app/code/community/Netresearch/OPS/Model/Payment/InterSolve.php new file mode 100644 index 0000000..70a42ef --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/InterSolve.php @@ -0,0 +1,51 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_InterSolve + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'InterSolve'; + protected $brand = 'InterSolve'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + protected $_formBlockType = 'ops/form_interSolve'; + + /** payment code */ + protected $_code = 'ops_interSolve'; + + /** + * Assign data to info model instance + * + * @param mixed $data + * @return Mage_Payment_Model_Info + */ + public function assignData($data) + { + + $brand = ''; + if (is_object($data) && $data instanceof Varien_Object) { + $brand = $data->getIntersolveBrand(); + } elseif (is_array($data) && isset($data['intersolve_brand'])) { + $brand = $data['intersolve_brand']; + } + if (strlen(trim($brand)) === 0) { + $brand = 'InterSolve'; + } + $payment = $this->getInfoInstance(); + $payment->setAdditionalInformation('BRAND', $brand); + + parent::assignData($data); + return $this; + } +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/KbcOnline.php b/app/code/community/Netresearch/OPS/Model/Payment/KbcOnline.php new file mode 100644 index 0000000..35a8aa3 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/KbcOnline.php @@ -0,0 +1,26 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_KbcOnline + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'KBC Online'; + protected $brand = 'KBC Online'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_kbcOnline'; + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Kwixo/Abstract.php b/app/code/community/Netresearch/OPS/Model/Payment/Kwixo/Abstract.php new file mode 100644 index 0000000..c318044 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Kwixo/Abstract.php @@ -0,0 +1,551 @@ +getShippingAddress()) { + $isVirtual = false; + $carrierCode = $order->getShippingCarrier()->getCarrierCode(); + $this->loadShippingSettingForCarrierCode($carrierCode); + $shippingMethod = $carrierCode; + } + + $formFields['ECOM_ESTIMATEDELIVERYDATE'] + = $this->getEstimatedDeliveryDate( + $this->getCode(), $order->getStoreId() + ); + $formFields['RNPOFFERT'] = $this->getRnpFee( + $this->getCode(), $order->getStoreId() + ); + $formFields['ECOM_SHIPMETHODTYPE'] = $this->getShippingMethodType( + $this->getCode(), $order->getStoreId(), $isVirtual + ); + $formFields['ECOM_SHIPMETHODSPEED'] = $this->getShippingMethodSpeed( + $this->getCode(), $order->getStoreId() + ); + $shipMethodDetails = $this->getShippingMethodDetails( + $this->getCode(), $order->getStoreId() + ); + if (0 < strlen(trim($shipMethodDetails))) { + $formFields['ECOM_SHIPMETHODDETAILS'] = $shipMethodDetails; + } + if (4 == $formFields['ECOM_SHIPMETHODTYPE'] + && !array_key_exists( + 'ECOM_SHIPMETHODDETAILS', $formFields + ) + ) { + $address = $order->getShippingAddress() + ? $order->getShippingAddress()->toString() + : $order->getBillingAddress()->toString(); + $formFields['ECOM_SHIPMETHODDETAILS'] = substr($address, 0, 50); + } + + $formFields['ORDERSHIPMETH'] = $shippingMethod; + + $formFields['CIVILITY'] + = $this->getGender($order) == 'Male' ? 'Mr' : 'Mrs'; + $formFields = array_merge( + $formFields, $this->getKwixoBillToParams($order) + ); + $formFields = array_merge( + $formFields, $this->getKwixoShipToParams($order) + ); + $formFields = array_merge( + $formFields, $this->getItemParams($order) + ); + + $formFields['ORDERID'] = Mage::helper('ops/order')->getOpsOrderId( + $order, false + ); + $formFields = $this->populateFromArray( + $formFields, $requestParams, $order + ); + + return $formFields; + } + + + protected function getKwixoCategoryFromOrderItem( + Mage_Sales_Model_Order_Item $item + ) + { + $product = Mage::getModel('catalog/product')->load( + $item->getProductId() + ); + $kwixoCategoryId = null; + foreach ($product->getCategoryIds() as $categoryId) { + $kwixoCategory = Mage::getModel('ops/kwixo_category_mapping') + ->loadByCategoryId($categoryId); + if (null != $kwixoCategory->getId()) { + $kwixoCategoryId = $kwixoCategory->getKwixoCategoryId(); + break; + } + } + + return $kwixoCategoryId; + } + + /** + * + * + * @param Mage_Sales_Model_Order $order + * + * @return array + */ + public function getKwixoBillToParams(Mage_Sales_Model_Order $order) + { + $formFields = array(); + $billingAddress = $order->getBillingAddress(); + + $billingStreet = str_replace("\n", ' ', $billingAddress->getStreet(-1)); + $splittedBillingStreet = Mage::Helper('ops/address')->splitStreet($billingStreet); + + $formFields['ECOM_BILLTO_POSTAL_NAME_FIRST'] = $billingAddress->getFirstname(); + $formFields['ECOM_BILLTO_POSTAL_NAME_LAST'] = $billingAddress->getLastname(); + $formFields['OWNERADDRESS'] = $splittedBillingStreet['street_name']; + $formFields['OWNERADDRESS2'] = $splittedBillingStreet['supplement']; + $formFields['ECOM_BILLTO_POSTAL_STREET_NUMBER'] = $splittedBillingStreet['street_number']; + $formFields['OWNERTELNO'] = $billingAddress->getTelephone(); + + return $formFields; + } + + /** + * return the shipping parameters as array based on shipping method type + * + * @param Mage_Sales_Model_Order $order + * + * @return array + */ + public function getKwixoShipToParams(Mage_Sales_Model_Order $order) + { + $formFields = array(); + $shippingAddress = $order->getShippingAddress(); + + if ($shippingAddress === false) { + $shippingAddress = $order->getBillingAddress(); + } + + $shippingStreet = str_replace("\n", ' ', $shippingAddress->getStreet(-1)); + $splittedShippingStreet = Mage::Helper('ops/address')->splitStreet($shippingStreet); + $shippingMethodType = (int)$this->getShippingMethodType($this->getCode(), $order->getStoreId()); + + if (in_array($shippingMethodType, $this->getShippingMethodTypeValues())) { + if (4 === $shippingMethodType) { + $formFields['ECOM_SHIPTO_POSTAL_NAME_PREFIX'] = $shippingAddress->getPrefix(); + } + + $company = trim($shippingAddress->getCompany()); + if (0 < strlen($company)) { + $formFields['ECOM_SHIPTO_COMPANY'] = $company; + } + + $fax = trim($shippingAddress->getFax()); + if (0 < strlen($fax)) { + $formFields['ECOM_SHIPTO_TELECOM_FAX_NUMBER'] = $fax; + } + + $formFields['ECOM_SHIPTO_POSTAL_STREET_LINE1'] = $shippingAddress->getStreet1(); + $formFields['ECOM_SHIPTO_POSTAL_STREET_LINE1'] = $splittedShippingStreet['street_name']; + $formFields['ECOM_SHIPTO_POSTAL_STREET_LINE2'] = $splittedShippingStreet['supplement']; + $formFields['ECOM_SHIPTO_POSTAL_STREET_NUMBER'] = $splittedShippingStreet['street_number']; + $formFields['ECOM_SHIPTO_POSTAL_POSTALCODE'] = $shippingAddress->getPostcode(); + $formFields['ECOM_SHIPTO_POSTAL_CITY'] = $shippingAddress->getCity(); + $formFields['ECOM_SHIPTO_POSTAL_COUNTRYCODE'] = $shippingAddress->getCountryId(); + } + + $formFields['ECOM_SHIPTO_POSTAL_NAME_FIRST'] = $shippingAddress->getFirstname(); + $formFields['ECOM_SHIPTO_POSTAL_NAME_LAST'] = $shippingAddress->getLastname(); + $formFields['ECOM_SHIPTO_TELECOM_PHONE_NUMBER'] = $shippingAddress->getTelephone(); + + return $formFields; + } + + /** + * return item params for the order + * for each item a ascending number will be added to the parameter name + * + * @param Mage_Sales_Model_Order $order + * + * @return array + */ + public function getItemParams(Mage_Sales_Model_Order $order) + { + $formFields = array(); + $items = $order->getAllItems(); + $subtotal = 0; + if (is_array($items)) { + $itemCounter = 1; + foreach ($items as $item) { + if ($item->getParentItemId()) { + continue; + } + + $subtotal += $item->getBasePriceInclTax( + ) * $item->getQtyOrdered(); + $formFields['ITEMFDMPRODUCTCATEG' . $itemCounter] + = $this->getKwixoCategoryFromOrderItem( + $item + ); + $formFields['ITEMID' . $itemCounter] = $item->getItemId(); + $formFields['ITEMNAME' . $itemCounter] = substr( + $item->getName(), 0, 40 + ); + $formFields['ITEMPRICE' . $itemCounter] = number_format( + $item->getBasePriceInclTax(), 2, '.', '' + ); + $formFields['ITEMQUANT' . $itemCounter] + = (int)$item->getQtyOrdered( + ); + $formFields['ITEMVAT' . $itemCounter] = str_replace( + ',', '.', (string)(float)$item->getBaseTaxAmount() + ); + $formFields['TAXINCLUDED' . $itemCounter] = 1; + $itemCounter++; + } + $shippingPrice = $order->getBaseShippingAmount(); + $shippingPriceInclTax = $order->getBaseShippingInclTax(); + $subtotal += $shippingPriceInclTax; + $shippingTaxAmount = $shippingPriceInclTax - $shippingPrice; + + $roundingError = $order->getBaseGrandTotal() - $subtotal; + $shippingPrice += $roundingError; + /* add shipping item */ + $formFields['ITEMFDMPRODUCTCATEG' . $itemCounter] = 1; + $formFields['ITEMID' . $itemCounter] = 'SHIPPING'; + $shippingDescription + = + 0 < strlen(trim($order->getShippingDescription())) + ? $order->getShippingDescription() : 'shipping'; + $formFields['ITEMNAME' . $itemCounter] = substr( + $shippingDescription, 0, 30 + ); + $formFields['ITEMPRICE' . $itemCounter] = number_format( + $shippingPrice, 2, '.', '' + ); + $formFields['ITEMQUANT' . $itemCounter] = 1; + $formFields['ITEMVAT' . $itemCounter] = number_format( + $shippingTaxAmount, 2, '.', '' + ); + } + + return $formFields; + } + + + /** + * returns the delivery date as date based on actual date and adding + * the configurated value as days to it + * + * @param string $code + * @param string $storeId + * + * @return bool|string + */ + public function getEstimatedDeliveryDate($code, $storeId = null) + { + $dateNow = date("Y-m-d"); + $dayValue = (string)Mage::getStoreConfig( + 'payment/' . $code . "/delivery_date", $storeId + ); + $deliveryDate = strtotime($dateNow . "+" . $dayValue . "days"); + + return date("Y-m-d", $deliveryDate); + } + + /** + * return the RNP Fee value + * + * @param string $code + * @param int $storeId + * + * @return boolean + */ + public function getRnpFee($code, $storeId = null) + { + return (int)(bool)Mage::getStoreConfig( + "payment/" . $code . "/rnp_fee", $storeId + ); + } + + /** + * returns the Shipping Method Type configured in backend + * + * @param $code + * @param null $storeId + * @param bool $isVirtual + * @return int + */ + public function getShippingMethodType( + $code, $storeId = null, $isVirtual = false + ) + { + // use download type for orders containing virtual products only + if ($isVirtual) { + return Netresearch_OPS_Model_Source_Kwixo_ShipMethodType::DOWNLOAD; + } + $shippingMethodType = $this->getKwixoShippingModel() + ->getKwixoShippingType(); + if (null === $shippingMethodType) { + $shippingMethodType = Mage::getStoreConfig( + "payment/" . $code . "/ecom_shipMethodType", $storeId + ); + } + + return $shippingMethodType; + } + + /** + * return the shipping method speed configured in backend + * + * @param string $code + * @param int $storeId + * + * @return int + */ + public function getShippingMethodSpeed($code, $storeId = null) + { + $shippingMethodSpeed = $this->getKwixoShippingModel() + ->getKwixoShippingMethodSpeed(); + if (null === $shippingMethodSpeed) { + $shippingMethodSpeed = Mage::getStoreConfig( + "payment/" . $code . "/ecom_shipMethodSpeed", $storeId + ); + } + + return (int)$shippingMethodSpeed; + } + + /** + * return the item product categories configured in backend as array + * + * @param string $code + * @param int $storeId + * + * @return array + */ + public function getItemFmdProductCateg($code, $storeId = null) + { + return explode( + ",", Mage::getStoreConfig( + "payment/" . $code . "/product_categories", $storeId + ) + ); + } + + /** + * return the shipping method detail text + * + * @param string $code + * @param int $storeId + * + * @return string + */ + public function getShippingMethodDetails($code, $storeId = null) + { + $shippingMethodDetails = $this->getKwixoShippingModel() + ->getKwixoShippingDetails(); + if (null === $shippingMethodDetails) { + $shippingMethodDetails = Mage::getStoreConfig( + "payment/" . $code . "/shiping_method_details", $storeId + ); + } + + return $shippingMethodDetails; + } + + /** + * get question for fields with disputable value + * users are asked to correct the values before redirect to Ingenico ePayments + * + * + * @return string + */ + public function getQuestion() + { + return Mage::helper('ops/data')->__( + 'Please make sure that the displayed data is correct.' + ); + } + + /** + * get an array of fields with disputable value + * users are asked to correct the values before redirect to Ingenico ePayments + * + * @param Mage_Sales_Model_Order $order Current order + * + * @return array + */ + public function getQuestionedFormFields($order) + { + + $questionedFormFields = array( + 'CIVILITY', + 'OWNERADDRESS', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER', + + ); + $storeId = null; + if ($order instanceof Mage_Sales_Model_Order) { + $storeId = $order->getStoreId(); + } + $shippingMethodType = (int)$this->getShippingMethodType( + $this->getCode(), $storeId + ); + if (in_array($shippingMethodType, $this->getShippingMethodTypeValues())) { + $questionedFormFields [] = 'ECOM_SHIPTO_POSTAL_STREET_NUMBER'; + $questionedFormFields [] = 'ECOM_SHIPTO_POSTAL_STREET_LINE1'; + } + + if ($shippingMethodType === 4) { + $questionedFormFields [] = 'ECOM_SHIPTO_TELECOM_PHONE_NUMBER'; + $questionedFormFields [] = 'ECOM_SHIPTO_POSTAL_NAME_PREFIX'; + } + + return $questionedFormFields; + } + + /** + * return shipping method values except for the type download + * + * @return array + */ + public function getShippingMethodTypeValues() + { + return array(1, 2, 3, 4); + } + + /** + * populates an array with the values from another if the keys are matching + * + * @param array $formFields - the array to populate + * @param null $dataArray - the array containing the data + * @param Mage_Sales_Model_Order $order + * + * @return array - the populated array + */ + protected function populateFromArray( + array $formFields, $dataArray = null, $order + ) + { + // copy some already known values, but only the ones from the questioned + // form fields + if (is_array($dataArray)) { + foreach ($dataArray as $key => $value) { + if (array_key_exists($key, $formFields) + && in_array( + $key, + $this->getQuestionedFormFields($order), true + ) + || $key == 'CIVILITY' + ) { + $formFields[$key] = $value; + } + } + } + + return $formFields; + } + + /** + * get gender text for customer + * + * @param Mage_Sales_Model_Order $order + */ + public function getGender(Mage_Sales_Model_Order $order) + { + $gender = Mage::getSingleton('eav/config') + ->getAttribute('customer', 'gender') + ->getSource() + ->getOptionText($order->getCustomerGender()); + + return $gender; + } + + /** + * sets the kwixo shipping setting model + * + * @param Netresearch_OPS_Model_Kwixo_Shipping_Setting $kwixoShippingModel + */ + public function setKwixoShippingModel( + Netresearch_OPS_Model_Kwixo_Shipping_Setting $kwixoShippingModel + ) + { + $this->kwixoShippingModel = $kwixoShippingModel; + } + + /** + * returns the kwixo shipping setting model + * + * @return Netresearch_OPS_Model_Kwixo_Shipping_Setting + */ + public function getKwixoShippingModel() + { + if (null === $this->kwixoShippingModel) { + $this->kwixoShippingModel = Mage::getModel( + 'ops/kwixo_shipping_setting' + ); + } + + return $this->kwixoShippingModel; + } + + /** + * @param $carrierCode + * @return null + */ + protected function loadShippingSettingForCarrierCode($carrierCode) + { + $this->shippingSettings = $this->getKwixoShippingModel()->load( + $carrierCode, 'shipping_code' + ); + + return $this->shippingSettings; + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Payment/KwixoApresReception.php b/app/code/community/Netresearch/OPS/Model/Payment/KwixoApresReception.php new file mode 100644 index 0000000..baf2d12 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/KwixoApresReception.php @@ -0,0 +1,25 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_KwixoApresReception extends Netresearch_OPS_Model_Payment_Kwixo_Abstract +{ + protected $pm = 'KWIXO_RNP'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + protected $_formBlockType = 'ops/form_kwixo_apresReception'; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_kwixoApresReception'; + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Payment/KwixoComptant.php b/app/code/community/Netresearch/OPS/Model/Payment/KwixoComptant.php new file mode 100644 index 0000000..de961f5 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/KwixoComptant.php @@ -0,0 +1,26 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_KwixoComptant extends Netresearch_OPS_Model_Payment_Kwixo_Abstract +{ + protected $pm = 'KWIXO_STANDARD'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + protected $_formBlockType = 'ops/form_kwixo_comptant'; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_kwixoComptant'; + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Payment/KwixoCredit.php b/app/code/community/Netresearch/OPS/Model/Payment/KwixoCredit.php new file mode 100644 index 0000000..2492496 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/KwixoCredit.php @@ -0,0 +1,27 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_KwixoCredit extends Netresearch_OPS_Model_Payment_Kwixo_Abstract +{ + protected $pm = 'KWIXO_CREDIT'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + + protected $_formBlockType = 'ops/form_kwixo_credit'; + + /** payment code */ + protected $_code = 'ops_kwixoCredit'; + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Masterpass.php b/app/code/community/Netresearch/OPS/Model/Payment/Masterpass.php new file mode 100644 index 0000000..523b20e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Masterpass.php @@ -0,0 +1,27 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_Masterpass + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'MasterPass'; + protected $brand = 'MasterPass'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_Masterpass'; + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoice/Abstract.php b/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoice/Abstract.php new file mode 100644 index 0000000..f6f1574 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoice/Abstract.php @@ -0,0 +1,104 @@ +setEncoding($this->getConfigData('encoding')); + + } + + public function getMethodDependendFormFields($order, $requestParams = null) + { + $formFields = parent::getMethodDependendFormFields($order, $requestParams); + $billingAddress = $order->getBillingAddress(); + $birthday = new DateTime($order->getCustomerDob()); + $gender = $order->getCustomerGender() == 1 ? 'M' : 'F'; + $street = str_replace("\n", ' ', $billingAddress->getStreet(-1)); + $regexp = '/^([^0-9]*)([0-9].*)$/'; + + if (!preg_match($regexp, $street, $splittedStreet)) { + $splittedStreet[1] = $street; + $splittedStreet[2] = ''; + } + + $formFields['OWNERADDRESS'] = trim($splittedStreet[1]); + $formFields['ECOM_BILLTO_POSTAL_STREET_NUMBER'] = trim($splittedStreet[2]); + $formFields['ECOM_BILLTO_POSTAL_NAME_FIRST'] = substr($billingAddress->getFirstname(), 0, 50); + $formFields['ECOM_BILLTO_POSTAL_NAME_LAST'] = substr($billingAddress->getLastname(), 0, 50); + $formFields['ECOM_SHIPTO_DOB'] = $birthday->format('d/m/Y'); + $formFields['ECOM_CONSUMER_GENDER'] = $gender; + + return $formFields; + } + + /** + * @return string title for invoice termes configured in backend + */ + public function getInvoiceTermsTitle() + { + return $this->getConfigData('invoice_terms_title'); + } + + /** + * @return string url to the invoice terms configured in backend + */ + public function getInvoiceTermsUrl() + { + return $this->getConfigData('invoice_terms_url'); + } + + /** + * @return bool if invoice terms should be displayed in checkout + */ + public function showInvoiceTermsLink() + { + return (bool) $this->getConfigData('show_invoice_terms'); + } + + /** + * @param Mage_Sales_Model_Quote $quote + * + * @return bool + */ + public function isAvailable($quote = null) + { + if ($quote && !$quote->isVirtual() && !$quote->getShippingAddress()->getSameAsBilling()) { + return false; + } + + return parent::isAvailable($quote); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceAt.php b/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceAt.php new file mode 100644 index 0000000..88c5e2b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceAt.php @@ -0,0 +1,107 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_OpenInvoiceAt + extends Netresearch_OPS_Model_Payment_OpenInvoice_Abstract +{ + protected $pm = 'Open Invoice AT'; + protected $brand = 'Open Invoice AT'; + + /** if we can capture directly from the backend */ + protected $_canBackendDirectCapture = false; + protected $_canCapturePartial = false; + protected $_canRefundInvoicePartial = false; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_openInvoiceAt'; + + /** + * Open Invoice AT is not available if quote has a coupon + * + * @param Mage_Sales_Model_Quote $quote + * + * @return boolean + */ + public function isAvailable( $quote = null ) + { + /* availability depends on quote */ + if ( false == $quote instanceof Mage_Sales_Model_Quote ) { + return false; + } + + /* not available if quote contains a coupon and allow_discounted_carts is disabled */ + if ( !$this->isAvailableForDiscountedCarts() + && $quote->getSubtotal() != $quote->getSubtotalWithDiscount() + ) { + return false; + } + + /* not available if there is no gender or no birthday */ + if ($quote->getCustomerGender() == null || $quote->getCustomerDob() == null) { + return false; + } + + return parent::isAvailable($quote); + } + + public function getPaymentAction() + { + return Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE; + } + + /** + * @param Mage_Sales_Model_Order $order + * @param array|null $requestParams + * + * @return array + */ + public function getMethodDependendFormFields( $order, $requestParams = null ) + { + $formFields = parent::getMethodDependendFormFields($order, $requestParams); + + $shippingAddress = $order->getShippingAddress(); + + $gender = Mage::getSingleton('eav/config') + ->getAttribute('customer', 'gender') + ->getSource() + ->getOptionText($order->getCustomerGender()); + + $formFields[ 'CIVILITY' ] = $gender == 'Male' ? 'Herr' : 'Frau'; + $formFields[ 'ECOM_CONSUMER_GENDER' ] = $gender == 'Male' ? 'M' : 'F'; + + // Change address format to make austrian addresses compatible with platform data transfer to Klarna + $billToParams = $this->getRequestHelper()->extractBillToParameters($order->getBillingAddress(), $order); + $formFields['OWNERADDRESS'] = $billToParams['ECOM_BILLTO_POSTAL_STREET_LINE1'] . ' ' + . $billToParams['ECOM_BILLTO_POSTAL_STREET_NUMBER']; + $formFields['ECOM_BILLTO_POSTAL_STREET_NUMBER'] = ' '; + + if (!$this->getConfig()->canSubmitExtraParameter($order->getStoreId()) ) { + // add the shipto parameters even if the submitOption is false, because they are required for OpenInvoice + $shipToParams = $this->getRequestHelper()->extractShipToParameters($shippingAddress, $order); + $formFields = array_merge($formFields, $shipToParams); + } + + return $formFields; + } + + /** + * getter for the allow_discounted_carts + * + * @return bool + */ + protected function isAvailableForDiscountedCarts() + { + return (bool) $this->getConfigData('allow_discounted_carts'); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceDe.php b/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceDe.php new file mode 100644 index 0000000..62c1038 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceDe.php @@ -0,0 +1,95 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_OpenInvoiceDe + extends Netresearch_OPS_Model_Payment_OpenInvoice_Abstract +{ + protected $pm = 'Open Invoice DE'; + protected $brand = 'Open Invoice DE'; + + /** if we can capture directly from the backend */ + protected $_canBackendDirectCapture = false; + + protected $_canCapturePartial = false; + protected $_canRefundInvoicePartial = false; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_openInvoiceDe'; + + /** + * Open Invoice DE is not available if quote has a coupon + * + * @param Mage_Sales_Model_Quote $quote + * @return boolean + */ + public function isAvailable($quote=null) + { + /* availability depends on quote */ + if (false == $quote instanceof Mage_Sales_Model_Quote) { + return false; + } + + /* not available if quote contains a coupon and allow_discounted_carts is disabled */ + if (!$this->isAvailableForDiscountedCarts() + && $quote->getSubtotal() != $quote->getSubtotalWithDiscount() + ) { + return false; + } + + /* not available if there is no gender or no birthday */ + if (null === $quote->getCustomerGender() || is_null($quote->getCustomerDob())) { + return false; + } + + return parent::isAvailable($quote); + } + + public function getPaymentAction() + { + return Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE; + } + + public function getMethodDependendFormFields($order, $requestParams=null) + { + $formFields = parent::getMethodDependendFormFields($order, $requestParams); + + $shippingAddress = $order->getShippingAddress(); + + $gender = Mage::getSingleton('eav/config') + ->getAttribute('customer', 'gender') + ->getSource() + ->getOptionText($order->getCustomerGender()); + + $formFields[ 'CIVILITY' ] = $gender == 'Male' ? 'Herr' : 'Frau'; + $formFields[ 'ECOM_CONSUMER_GENDER' ] = $gender == 'Male' ? 'M' : 'F'; + + if (!$this->getConfig()->canSubmitExtraParameter($order->getStoreId())) { + // add the shipto parameters even if the submitOption is false, because they are required for OpenInvoice + $shipToParams = $this->getRequestHelper()->extractShipToParameters($shippingAddress, $order); + $formFields = array_merge($formFields, $shipToParams); + } + + return $formFields; + } + + /** + * getter for the allow_discounted_carts + * + * @return array + */ + protected function isAvailableForDiscountedCarts() + { + return (bool) $this->getConfigData('allow_discounted_carts'); + } + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceNl.php b/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceNl.php new file mode 100644 index 0000000..ebfc182 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/OpenInvoiceNl.php @@ -0,0 +1,140 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_OpenInvoiceNl + extends Netresearch_OPS_Model_Payment_OpenInvoice_Abstract +{ + protected $pm = 'Open Invoice NL'; + protected $brand = 'Open Invoice NL'; + + /** if we can capture directly from the backend */ + protected $_canBackendDirectCapture = false; + + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_openInvoiceNl'; + + + /** + * Open Invoice NL is not available if quote has a coupon + * + * @param Mage_Sales_Model_Quote $quote + * @return boolean + */ + public function isAvailable($quote=null) + { + /* availability depends on quote */ + if (false == $quote instanceof Mage_Sales_Model_Quote) { + return false; + } + + /* not available if there is no gender or no birthday */ + if (null === $quote->getCustomerGender() || is_null($quote->getCustomerDob())) { + return false; + } + + return parent::isAvailable($quote); + } + + /** + * get some method dependend form fields + * + * @param Mage_Sales_Model_Quote $order + * @param array $requestParams + * @return array + */ + public function getMethodDependendFormFields($order, $requestParams=null) + { + $billingAddress = $order->getBillingAddress(); + $shippingAddress = $order->getShippingAddress(); + $billingStreet = str_replace("\n", ' ', $billingAddress->getStreet(-1)); + + $splittedBillingStreet = Mage::helper('ops/address')->splitStreet($billingStreet); + $formFields = parent::getMethodDependendFormFields($order, $requestParams); + + $gender = Mage::getSingleton('eav/config') + ->getAttribute('customer', 'gender') + ->getSource() + ->getOptionText($order->getCustomerGender()); + + $formFields['CIVILITY'] = $gender == 'Male' ? 'M' : 'V'; + $formFields['ECOM_CONSUMER_GENDER'] = $gender == 'Male' ? 'M' : 'V'; + $formFields['OWNERADDRESS'] = $splittedBillingStreet['street_name']; + $formFields['ECOM_BILLTO_POSTAL_STREET_NUMBER'] = $splittedBillingStreet['street_number']; + $formFields['OWNERZIP'] = $billingAddress->getPostcode(); + $formFields['OWNERTOWN'] = $billingAddress->getCity(); + $formFields['OWNERCTY'] = $billingAddress->getCountry(); + $formFields['OWNERTELNO'] = $billingAddress->getTelephone(); + + $shippingStreet = str_replace("\n", ' ', $shippingAddress->getStreet(-1)); + + $splittedShippingStreet = Mage::Helper('ops/address')->splitStreet($shippingStreet); + + $formFields['ECOM_SHIPTO_POSTAL_NAME_PREFIX'] = $shippingAddress->getPrefix(); + $formFields['ECOM_SHIPTO_POSTAL_NAME_FIRST'] = $shippingAddress->getFirstname(); + $formFields['ECOM_SHIPTO_POSTAL_NAME_LAST'] = $shippingAddress->getLastname(); + $formFields['ECOM_SHIPTO_POSTAL_STREET_LINE1'] = $splittedShippingStreet['street_name']; + $formFields['ECOM_SHIPTO_POSTAL_STREET_NUMBER'] = $splittedShippingStreet['street_number']; + $formFields['ECOM_SHIPTO_POSTAL_POSTALCODE'] = $shippingAddress->getPostcode(); + $formFields['ECOM_SHIPTO_POSTAL_CITY'] = $shippingAddress->getCity(); + $formFields['ECOM_SHIPTO_POSTAL_COUNTRYCODE'] = $shippingAddress->getCountry(); + + // copy some already known values + $formFields['ECOM_SHIPTO_ONLINE_EMAIL'] = $order->getCustomerEmail(); + + if (is_array($requestParams)) { + if (array_key_exists('OWNERADDRESS', $requestParams)) { + $formFields['OWNERADDRESS'] = $requestParams['OWNERADDRESS']; + } + if (array_key_exists('ECOM_BILLTO_POSTAL_STREET_NUMBER', $requestParams)) { + $formFields['ECOM_BILLTO_POSTAL_STREET_NUMBER'] = $requestParams['ECOM_BILLTO_POSTAL_STREET_NUMBER']; + } + if (array_key_exists('ECOM_SHIPTO_POSTAL_STREET_LINE1', $requestParams)) { + $formFields['ECOM_SHIPTO_POSTAL_STREET_LINE1'] = $requestParams['ECOM_SHIPTO_POSTAL_STREET_LINE1']; + } + if (array_key_exists('ECOM_SHIPTO_POSTAL_STREET_NUMBER', $requestParams)) { + $formFields['ECOM_SHIPTO_POSTAL_STREET_NUMBER'] = $requestParams['ECOM_SHIPTO_POSTAL_STREET_NUMBER']; + } + } + + return $formFields; + } + + /** + * get question for fields with disputable value + * users are asked to correct the values before redirect to Ingenico ePayments + * + * @return string + */ + public function getQuestion() + { + return Mage::helper('ops')->__('Please make sure that your street and house number are correct.'); + } + + /** + * get an array of fields with disputable value + * users are asked to correct the values before redirect to Ingenico ePayments + * + * + * @return array + */ + public function getQuestionedFormFields($order) + { + return array( + 'OWNERADDRESS', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER', + 'ECOM_SHIPTO_POSTAL_STREET_LINE1', + 'ECOM_SHIPTO_POSTAL_STREET_NUMBER', + ); + } + +} diff --git a/app/code/community/Netresearch/OPS/Model/Payment/PayPerMail.php b/app/code/community/Netresearch/OPS/Model/Payment/PayPerMail.php new file mode 100644 index 0000000..bec2d49 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/PayPerMail.php @@ -0,0 +1,41 @@ +getInfoInstance()->getAdditionalInformation(self::INFO_KEY_PM); + } + + public function getOpsBrand($payment = null) + { + return $this->getInfoInstance()->getAdditionalInformation(self::INFO_KEY_BRAND); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Paypal.php b/app/code/community/Netresearch/OPS/Model/Payment/Paypal.php new file mode 100644 index 0000000..55caa8d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Paypal.php @@ -0,0 +1,26 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_Paypal + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'PAYPAL'; + protected $brand = 'PAYPAL'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_paypal'; + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Paysafecard.php b/app/code/community/Netresearch/OPS/Model/Payment/Paysafecard.php new file mode 100644 index 0000000..bf8ae66 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Paysafecard.php @@ -0,0 +1,26 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_Paysafecard + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'paysafecard'; + protected $brand = 'paysafecard'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_paysafecard'; + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/PingPing.php b/app/code/community/Netresearch/OPS/Model/Payment/PingPing.php new file mode 100644 index 0000000..4a5e75f --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/PingPing.php @@ -0,0 +1,26 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_PingPing + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'PingPing'; + protected $brand = 'PingPing'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_pingPing'; + +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/PostFinanceEFinance.php b/app/code/community/Netresearch/OPS/Model/Payment/PostFinanceEFinance.php new file mode 100644 index 0000000..c749e71 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/PostFinanceEFinance.php @@ -0,0 +1,25 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Model_Payment_PostFinanceEFinance + extends Netresearch_OPS_Model_Payment_Abstract +{ + protected $pm = 'PostFinance e-finance'; + protected $brand = 'PostFinance e-finance'; + + /** Check if we can capture directly from the backend */ + protected $_canBackendDirectCapture = true; + + /** info source path */ + protected $_infoBlockType = 'ops/info_redirect'; + + /** payment code */ + protected $_code = 'ops_postFinanceEFinance'; +} + diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Recurring/Cc.php b/app/code/community/Netresearch/OPS/Model/Payment/Recurring/Cc.php new file mode 100644 index 0000000..6372646 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Recurring/Cc.php @@ -0,0 +1,391 @@ + + */ +class Netresearch_OPS_Model_Payment_Recurring_Cc + extends Netresearch_OPS_Model_Payment_Cc + implements Mage_Payment_Model_Recurring_Profile_MethodInterface +{ + const CODE = 'ops_recurring_cc'; + protected $_code = self::CODE; + + protected $_canFetchTransactionInfo = false; + protected $_canManageRecurringProfiles = true; + protected $_canUseInternal = false; + + /** info source path */ + protected $_infoBlockType = 'ops/info_recurringCc'; + + /** @var string $_formBlockType define a specific form block */ + protected $_formBlockType = 'ops/form_recurringCc'; + + protected $parameterModel = null; + protected $subscriptionManager = null; + + /** + * @return Netresearch_OPS_Model_Subscription_Manager + */ + public function getSubscriptionManager() + { + if (null === $this->subscriptionManager) { + $this->subscriptionManager = Mage::getModel('ops/subscription_manager'); + } + + return $this->subscriptionManager; + } + + /** + * @param Netresearch_OPS_Model_Subscription_Manager $subscriptionManager + * + * @returns $this + */ + public function setSubscriptionManager($subscriptionManager) + { + $this->subscriptionManager = $subscriptionManager; + + return $this; + } + + + /** + * @return Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag + */ + public function getParameterModel() + { + if (null === $this->parameterModel) { + $this->parameterModel = Mage::getModel('ops/payment_recurring_cc_parameterBag'); + } + + return $this->parameterModel; + } + + /** + * @param Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag $parameterModel + * + * @returns $this + */ + public function setParameterModel($parameterModel) + { + $this->parameterModel = $parameterModel; + + return $this; + } + + + /** + * Validate data + * + * @param Mage_Payment_Model_Recurring_Profile $profile + * + * @throws Mage_Core_Exception + */ + public function validateRecurringProfile(Mage_Payment_Model_Recurring_Profile $profile) + { + if ($profile->getState() === Mage_Sales_Model_Recurring_Profile::STATE_UNKNOWN) { + $this->invokeRequestParamValidation( + $this->getParameterModel()->collectProfileParameters($profile)->toArray() + ); + } + } + + /** + * Submits the trial subscription to the Ingenico ePayments webservice + * + * @param Mage_Payment_Model_Recurring_Profile $profile + * @param Mage_Payment_Model_Info $paymentInfo + * + */ + protected function submitTrialSubscription( + Mage_Payment_Model_Recurring_Profile $profile, + Mage_Payment_Model_Info $paymentInfo + ) + { + if ($profile->getTrialPeriodUnit()) { + $requestParams = $this->getParameterModel()->collectAllParametersForTrial($paymentInfo, $profile); + $this->getParameterModel()->unsetData(); + $response = $this->getDirectLinkHelper()->performDirectLinkRequest( + $profile->getQuote(), $requestParams, $profile->getQuote()->getStoreId() + ); + + if ($this->getPaymentHelper()->isPaymentFailed($response['STATUS']) + || $response['creation_status'] == Netresearch_OPS_Model_Subscription_Manager::CREATION_FAILED + ) { + Mage::throwException($this->getDataHelper()->__('Placing of trial subscription transaction failed')); + } + } + } + + /** + * Submit to the gateway + * + * @param Mage_Payment_Model_Recurring_Profile $profile + * @param Mage_Payment_Model_Info $paymentInfo + */ + public function submitRecurringProfile( + Mage_Payment_Model_Recurring_Profile $profile, + Mage_Payment_Model_Info $paymentInfo + ) + { + $this->performPreDirectLinkCallActions($profile->getQuote(), $paymentInfo); + + $this->submitTrialSubscription($profile, $paymentInfo); + + $this->submitRegularSubscription($profile, $paymentInfo); + + $this->submitInitialFee($profile, $paymentInfo); + } + + /** + * Fetch details + * + * @param string $referenceId + * @param Varien_Object $result + */ + public function getRecurringProfileDetails($referenceId, Varien_Object $result) + { + Mage::throwException('Fetching profile details from Ingenico ePayments not supported'); + } + + /** + * Check whether can get recurring profile details + * + * @return bool + */ + public function canGetRecurringProfileDetails() + { + // querying the subscription status via API is not possible + return false; + } + + /** + * Update data + * + * @param Mage_Payment_Model_Recurring_Profile $profile + */ + public function updateRecurringProfile(Mage_Payment_Model_Recurring_Profile $profile) + { + Mage::throwException('Function not supported'); + } + + /** + * Manage status update according to given new state on the profile + * + * @param Mage_Payment_Model_Recurring_Profile $profile + */ + public function updateRecurringProfileStatus(Mage_Payment_Model_Recurring_Profile $profile) + { + /** @var Mage_Sales_Model_Recurring_Profile $profile */ + switch ($profile->getNewState()) { + case Mage_Sales_Model_Recurring_Profile::STATE_ACTIVE: + if (Mage::getSingleton('admin/session')->isLoggedIn()) { + $this->addAdminNotice( + 'To actually activate the subscription an update in the Ingenico ePayments backend is needed.' + ); + } else { + Mage::throwException( + $this->getDataHelper()->__( + 'Automatic activation not possible. Please contact our support team.' + ) + ); + } + break; + case Mage_Sales_Model_Recurring_Profile::STATE_CANCELED: + if (Mage::getSingleton('admin/session')->isLoggedIn()) { + $this->addAdminNotice( + 'To actually cancel the subscription an update in the Ingenico ePayments backend is needed.' + ); + } else { + $this->sendSuspendMail($profile); + } + break; + case Mage_Sales_Model_Recurring_Profile::STATE_SUSPENDED: + if (Mage::getSingleton('admin/session')->isLoggedIn()) { + $this->addAdminNotice( + 'To actually suspend the subscription an update in the Ingenico ePayments backend is needed.' + ); + } else { + $this->sendSuspendMail($profile); + } + break; + case Mage_Sales_Model_Recurring_Profile::STATE_EXPIRED: + Mage::throwException('Expire function not implemented!'); + break; + default: + $message = $this->getDataHelper()->__('Action for state %s not supported', $profile->getNewState()); + Mage::throwException($message); + break; + } + } + + /** + * Adds translated message to admin session as notice + * + * @param string $message + */ + protected function addAdminNotice($message) + { + Mage::getSingleton('adminhtml/session')->addNotice($this->getDataHelper()->__($message)); + } + + /** + * + * @param Mage_Sales_Model_Recurring_Profile $profile + * + * @throws Mage_Core_Exception + */ + protected function sendSuspendMail($profile) + { + $session = Mage::getSingleton('customer/session'); + $mailModel = Mage::getModel('ops/payment_features_paymentEmail'); + if ($session->getCustomer()->getId() != $profile->getCustomerId()) { + // prevent access to subscriptions not of the customers account + Mage::throwException( + $this->getDataHelper()->__('You are not allowed to suspend this subscription!') + ); + } + $customer = Mage::getModel('customer/customer')->load($profile->getCustomerId()); + $result = $mailModel->sendSuspendSubscriptionMail($profile, $customer); + if ($result) { + // mail successfully sent + $profile->setNewState($profile::STATE_PENDING); + $profile->setOverrideState(true); + $session->addSuccess( + $this->getDataHelper() + ->__( + 'Your suspend request was successfully sent. A copy of the email will be sent to your address.' + ) + ); + } else { + // sending the mail failed + Mage::throwException( + $this->getDataHelper() + ->__('Could not send suspend mail, please try again or contact our support directly.') + ); + } + } + + /** + * Check wether payment method is available for quote + * + * @param Mage_Sales_Model_Quote $quote + * + * @return bool + */ + public function isAvailable($quote = null) + { + if ($quote && !$quote->isNominal()) { + // allow only nominal quotes + return false; + } + + return parent::isAvailable($quote); + + } + + public function hasBrandAliasInterfaceSupport($payment = null) + { + // only support inline, since we need the alias + return true; + } + + public function getOrderPlaceRedirectUrl($payment = null) + { + if ('' == $this->getOpsHtmlAnswer($payment)) { + // Prevent redirect on cc payment + return false; + } else { + // 3ds redirect + return Mage::getModel('ops/config')->get3dSecureRedirectUrl(); + + } + } + + public function isZeroAmountAuthorizationAllowed($storeId = null) + { + return false; + } + + public function getBrandsForAliasInterface() + { + return $this->getConfigData('availableTypes'); + } + + /** + * @param Mage_Payment_Model_Recurring_Profile $profile + * @param Mage_Payment_Model_Info $paymentInfo + * + * @throws Mage_Core_Exception + */ + protected function submitRegularSubscription(Mage_Payment_Model_Recurring_Profile $profile, + Mage_Payment_Model_Info $paymentInfo + ) + { + $requestParams = $this->getParameterModel()->collectAllParameters($paymentInfo, $profile); + $this->getParameterModel()->unsetData(); + $response = $this->getDirectLinkHelper()->performDirectLinkRequest( + $profile->getQuote(), $requestParams, $profile->getQuote()->getStoreId() + ); + + if ($this->getPaymentHelper()->isPaymentFailed($response['STATUS']) + || $response['creation_status'] == Netresearch_OPS_Model_Subscription_Manager::CREATION_FAILED + ) { + Mage::throwException($this->getDataHelper()->__('Placing of subscription transaction failed')); + } + + $this->getSubscriptionManager()->processSubscriptionFeedback($response, $profile); + } + + protected function submitInitialFee( + Mage_Payment_Model_Recurring_Profile $profile, + Mage_Payment_Model_Info $paymentInfo + ) + { + /** @var $profile Mage_Sales_Model_Recurring_Profile */ + if ($profile->getInitAmount() > 0) { + $order = $this->getSubscriptionManager()->createInitialOrder($profile); + + $requestParams = $this->getParameterModel() + ->collectAllParametersForInitialFee($paymentInfo, $profile, $order); + $this->getParameterModel()->unsetData(); + try{ + + $response = $this->getDirectLinkHelper()->performDirectLinkRequest( + $profile->getQuote(), $requestParams, $profile->getQuote()->getStoreId() + ); + } catch (Exception $e){ + Mage::logException($e); + } + + $this->getSubscriptionManager()->processSubscriptionFeedback($response, $profile, $order); + + } + + } + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Payment/Recurring/Cc/ParameterBag.php b/app/code/community/Netresearch/OPS/Model/Payment/Recurring/Cc/ParameterBag.php new file mode 100644 index 0000000..535fbb1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Payment/Recurring/Cc/ParameterBag.php @@ -0,0 +1,533 @@ + + */ +?> +subscriptionHelper) { + $this->subscriptionHelper = Mage::helper('ops/subscription'); + } + + return $this->subscriptionHelper; + } + + /** + * @param Netresearch_OPS_Helper_Subscription $subscriptionHelper + * + * @returns $this + */ + public function setSubscriptionHelper($subscriptionHelper) + { + $this->subscriptionHelper = $subscriptionHelper; + + return $this; + } + + /** + * @return Netresearch_OPS_Helper_Data + */ + public function getDataHelper() + { + if (null === $this->dataHelper) { + $this->dataHelper = Mage::helper('ops'); + } + + return $this->dataHelper; + } + + /** + * @param Netresearch_OPS_Helper_Data $dataHelper + * + * @returns $this + */ + public function setDataHelper($dataHelper) + { + $this->dataHelper = $dataHelper; + + return $this; + } + + /** + * @return Netresearch_OPS_Helper_Quote + */ + public function getQuoteHelper() + { + if (null === $this->quoteHelper) { + $this->quoteHelper = Mage::helper('ops/quote'); + } + + return $this->quoteHelper; + } + + /** + * @param Netresearch_OPS_Helper_Quote $quoteHelper + * + * @returns $this + */ + public function setQuoteHelper($quoteHelper) + { + $this->quoteHelper = $quoteHelper; + + return $this; + } + + /** + * @return Netresearch_OPS_Model_Config + */ + public function getConfig() + { + if (null === $this->config) { + $this->config = Mage::getModel('ops/config'); + } + + return $this->config; + } + + /** + * @param Netresearch_OPS_Model_Config $config + * + * @returns $this + */ + public function setConfig($config) + { + $this->config = $config; + + return $this; + } + + /** + * @return Netresearch_OPS_Helper_Payment_Request + */ + public function getRequestHelper() + { + if (null === $this->requestHelper) { + $this->requestHelper = Mage::helper('ops/payment_request'); + } + + return $this->requestHelper; + } + + /** + * @param Netresearch_OPS_Helper_Payment_Request $requestHelper + * + * @returns $this + */ + public function setRequestHelper($requestHelper) + { + $this->requestHelper = $requestHelper; + + return $this; + } + + /** + * Maps the Magento recurring profile units to the Ingenico ePayments ones + * + * @param string $unit + * + * @return string + */ + protected function mapUnit($unit) + { + switch ($unit) { + case Mage_Payment_Model_Recurring_Profile::PERIOD_UNIT_DAY: + return self::PERIOD_UNIT_DAY; + case Mage_Payment_Model_Recurring_Profile::PERIOD_UNIT_MONTH: + return self::PERIOD_UNIT_MONTH; + case Mage_Payment_Model_Recurring_Profile::PERIOD_UNIT_WEEK: + case Mage_Payment_Model_Recurring_Profile::PERIOD_UNIT_SEMI_MONTH: + return self::PERIOD_UNIT_WEEK; + case Mage_Payment_Model_Recurring_Profile::PERIOD_UNIT_YEAR: + return self::PERIOD_UNIT_MONTH; + default: + return ''; + } + } + + /** + * Some period units are not supported by default by Ingenico, therefore we must adjust the frequency for + * the following period units to match the MONTH unit: + * + * Mage_Payment_Model_Recurring_Profile::PERIOD_UNIT_SEMI_MONTH + * Mage_Payment_Model_Recurring_Profile::PERIOD_UNIT_YEAR + * + * @param string $unit + * @param int $frequency + * + * @return int adjusted frequency + */ + protected function adjustFrequencyToUnitSpecialCases($unit, $frequency) + { + switch ($unit) { + case Mage_Payment_Model_Recurring_Profile::PERIOD_UNIT_SEMI_MONTH: + $frequency *= 2; + break; + case Mage_Payment_Model_Recurring_Profile::PERIOD_UNIT_YEAR: + $frequency *= 12; + break; + default: + break; + } + + return $frequency; + } + + /** + * Collects parameters specific to customer (addresses) and quote + * + * @param Mage_Payment_Model_Info $paymentInfo + * + * @return Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag + */ + public function collectPaymentParameters(Mage_Payment_Model_Info $paymentInfo) + { + $this->setData('CN', $paymentInfo->getAdditionalInformation('CC_CN')) + ->setData('ALIAS', $paymentInfo->getAdditionalInformation('alias')) + ->setData('BRAND', $paymentInfo->getAdditionalInformation('CC_BRAND')) + ->setData('CURRENCY', $this->getQuoteHelper()->getQuoteCurrency($paymentInfo->getQuote())) + ->setData('OPERATION', Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION) + ->setData('ORIG', $this->getDataHelper()->getModuleVersionString()) + ->setData('REMOTE_ADDR', $paymentInfo->getQuote()->getRemoteIp()); + + + if ($paymentInfo->getMethodInstance()->getConfigData('enabled_3dsecure')) { + $this->addData( + array( + 'FLAG3D' => 'Y', + 'WIN3DS' => Netresearch_OPS_Model_Payment_Abstract::OPS_DIRECTLINK_WIN3DS, + 'LANGUAGE' => Mage::app()->getLocale()->getLocaleCode(), + 'HTTP_ACCEPT' => '*/*', + 'HTTP_USER_AGENT' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)', + 'ACCEPTURL' => $this->getConfig()->getAcceptUrl(), + 'DECLINEURL' => $this->getConfig()->getDeclineUrl(), + 'EXCEPTIONURL' => $this->getConfig()->getExceptionUrl(), + ) + ); + } + + return $this; + } + + /** + * Collects profile specific request parameters + * + * @see http://payment-services.ingenico.com/int/en/ogone/support/guides/integration%20guides/subscription-manager/via-e-commerce-and-directlink + * + * @param Mage_Payment_Model_Recurring_Profile $profile + * @param boolean $trial - if the array should contain the parameters necessary for the trial period + * + * @return $this + */ + public function collectProfileParameters(Mage_Payment_Model_Recurring_Profile $profile, $trial = false) + { + /** @var Mage_Sales_Model_Recurring_Profile $profile */ + + // get subscription amount from profile values + list($subscriptionAmount) = $this->getProfileValues($profile, $trial); + + // determine dates and adjust period values depending on trial parameter + list($startDate, $endDate, $periodUnit, $periodFrequency) = $this->generateDates($profile, $trial); + + $subOrderId = $this->getSubscriptionHelper()->generateSubscriptionId($profile, $trial); + + $this->setData('SUB_COM', $profile->getScheduleDescription()) + ->setData('SUB_ORDERID', $subOrderId) + ->setData('SUB_PERIOD_NUMBER', $periodFrequency) + ->setData('SUB_PERIOD_UNIT', $periodUnit) + ->setData('SUB_STARTDATE', $startDate->format('d/m/Y')) + ->setData('SUB_ENDDATE', $endDate ? $endDate->format('d/m/Y') : '') + ->setData('SUB_STATUS', 1) + ->setData('SUBSCRIPTION_ID', $subOrderId) + ->setData('ORDERID', $subOrderId) + ->setData('SUB_AMOUNT', $this->getDataHelper()->getAmount($subscriptionAmount)) + // amount is always 0 for subscription transactions + ->setData('AMOUNT', 0) + ->setData( + 'SUB_PERIOD_MOMENT', + $this->getSubscriptionHelper()->getBillingDayForPeriodUnit($periodUnit, $profile->getStoreId()) + ); + + // add OWNER* and ECOM_BILLTO_* and ECOM_SHIPTO_* parameters + $this->collectAddressParameters($profile); + + return $this; + } + + + /** + * Calculates the end date of the profile from the start date, frequency, period unit and maximum cycles + * + * @param DateTime $startDate | start date of the subscription + * @param string $periodUnit | unit of the frequency (d, w, m) + * @param int $frequency | every nth unit the customer will be charged + * @param int $maxCycles | maximum amount of cycles + * + * @return DateTime|null - the end date or null if no maximum cycle amount is set + */ + public function calculateEndDate(DateTime $startDate, $periodUnit, $frequency, $maxCycles) + { + if (!$this->isMappedUnit($periodUnit)) { + $frequency = $this->adjustFrequencyToUnitSpecialCases($periodUnit, $frequency); + $periodUnit = $this->mapUnit($periodUnit); + } + + $endDate = null; + if ($maxCycles) { + $endDate = clone $startDate; + if ($endDate->format('d') > 28 && $periodUnit != self::PERIOD_UNIT_DAY) { + $endDate->sub(new DateInterval('P3D')); + } + + $dateDiff = $frequency * $maxCycles; + // fix period unit for week, since the payment provider requests a strange unit + $periodUnit = $periodUnit == self::PERIOD_UNIT_WEEK ? 'w' : $periodUnit; + + $endDate->add(new DateInterval('P' . $dateDiff . strtoupper($periodUnit))); + } + return $endDate; + } + + /** + * Tests if the given unit is already mapped to the Ingenico types + * + * @param string $unit - unit to check + * + * @return bool + */ + protected function isMappedUnit($unit) + { + return in_array( + $unit, array( + self::PERIOD_UNIT_DAY, + self::PERIOD_UNIT_WEEK, + self::PERIOD_UNIT_MONTH + ) + ); + } + + /** + * Collects all parameters from the given objects and returns the parameter array + * + * @param Mage_Payment_Model_Info $paymentInfo + * @param Mage_Payment_Model_Recurring_Profile $profile + * + * @return mixed[] - utf8_encoded request parameters + */ + public function collectAllParameters(Mage_Payment_Model_Info $paymentInfo, + Mage_Payment_Model_Recurring_Profile $profile + ) + { + $this->collectProfileParameters($profile) + ->collectPaymentParameters($paymentInfo); + + return $this->encodeValues()->toArray(); + } + + /** + * Collects all parameters relevant for the trial period from the given objects and returns the parameter array + * + * @param Mage_Payment_Model_Info $paymentInfo + * @param Mage_Payment_Model_Recurring_Profile $profile + * + * @return mixed[] - utf8_encoded request parameters + */ + public function collectAllParametersForTrial(Mage_Payment_Model_Info $paymentInfo, + Mage_Payment_Model_Recurring_Profile $profile + ) + { + $this->collectProfileParameters($profile, true) + ->collectPaymentParameters($paymentInfo, true); + + return $this->encodeValues()->toArray(); + } + + /** + * collects all relevant parameters for the initial fee + * + * @param Mage_Payment_Model_Info $paymentInfo + * @param Mage_Payment_Model_Recurring_Profile $profile + * @param Mage_Sales_Model_Order $order + * + * @return string[] - utf8 encoded request parameters + */ + public function collectAllParametersForInitialFee(Mage_Payment_Model_Info $paymentInfo, + Mage_Payment_Model_Recurring_Profile $profile, + Mage_Sales_Model_Order $order + ) + { + /** @var $profile Mage_Sales_Model_Recurring_Profile */ + $this->collectPaymentParameters($paymentInfo)->collectAddressParameters($profile); + + $this->setData('AMOUNT', $this->getDataHelper()->getAmount($profile->getInitAmount())); + $this->setData('ORDERID', Mage::helper('ops/order')->getOpsOrderId($order)); + + return $this->encodeValues()->toArray(); + } + + /** + * utf8_decodes all parameters + * + * @return $this + */ + protected function encodeValues() + { + foreach ($this->getData() as $key => $value) { + $this->setData($key, utf8_decode($value)); + } + + return $this; + } + + /** + * Determines base values for the subscription depending on the trial parameter + * + * @param Mage_Sales_Model_Recurring_Profile $profile + * @param $trial - if the values for the trial subscription should be used or not + * + * @return string[] - containing the following: + * [0] => amount for the subscription, + * [1] => period Unit, + * [2] => periodFrequency, + * [3] => max + */ + protected function getProfileValues($profile, $trial) + { + // subscription equals shipping for the product + the product amount+tax itself + if ($trial) { + $subscriptionAmount = $profile->getTrialBillingAmount() + $profile->getShippingAmount(); + $periodUnit = $this->mapUnit($profile->getTrialPeriodUnit()); + $periodFrequency = $this->adjustFrequencyToUnitSpecialCases( + $profile->getTrialPeriodUnit(), $profile->getTrialPeriodFrequency() + ); + $maxCycles = $profile->getTrialPeriodMaxCycles(); + + } else { + $subscriptionAmount + = $profile->getBillingAmount() + $profile->getTaxAmount() + $profile->getShippingAmount(); + $periodUnit = $this->mapUnit($profile->getPeriodUnit()); + $periodFrequency = $this->adjustFrequencyToUnitSpecialCases( + $profile->getPeriodUnit(), $profile->getPeriodFrequency() + ); + $maxCycles = $profile->getPeriodMaxCycles(); + + } + + return array($subscriptionAmount, $periodUnit, $periodFrequency, $maxCycles); + } + + /** + * Determines the start and end date from the given values. + * + * @param Mage_Sales_Model_Recurring_Profile $profile + * @param $trial + * + * @return string[] - containing the following: + * 0 => updated start date + * 1 => updated enddate + * 2 => updated period unit + * 3 => updated period frequency + */ + protected function generateDates($profile, $trial) + { + // get profile values - subscription amount is not needed here + list(, $periodUnit, $periodFrequency, $maxCycles) = $this->getProfileValues($profile, $trial); + + if (!$profile->getTrialPeriodUnit() && !$trial || $trial) { + // if we collect the trial parameters, or if we don't have a trial period at all + $startDate = new DateTime($profile->getStartDateTime()); + $endDate = $this->calculateEndDate( + $startDate, $periodUnit, $periodFrequency, $maxCycles + ); + } else { + // if we collect the regular subscription and a trial is existent + $trialStartDate = new DateTime($profile->getStartDateTime()); + $trialPeriodUnit = $this->mapUnit($profile->getTrialPeriodUnit()); + $trialPeriodFrequency = $this->adjustFrequencyToUnitSpecialCases( + $profile->getTrialPeriodUnit(), $profile->getTrialPeriodFrequency() + ); + // calculate trial end date and use it as start date for the regular subscription + $trialEndDate = $this->calculateEndDate( + $trialStartDate, $trialPeriodUnit, $trialPeriodFrequency, $profile->getTrialPeriodMaxCycles() + ); + + $startDate = clone $trialEndDate; + $periodUnit = $this->mapUnit($profile->getPeriodUnit()); + $periodFrequency = $this->adjustFrequencyToUnitSpecialCases( + $profile->getPeriodUnit(), $profile->getPeriodFrequency() + ); + $maxCycles = $profile->getPeriodMaxCycles(); + $endDate = $this->calculateEndDate( + $trialEndDate, $periodUnit, $periodFrequency, $maxCycles + ); + } + + return array($startDate, $endDate, $periodUnit, $periodFrequency); + } + + /** + * Add address data to the bag if it is available + * + * @param Mage_Payment_Model_Recurring_Profile $profile + */ + protected function collectAddressParameters(Mage_Payment_Model_Recurring_Profile $profile) + { + /** @var $profile Mage_Sales_Model_Recurring_Profile */ + if ($profile->getQuote()) { + $this->addData( + $this->getRequestHelper()->getOwnerParams( + $profile->getQuote()->getBillingAddress(), $profile->getQuote() + ) + )->addData($this->getRequestHelper()->extractBillToParameters($profile->getQuote()->getBillingAddress())); + + if ($profile->getQuote()->getShippingAddress()) { + $this->addData( + $this->getRequestHelper()->extractShipToParameters($profile->getQuote()->getShippingAddress()) + ); + } + } + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Response/Handler.php b/app/code/community/Netresearch/OPS/Model/Response/Handler.php new file mode 100644 index 0000000..eb77bbd --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Response/Handler.php @@ -0,0 +1,77 @@ + + */ +?> +getTypeHandler($responseArray['status']) + ->handleResponse($responseArray, $paymentMethod, $shouldRegisterFeedback); + } + + /** + * @param $status + * + * @return Netresearch_OPS_Model_Response_TypeInterface + */ + protected function getTypeHandler($status) + { + $type = null; + + if (Netresearch_OPS_Model_Status::isCapture($status)) { + $type = Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_TRANSACTION_TYPE; + } elseif (Netresearch_OPS_Model_Status::isRefund($status)) { + $type = Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_TRANSACTION_TYPE; + } elseif (Netresearch_OPS_Model_Status::isVoid($status)) { + $type = Netresearch_OPS_Model_Payment_Abstract::OPS_VOID_TRANSACTION_TYPE; + } elseif (Netresearch_OPS_Model_Status::isAuthorize($status)) { + $type = Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_TRANSACTION_TYPE; + } elseif (Netresearch_OPS_Model_Status::isSpecialStatus($status)) { + $type = 'special'; + } else { + Mage::throwException(Mage::helper('ops')->__('Can not handle status %s.', $status)); + } + + return Mage::getModel('ops/response_type_' . $type); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Response/Type/Abstract.php b/app/code/community/Netresearch/OPS/Model/Response/Type/Abstract.php new file mode 100644 index 0000000..b55e5d5 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Response/Type/Abstract.php @@ -0,0 +1,373 @@ + + */ +?> +getData('config') === null) { + $this->setData('config', Mage::getModel('ops/config')); + } + + return $this->getData('config'); + } + + /** + * Performs the necessary actions for Magento to progress the order state correctly and automatically build the + * create sales objects + * + * @param array $responseArray + * @param Netresearch_OPS_Model_Payment_Abstract $paymentMethod + * @param bool $shouldRegisterFeedback + * determines if the Mage_Sales_Model_Order_Payments register*Feedback + * functions get called, defaults to true + * + * @return Netresearch_OPS_Model_Response_TypeInterface + */ + public function handleResponse($responseArray, Netresearch_OPS_Model_Payment_Abstract $paymentMethod, + $shouldRegisterFeedback = true + ) { + $this->setData(array_change_key_case($responseArray, CASE_LOWER)); + $this->setMethodInstance($paymentMethod); + $this->setShouldRegisterFeedback($shouldRegisterFeedback); + + if ($this->getStatus() == $this->getMethodInstance()->getInfoInstance()->getAdditionalInformation('status') + && $this->getTransactionId() == $paymentMethod->getInfoInstance()->getLastTransId() + ) { + return $this; + } + + $this->setGeneralTransactionInfo(); + $this->_handleResponse(); + $this->updateAdditionalInformation(); + + if ($this->getShouldRegisterFeedback() && $this->hasAlias()) { + Mage::helper('ops/alias')->saveAlias($responseArray); + } + + return $this; + } + + /** + * Handles the specific actions for the concrete payment status + */ + protected abstract function _handleResponse(); + + + /** + * Updates the additional information of the payment info object + * + * @see \Netresearch_OPS_Model_Response_Type_Abstract::updateDefaultInformation + * @see \Netresearch_OPS_Model_Response_Type_Abstract::setFraudDetectionParameters + */ + protected function updateAdditionalInformation() + { + $this->getMethodInstance()->getInfoInstance()->setLastTransId($this->getTransactionId()); + $this->updateDefaultInformation(); + $this->setFraudDetectionParameters(); + $this->setDeviceInformationParameters(); + } + + /** + * Updates default information in additional information of the payment info object + */ + protected function updateDefaultInformation() + { + $payment = $this->getMethodInstance()->getInfoInstance(); + + $payment->setAdditionalInformation('paymentId', $this->getPayid()) + ->setAdditionalInformation('status', $this->getStatus()); + + if ($this->hasAlias()) { + $payment->setAdditionalInformation('alias', $this->getAlias()); + } + + if ($this->hasAcceptance()) { + $payment->setAdditionalInformation('acceptence', $this->getAcceptance()); + } + + if ($this->hasBrand() && $this->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_Cc) { + $payment->setAdditionalInformation('CC_BRAND', $this->getBrand()); + } + } + + protected function setDeviceInformationParameters() + { + if (!$this->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_Bancontact) { + return; + } + + $payment = $this->getMethodInstance()->getInfoInstance(); + if ($this->hasMobilemode()) { + $payment->setAdditionalInformation('MOBILEMODE', $this->getMobilemode()); + } + } + + /** + * Sets Transaction details (TransactionId etc.) + */ + protected function setGeneralTransactionInfo() + { + $payment = $this->getMethodInstance()->getInfoInstance(); + + $payment->setTransactionParentId($this->getPayid()); + + if (!$this->hasPayidsub()) { + $transId = $payment->getLastTransId(); + } else { + $transId = $this->getTransactionId(); + } + + $payment->setTransactionId($transId); + $payment->setIsTransactionClosed(false); + } + + /** + * Updates fraud detection information on additional information of the payment info object + */ + protected function setFraudDetectionParameters() + { + $payment = $this->getMethodInstance()->getInfoInstance(); + if ($this->hasScoring()) { + $payment->setAdditionalInformation('scoring', $this->getScoring()); + } + + if ($this->hasScoCategory()) { + $payment->setAdditionalInformation('scoringCategory', $this->getScoCategory()); + } + + $additionalScoringData = array(); + foreach ($this->getConfig()->getAdditionalScoringKeys() as $key) { + if ($this->hasData(strtolower($key))) { + if (false === mb_detect_encoding($this->getData(strtolower($key)), 'UTF-8', true)) { + $additionalScoringData[$key] = utf8_encode($this->getData(strtolower($key))); + } else { + $additionalScoringData[$key] = $this->getData(strtolower($key)); + } + + } + } + + $payment->setAdditionalInformation('additionalScoringData', $additionalScoringData); + } + + /** + * @param string $orderComment + * @param string $additionalInfo + * + */ + protected function addOrderComment($orderComment, $additionalInfo = '') + { + $orderComment = $this->getOrderComment($orderComment, $additionalInfo); + $this->getMethodInstance()->getInfoInstance()->getOrder()->addStatusHistoryComment($orderComment); + } + + /** + * Add order comment about final status + * + * @param string $additionalInfo + */ + protected function addFinalStatusComment($additionalInfo = '') + { + $this->addOrderComment($this->getFinalStatusComment($additionalInfo)); + } + + /** + * Add order comment about intermediate status + * + * @param string $additionalInfo + */ + protected function addIntermediateStatusComment($additionalInfo = '') + { + $this->addOrderComment($this->getIntermediateStatusComment($additionalInfo)); + } + + /** + * Add order comment about refused status + * + * @param string $additionalInfo + */ + protected function addRefusedStatusComment($additionalInfo = '') + { + $this->addOrderComment($this->getRefusedStatusComment($additionalInfo)); + } + + /** + * Add order comment about fraud status + * + * @param string $additionalInfo + */ + protected function addFraudStatusComment($additionalInfo = '') + { + $this->addOrderComment($this->getFraudStatusComment($additionalInfo)); + } + + + /** + * @param string $additionalInfo + * + * @return string + */ + protected function getFinalStatusComment($additionalInfo = '') + { + $orderComment = Mage::helper('ops')->__( + 'Received Ingenico ePayments feedback status update with final status %s.', + $this->getStatus() + ); + + return $this->getOrderComment($orderComment, $additionalInfo); + + } + + /** + * @param string $additionalInfo + * + * @return string + */ + protected function getIntermediateStatusComment($additionalInfo = '') + { + $orderComment = Mage::helper('ops')->__( + 'Received Ingenico ePayments feedback status update with intermediate status %s.', + $this->getStatus() + ); + + return $this->getOrderComment($orderComment, $additionalInfo); + } + + /** + * @param string $additionalInfo + * + * @return string + */ + protected function getRefusedStatusComment($additionalInfo = '') + { + $orderComment = Mage::helper('ops')->__( + 'Received Ingenico ePayments feedback status update with refused status %s.', + $this->getStatus() + ); + + return $this->getOrderComment($orderComment, $additionalInfo); + } + + /** + * @param string $additionalInfo + * + * @return string + */ + protected function getFraudStatusComment($additionalInfo = '') + { + $orderComment = Mage::helper('ops')->__( + 'Received Ingenico ePayments feedback status update with suspected fraud status %s.', + $this->getStatus() + ); + + return $this->getOrderComment($orderComment, $additionalInfo); + } + + /** + * @param string $additionalInfo + * @param string $orderComment + * + * @return string + */ + protected function getOrderComment($orderComment, $additionalInfo = '') + { + if ($additionalInfo) { + $orderComment .= ' ' . $additionalInfo; + } + + return $orderComment; + } + + /** + * Merges the PAYID with the PAYIDSUB, if the latter is present, otherwise just returns the PAYID + * + * @return string + */ + public function getTransactionId() + { + $transId = $this->getPayid(); + if ($this->hasPayidsub()) { + $transId .= '/' . $this->getPayidsub(); + } + + return $transId; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Response/Type/Authorize.php b/app/code/community/Netresearch/OPS/Model/Response/Type/Authorize.php new file mode 100644 index 0000000..336b675 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Response/Type/Authorize.php @@ -0,0 +1,251 @@ + + */ +?> +getStatus())) { + Mage::throwException(Mage::helper('ops')->__('%s is not a authorize status!', $this->getStatus())); + } + + /** @var Mage_Sales_Model_Order $order */ + $order = $this->getMethodInstance()->getInfoInstance()->getOrder(); + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $this->getMethodInstance()->getInfoInstance(); + + // if no parent transaction id has been set yet set the parentTransactionId so we can void + if (!$payment->getParentTransactionId()) { + $payment->setParentTransactionId($this->getPayid()); + } + + if (Netresearch_OPS_Model_Status::isFinal($this->getStatus())) { + $this->processFinalStatus($order, $payment); + } else { + $this->processIntermediateState($payment, $order); + } + + $this->persistSalesObject($payment, $order); + } + + /** + * in CE 1.7 Mage_Sales_Model_Order::registerCancellation will always fail with exception if the + * order is in payment_review state. we therefore cancel the order 'manually'. + * + * below code is c&p from Mage_Sales_Model_Order::registerCancellation: + * + * @see Mage_Sales_Model_Order::registerCancellation + * + * @param Mage_Sales_Model_Order $order + */ + protected function registerCancellation($order) + { + $cancelState = Mage_Sales_Model_Order::STATE_CANCELED; + /** @var Mage_Sales_Model_Order_Item $item */ + foreach ($order->getAllItems() as $item) { + if ($cancelState != Mage_Sales_Model_Order::STATE_PROCESSING + && $item->getQtyToRefund() + ) { + if ($item->getQtyToShip() > $item->getQtyToCancel()) { + $cancelState = Mage_Sales_Model_Order::STATE_PROCESSING; + } else { + $cancelState = Mage_Sales_Model_Order::STATE_COMPLETE; + } + } + $item->cancel(); + } + + $order->setSubtotalCanceled($order->getSubtotal() - $order->getSubtotalInvoiced()); + $order->setBaseSubtotalCanceled( + $order->getBaseSubtotal() - $order->getBaseSubtotalInvoiced() + ); + + $order->setTaxCanceled($order->getTaxAmount() - $order->getTaxInvoiced()); + $order->setBaseTaxCanceled($order->getBaseTaxAmount() - $order->getBaseTaxInvoiced()); + + $order->setShippingCanceled($order->getShippingAmount() - $order->getShippingInvoiced()); + $order->setBaseShippingCanceled( + $order->getBaseShippingAmount() - $order->getBaseShippingInvoiced() + ); + + $order->setDiscountCanceled( + abs($order->getDiscountAmount()) - $order->getDiscountInvoiced() + ); + $order->setBaseDiscountCanceled( + abs($order->getBaseDiscountAmount()) - $order->getBaseDiscountInvoiced() + ); + + $order->setTotalCanceled($order->getGrandTotal() - $order->getTotalPaid()); + $order->setBaseTotalCanceled($order->getBaseGrandTotal() - $order->getBaseTotalPaid()); + + $order->setState($cancelState, true, $this->getFinalStatusComment()); + } + + /** + * process final state + * + * @param Mage_Sales_Model_Order $order + * @param Mage_Sales_Model_Order_Payment $payment + */ + protected function processFinalStatus($order, $payment) + { + // handle authorization declined + // thrown exception gets catched by core and order will not been created + if ($this->getStatus() == Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED + ) { + $this->processAuthorizationDeclined($order, $payment); + + } elseif ($this->getStatus() == Netresearch_OPS_Model_Status::CANCELED_BY_CUSTOMER) { + $order->registerCancellation($this->getFinalStatusComment()); + } + + if ($this->getShouldRegisterFeedback()) { + if ($order->getState() === Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW) { + $this->processFinalStatusInPaymentReview($order, $payment); + } elseif ($order->getState() === Mage_Sales_Model_Order::STATE_PENDING_PAYMENT + || $order->getState() === Mage_Sales_Model_Order::STATE_NEW + ) { + $payment->registerAuthorizationNotification($this->getAmount()); + $order->setState( + Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, true, $this->getFinalStatusComment() + ); + + } + } else { + $this->addFinalStatusComment(); + } + } + + /** + * save payment and order object and send transaction email if order state is not canceled + * + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param Mage_Sales_Model_Order $order + */ + protected function persistSalesObject($payment, $order) + { + if ($this->getShouldRegisterFeedback()) { + $payment->save(); + $order->save(); + + // gateway payments do not send confirmation emails by default + if ($order->getState() != Mage_Sales_Model_Order::STATE_CANCELED) { + Mage::helper('ops/data')->sendTransactionalEmail($order); + } + } + } + + /** + * process non final state + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param Mage_Sales_Model_Order $order + */ + protected function processIntermediateState($payment, $order) + { + $payment->setIsTransactionPending(true); + if ($this->getStatus() == Netresearch_OPS_Model_Status::AUTHORIZED_WAITING_EXTERNAL_RESULT) { + $payment->setIsFraudDetected(true); + $order->setState( + Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW, + Mage_Sales_Model_Order::STATUS_FRAUD, + $this->getFraudStatusComment( + Mage::helper('ops')->__('Please have a look in Ingenico ePayments backend for more information.') + ) + ); + } else { + $order->addStatusHistoryComment($this->getIntermediateStatusComment()); + } + if ($this->getShouldRegisterFeedback()) { + $payment->registerAuthorizationNotification($this->getAmount()); + } + } + + /** + * process authorization declined feedback + * + * @param Mage_Sales_Model_Order $order + * @param Mage_Sales_Model_Order_Payment $payment + */ + protected function processAuthorizationDeclined($order, $payment) + { + if (!$this->getShouldRegisterFeedback()) { + Mage::throwException( + Mage::helper('ops')->__( + 'Payment failed because the authorization was declined! Please choose another payment method.' + ) + ); + } elseif ($order->getState() === Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW) { + + try { + // if the payment was previously in payment review/has status 46 + // the identification obviously failed and the order gets canceled + $payment->setNotificationResult(true); + $payment->registerPaymentReviewAction( + Mage_Sales_Model_Order_Payment::REVIEW_ACTION_DENY, false + ); + } catch (Mage_Core_Exception $e) { + // perform manual cancellation if we are on CE1.7 and receive the error message + if ($e->getMessage() === Mage::helper('sales')->__('Order does not allow to be canceled.')) { + $this->registerCancellation($order); + } + } + } + } + + /** + * If order is in payment_review and we receive a final status, the order needs to be moved to + * new the proper order state, e.g. canceled for failed payments or processing for successful ones + * + * @param Mage_Sales_Model_Order $order + * @param Mage_Sales_Model_Order_Payment $payment + */ + protected function processFinalStatusInPaymentReview($order, $payment) + { + $action = Mage_Sales_Model_Order_Payment::REVIEW_ACTION_ACCEPT; + $targetState = Mage_Sales_Model_Order::STATE_PENDING_PAYMENT; + if (Netresearch_OPS_Model_Status::canResendPaymentInfo($this->getStatus()) + && Mage::helper('ops/payment')->isInlinePayment($payment) + ) { + $targetState = Mage_Sales_Model_Order::STATE_CANCELED; + $action = Mage_Sales_Model_Order_Payment::REVIEW_ACTION_DENY; + } + $payment->setNotificationResult(true); + $payment->registerPaymentReviewAction($action, false); + if ($order->getState() != $targetState) { + $order->setState($targetState, true, $this->getFinalStatusComment()); + } + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Response/Type/Capture.php b/app/code/community/Netresearch/OPS/Model/Response/Type/Capture.php new file mode 100644 index 0000000..4c134d3 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Response/Type/Capture.php @@ -0,0 +1,151 @@ + + */ +?> +getStatus())) { + Mage::throwException(Mage::helper('ops')->__('%s is not a capture status!', $this->getStatus())); + } + + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $this->getMethodInstance()->getInfoInstance(); + /** @var Mage_Sales_Model_Order $order */ + $order = $payment->getOrder(); + + + /** + * Basically we have to check the following things here: + * + * Order state - payment_review suggests an already existing intermediate status + * - pending_payment or new suggests no feedback yet + * + * payment status - intermediate and not failed -> move to payment review or add another comment + * - intermediate and failed -> if recoverable let the order open and place comment + * - finished - finish invoice dependent on order state + */ + + if (Netresearch_OPS_Model_Status::isIntermediate($this->getStatus())) { + $this->processIntermediateState($payment, $order); + } else { + // final means state 9 or 95 + $this->processFinalState($order, $payment); + } + + if ($this->getShouldRegisterFeedback()) { + $this->registerFeedBack($payment, $order); + } + } + + /** + * process intermediate state + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param Mage_Sales_Model_Order $order + */ + protected function processIntermediateState($payment, $order) + { + $message = $this->getIntermediateStatusComment(); + $payment->setIsTransactionPending(true); + if ($order->getState() == Mage_Sales_Model_Order::STATE_PENDING_PAYMENT + || $order->getState() == Mage_Sales_Model_Order::STATE_PROCESSING + ) { + // transaction was placed on PSP, initial feedback to shop or partial capture case + $payment->setPreparedMessage($message); + if ($this->getShouldRegisterFeedback()) { + $payment->registerCaptureNotification($this->getAmount()); + } + + } elseif ($order->getState() == Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW) { + // payment was pending and is still pending + $payment->setIsTransactionApproved(false); + $payment->setIsTransactionDenied(false); + $payment->setPreparedMessage($message); + + if ($this->getShouldRegisterFeedback()) { + $payment->setNotificationResult(true); + $payment->registerPaymentReviewAction(Mage_Sales_Model_Order_Payment::REVIEW_ACTION_UPDATE, false); + } + } + } + + /** + * process final state + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param Mage_Sales_Model_Order $order + */ + protected function processFinalState($order, $payment) + { + $message = $this->getFinalStatusComment(); + if ($order->getState() == Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW) { + $payment->setNotificationResult(true); + $payment->setPreparedMessage($message); + if ($this->getShouldRegisterFeedback()) { + $payment->registerPaymentReviewAction(Mage_Sales_Model_Order_Payment::REVIEW_ACTION_ACCEPT, false); + $transaction = $payment->getTransaction($payment->getLastTransId()); + if ($transaction) { + $transaction->close(true); + } + } + } else { + $payment->setPreparedMessage($message); + if ($this->getShouldRegisterFeedback()) { + $payment->registerCaptureNotification($this->getAmount()); + } + } + } + + /** + * save payment and order object and send transaction email + * + * @param Mage_Sales_Model_Order_Payment $payment + * @param Mage_Sales_Model_Order $order + */ + protected function registerFeedBack($payment, $order) + { + $payment->save(); + $order->save(); + + // gateway payments do not send confirmation emails by default + Mage::helper('ops/data')->sendTransactionalEmail($order); + + /** @var Mage_Sales_Model_Order_Invoice $invoice */ + $invoice = Mage::getModel('sales/order_invoice')->load($this->getTransactionId(), 'transaction_id'); + if ($invoice->getId()) { + Mage::helper('ops')->sendTransactionalEmail($invoice); + } + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Response/Type/Refund.php b/app/code/community/Netresearch/OPS/Model/Response/Type/Refund.php new file mode 100644 index 0000000..75b7675 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Response/Type/Refund.php @@ -0,0 +1,193 @@ + + */ +?> +getStatus())) { + Mage::throwException(Mage::helper('ops')->__('%s is not a refund status!', $this->getStatus())); + } + + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $this->getMethodInstance()->getInfoInstance(); + + /** @var Mage_Sales_Model_Order $order */ + $order = $payment->getOrder(); + + if (!$this->hasPayidsub()) { + $creditMemo = $this->determineCreditMemo(); + $payment->setRefundTransactionId($creditMemo->getTransactionId()); + } else { + $creditMemo = Mage::getModel('sales/order_creditmemo')->load( + $this->getTransactionId(), 'transaction_id' + ); + $payment->setRefundTransactionId($this->getTransactionId()); + } + + if ($creditMemo->getId()) { + if (Netresearch_OPS_Model_Status::isFinal($this->getStatus()) + && $creditMemo->getState() == Mage_Sales_Model_Order_Creditmemo::STATE_OPEN + ) { + $creditMemo->setState(Mage_Sales_Model_Order_Creditmemo::STATE_REFUNDED); + $this->closeRefundTransaction($creditMemo); + $this->addFinalStatusComment(); + } elseif ($this->getStatus() == Netresearch_OPS_Model_Status::REFUND_REFUSED) { + $order = $this->processRefundRefused($creditMemo); + } else { + $this->addIntermediateStatusComment(); + } + + } else { + if ($this->getShouldRegisterFeedback()) { + $payment->setParentTransactionId($this->getPayid()); + $payment->setTransactionId($this->getTransactionId()); + $payment->setIsTransactionClosed(Netresearch_OPS_Model_Status::isFinal($this->getStatus())); + $payment->registerRefundNotification($this->getAmount()); + } + if (Netresearch_OPS_Model_Status::isFinal($this->getStatus())) { + $this->addFinalStatusComment(); + } else { + $this->addIntermediateStatusComment(); + $creditMemo = $payment->getCreatedCreditMemo() ?: $payment->getCreditmemo(); + if ($creditMemo) { + $creditMemo->setState(Mage_Sales_Model_Order_Creditmemo::STATE_OPEN); + } + } + } + + if ($this->getShouldRegisterFeedback()) { + $this->registerFeedBack($order, $payment, $creditMemo); + } + + } + + /** + * Will load the creditmemo by identifying open refund transactions + * + * @return Mage_Sales_Model_Order_Creditmemo|null + */ + protected function determineCreditMemo() + { + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $this->getMethodInstance()->getInfoInstance(); + $refundTransaction = Mage::helper('ops/order_refund')->getOpenRefundTransaction($payment); + /** @var Mage_Sales_Model_Order_Creditmemo $creditmemo */ + $creditmemo = Mage::getModel('sales/order_creditmemo')->load( + $refundTransaction->getTxnId(), 'transaction_id' + ); + + return $creditmemo; + } + + /** + * Closes the refund transaction for the given creditmemo + * + * @param $creditMemo + */ + protected function closeRefundTransaction($creditMemo) + { + $refundTransaction = $this->getMethodInstance()->getInfoInstance()->lookupTransaction( + $creditMemo->getTransactionId(), Mage_Sales_Model_Order_Payment_Transaction::TYPE_REFUND + ); + if ($refundTransaction) { + $refundTransaction->setIsClosed(true) + ->save(); + } + } + + /** + * process refund refused response + * + * @param $creditMemo + * @return mixed + */ + protected function processRefundRefused($creditMemo) + { + $order = $creditMemo->getOrder(); + $creditMemo->cancel()->save(); + $this->closeRefundTransaction($creditMemo); + $invoice = Mage::getModel('sales/order_invoice')->load($creditMemo->getInvoiceId()); + $invoice->setIsUsedForRefund(0) + ->setBaseTotalRefunded( + $invoice->getBaseTotalRefunded() - $creditMemo->getBaseGrandTotal() + ); + $creditMemo->setInvoice($invoice); + /** @var Mage_Sales_Model_Order_Creditmemo_Item $item */ + foreach ($creditMemo->getAllItems() as $item) { + $item->getOrderItem()->setAmountRefunded( + $item->getOrderItem()->getAmountRefunded() - $item->getRowTotal() + ); + $item->getOrderItem()->setBaseAmountRefunded( + $item->getOrderItem()->getBaseAmountRefunded() - $item->getBaseRowTotal() + ); + } + $order->setTotalRefunded($order->getTotalRefunded() - $creditMemo->getBaseGrandTotal()); + $order->setBaseTotalRefunded($order->getBaseTotalRefunded() - $creditMemo->getBaseGrandTotal()); + + $this->addRefusedStatusComment(); + $state = Mage_Sales_Model_Order::STATE_COMPLETE; + if ($order->canShip() || $order->canInvoice()) { + $state = Mage_Sales_Model_Order::STATE_PROCESSING; + } + $order->setState( + $state, + true, + $this->getRefusedStatusComment(Mage::helper('ops')->__('Refund refused by Ingenico ePayments.')) + ); + + return $order; + } + + /** + * register feedback + * + * @param $order + * @param $payment + * @param $creditMemo + */ + protected function registerFeedBack($order, $payment, $creditMemo) + { + $transactionSave = Mage::getModel('core/resource_transaction') + ->addObject($order) + ->addObject($payment) + ->addObject($creditMemo); + + if ($creditMemo->getInvoice()) { + $transactionSave->addObject($creditMemo->getInvoice()); + } + $transactionSave->save(); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Response/Type/Special.php b/app/code/community/Netresearch/OPS/Model/Response/Type/Special.php new file mode 100644 index 0000000..c81e954 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Response/Type/Special.php @@ -0,0 +1,87 @@ + + */ +?> +getStatus())) { + Mage::throwException(Mage::helper('ops')->__('%s is not a special status!', $this->getStatus())); + } + + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $this->getMethodInstance()->getInfoInstance(); + $order = $payment->getOrder(); + $helper = Mage::helper('ops'); + + if ($this->getStatus() == Netresearch_OPS_Model_Status::WAITING_FOR_IDENTIFICATION) { + $payment->setIsTransactionPending(true); + $payment->setAdditionalInformation('HTML_ANSWER', $this->getHtmlAnswer()); + $order->addStatusHistoryComment( + $this->getIntermediateStatusComment($helper->__('Customer redirected for 3DS authorization.')) + ); + } + + if ($this->getStatus() == Netresearch_OPS_Model_Status::WAITING_CLIENT_PAYMENT) { + $order->addStatusHistoryComment( + $this->getIntermediateStatusComment( + $helper->__( + 'Customer received your payment instructions, waiting for actual payment.' + ) + ) + ); + + // gateway payments do not send confirmation emails by default + Mage::helper('ops/data')->sendTransactionalEmail($order); + } + + if ($this->getStatus() == Netresearch_OPS_Model_Status::INVALID_INCOMPLETE) { + //save status information to order before exception + if($this->getShouldRegisterFeedback()){ + $this->updateAdditionalInformation(); + $payment->save(); + } + + + $message = Mage::helper('ops')->__('Ingenico ePayments status 0, the action failed.'); + if ($helper->isAdminSession()) { + $message .= ' ' . $this->getNcerror() . ' ' . $this->getNcerrorplus(); + } + Mage::throwException($message); + } + + $payment->save(); + $order->save(); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Response/Type/Void.php b/app/code/community/Netresearch/OPS/Model/Response/Type/Void.php new file mode 100644 index 0000000..40d5891 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Response/Type/Void.php @@ -0,0 +1,67 @@ + + */ +?> +getStatus())) { + Mage::throwException(Mage::helper('ops')->__('%s is not a void status!', $this->getStatus())); + } + + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $this->getMethodInstance()->getInfoInstance(); + /** @var Mage_Sales_Model_Order $order */ + $order = $payment->getOrder(); + if (Netresearch_OPS_Model_Status::isFinal($this->getStatus())) { + if ($this->getShouldRegisterFeedback()) { + + $payment->setMessage( + Mage::helper('ops')->__('Received Ingenico ePayments status %s. Order cancelled.', $this->getStatus()) + ); + $payment->registerVoidNotification($this->getAmount()); + + // payment void does not cancel the order, but sets it to processing. + // We therefore need to cancel the order ourselves. + $order->registerCancellation($this->getFinalStatusComment(), true); + } else { + $this->addFinalStatusComment(); + } + } else { + $payment->setMessage($this->getIntermediateStatusComment()); + } + + $order->save(); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Response/TypeInterface.php b/app/code/community/Netresearch/OPS/Model/Response/TypeInterface.php new file mode 100644 index 0000000..cd5e3d2 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Response/TypeInterface.php @@ -0,0 +1,51 @@ + + */ +?> +options) { + $this->options = Mage::getResourceModel('directory/country_collection')->loadData()->toOptionArray(false); + if (!$isMultiselect) { + array_unshift( + $this->options, + array( + 'value'=>'', + 'label'=> Mage::helper('adminhtml')->__('--Please Select--') + ) + ); + } + foreach ($this->options as $offset=>$option) { + if (!in_array($option['value'], $this->countries)) { + unset($this->options[$offset]); + } + } + $this->options['*'] = array( + 'value'=>'*', + 'label'=> Mage::helper('adminhtml')->__(Mage::helper('ops')->__('Miscellaneous Countries')) + ); + } + + return $this->options; + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/Cc/AliasInterfaceEnabledTypes.php b/app/code/community/Netresearch/OPS/Model/Source/Cc/AliasInterfaceEnabledTypes.php new file mode 100644 index 0000000..5a025cf --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/Cc/AliasInterfaceEnabledTypes.php @@ -0,0 +1,62 @@ +getAllCcTypes(), + $this->getAliasInterfaceCompatibleTypes() + ); + foreach ($types as $type) { + $options[] = array( + 'value' => $type, + 'label' => Mage::helper('ops')->__($type) + ); + } + return $options; + } + + public function getAliasInterfaceCompatibleTypes() + { + return array( + 'American Express', + 'Diners Club', + 'MaestroUK', + 'MasterCard', + 'VISA', + ); + } + +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/Cc/RecurringTypes.php b/app/code/community/Netresearch/OPS/Model/Source/Cc/RecurringTypes.php new file mode 100644 index 0000000..6ca88fa --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/Cc/RecurringTypes.php @@ -0,0 +1,50 @@ + + */ +class Netresearch_OPS_Model_Source_Cc_RecurringTypes +{ + + public function toOptionArray() + { + $options = array(); + $types = Mage::getModel('ops/config')->getAllRecurringCcTypes(); + $translatedTypes = array(); + foreach ($types as $type) { + $translatedTypes[Mage::helper('ops')->__($type)] = $type; + } + ksort($translatedTypes); + foreach ($translatedTypes as $label => $value) { + $options[] = array( + 'value' => $value, + 'label' => $label + ); + } + return $options; + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Source/Cc/Types.php b/app/code/community/Netresearch/OPS/Model/Source/Cc/Types.php new file mode 100644 index 0000000..0620a97 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/Cc/Types.php @@ -0,0 +1,52 @@ +getAllCcTypes(); + $translatedTypes = array(); + foreach ($types as $type) { + $translatedTypes[Mage::helper('ops')->__($type)] = $type; + } + ksort($translatedTypes); + foreach ($translatedTypes as $label=>$value) { + $options[] = array( + 'value' => $value, + 'label' => $label + ); + } + return $options; + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/DebitCard/AliasInterfaceEnabledTypes.php b/app/code/community/Netresearch/OPS/Model/Source/DebitCard/AliasInterfaceEnabledTypes.php new file mode 100644 index 0000000..ef5ea2f --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/DebitCard/AliasInterfaceEnabledTypes.php @@ -0,0 +1,61 @@ +getAllDcTypes(), + $this->getAliasInterfaceCompatibleTypes() + ); + foreach ($types as $type) { + $options[] = array( + 'value' => $type, + 'label' => Mage::helper('ops')->__($type) + ); + } + return $options; + } + + public function getAliasInterfaceCompatibleTypes() + { + return array( + 'Maestro', + 'MaestroUK', + 'MasterCard', + 'VISA', + ); + } + +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/DebitCard/Types.php b/app/code/community/Netresearch/OPS/Model/Source/DebitCard/Types.php new file mode 100644 index 0000000..f02fdac --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/DebitCard/Types.php @@ -0,0 +1,52 @@ +getAllDcTypes(); + $translatedTypes = array(); + foreach ($types as $type) { + $translatedTypes[Mage::helper('ops')->__($type)] = $type; + } + ksort($translatedTypes); + foreach ($translatedTypes as $label=>$value) { + $options[] = array( + 'value' => $value, + 'label' => $label + ); + } + return $options; + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/DirectDebit/Countries.php b/app/code/community/Netresearch/OPS/Model/Source/DirectDebit/Countries.php new file mode 100644 index 0000000..cd09167 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/DirectDebit/Countries.php @@ -0,0 +1,65 @@ +options) { + $this->options = Mage::getResourceModel('directory/country_collection')->loadData()->toOptionArray(false); + if (!$isMultiselect) { + array_unshift( + $this->options, + array( + 'value'=>'', + 'label'=> Mage::helper('adminhtml')->__('--Please Select--') + ) + ); + } + foreach ($this->options as $offset=>$option) { + if (!in_array($option['value'], $this->countries)) { + unset($this->options[$offset]); + } + } + } + + return $this->options; + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/DirectEbanking/Brands.php b/app/code/community/Netresearch/OPS/Model/Source/DirectEbanking/Brands.php new file mode 100644 index 0000000..a469eb2 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/DirectEbanking/Brands.php @@ -0,0 +1,67 @@ + 'DirectEbanking', + ), + array( + 'value' => 'DirectEbankingAT', + ), + array( + 'value' => 'DirectEbankingBE', + ), + array( + 'value' => 'DirectEbankingCH', + ), + array( + 'value' => 'DirectEbankingDE', + ), + array( + 'value' => 'DirectEbankingFR', + ), + array( + 'value' => 'DirectEbankingGB', + ) +); + + /** + * @return array + */ + public function toOptionArray() + { + foreach ($this->options as $key => $value) { + $this->options[$key]['label'] = Mage::helper('ops')->__($value['value']); + } + return $this->options; + } + +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/Kwixo/ProductCategories.php b/app/code/community/Netresearch/OPS/Model/Source/Kwixo/ProductCategories.php new file mode 100644 index 0000000..3d16259 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/Kwixo/ProductCategories.php @@ -0,0 +1,127 @@ + 1, + 'label' => Mage::helper('ops/data')->__('Food & gastronomy') + ), + array( + 'value' => 2, + 'label' => Mage::helper('ops/data')->__('Car & Motorbike') + ), + array( + 'value' => 3, + 'label' => Mage::helper('ops/data')->__('Culture & leisure') + ), + array( + 'value' => 4, + 'label' => Mage::helper('ops/data')->__('Home & garden') + ), + array( + 'value' => 5, + 'label' => Mage::helper('ops/data')->__('Appliances') + ), + array( + 'value' => 6, + 'label' => Mage::helper('ops/data')->__('Auctions and bulk purchases') + ), + array( + 'value' => 7, + 'label' => Mage::helper('ops/data')->__('Flowers & gifts') + ), + array( + 'value' => 8, + 'label' => Mage::helper('ops/data')->__('Computer & software') + ), + array( + 'value' => 9, + 'label' => Mage::helper('ops/data')->__('Health & beauty') + ), + array( + 'value' => 10, + 'label' => Mage::helper('ops/data')->__('Services for individuals') + ), + array( + 'value' => 11, + 'label' => Mage::helper('ops/data')->__('Services for professionals') + ), + array( + 'value' => 12, + 'label' => Mage::helper('ops/data')->__('Sports') + ), + array( + 'value' => 13, + 'label' => Mage::helper('ops/data')->__('Clothing & accessories') + ), + array( + 'value' => 14, + 'label' => Mage::helper('ops/data')->__('Travel & tourism') + ), + array( + 'value' => 15, + 'label' => Mage::helper('ops/data')->__('Hifi, photo & video') + ), + array( + 'value' => 16, + 'label' => Mage::helper('ops/data')->__('Telephony & communication') + ), + array( + 'value' => 17, + 'label' => Mage::helper('ops/data')->__('Jewelry & precious metals') + ), + array( + 'value' => 18, + 'label' => Mage::helper('ops/data')->__('Baby articles and accessories') + ), + array( + 'value' => 19, + 'label' => Mage::helper('ops/data')->__('Sound & light') + ) + ); + } + + public function getValidKwixoCategoryIds() + { + $kwixoValues = array(); + foreach ($this->toOptionArray() as $option) { + $kwixoValues[] = $option['value']; + } + return $kwixoValues; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Source/Kwixo/ShipMethodType.php b/app/code/community/Netresearch/OPS/Model/Source/Kwixo/ShipMethodType.php new file mode 100644 index 0000000..c5af638 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/Kwixo/ShipMethodType.php @@ -0,0 +1,94 @@ + "", + 'label' => Mage::helper('ops/data')->__('--Please select--') + ), + array( + 'value' => self::PICK_UP_AT_MERCHANT, + 'label' => Mage::helper('ops/data')->__('Pick up at merchant') + ), + array( + 'value' => self::COLLECTION_POINT, + 'label' => Mage::helper('ops/data')->__( + 'Collection point (Kiala...)' + ) + ), + array( + 'value' => self::COLLECT_AT_AIRPORT, + 'label' => Mage::helper('ops/data')->__( + 'Collect at airport, train station or travel agency' + ) + ), + array( + 'value' => self::TRANSPORTER, + 'label' => Mage::helper('ops/data')->__( + 'Transporter (La Poste, UPS...)' + ) + ), + array( + 'value' => self::DOWNLOAD, + 'label' => Mage::helper('ops/data')->__('Download') + ) + ); + } + + public function getValidValues() + { + return array( + self::PICK_UP_AT_MERCHANT, + self::COLLECTION_POINT, + self::COLLECT_AT_AIRPORT, + self::TRANSPORTER, + self::DOWNLOAD + ); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Source/Mode.php b/app/code/community/Netresearch/OPS/Model/Source/Mode.php new file mode 100644 index 0000000..f1819d2 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/Mode.php @@ -0,0 +1,31 @@ + self::TEST, 'label' => Mage::helper('ops')->__(self::TEST)), + array('value' => self::PROD, 'label' => Mage::helper('ops')->__(self::PROD)), + array('value' => self::CUSTOM, 'label' => Mage::helper('ops')->__(self::CUSTOM)), + ); + } + +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/OrderReference.php b/app/code/community/Netresearch/OPS/Model/Source/OrderReference.php new file mode 100644 index 0000000..6eac41e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/OrderReference.php @@ -0,0 +1,50 @@ + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID, + 'label' => Mage::helper('ops')->__('quote id') + ), + array( + 'value' => Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID, + 'label' => Mage::helper('ops')->__('order id') + ), + ); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Source/PaymentAction.php b/app/code/community/Netresearch/OPS/Model/Source/PaymentAction.php new file mode 100644 index 0000000..5275376 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/PaymentAction.php @@ -0,0 +1,50 @@ + Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE, + 'label' => Mage::helper('ops')->__('Authorization') + ), + array( + 'value' => Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE, + 'label' => Mage::helper('ops')->__('Direct Sale') + ), + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/Pmlist.php b/app/code/community/Netresearch/OPS/Model/Source/Pmlist.php new file mode 100644 index 0000000..831fdd1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/Pmlist.php @@ -0,0 +1,54 @@ + Netresearch_OPS_Model_Payment_Abstract::PMLIST_HORIZONTAL_LEFT, + 'label' => Mage::helper('ops')->__('Horizontally grouped logo with group name on left') + ), + array( + 'value' => Netresearch_OPS_Model_Payment_Abstract::PMLIST_HORIZONTAL, + 'label' => Mage::helper('ops')->__('Horizontally grouped logo with no group name') + ), + array( + 'value' => Netresearch_OPS_Model_Payment_Abstract::PMLIST_VERTICAL, + 'label' => Mage::helper('ops')->__('Verical list') + ), + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/Template.php b/app/code/community/Netresearch/OPS/Model/Source/Template.php new file mode 100644 index 0000000..e163fcf --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/Template.php @@ -0,0 +1,58 @@ + Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_REDIRECT, + 'label' => Mage::helper('ops')->__('Ingenico ePayments - Redirect mode') + ), + array( + 'value' => Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_MAGENTO_INTERNAL, + 'label' => Mage::helper('ops')->__('Magento - internal shop template') + ), + array( + 'value' => Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_IFRAME, + 'label' => Mage::helper('ops')->__('Ingenico ePayments - iFrame mode') + ), + array( + 'value' => Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_TEMPLATE, + 'label' => Mage::helper('ops')->__('Ingenico ePayments - Dynamic template') + ) + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/Source/TemplateType.php b/app/code/community/Netresearch/OPS/Model/Source/TemplateType.php new file mode 100644 index 0000000..193a4b2 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Source/TemplateType.php @@ -0,0 +1,23 @@ + self::URL, 'label' => Mage::helper('ops')->__(self::URL)), + array('value' => self::ID, 'label' => Mage::helper('ops')->__(self::ID)) + ); + } + +} diff --git a/app/code/community/Netresearch/OPS/Model/Status.php b/app/code/community/Netresearch/OPS/Model/Status.php new file mode 100644 index 0000000..3df149c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Status.php @@ -0,0 +1,301 @@ + + */ +?> + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Model_Status_Update +{ + + /** @var Netresearch_OPS_Model_Api_DirectLink $directLinkApi */ + protected $directLinkApi = null; + + protected $order = null; + + protected $requestParams = array(); + + /** @var Netresearch_OPS_Model_Config $opsConfig */ + protected $opsConfig = null; + + protected $opsResponse = array(); + + protected $paymentHelper = null; + + protected $directLinkHelper = null; + + protected $messageContainer = null; + + protected $dataHelper = null; + + /** + * @param null $dataHelper + */ + public function setDataHelper($dataHelper) + { + $this->dataHelper = $dataHelper; + } + + /** + * @return Netresearch_OPS_Helper_Data + */ + public function getDataHelper() + { + if (null == $this->dataHelper) { + $this->dataHelper = Mage::helper('ops/data'); + } + + return $this->dataHelper; + } + /** + * @param Mage_Core_Model_Session_Abstract $messageContainer + */ + public function setMessageContainer(Mage_Core_Model_Session_Abstract $messageContainer) + { + $this->messageContainer = $messageContainer; + } + + /** + * @return Mage_Core_Model_Session_Abstract + */ + public function getMessageContainer() + { + if (null == $this->messageContainer) { + $this->messageContainer = Mage::getSingleton('adminhtml/session'); + } + return $this->messageContainer; + } + + /** + * @param Netresearch_OPS_Model_Config $opsConfig + */ + public function setOpsConfig(Netresearch_OPS_Model_Config $opsConfig) + { + $this->opsConfig = $opsConfig; + } + + /** + * @return Netresearch_OPS_Model_Config + */ + public function getOpsConfig() + { + if (null === $this->opsConfig) { + $this->opsConfig = Mage::getModel('ops/config'); + } + return $this->opsConfig; + } + + /** + * @param array $requestParams + */ + public function setRequestParams($requestParams) + { + $this->requestParams = $requestParams; + } + + /** + * @return array + */ + public function getRequestParams() + { + return $this->requestParams; + } + + /** + * @param Netresearch_OPS_Helper_Order $orderHelper + */ + public function setOrderHelper(Netresearch_OPS_Helper_Order $orderHelper) + { + $this->orderHelper = $orderHelper; + } + + /** + * @return Netresearch_OPS_Helper_Order + */ + public function getOrderHelper() + { + if (null == $this->orderHelper) { + $this->orderHelper = Mage::helper('ops/order'); + } + + return $this->orderHelper; + } + + /** + * @param array $opsResponse + */ + public function setOpsResponse($opsResponse) + { + $this->opsResponse = $opsResponse; + } + + /** + * @return array + */ + public function getOpsResponse() + { + return $this->opsResponse; + } + + /** @var Netresearch_OPS_Helper_Order $orderHelper */ + protected $orderHelper = null; + + /** + * @param Netresearch_OPS_Model_Api_DirectLink $directLinkApi + */ + public function setDirectLinkApi(Netresearch_OPS_Model_Api_DirectLink $directLinkApi) + { + $this->directLinkApi = $directLinkApi; + } + + /** + * @return Netresearch_OPS_Model_Api_DirectLink + */ + public function getDirectLinkApi() + { + if (null === $this->directLinkApi) { + $this->directLinkApi = Mage::getModel('ops/api_directlink'); + } + return $this->directLinkApi; + } + + /** + * @param Mage_Sales_Model_Order $order + */ + public function setOrder(Mage_Sales_Model_Order $order) + { + $this->order = $order; + } + + /** + * @return Mage_Sales_Model_Order + */ + public function getOrder() + { + return $this->order; + } + + public function updateStatusFor(Mage_Sales_Model_Order $order) + { + if (false === ($order->getPayment()->getMethodInstance() instanceof Netresearch_OPS_Model_Payment_Abstract)) { + return $this; + } + $this->setOrder($order); + $this->buildParams($order->getPayment()); + + try { + $this->performRequest(); + $this->updatePaymentStatus(); + } catch (Mage_Core_Exception $e) { + $this->getMessageContainer()->addError($e->getMessage()); + } + return $this; + } + + protected function buildParams(Mage_Sales_Model_Order_Payment $payment) + { + // use PAYID if possible + if (0 < strlen(trim($payment->getAdditionalInformation('paymentId')))) { + $this->requestParams['PAYID'] = $payment->getAdditionalInformation('paymentId'); + + } else { + $useOrderId = true; + if ($this->canNotUseOrderId($payment) + ) { + $useOrderId = false; + } + $this->requestParams['ORDERID'] = $this->getOrderHelper()->getOpsOrderId($this->getOrder(), $useOrderId); + } + $this->addPayIdSub($payment); + + + return $this; + } + + protected function performRequest() + { + $storeId = $this->getOrder()->getStoreId(); + $url = $this->getOpsConfig()->getDirectLinkMaintenanceApiPath($storeId); + try { + $this->opsResponse = $this->getDirectLinkApi()->performRequest($this->getRequestParams(), $url, $storeId); + } catch (Mage_Core_Exception $e) { + $this->getMessageContainer()->addError($this->getDataHelper()->__($e->getMessage())); + return $this; + } + $this->opsResponse = array_change_key_case($this->opsResponse, CASE_UPPER); + // in further processing the amount is sometimes in upper and sometimes in lower case :( + if (array_key_exists('AMOUNT', $this->opsResponse)) { + $this->opsResponse['amount'] = $this->opsResponse['AMOUNT']; + } + + + return $this; + } + + protected function updatePaymentStatus() + { + if (!array_key_exists('STATUS', $this->getOpsResponse()) + || $this->opsResponse['STATUS'] == $this->getOrder()->getPayment()->getAdditionalInformation('status') + ) { + $this->getMessageContainer()->addNotice($this->getDataHelper()->__('No update available from Ingenico ePayments.')); + return $this; + } + + if (false != strlen(trim($this->getOrder()->getPayment()->getAdditionalInformation('paymentId')))) { + Mage::getModel('ops/response_handler')->processResponse( + $this->getOpsResponse(), + $this->getOrder()->getPayment()->getMethodInstance() + ); + } else { + // simulate initial request + $this->getPaymentHelper()->applyStateForOrder($this->getOrder(), $this->getOpsResponse()); + } + + $this->getPaymentHelper()->saveOpsStatusToPayment($this->getOrder()->getPayment(), $this->getOpsResponse()); + $this->getMessageContainer()->addSuccess($this->getDataHelper()->__('Ingenico ePayments status successfully updated')); + + return $this; + } + + public function getPaymentHelper() + { + if (null == $this->paymentHelper) { + $this->paymentHelper = Mage::helper('ops/payment'); + } + + return $this->paymentHelper; + } + + public function setPaymentHelper(Netresearch_OPS_Helper_Payment $paymentHelper) + { + $this->paymentHelper = $paymentHelper; + } + + public function getDirectLinkHelper() + { + if (null == $this->directLinkHelper) { + $this->directLinkHelper = Mage::helper('ops/directlink'); + } + + return $this->directLinkHelper; + } + + public function setDirectLinkHelper(Netresearch_OPS_Helper_Directlink $directLinkHelper) + { + $this->directLinkHelper = $directLinkHelper; + } + + /** + * @param Mage_Sales_Model_Order_Payment $payment + * @return $this + */ + protected function addPayIdSub(Mage_Sales_Model_Order_Payment $payment) + { + $lastTransaction = $payment->getLastTransId(); + $lastTransactionParts = explode('/', $lastTransaction); + if ($lastTransaction && count($lastTransactionParts)>1) { + $this->requestParams['PAYIDSUB'] = $lastTransactionParts[1]; + } + return $this; + } + + protected function canNotUseOrderId(Mage_Sales_Model_Order_Payment $payment) + { + $methodInstance = $payment->getMethodInstance(); + + return ($methodInstance instanceof Netresearch_OPS_Model_Payment_Kwixo_Abstract) + || ($methodInstance instanceof Netresearch_OPS_Model_Payment_DirectDebit) + || ($methodInstance instanceof Netresearch_OPS_Model_Payment_Cc + && $methodInstance->hasBrandAliasInterfaceSupport($payment)); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Subscription/Manager.php b/app/code/community/Netresearch/OPS/Model/Subscription/Manager.php new file mode 100644 index 0000000..3c55c73 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Subscription/Manager.php @@ -0,0 +1,306 @@ + + */ +?> +dataHelper) { + $this->dataHelper = Mage::helper('ops'); + } + + return $this->dataHelper; + } + + /** + * @param Netresearch_OPS_Helper_Data $dataHelper + * + * @returns $this + */ + public function setDataHelper($dataHelper) + { + $this->dataHelper = $dataHelper; + + return $this; + } + + /** + * @return Netresearch_OPS_Helper_Payment + */ + public function getPaymentHelper() + { + if (null === $this->paymentHelper) { + $this->paymentHelper = Mage::helper('ops/payment'); + } + + return $this->paymentHelper; + } + + /** + * @param Netresearch_OPS_Helper_Payment $paymentHelper + * + * @returns $this + */ + public function setPaymentHelper($paymentHelper) + { + $this->paymentHelper = $paymentHelper; + + return $this; + } + + + /** + * @return Netresearch_OPS_Helper_Subscription + */ + public function getSubscriptionHelper() + { + if (null === $this->subscriptionHelper) { + $this->subscriptionHelper = Mage::helper('ops/subscription'); + } + + return $this->subscriptionHelper; + } + + /** + * @param Netresearch_OPS_Helper_Subscription $subscriptionHelper + * + * @returns $this + */ + public function setSubscriptionHelper($subscriptionHelper) + { + $this->subscriptionHelper = $subscriptionHelper; + + return $this; + } + + + /** + * Process request from Ingenico ePayments automatic subscription payments and initial creations + * + * + * @param mixed[] $responseParams + * @param Mage_Sales_Model_Recurring_Profile $profile - only has to be provided during the initial setup of + * the subscription + * @param Mage_Sales_Model_Order $order + * + * + * @return Mage_Sales_Model_Order | false + */ + public function processSubscriptionFeedback($responseParams, $profile = null, $order = null) + { + $createOrder = true; + $feedbackType = false; + $orderId = $responseParams['orderID']; + if (!is_array($responseParams)) { + Mage::throwException($this->getDataHelper()->__('No response array provided')); + } + + if (null === $profile) { + $profile = $this->getSubscriptionHelper()->getProfileForSubscription($orderId); + } + + if (array_key_exists('creation_status', $responseParams)) { + switch ($responseParams['creation_status']) { + case self::CREATION_SUCCEEDED: + $profile->setState(Mage_Sales_Model_Recurring_Profile::STATE_ACTIVE); + break; + default: + $profile->setState(Mage_Sales_Model_Recurring_Profile::STATE_UNKNOWN); + break; + } + $profile->setReferenceId($responseParams['subscription_id']); + $profile->setProfileVendorInfo(serialize($responseParams)); + $profile->setAdditionalInfo(serialize($responseParams)); + $feedbackType = Mage_Sales_Model_Recurring_Profile::PAYMENT_TYPE_INITIAL; + // do not create an order since it already got created + $createOrder = false; + } + + if (!$feedbackType) { + if ($this->getSubscriptionHelper()->isTrialFeedback($orderId)) { + $feedbackType = Mage_Sales_Model_Recurring_Profile::PAYMENT_TYPE_TRIAL; + } else { + $feedbackType = Mage_Sales_Model_Recurring_Profile::PAYMENT_TYPE_REGULAR; + } + } + + if ($createOrder && null === $order) { + return $this->createOrderFromFeedback($profile, $feedbackType, $responseParams); + } elseif (null != $order) { + return $this->processPaymentFeedback($responseParams, $profile, $order); + } + + return $createOrder; + } + + /** + * Creates an order for the given item type using the recurring profile model + * + * @see Mage_Sales_Model_Recurring_Profile::createOrder() + * + * @param Mage_Sales_Model_Recurring_Profile $profile + * @param string $feedbackType - @see Mage_Sales_Model_Recurring_Profile payment types + * @param string[] $responseParams + * + * @return Mage_Sales_Model_Order the created order + */ + protected function createOrderFromFeedback($profile, $feedbackType, $responseParams) + { + // Just set the payment type, the recurring profile sets the correct amounts automatically + $orderItem = new Varien_Object(); + $orderItem->setPaymentType($feedbackType); + if ($feedbackType == Mage_Sales_Model_Recurring_Profile::PAYMENT_TYPE_TRIAL) { + $orderItemInfo = new Varien_Object($profile->getOrderItemInfo()); + $calculator = Mage::getModel('tax/calculation'); + $tax = $calculator->calcTaxAmount( + $profile->getTrialBillingAmount(), + $orderItemInfo->getTaxPercent(), true, true + ); + $price = $profile->getTrialBillingAmount() - $tax; + $orderItem->setPrice($price) + ->setTaxAmount($tax); + } + + $order = $profile->createOrder($orderItem); + $billingAddressInfo = $profile->getBillingAddressInfo(); + $order->setQuoteId($billingAddressInfo['quote_id']); + + $order = $this->processPaymentFeedback($responseParams, $profile, $order); + + return $order; + } + + /** + * Creates an order for the initial fee. It also fixes the missing data for the order item, so a capture can be + * properly processed and an invoice created. + * + * @param Mage_Payment_Model_Recurring_Profile $profile + * + * @return Mage_Sales_Model_Order + */ + public function createInitialOrder(Mage_Payment_Model_Recurring_Profile $profile) + { + /** @var $profile Mage_Sales_Model_Recurring_Profile */ + $item = new Varien_Object(); + $orderItemInfo = new Varien_Object($profile->getOrderItemInfo()); + $calculator = Mage::getModel('tax/calculation'); + $taxAmount = $calculator->calcTaxAmount( + $profile->getInitAmount(), $orderItemInfo->getTaxPercent(), true, + true + ); + $amountWithoutTax = $profile->getInitAmount() - $taxAmount; + $item->setPaymentType(Mage_Sales_Model_Recurring_Profile::PAYMENT_TYPE_INITIAL) + ->setPrice($amountWithoutTax) + ->setTaxAmount($taxAmount); + + $order = $profile->createOrder($item); + $order->setQuoteId($profile->getQuote()->getId()); + + /** @var $item Mage_Sales_Model_Order_Item */ + list($item) = $order->getAllItems(); + $item->setData('base_discount_amount', 0) + ->setData('base_discount_calculation_price', $profile->getInitAmount()) + ->setData('base_hidden_tax_amount', 0) + ->setData('base_original_price', $profile->getInitAmount()) + ->setData('base_price_incl_tax', $profile->getInitAmount()) + ->setData('base_row_tax', $taxAmount) + ->setData('base_row_total_incl_tax', $profile->getInitAmount()) + ->setData('base_tax_amount', $taxAmount) + ->setData('base_taxable_amount', $profile->getInitAmount()) + ->setData('calculation_price', $amountWithoutTax) + ->setData('converted_price', $amountWithoutTax) + ->setData('discount_calculation_price', $profile->getInitAmount()) + ->setData('price_incl_tax', $profile->getInitAmount()) + ->setData('row_tax', $taxAmount) + ->setData('row_total_incl_tax', $profile->getInitAmount()) + ->setData('taxable_amount', $profile->getInitAmount()); + + $itemData = $item->getData(); + $item->addData($profile->getOrderItemInfo()); + $item->addData($itemData); + $item->setId(null); + $order->getItemsCollection()->removeItemByKey(0); + $order->addItem($item); + $order->save(); + + return $order; + } + + /** + * Takes the responseparameters and applies the corresponding action (capture/authorize) on the order payment. + * Also updates the Ingenico ePayments information on the payment. + * + * @param string[] $responseParams + * @param Mage_Sales_Model_Recurring_Profile $profile + * @param Mage_Sales_Model_Order $order + * + * @return Mage_Sales_Model_Order $order - updated order + */ + protected function processPaymentFeedback($responseParams, $profile, $order) + { + $payment = $order->getPayment(); + foreach ($responseParams as $key => $value) { + $payment->setTransactionAdditionalInfo($key, $value); + } + $payment->setTransactionId($responseParams['PAYID']) + ->setCurrencyCode($responseParams['currency']) + ->setIsTransactionClosed(0); + $order->save(); + $profile->addOrderRelation($order->getId()); + if ($this->getPaymentHelper()->isPaymentAuthorizeType($responseParams['STATUS'])) { + $payment->registerAuthorizationNotification($responseParams['amount']); + } elseif ($this->getPaymentHelper()->isPaymentCaptureType($responseParams['STATUS'])) { + $payment->registerCaptureNotification($responseParams['amount']); + } elseif ($responseParams['STATUS'] + == Netresearch_OPS_Model_Payment_Abstract::OPS_WAITING_FOR_IDENTIFICATION + ) { + // handle 3ds payment - only relevant for initial order creation + $payment->setIsTransactionPending(1); + $payment->registerCaptureNotification($responseParams['amount'], true); + } + $order->save(); + $this->getPaymentHelper()->applyStateForOrder($order, $responseParams); + + return $order; + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/System/Config/Backend/Encoding.php b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/Encoding.php new file mode 100644 index 0000000..6ccc221 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/Encoding.php @@ -0,0 +1,46 @@ + + */ +class Netresearch_OPS_Model_System_Config_Backend_Encoding +{ + + /** + * Options getter + * + * @return array + */ + public function toOptionArray() + { + return array( + array('value' => 'utf-8', 'label' => Mage::helper('ops')->__('UTF-8')), + array('value' => 'other', 'label' => Mage::helper('ops')->__('Other')), + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/System/Config/Backend/Flex/Methods.php b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/Flex/Methods.php new file mode 100644 index 0000000..05d8710 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/Flex/Methods.php @@ -0,0 +1,67 @@ + + */ + +class Netresearch_OPS_Model_System_Config_Backend_Flex_Methods + extends Mage_Adminhtml_Model_System_Config_Backend_Serialized_Array +{ + protected $_eventPrefix = 'ops_config_backend_flex_methods'; + + /** + * additional validation for unique brands + * + * @override + * @throws Mage_Core_Exception if the brands are not unique -> validation failed + * @return Netresearch_Ops_Model_System_Config_Backend_Intersolve_Brands + */ + public function save() + { + $methods = $this->getValue(); + if (is_array($methods) && count($methods) > 1) { + $alreadyProcessedMethods = array(); + foreach ($methods as $method) { + + if (is_array($method) + && array_key_exists('pm', $method) + && array_key_exists('brand', $method) + ) { + if (empty($method['title'])||empty($method['pm'])) { + Mage::throwException("Can not save empty title or PM fields"); + } + + + if (in_array($method['pm'].'_'.$method['brand'], $alreadyProcessedMethods)) { + Mage::throwException("PM and Brand combination must be unique"); + } + $alreadyProcessedMethods[] = $method['pm'].'_'.$method['brand']; + } + } + } + return parent::save(); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/System/Config/Backend/HashMethods.php b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/HashMethods.php new file mode 100644 index 0000000..c9ff3eb --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/HashMethods.php @@ -0,0 +1,48 @@ + + */ + +class Netresearch_Ops_Model_System_Config_Backend_HashMethods + extends Mage_Adminhtml_Model_System_Config_Backend_Serialized_Array +{ + /** + * Options getter + * + * @return array + */ + public function toOptionArray() + { + return array( + array('value' => 'sha1', 'label'=>Mage::helper('ops')->__('SHA-1')), + array('value' => 'sha256', 'label'=>Mage::helper('ops')->__('SHA-256')), + array('value' => 'sha512', 'label'=>Mage::helper('ops')->__('SHA-512')) + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/System/Config/Backend/Intersolve/Brands.php b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/Intersolve/Brands.php new file mode 100644 index 0000000..6d3ed3b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/Intersolve/Brands.php @@ -0,0 +1,60 @@ + + */ + +class Netresearch_Ops_Model_System_Config_Backend_Intersolve_Brands + extends Mage_Adminhtml_Model_System_Config_Backend_Serialized_Array +{ + protected $_eventPrefix = 'ops_config_backend_intersolve_brands'; + + /** + * additional validation for unique brands + * + * @override + * @throws Mage_Core_Exception if the brands are not unique -> validation failed + * @return Netresearch_Ops_Model_System_Config_Backend_Intersolve_Brands + */ + public function save() + { + $brands = $this->getValue(); + if (is_array($brands) && count($brands) > 1) { + $alreadyProcessedBrands = array(); + foreach ($brands as $brand) { + if (is_array($brand) && array_key_exists('brand', $brand)) { + if (in_array($brand['brand'], $alreadyProcessedBrands)) { + Mage::throwException("Brands must be unique"); + } + $alreadyProcessedBrands[] = $brand['brand']; + } + } + } + return parent::save(); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/System/Config/Backend/PaymentLogo.php b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/PaymentLogo.php new file mode 100644 index 0000000..b5ce370 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/System/Config/Backend/PaymentLogo.php @@ -0,0 +1,47 @@ + + */ +class Netresearch_Ops_Model_System_Config_Backend_PaymentLogo +{ + + /** + * Options getter + * + * @return array + */ + public function toOptionArray() + { + return array( + array('value' => 'left', 'label' => Mage::helper('ops')->__('Left')), + array('value' => 'right', 'label' => Mage::helper('ops')->__('Right')), + array('value' => 'hidden', 'label' => Mage::helper('ops')->__('Non Visible')), + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Model/System/Config/Mode.php b/app/code/community/Netresearch/OPS/Model/System/Config/Mode.php new file mode 100644 index 0000000..ae3cbad --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/System/Config/Mode.php @@ -0,0 +1,40 @@ +getValue() != Netresearch_OPS_Model_Source_Mode::CUSTOM && $this->isValueChanged()) { + $xmlConfig = Mage::getConfig()->loadModulesConfiguration('config.xml'); + foreach ($this->getUrlPaths() as $path) { + $default = $xmlConfig->getNode('default/'.$path); + $newValue = preg_replace('/\/ncol\/\w+/', '/ncol/'.$this->getValue(), $default); + Mage::getConfig()->saveConfig($path, $newValue, $this->getScope(), $this->getScopeId()); + + } + } + + return parent::_afterSave(); + } + + protected function getUrlPaths() + { + return array( + Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH.'ops_gateway', + Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH.'ops_alias_gateway', + Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH.'frontend_gateway', + Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH.'directlink_gateway', + Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH.'directlink_gateway_order', + Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH.'directlink_maintenance_api' + ); + } + +} diff --git a/app/code/community/Netresearch/OPS/Model/System/Config/Template.php b/app/code/community/Netresearch/OPS/Model/System/Config/Template.php new file mode 100644 index 0000000..72613be --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/System/Config/Template.php @@ -0,0 +1,63 @@ +getConfig()->getPayPageTemplate(); + $paypageInfo = Mage::helper('ops')->__( + 'With this setting the customer will be redirected to the Ingenico ePayments paypage with the look and feel of your shop. ' . + '
The template used can be seen here:
' + ); + $paypageInfo .= "" . $paypageUrl . ""; + + $result = "

"; + $result .= " + "; + + return $result; + } + +} diff --git a/app/code/community/Netresearch/OPS/Model/Validator/CompositeInterface.php b/app/code/community/Netresearch/OPS/Model/Validator/CompositeInterface.php new file mode 100644 index 0000000..46b0672 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Validator/CompositeInterface.php @@ -0,0 +1,12 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + interface Netresearch_OPS_Model_Validator_CompositeInterface +{ + public function addValidator(Zend_Validate_Interface $validator); + + } \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Validator/Kwixo/Shipping/Setting.php b/app/code/community/Netresearch/OPS/Model/Validator/Kwixo/Shipping/Setting.php new file mode 100644 index 0000000..02e3ba7 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Validator/Kwixo/Shipping/Setting.php @@ -0,0 +1,95 @@ + + * @category Netresearch + * @package ${MODULENAME} + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Model_Validator_Kwixo_Shipping_Setting +{ + + protected $messages = array(); + + public function isValid(array $data) + { + $result = false; + $methodCodes = array_keys(Mage::getSingleton('shipping/config')->getAllCarriers()); + if (0 < count($data)) { + $result = true; + foreach ($data as $code => $row) { + if (!in_array($code, $methodCodes)) continue; + $result = $this->validateRow($code, $row) && $result; + } + } + return $result; + } + + public function getMessages() + { + return $this->messages; + } + + protected function validateRow($code, $row) + { + $shippingTypeResult = $this->validateShippingType($code, $row); + $shippingSpeedResult = $this->validateShippingSpeed($code, $row); + $shippingDetailsResult = $this->validateShippingDetails($code, $row); + return $shippingTypeResult && $shippingSpeedResult && $shippingDetailsResult; + } + + private function validateShippingType($code, $row) + { + $validValues = Mage::getModel('ops/source_kwixo_shipMethodType') + ->getValidValues(); + + if (array_key_exists('kwixo_shipping_type', $row) + && Zend_Validate::is( + $row['kwixo_shipping_type'], 'Digits' + ) + && 0 < $row['kwixo_shipping_type'] + && in_array((int)$row['kwixo_shipping_type'], $validValues) + + ) { + return true; + } + $this->messages[$code]['kwixo_shipping_type_error'] + = 'invalid shipping type provided'; + + return false; + } + + protected function validateShippingSpeed($code, $row) + { + if (array_key_exists('kwixo_shipping_speed', $row) + && Zend_Validate::is( + $row['kwixo_shipping_speed'], 'Digits' + ) + && 0 < $row['kwixo_shipping_speed'] + ) { + return true; + } + $this->messages[$code]['kwixo_shipping_speed_error'] + = 'invalid shipping speed provided'; + + return false; + } + + protected function validateShippingDetails($code, $row) + { + if (array_key_exists('kwixo_shipping_details', $row) + && Zend_Validate::is( + $row['kwixo_shipping_details'], 'StringLength', + array('min' => 0, 'max' => 50) + ) + ) { + return true; + } + $this->messages[$code]['kwixo_shipping_details_error'] + = 'invalid shipping details provided'; + + return false; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Validator/Parameter/Factory.php b/app/code/community/Netresearch/OPS/Model/Validator/Parameter/Factory.php new file mode 100644 index 0000000..9c2cf5c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Validator/Parameter/Factory.php @@ -0,0 +1,63 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Model_Validator_Parameter_Factory extends Mage_Core_Model_Abstract +{ + + const TYPE_REQUEST_PARAMS_VALIDATION = 'request_validation'; + + /** + * @var Netresearch_OPS_Model_Validator_Parameter_Validator + */ + protected $validator = null; + + /** + * @var Netresearch_OPS_Model_Config + */ + protected $config = null; + + /** + * sets the necessary dependencies for this class + */ + public function _construct() + { + parent::_construct(); + $this->validator = Mage::getModel('ops/validator_parameter_validator'); + $this->config = Mage::getModel('ops/config'); + } + + /** + * creates validator for given type + * + * @param $type - the requested type + * + * @return Netresearch_OPS_Model_Validator_Parameter_Validator + */ + public function getValidatorFor($type) + { + if ($type == self::TYPE_REQUEST_PARAMS_VALIDATION) { + $this->createRequestParamsValidator(); + } + + return $this->validator; + } + + /** + * configures the validator for validation of the request parameter + * + * @return $this + */ + protected function createRequestParamsValidator() + { + $validator = Mage::getModel('ops/validator_parameter_length'); + $validator->setFieldLengths($this->config->getParameterLengths()); + $this->validator->addValidator($validator); + + return $this; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Validator/Parameter/Length.php b/app/code/community/Netresearch/OPS/Model/Validator/Parameter/Length.php new file mode 100644 index 0000000..0c78b51 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Validator/Parameter/Length.php @@ -0,0 +1,96 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Model_Validator_Parameter_Length implements Zend_Validate_Interface +{ + + protected $messages = array(); + + protected $fieldLengths = array(); + + /** + * @param array $fieldLengths - the fieldLengths which are assumed as valid + */ + public function setFieldLengths(array $fieldLengths) + { + $this->fieldLengths = $fieldLengths; + } + + /** + * gets the current configuration of the field lengths + * + * @return array + */ + public function getFieldLengths() + { + return $this->fieldLengths; + } + + /** + * checks if the given data matching the given string lengths + * + * @param mixed $dataToValidate + * + * @return bool + */ + public function isValid($dataToValidate) + { + $validationResult = true; + if (is_array($dataToValidate) && 0 < count($dataToValidate)) { + foreach ($dataToValidate as $key => $value) { + $maxLength = $this->getFieldLengthFor($key); + if ($value === null) { + $value = ''; + } + if (0 < $maxLength) { + if ( + false == Zend_Validate::is( + utf8_encode($value), + 'StringLength', + array('max' => $maxLength, 'encoding' => 'utf-8') + ) + ) { + $this->messages[$key] = Mage::helper('ops/data')->__('Value exceeds %d characters', $maxLength); + $validationResult = false; + } + } + } + } + + return $validationResult; + } + + /** + * gets the messages + * + * @return array + */ + public function getMessages() + { + return $this->messages; + } + + /** + * gets the valid string length for a given value + * + * @param $paramName + * + * @return int -1 if not found, the corresponding value otherwise + */ + protected function getFieldLengthFor($paramName) + { + $value = -1; + if (array_key_exists($paramName, $this->fieldLengths)) { + $value = $this->fieldLengths[$paramName]; + } + + return $value; + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Model/Validator/Parameter/Validator.php b/app/code/community/Netresearch/OPS/Model/Validator/Parameter/Validator.php new file mode 100644 index 0000000..f8a8959 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Model/Validator/Parameter/Validator.php @@ -0,0 +1,56 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Model_Validator_Parameter_Validator + implements Netresearch_OPS_Model_Validator_CompositeInterface, Zend_Validate_Interface +{ + + protected $validators = array(); + + protected $messages = array(); + + /** + * adds a validator + * + * @param Zend_Validate_Interface $validator + */ + public function addValidator(Zend_Validate_Interface $validator) + { + $this->validators[] = $validator; + } + + /** + * @param $dataToValidate + * + * @return bool - indicates whether the data are valid or not + */ + public function isValid($dataToValidate) + { + $isValid = true; + foreach ($this->validators as $validator) { + /** @var Zend_Validate_Interface $validator */ + $isValid = $isValid && $validator->isValid($dataToValidate); + if (false === $validator->isValid($dataToValidate)) { + $this->messages = array_merge($this->messages, $validator->getMessages()); + } + } + + return $isValid; + } + + public function getMessages() + { + return $this->messages; + } + + public function getValidators() + { + return $this->validators; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Customer/Renderer/StateTest.php b/app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Customer/Renderer/StateTest.php new file mode 100644 index 0000000..3ec5ed9 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Customer/Renderer/StateTest.php @@ -0,0 +1,23 @@ +setIndex('state'); + $row = new Varien_Object(); + $row->setData(array('state' => Netresearch_OPS_Model_Alias_State::ACTIVE)); + $block = Mage::app()->getLayout()->getBlockSingleton( + 'ops/adminhtml_customer_renderer_state' + ); + $block->setColumn($column); + $this->assertEquals( + Mage::helper('ops/data')->__( + Netresearch_OPS_Model_Alias_State::ACTIVE + ), $block->render($row) + ); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Kwixo/Shipping/EditTest.php b/app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Kwixo/Shipping/EditTest.php new file mode 100644 index 0000000..dc448fb --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Kwixo/Shipping/EditTest.php @@ -0,0 +1,81 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Block_Adminhtml_Kwixo_Shipping_EditTest + extends EcomDev_PHPUnit_Test_Case +{ + + public function setUp() + { + $sessionMock = $this->getModelMock( + 'core/session', array('init', 'save', 'getSessionIdForHost') + ); + + $this->replaceByMock('model', 'core/session', $sessionMock); + } + + public function testGetFormActionUrl() + { + + $widgetContainer = new Mage_Adminhtml_Block_Widget_Form_Container(); + $block + = new Netresearch_OPS_Block_Adminhtml_Kwixo_Shipping_Edit(); + $this->assertEquals( + $widgetContainer->getUrl('adminhtml/kwixoshipping/save', array()), + $block->getFormActionUrl() + ); + } + + public function testGetShippingMethods() + { + $shippingConfigMock = $this->getModelMock( + 'shipping/config', array('getAllCarriers') + ); + $shippingConfigMock->expects($this->any()) + ->method('getAllCarriers') + ->will( + $this->returnValue( + array('dhl' => 'dhl', 'hermes' => 'hermes', 'ips' => 'ips') + ) + ); + $this->replaceByMock('model', 'shipping/config', $shippingConfigMock); + + $block = new Netresearch_OPS_Block_Adminhtml_Kwixo_Shipping_Edit(); + $result = $block->getShippingMethods(); + $this->assertEquals(3, count($result)); + $this->assertEquals($result[0]['code'], 'dhl'); + $this->assertEquals($result[1]['code'], 'hermes'); + $this->assertEquals($result[1]['label'], 'hermes'); + $this->assertEquals($result[2]['code'], 'ips'); + $this->assertEquals($result[2]['label'], 'ips'); + + $block->setData( + 'postData', array('dhl' => array('error' => 'sample error')) + ); + $result = $block->getShippingMethods(); + $this->assertEquals($result[0]['values']['error'], 'sample error'); + } + + public function testGetKwixoShippingTypes() + { + $block = new Netresearch_OPS_Block_Adminhtml_Kwixo_Shipping_Edit(); + $expectedResult = Mage::getModel('ops/source_kwixo_shipMethodType') + ->toOptionArray(); + $this->assertEquals($expectedResult, $block->getKwixoShippingTypes()); + } + + + public function testGetFormKey() + { + $block = new Netresearch_OPS_Block_Adminhtml_Kwixo_Shipping_Edit(); + $expectedResult = Mage::getSingleton('core/session')->getFormKey(); + $this->assertEquals($expectedResult, $block->getFormKey()); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Sales/Order/Creditmemo/Totals/CheckboxTest.php b/app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Sales/Order/Creditmemo/Totals/CheckboxTest.php new file mode 100644 index 0000000..a359734 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Adminhtml/Sales/Order/Creditmemo/Totals/CheckboxTest.php @@ -0,0 +1,11 @@ +getLayout()->getBlockSingleton('ops/adminhtml_sales_order_creditmemo_totals_checkbox'); + $this->assertEquals('ops/sales/order/creditmemo/totals/checkbox.phtml', $block->getTemplate()); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/Alias/ListTest.php b/app/code/community/Netresearch/OPS/Test/Block/Alias/ListTest.php new file mode 100644 index 0000000..3828926 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Alias/ListTest.php @@ -0,0 +1,77 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Test_Block_Alias_ListTest + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Block_Alias_ListTest + extends EcomDev_PHPUnit_Test_Case +{ + private $block; + + public function setUp() + { + parent::setup(); + $this->block = Mage::app()->getLayout()->getBlockSingleton('ops/alias_list'); + } + + public function testGetMethodName() + { + $this->assertEquals('', $this->block->getMethodName('something_stupid')); + + Mage::app()->getStore()->setConfig('payment/ops_cc/title', 'OPS Credit Card'); + $this->assertEquals( + 'OPS Credit Card', + $this->block->getMethodName('ops_cc') + ); + } + + public function testGetAliasDeleteUrl() + { +// array('_secure' => Mage::app()->getFrontController()->getRequest()->isSecure()); + $this->mockSessions(); + $aliasId = 1; + // previous behaviour until OGNB-229: Mage::getUrl('ops/customer/deleteAlias/id/' . $aliasId) + $this->assertEquals(Mage::getUrl('ops/customer/deleteAlias/id/' . $aliasId), $this->block->getAliasDeleteUrl($aliasId)); + + // new since OGNB-229: passed aliasId as array param + $this->assertEquals(Mage::getUrl('ops/customer/deleteAlias/', array('id' => $aliasId)), $this->block->getAliasDeleteUrl($aliasId)); + + $_SERVER['HTTPS'] = 'on'; + $this->assertEquals( + Mage::getUrl( + 'ops/customer/deleteAlias/', + array( + '_secure' => true, + 'id' => $aliasId) + ), + $this->block->getAliasDeleteUrl($aliasId) + ); + } + protected function mockSessions() + { + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor() // This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'core/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() // This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + } + +} + diff --git a/app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest.php b/app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest.php new file mode 100644 index 0000000..526aa25 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest.php @@ -0,0 +1,75 @@ + + * @copyright 2016 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ + +/** + * Netresearch_OPS_Test_Block_Checkout_DeviceFingerprintingTest + * + * @category Netresearch + * @package Netresearch_OPS + * @author Christoph Aßmann + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ +class Netresearch_OPS_Test_Block_Checkout_DeviceFingerprintingTest + extends EcomDev_PHPUnit_Test_Case +{ + protected function setUp() + { + $sessionMock = $this->getModelMock('core/session', array('init')); + $this->replaceByMock('singleton', 'core/session', $sessionMock); + } + + /** + * @test + * @loadFixture + */ + public function fingerPrintingEnabled() + { + /** @var Netresearch_OPS_Block_Checkout_DeviceFingerprinting $block */ + $block = Mage::app()->getLayout()->createBlock('ops/checkout_deviceFingerprinting'); + + $html = $block->toHtml(); + $this->assertNotEmpty($html); + + $url = $block->getConsentUrl(); + $this->assertContains('ops/device/', $url); + } + + /** + * @test + * @loadFixture + */ + public function fingerPrintingDisabled() + { + /** @var Netresearch_OPS_Block_Checkout_DeviceFingerprinting $block */ + $block = Mage::app()->getLayout()->createBlock('ops/checkout_deviceFingerprinting'); + $html = $block->toHtml(); + $this->assertEmpty($html); + + $url = $block->getConsentUrl(); + $this->assertContains('ops/device/', $url); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest/fixtures/fingerPrintingDisabled.yaml b/app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest/fixtures/fingerPrintingDisabled.yaml new file mode 100644 index 0000000..0a0ea75 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest/fixtures/fingerPrintingDisabled.yaml @@ -0,0 +1,3 @@ +config: + default/dev/template/allow_symlink: 1 + default/payment_services/ops/device_fingerprinting: 0 diff --git a/app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest/fixtures/fingerPrintingEnabled.yaml b/app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest/fixtures/fingerPrintingEnabled.yaml new file mode 100644 index 0000000..abe96fc --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Checkout/DeviceFingerprintingTest/fixtures/fingerPrintingEnabled.yaml @@ -0,0 +1,3 @@ +config: + default/dev/template/allow_symlink: 1 + default/payment_services/ops/device_fingerprinting: 1 diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/CcTest.php b/app/code/community/Netresearch/OPS/Test/Block/Form/CcTest.php new file mode 100644 index 0000000..cfa06cf --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/CcTest.php @@ -0,0 +1,459 @@ +_block = Mage::app()->getLayout()->getBlockSingleton('ops/form_cc'); + $this->_block->setMethod(Mage::getModel('ops/payment_cc')); + $this->mockSessions(); + } + + public function testGetAliasBrands() + { + + $aliasBrands = array( + 'American Express', + 'Diners Club', + 'MaestroUK', + 'MasterCard', + 'VISA', + ); + + $ccAliasInterfaceEnabledTypesMock = $this->getModelMock( + 'ops/source_cc_aliasInterfaceEnabledTypes', array('getAliasInterfaceCompatibleTypes') + ); + $ccAliasInterfaceEnabledTypesMock->expects($this->any()) + ->method('getAliasInterfaceCompatibleTypes') + ->will($this->returnValue($aliasBrands)); + $this->replaceByMock('model', 'ops/source_cc_aliasInterfaceEnabledTypes', $ccAliasInterfaceEnabledTypesMock); + /** @var Netresearch_OPS_Block_Form_Cc $ccForm */ + $ccForm = Mage::app()->getLayout()->getBlockSingleton('ops/form_cc'); + $ccAliases = $ccForm->getAliasBrands(); + $this->assertEquals($aliasBrands, $ccAliases); + } + + + public function testTemplate() + { + //Frontend case + $modelMock = $this->getModelMock('ops/config', array('isFrontendEnvironment')); + $modelMock->expects($this->any()) + ->method('isFrontendEnvironment') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/config', $modelMock); + $ccForm = new Netresearch_OPS_Block_Form_Cc(); + $this->assertEquals(Netresearch_OPS_Block_Form_Cc::FRONTEND_TEMPLATE, $ccForm->getTemplate()); + + //Backend case + $modelMock = $this->getModelMock('ops/config', array('isFrontendEnvironment')); + $modelMock->expects($this->any()) + ->method('isFrontendEnvironment') + ->will($this->returnValue(false)); + $this->replaceByMock('model', 'ops/config', $modelMock); + $ccForm = new Netresearch_OPS_Block_Form_Cc(); + $this->assertEquals(Netresearch_OPS_Block_Form_Cc::FRONTEND_TEMPLATE, $ccForm->getTemplate()); + } + + public function testGetCcBrands() + { + $blockMock = $this->getBlockMock('ops/form_cc', array('getMethod')); + $method = new Varien_Object(); + $method->setCode('ops_cc'); + $blockMock->expects($this->any()) + ->method('getMethod') + ->will($this->returnValue($method)); + $this->replaceByMock('block', 'ops/form_cc', $blockMock); + + + $this->assertInternalType('array', $blockMock->getCcBrands()); + } + + public function testIsAliasPMEnabled() + { + $model = Mage::getModel('ops/config'); + $this->assertEquals( + $model->isAliasManagerEnabled('ops_cc'), $this->_block->isAliasPMEnabled() + ); + } + + public function testGetStoredAliasDataForCustomer() + { + + $reflectionClass = new ReflectionClass(get_class($this->_block)); + $method = $reflectionClass->getMethod("getStoredAliasDataForCustomer"); + $method->setAccessible(true); + $this->assertNull($method->invoke($this->_block, 0, 'bla')); + + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasForCustomer') + ); + $blockMock->expects($this->once()) + ->method('getStoredAliasForCustomer') + ->will($this->returnValue(array())); + $reflectionClass = new ReflectionClass(get_class($blockMock)); + $method = $reflectionClass->getMethod("getStoredAliasDataForCustomer"); + $method->setAccessible(true); + $this->assertNull($method->invoke($blockMock, 0, 'bla')); + + + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasForCustomer') + ); + $blockMock->expects($this->once()) + ->method('getStoredAliasForCustomer') + ->will($this->returnValue(array(0 => new Varien_Object(array('bla' => 'foo'))))); + $reflectionClass = new ReflectionClass(get_class($blockMock)); + $method = $reflectionClass->getMethod("getStoredAliasDataForCustomer"); + $method->setAccessible(true); + $this->assertEquals( + 'foo', $method->invoke($blockMock, 0, 'bla') + ); + + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasForCustomer') + ); + $blockMock->expects($this->once()) + ->method('getStoredAliasForCustomer') + ->will($this->returnValue(array(0 => new Varien_Object(array('bla' => 'foo'))))); + $reflectionClass = new ReflectionClass(get_class($blockMock)); + $method = $reflectionClass->getMethod("getStoredAliasDataForCustomer"); + $method->setAccessible(true); + $this->assertNull($method->invoke($blockMock, 0, 'foo')); + + } + + public function testGetAliasCardNumber() + { + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasForCustomer') + ); + $blockMock->expects($this->any()) + ->method('getStoredAliasForCustomer') + ->will( + $this->returnValue( + array( + 0 => new Varien_Object( + array( + 'pseudo_account_or_cc_no' => 'xxxxxxxxxxxx1111', + 'brand' => 'visa' + ) + ) + ) + ) + ); + $this->assertEquals('XXXX XXXX XXXX 1111', $blockMock->getAliasCardNumber(0)); + } + + /** + * @loadFixture aliases.yaml + */ + public function testGetStoredAliasForCustomer() + { + + $reflectionClass = new ReflectionClass(get_class($this->_block)); + $method = $reflectionClass->getMethod("getStoredAliasForCustomer"); + $method->setAccessible(true); + $this->assertEquals(0, count($method->invoke($this->_block))); + + + $configMock = $this->getModelMock('ops/config', array('isAliasManagerEnabled')); + $configMock->expects($this->any()) + ->method('isAliasManagerEnabled') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/config', $configMock); + + $aliases = Mage::getModel('ops/alias') + ->getCollection() + ->addFieldToFilter('customer_id', 1) + ->addFieldToFilter('state', Netresearch_OPS_Model_Alias_State::ACTIVE) + ->setOrder('created_at', Varien_Data_Collection::SORT_ORDER_DESC); + $alias = $aliases->getFirstItem(); + $aliasHelperMock = $this->getHelperMock('ops/alias', array('getAliasesForAddresses')); + $aliasHelperMock->expects($this->once()) + ->method('getAliasesForAddresses') + ->will($this->returnValue($aliases)); + $this->replaceByMock('helper', 'ops/alias', $aliasHelperMock); + + $customerMock = $this->getHelperMock('customer/data', array('isLoggedIn')); + $customerMock->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->replaceByMock('helper', 'customer/data', $customerMock); + + $fakeCustomer = new Varien_Object(); + $fakeCustomer->setId(1); + + $fakeQuote = new Varien_Object(); + $fakeQuote->setCustomer($fakeCustomer); + + + $blockMock = $this->getBlockMock('ops/form_cc', array('getQuote', 'getMethodCode')); + $blockMock->expects($this->once()) + ->method('getQuote') + ->will($this->returnValue($fakeQuote)); + $blockMock->expects($this->any()) + ->method('getMethodCode') + ->will($this->returnValue('ops_cc')); + $this->replaceByMock('block', 'ops/form_cc', $blockMock); + + $reflectionClass = new ReflectionClass(get_class($blockMock)); + $method = $reflectionClass->getMethod("getStoredAliasForCustomer"); + $method->setAccessible(true); + $aliases = $method->invoke($blockMock); + $this->assertEquals($alias->getData(), $aliases[1]->getData()); + + } + + + public function testGetExpirationDatePart() + { + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasForCustomer') + ); + $blockMock->expects($this->any()) + ->method('getStoredAliasForCustomer') + ->will($this->returnValue(array(0 => new Varien_Object(array('expiration_date' => '0416'))))); + $this->assertEquals('04', $blockMock->getExpirationDatePart(0, 'month')); + $this->assertEquals('16', $blockMock->getExpirationDatePart(0, 'year')); + } + + public function testGetCardHolderName() + { + $configMock = $this->getModelMock('ops/config', array('isAliasManagerEnabled')); + $configMock->expects($this->any()) + ->method('isAliasManagerEnabled') + ->will($this->returnValue(false)); + $this->replaceByMock('model', 'ops/config', $configMock); + + $this->assertNull($block = $this->_block->getCardHolderName(2)); + + $configMock = $this->getModelMock('ops/config', array('isAliasManagerEnabled')); + $configMock->expects($this->any()) + ->method('isAliasManagerEnabled') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/config', $configMock); + + + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasDataForCustomer', 'getStoredAlias') + ); + + $blockMock->expects($this->any()) + ->method('getStoredAliasDataForCustomer') + ->will($this->returnValue('Hubertus von Fürstenberg')); + + $blockMock->expects($this->any()) + ->method('getStoredAlias') + ->will($this->returnValue('4711')); + $this->assertEquals('Hubertus von Fürstenberg', $blockMock->getCardHolderName(2)); + + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasDataForCustomer', 'getStoredAlias', 'getMethodCode') + ); + $blockMock->expects($this->any()) + ->method('getMethodCode') + ->will($this->returnValue('ops_cc')); + $blockMock->expects($this->once()) + ->method('getStoredAliasDataForCustomer') + ->will($this->returnValue(null)); + + $blockMock->expects($this->any()) + ->method('getStoredAlias') + ->will($this->returnValue('4711')); + + $customerHelperMock = $this->getHelperMock('customer/data', array('isLoggedIn', 'getCustomerName')); + $customerHelperMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $customerHelperMock->expects($this->any()) + ->method('getCustomerName') + ->will($this->returnValue('Hubertus zu Fürstenberg')); + $this->replaceByMock('helper', 'customer/data', $customerHelperMock); + + + $this->assertEquals('Hubertus zu Fürstenberg', $blockMock->getCardHolderName(2)); + + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasDataForCustomer', 'getStoredAlias', 'getMethodCode') + ); + $blockMock->expects($this->any()) + ->method('getMethodCode') + ->will($this->returnValue('ops_cc')); + $blockMock->expects($this->once()) + ->method('getStoredAliasDataForCustomer') + ->will($this->returnValue('')); + + $blockMock->expects($this->any()) + ->method('getStoredAlias') + ->will($this->returnValue('4711')); + + $this->assertEquals('Hubertus zu Fürstenberg', $blockMock->getCardHolderName(2)); + + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasDataForCustomer', 'getStoredAlias') + ); + $blockMock->expects($this->once()) + ->method('getStoredAliasDataForCustomer') + ->will($this->returnValue(null)); + + $blockMock->expects($this->any()) + ->method('getStoredAlias') + ->will($this->returnValue('4711')); + + $customerHelperMock = $this->getHelperMock('customer/data', array('isLoggedIn')); + $customerHelperMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'customer/data', $customerHelperMock); + + $this->assertNull($blockMock->getCardHolderName(2)); + + + } + + public function testGetStoredAliasBrandWithInlineBrand() + { + /** @var Netresearch_OPS_Block_Form_Cc $blockMock */ + + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasForCustomer', 'getMethodCode') + ); + $blockMock->expects($this->any()) + ->method('getStoredAliasForCustomer') + ->will( + $this->returnValue( + array(0 => new Varien_Object( + array( + 'pseudo_account_or_cc_no' => 'xxxxxxxxxxxx1111', + 'brand' => 'VISA' + ) + )) + ) + ); + $blockMock->expects($this->any()) + ->method('getMethodCode') + ->will($this->returnValue('ops_cc')); + + $modelMock = $this->getModelMock( + 'ops/config', array('getInlinePaymentCcTypes') + ); + $modelMock->expects($this->any()) + ->method('getInlinePaymentCcTypes') + ->will( + $this->returnValue( + array( + 'VISA' + ) + ) + ); + + $this->assertEquals('VISA', $blockMock->getStoredAliasBrand(0)); + } + + + public function testGetStoredAliasBrand() + { + /** @var Netresearch_OPS_Block_Form_Cc $blockMock */ + + $blockMock = $this->getBlockMock( + 'ops/form_cc', array('getStoredAliasForCustomer', 'getMethodCode') + ); + $blockMock->expects($this->any()) + ->method('getStoredAliasForCustomer') + ->will( + $this->returnValue( + array(new Varien_Object( + array( + 'pseudo_account_or_cc_no' => 'xxxxxxxxxxxx1111', + 'brand' => 'VISA' + ) + )) + ) + ); + $blockMock->expects($this->any()) + ->method('getMethodCode') + ->will($this->returnValue('ops_cc')); + + $modelMock = $this->getModelMock( + 'ops/config', array('getInlinePaymentCcTypes') + ); + $modelMock->expects($this->any()) + ->method('getInlinePaymentCcTypes') + ->will( + $this->returnValue( + array( + 'FOO' + ) + ) + ); + + $this->replaceByMock('model', 'ops/config', $modelMock); + + $this->assertEquals('VISA', $blockMock->getStoredAliasBrand(0)); + } + + public function testIsAliasInfoBlockEnabled() + { + $configMock = $this->getModelMock('ops/config', array('isAliasPMEnabled', 'isAliasInfoBlockEnabled')); + $configMock->expects($this->any()) + ->method('isAliasPMEnabled') + ->will($this->returnValue(false)); + $configMock->expects($this->any()) + ->method('isAliasInfoBlockEnabled') + ->will($this->returnValue(false)); + $this->replaceByMock('model', 'ops/config', $configMock); + + $this->assertFalse(Mage::app()->getLayout()->getBlockSingleton('ops/form_cc')->isAliasInfoBlockEnabled()); + + $configMock = $this->getModelMock('ops/config', array('isAliasPMEnabled', 'isAliasInfoBlockEnabled')); + $configMock->expects($this->any()) + ->method('isAliasPMEnabled') + ->will($this->returnValue(false)); + $configMock->expects($this->any()) + ->method('isAliasInfoBlockEnabled') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/config', $configMock); + + $this->assertFalse(Mage::app()->getLayout()->getBlockSingleton('ops/form_cc')->isAliasInfoBlockEnabled()); + + $configMock = $this->getModelMock('ops/config', array('isAliasPMEnabled', 'isAliasInfoBlockEnabled')); + $configMock->expects($this->any()) + ->method('isAliasPMEnabled') + ->will($this->returnValue(true)); + $configMock->expects($this->any()) + ->method('isAliasInfoBlockEnabled') + ->will($this->returnValue(false)); + $this->replaceByMock('model', 'ops/config', $configMock); + + $this->assertFalse(Mage::app()->getLayout()->getBlockSingleton('ops/form_cc')->isAliasInfoBlockEnabled()); + + $configMock = $this->getModelMock('ops/config', array('isAliasPMEnabled', 'isAliasInfoBlockEnabled')); + $configMock->expects($this->any()) + ->method('isAliasPMEnabled') + ->will($this->returnValue(true)); + $configMock->expects($this->any()) + ->method('isAliasInfoBlockEnabled') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/config', $configMock); + + $this->assertFalse(Mage::app()->getLayout()->getBlockSingleton('ops/form_cc')->isAliasInfoBlockEnabled()); + } + + protected function mockSessions() + { + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor()// This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'core/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor()// This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/CcTest/fixtures/aliases.yaml b/app/code/community/Netresearch/OPS/Test/Block/Form/CcTest/fixtures/aliases.yaml new file mode 100644 index 0000000..829dae0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/CcTest/fixtures/aliases.yaml @@ -0,0 +1,92 @@ +tables: + ops/alias: + - id: 1 + customer_id: 1 + alias: 4711 + brand: 'VISA' + billing_address_hash: '0815' + shipping_address_hash: '2342' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0116' + payment_method: 'ops_cd' + state: 'active' + store_id: 0 + created_at: '2000-01-01 00:00:01' + + - id: 2 + customer_id: 1 + alias: 4712 + brand: 'VISA' + billing_address_hash: '0816' + shipping_address_hash: '2342' + pseudo_account_or_cc_no: 'xxxx1112' + expiration_date: '0116' + payment_method: 'ops_cc' + state: 'active' + store_id: 0 + created_at: '1970-01-01 00:00:01' + + - id: 3 + customer_id: 1 + alias: 4713 + brand: 'VISA' + billing_address_hash: '0817' + shipping_address_hash: '0817' + pseudo_account_or_cc_no: 'xxxx1113' + expiration_date: '0116' + payment_method: 'ops_cc' + state: 'pending' + store_id: 0 + created_at: '1970-01-01 00:00:02' + + - id: 4 + customer_id: 2 + alias: 4711 + brand: 'VISA' + billing_address_hash: '1508' + shipping_address_hash: '4223' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0117' + payment_method: 'ops_cc' + state: 'active' + store_id: 0 + created_at: '1970-01-01 00:00:02' + + - id: 5 + customer_id: 1 + alias: 4711 + brand: 'VISA' + billing_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + shipping_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0117' + payment_method: 'ops_cc' + state: 'active' + store_id: 0 + created_at: '1970-01-01 00:00:02' + + - id: 6 + customer_id: 1 + alias: 4714 + brand: 'VISA' + billing_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + shipping_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0116' + payment_method: 'ops_cd' + state: 'pending' + store_id: 0 + created_at: '2000-11-01 00:00:01' + + - id: 7 + customer_id: 23 + alias: 4714 + brand: 'VISA' + billing_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + shipping_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0116' + payment_method: 'ops_cd' + state: 'active' + store_id: 0 + created_at: '2000-11-01 00:00:01' \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/DirectDebitTest.php b/app/code/community/Netresearch/OPS/Test/Block/Form/DirectDebitTest.php new file mode 100644 index 0000000..5a6a3e5 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/DirectDebitTest.php @@ -0,0 +1,51 @@ +getModelMock( + 'ops/config', array('isFrontendEnvironment') + ); + $modelMock->expects($this->any()) + ->method('isFrontendEnvironment') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/config', $modelMock); + $ccForm = new Netresearch_OPS_Block_Form_DirectDebit(); + $this->assertEquals( + Netresearch_OPS_Block_Form_DirectDebit::TEMPLATE, + $ccForm->getTemplate() + ); + + //Backend case + $modelMock = $this->getModelMock( + 'ops/config', array('isFrontendEnvironment') + ); + $modelMock->expects($this->any()) + ->method('isFrontendEnvironment') + ->will($this->returnValue(false)); + $this->replaceByMock('model', 'ops/config', $modelMock); + $ccForm = new Netresearch_OPS_Block_Form_DirectDebit(); + $this->assertEquals( + Netresearch_OPS_Block_Form_DirectDebit::TEMPLATE, + $ccForm->getTemplate() + ); + } + + public function testDirectDebitCountryIds() + { + $fakeConfig = new Varien_Object(); + $fakeConfig->setDirectDebitCountryIds("AT, DE, NL"); + $blockMock = $this->getBlockMock( + 'ops/form_directDebit', array('getconfig') + ); + $blockMock->expects($this->once()) + ->method('getConfig') + ->will($this->returnValue($fakeConfig)); + $this->assertEquals( + explode(',', 'AT, DE, NL'), $blockMock->getDirectDebitCountryIds() + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/Field/MethodTest.php b/app/code/community/Netresearch/OPS/Test/Block/Form/Field/MethodTest.php new file mode 100644 index 0000000..8414c8a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/Field/MethodTest.php @@ -0,0 +1,42 @@ + + */ +?> +getLayout()->createBlock('ops/system_config_form_field_method'); + $this->assertTrue($block instanceof Netresearch_OPS_Block_System_Config_Form_Field_Method); + + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/FlexTest.php b/app/code/community/Netresearch/OPS/Test/Block/Form/FlexTest.php new file mode 100644 index 0000000..3062049 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/FlexTest.php @@ -0,0 +1,89 @@ + + */ +?> +setCurrentStore(0); + Mage::getConfig()->saveConfig('payment/ops_flex/methods', serialize($this->getMethodArray())); + + /** @var Netresearch_OPS_Block_Form_Flex $block */ + $block = Mage::app()->getLayout()->createBlock('ops/form_flex'); + + $block->setMethod(Mage::getModel('ops/payment_flex')); + + $this->assertTrue(is_array($block->getFlexMethods())); + $this->assertEquals(1, count($block->getFlexMethods())); + } + + private function getMethodArray() + { + return array( + array( + 'title' => 'foo', + 'brand' => 'bar', + 'pm' => 'zzz' + ) + ); + } + + + public function testGetDefaultOptionTitle() + { + Mage::app()->setCurrentStore(0); + Mage::getConfig()->saveConfig('payment/ops_flex/default_title', 'flex'); + Mage::getConfig()->cleanCache(); + + /** @var Netresearch_OPS_Block_Form_Flex $block */ + $block = Mage::app()->getLayout()->createBlock('ops/form_flex'); + + $block->setMethod(Mage::getModel('ops/payment_flex')); + + $this->assertEquals('flex', $block->getDefaultOptionTitle()); + } + + public function testIsDefaultOptionActive() + { + Mage::app()->setCurrentStore(0); + Mage::getConfig()->saveConfig('payment/ops_flex/default', true); + Mage::getConfig()->cleanCache(); + + /** @var Netresearch_OPS_Block_Form_Flex $block */ + $block = Mage::app()->getLayout()->createBlock('ops/form_flex'); + + $block->setMethod(Mage::getModel('ops/payment_flex')); + + $this->assertTrue($block->isDefaultOptionActive()); + + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/Ideal.php b/app/code/community/Netresearch/OPS/Test/Block/Form/Ideal.php new file mode 100644 index 0000000..7593d84 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/Ideal.php @@ -0,0 +1,12 @@ +getIDealIssuers(); + $block = Mage::app()->getLayout()->createBlock('ops/form_Ideal'); + $this->assertEquals($issuers, $block->getIssuers()); + + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/InterSolveTest.php b/app/code/community/Netresearch/OPS/Test/Block/Form/InterSolveTest.php new file mode 100644 index 0000000..bee340d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/InterSolveTest.php @@ -0,0 +1,52 @@ + + */ +?> +saveConfig( + 'payment/ops_interSolve/brands', serialize($this->getBrandArray()) + ); + Mage::getConfig()->cleanCache(); + + $block = Mage::app()->getLayout()->createBlock('ops/form_interSolve'); + $this->assertEquals($this->getBrandArray(), $block->getInterSolveBrands()); + } + + private function getBrandArray() + { + return array( + array('brand' => 'foo', 'title' => 'bar') + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/ApresReceptionTest.php b/app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/ApresReceptionTest.php new file mode 100644 index 0000000..8c77b80 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/ApresReceptionTest.php @@ -0,0 +1,29 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +class Netresearch_OPS_Test_Block_Form_Kwixo_ApresReceptionTest extends EcomDev_PHPUnit_Test_Case +{ + + protected $block = null; + + public function setUp() + { + $this->block = new Netresearch_OPS_Block_Form_Kwixo_ApresReception(); + } + + public function testGetTemplate() + { + $this->assertEquals('ops/form/kwixo/apres_reception.phtml', $this->block->getTemplate()); + } + + public function testGetPmLogo() + { + $this->assertEquals('images/ops/kwixo/apres_reception.jpg', $this->block->getPmLogo()); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/ComptantTest.php b/app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/ComptantTest.php new file mode 100644 index 0000000..59cd62d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/ComptantTest.php @@ -0,0 +1,30 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Block_Form_Kwixo_ComptantTest extends EcomDev_PHPUnit_Test_Case +{ + protected $block = null; + + public function setUp() + { + $this->block = new Netresearch_OPS_Block_Form_Kwixo_Comptant(); + } + + public function testGetTemplate() + { + $this->assertEquals('ops/form/kwixo/comptant.phtml', $this->block->getTemplate()); + } + + public function testGetPmLogo() + { + $this->assertEquals('images/ops/kwixo/comptant.jpg', $this->block->getPmLogo()); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/CreditTest.php b/app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/CreditTest.php new file mode 100644 index 0000000..b4f6cf4 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Form/Kwixo/CreditTest.php @@ -0,0 +1,34 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + + + +class Netresearch_OPS_Test_Block_Form_Kwixo_CreditTest extends EcomDev_PHPUnit_Test_Case +{ + + protected $block = null; + + public function setUp() + { + $this->block = new Netresearch_OPS_Block_Form_Kwixo_Credit(); + } + + public function testGetTemplate() + { + $block = new Netresearch_OPS_Block_Form_Kwixo_Credit(); + $this->assertEquals('ops/form/kwixo/credit.phtml', $block->getTemplate()); + } + + public function testGetPmLogo() + { + $this->assertEquals('images/ops/kwixo/credit.jpg', $this->block->getPmLogo()); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/FormTest.php b/app/code/community/Netresearch/OPS/Test/Block/FormTest.php new file mode 100644 index 0000000..d89d416 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/FormTest.php @@ -0,0 +1,106 @@ +_block = Mage::app()->getLayout()->getBlockSingleton('ops/form'); + } + + public function testIsUserRegistering() + { + $dataHelperMock = $this->getHelperMock('ops/data', array('checkIfUserIsRegistering')); + $dataHelperMock->expects($this->any()) + ->method('checkIfUserIsRegistering') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/data', $dataHelperMock); + + $block = new Netresearch_OPS_Block_Form(); + $this->assertFalse($block->isUserRegistering()); + } + + public function testIsUserNotRegistering() + { + $dataHelperMock = $this->getHelperMock('ops/data', array('checkIfUserIsNotRegistering')); + $dataHelperMock->expects($this->any()) + ->method('checkIfUserIsNotRegistering') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/data', $dataHelperMock); + + $block = new Netresearch_OPS_Block_Form(); + $this->assertFalse($block->isUserNotRegistering()); + } + + public function testGetPmLogo() + { + $this->assertEquals(null, $this->_block->getPmLogo()); + } + + public function getMethodLabelAfterHtmlSuccess() + { + $method = new Varien_Object(); + $method->setData('code', 'ops_cc'); + + $blockMock = $this->getBlockMock('ops/form', array('getMethod')); + + $blockMock->expects($this->any()) + ->method('getMethod') + ->will($this->returnValue($method)); + $this->replaceByMock('block', 'ops/form', $blockMock); + + $formBlock = Mage::app()->getLayout()->createBlock('ops/form'); + + $result = $formBlock->getMethodLabelAfterHtml(); + + $this->assertContains('cc.jpg', $result); + $this->assertContains(' left', $result); + + $result = $formBlock->getMethodLabelAfterHtml(); + + $this->assertContains('store_one', $result); + } + + public function getMethodLabelAfterHtmlHidden() + { + $method = new Varien_Object(); + $method->setData('code', 'ops_dc'); + + $blockMock = $this->getBlockMock('ops/form', array('getMethod')); + + $blockMock->expects($this->any()) + ->method('getMethod') + ->will($this->returnValue($method)); + $this->replaceByMock('block', 'ops/form', $blockMock); + + $formBlock = Mage::app()->getLayout()->createBlock('ops/form'); + + $result = $formBlock->getMethodLabelAfterHtml(); + + $this->assertEmpty($result); + } + + public function getMethodLabelAfterHtmlFail() + { + $method = new Varien_Object(); + $method->setData('code', 'ops_iDEAL'); + + $blockMock = $this->getBlockMock('ops/form', array('getMethod')); + + $blockMock->expects($this->any()) + ->method('getMethod') + ->will($this->returnValue($method)); + $this->replaceByMock('block', 'ops/form', $blockMock); + + $formBlock = Mage::app()->getLayout()->createBlock('ops/form'); + + $result = $formBlock->getMethodLabelAfterHtml(); + + $this->assertContains('ops_iDEAL.jpg', $result); + $this->assertContains('skin/frontend', $result); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/FormTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Block/FormTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Block/FormTest/fixtures/FormTest.yaml b/app/code/community/Netresearch/OPS/Test/Block/FormTest/fixtures/FormTest.yaml new file mode 100644 index 0000000..c8abbc1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/FormTest/fixtures/FormTest.yaml @@ -0,0 +1,22 @@ +scope: + store: # Initializes store views +# Store 1 + - store_id: 1 + website_id: 1 + group_id: 1 + code: store_one + name: Store One + is_active: 1 +# Store 2 + - store_id: 2 + website_id: 1 + group_id: 1 + code: store_two + name: Store Two + is_active: 1 + +config: + default/payment/ops_dc/position: hidden + default/payment/ops_cc/position: left + default/payment/ops_cc/image: default/cc.jpg + stores/store_one/payment/ops_cc/image: store_one/cc.jpg diff --git a/app/code/community/Netresearch/OPS/Test/Block/FrauddetectionTest.php b/app/code/community/Netresearch/OPS/Test/Block/FrauddetectionTest.php new file mode 100644 index 0000000..7d7345f --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/FrauddetectionTest.php @@ -0,0 +1,65 @@ +store = Mage::app()->getStore(0)->load(0); + $this->store->resetConfig(); + } + + public function testToHtml() + { + $block = Mage::app()->getLayout()->getBlockSingleton('ops/frauddetection'); + $this->assertEquals(null, $block->toHtml()); + + $configMock = $this->getModelMock('ops/config', array('getDeviceFingerPrinting')); + $configMock->expects($this->once()) + ->method('getDeviceFingerPrinting') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/config', $configMock); + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() + ->setMethods(array('getData')) + ->getMock(); + $sessionMock->expects($this->once()) + ->method('getData') + ->with(Netresearch_OPS_Model_Payment_Abstract::FINGERPRINT_CONSENT_SESSION_KEY) + ->will($this->returnValue(true)); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + // for some reason the html is not rendered in the tests + $this->assertNotNull($block->toHtml()); + } + + + public function testGetTrackingCodeAid() + { + $block = Mage::app()->getLayout()->getBlockSingleton('ops/frauddetection'); + $this->assertEquals('10376', $block->getTrackingCodeAid()); + } + + + public function testGetTrackingSid() + { + $quote = Mage::getModel('sales/quote'); + $quote->setReservedOrderId('123456'); + $quote->getStoreId(0); + Mage::app(0)->getStore(0)->setConfig(Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH . 'PSPID', 'abc'); + + $block = Mage::app()->getLayout()->getBlockSingleton('ops/frauddetection'); + $modelMock = $this->getModelMock('checkout/type_onepage', array('getQuote')); + $modelMock->expects($this->once()) + ->method('getQuote') + ->will($this->returnValue($quote)); + $this->replaceByMock('singleton', 'checkout/type_onepage', $modelMock); + $this->assertEquals(md5(Mage::getModel('ops/config')->getPSPID() . '#123456'), $block->getTrackingSid()); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/Info/FlexTest.php b/app/code/community/Netresearch/OPS/Test/Block/Info/FlexTest.php new file mode 100644 index 0000000..8036877 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/Info/FlexTest.php @@ -0,0 +1,50 @@ + + */ +?> +setAdditionalInformation(Netresearch_OPS_Model_Payment_Flex::INFO_KEY_TITLE, 'FLEX'); + + /** @var Netresearch_OPS_Block_Info_Flex $block */ + $block = Mage::app()->getLayout()->createBlock('ops/info_flex'); + $method = Mage::getModel('ops/payment_flex'); + $method->setData('info_instance', $payment); + $payment->setMethodInstance($method); + $block->setInfo($payment); + + $this->assertEquals('FLEX', $block->getFlexTitle()); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/PlaceFormShaTest.php b/app/code/community/Netresearch/OPS/Test/Block/PlaceFormShaTest.php new file mode 100644 index 0000000..5985eab --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/PlaceFormShaTest.php @@ -0,0 +1,81 @@ +_helper = new Netresearch_OPS_Helper_Payment(); + $this->_shaKey = 'qu4rkkuchen12345'; + } + + public function testShaGenerationWithTestData() + { + $params = array( + 'AMOUNT' => '2129', + 'CIVILITY' => 'Herr', + 'CURRENCY' => 'EUR', + 'ECOM_BILLTO_POSTAL_NAME_FIRST' => 'John', + 'ECOM_BILLTO_POSTAL_NAME_LAST' => 'Doe', + 'ECOM_SHIPTO_DOB' => '09/10/1940', + 'EMAIL' => 'john@doe.com', + 'ITEMID1' => 'article1', + 'ITEMNAME1' => 'coffee', + 'ITEMPRICE1' => '3.00', + 'ITEMQUANT1' => '4', + 'ITEMVAT1' => '0.57', + 'LANGUAGE' => 'de_DE', + 'ORDERID' => 'order123', + 'ORDERSHIPCOST' => '100', + 'ORDERSHIPTAX' => '6', + 'OWNERADDRESS' => 'test street', + 'OWNERCTY' => 'DE', + 'OWNERTELNO' => '+49 111 222 33 444', + 'OWNERTOWN' => 'Berlin', + 'OWNERZIP' => '10000', + 'PM' => 'Open Invoice DE', + 'PSPID' => 'NRMAGbillpay1', + ); + $expected = '695103f8891dfc80ea46369203925b898a381334'; + $shaSign = $this->_helper->getSHASign($params, $this->_shaKey, 0); + $result = $this->_helper->shaCrypt($shaSign); + $this->assertEquals($expected, $result); + } + + public function testShaGenerationWithSpecialChars() + { + $params = array( + 'PSPID' => 'NRMAGbillpay1', + 'AMOUNT' => '560', + 'ORDERID' => 'TBI72', + 'CURRENCY' => 'EUR', + 'OWNERCTY' => 'DE', + 'ITEMVAT1' => '0.10', + 'LANGUAGE' => 'de_DE', + 'PM' => 'Open Invoice DE', + 'CIVILITY' => 'Herr', + 'EMAIL' => 'thomas.kappel@netresearch.de', + 'ORDERSHIPCOST' => '500', + 'ORDERSHIPTAX' => '0', + 'ECOM_BILLTO_POSTAL_NAME_FIRST' => 'Karla', + 'ECOM_BILLTO_POSTAL_NAME_LAST' => 'Kolumna', + 'OWNERTELNO' => '64065460', + 'ITEMPRICE1' => '0.60', + 'ECOM_SHIPTO_DOB' => '09/10/1940', + 'OWNERADDRESS' => 'Tierparkallee 2', + 'OWNERTOWN' => 'Leipzig', + 'ITEMID1' => '26', + 'ITEMNAME1' => 'Club Mate', + 'ITEMQUANT1' => '1', + 'OWNERZIP' => '04229', + ); + $expected = 'baf6099446e3bf93ecf26e622032e7db2139839c'; + $shaSign = $this->_helper->getSHASign($params, $this->_shaKey, 0); + $result = $this->_helper->shaCrypt($shaSign); + $this->assertEquals($expected, $result); + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Block/PlaceformTest.php b/app/code/community/Netresearch/OPS/Test/Block/PlaceformTest.php new file mode 100644 index 0000000..35cc863 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/PlaceformTest.php @@ -0,0 +1,92 @@ +mockSessions(); + + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_openInvoiceDe'); + $order->setPayment($payment); + + //$block = Mage::app()->getLayout()->getBlockSingleton('ops/placeform'); + $blockMock = $this->getBlockMock('ops/placeform', array('getQuestion', '_getOrder')); + $blockMock->expects($this->any()) + ->method('getQuestion') + ->will($this->returnValue('How much is the fish?')); + + + $blockMock->expects($this->any()) + ->method('_getOrder') + ->will($this->returnValue($order)); + + + $action = $blockMock->getFormAction(); + $this->assertEquals(Mage::getUrl('*/*/*', array('_secure' => Mage::app()->getFrontController()->getRequest()->isSecure())), $action); + + // check explicitly for https + $_SERVER['HTTPS'] = 'on'; + $action = $blockMock->getFormAction(); + $this->assertEquals(Mage::getUrl('*/*/*', array('_secure' =>true)), $action); + + $blockMock = $this->getBlockMock('ops/placeform', array('getQuestion', '_getOrder')); + $blockMock->expects($this->any()) + ->method('getQuestion') + ->will($this->returnValue(null)); + + $blockMock->expects($this->any()) + ->method('_getOrder') + ->will($this->returnValue($order)); + + + + $action = $blockMock->getFormAction(); + $this->assertEquals($blockMock->getConfig()->getFrontendGatewayPath(), $action); + } + + public function testIsKwixoPaymentMethodTrue() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_kwixoApresReception'); + $order->setPayment($payment); + + $blockMock = $this->getBlockMock('ops/placeform', array('_getOrder')); + $blockMock->expects($this->any()) + ->method('_getOrder') + ->will($this->returnValue($order)); + + $this->assertTrue($blockMock->isKwixoPaymentMethod()); + + } + + public function testIsKwixoPaymentMethodFalse() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_cc'); + $order->setPayment($payment); + + $blockMock = $this->getBlockMock('ops/placeform', array('_getOrder')); + $blockMock->expects($this->any()) + ->method('_getOrder') + ->will($this->returnValue($order)); + + $this->assertFalse($blockMock->isKwixoPaymentMethod()); + + } + protected function mockSessions() + { + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor() // This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'core/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() // This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/RetryPayment/MethodsTest.php b/app/code/community/Netresearch/OPS/Test/Block/RetryPayment/MethodsTest.php new file mode 100644 index 0000000..4d1f085 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/RetryPayment/MethodsTest.php @@ -0,0 +1,86 @@ + + */ +class Netresearch_OPS_Test_Block_RetryPayment_MethodsTest extends EcomDev_PHPUnit_Test_Case +{ + /** + * @test + */ + public function getMethods() + { + $this->mockSessions(); + + /** @var Netresearch_OPS_Block_RetryPayment_Methods $retryBlockMock */ + $retryBlockMock = $this->getBlockMock('ops/retryPayment_methods', array('_canUseMethod')); + $retryBlockMock->expects($this->any()) + ->method('_canUseMethod') + ->will($this->returnValue(true)); + + $method = Mage::getModel('ops/payment_ops_cc'); + $retryBlockMock->setMethods(array($method)); + + $paymentMethodMock = $this->getModelMock('ops/payment_abstract', array('isApplicableToQuote')); + $paymentMethodMock->expects($this->any()) + ->method('isApplicableToQuote') + ->will($this->returnValue(true)); + + $paymentHelperMock = $this->getHelperMock('payment', array('getStoreMethods')); + $paymentHelperMock->expects($this->any()) + ->method('getStoreMethods') + ->will($this->returnValue(array($paymentMethodMock))); + $this->replaceByMock('helper', 'payment', $paymentHelperMock); + + $result = $retryBlockMock->getMethods(); + + $this->assertInternalType('array', $result); + $this->assertEquals(1, count($result)); + + } + + /** + * Helper Function to remove Session Errors + */ + protected function mockSessions() + { + $sessionMock = $this->getModelMockBuilder('admin/session') + ->disableOriginalConstructor()// This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'admin/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor()// This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'core/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor()// This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/RetryPaymentTest.php b/app/code/community/Netresearch/OPS/Test/Block/RetryPaymentTest.php new file mode 100644 index 0000000..3e7ff44 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/RetryPaymentTest.php @@ -0,0 +1,94 @@ + + */ +class Netresearch_OPS_Test_Block_RetryPaymentTest extends EcomDev_PHPUnit_Test_Case +{ + /** + * @test + */ + public function getFormAction() + { + $this->mockSessions(); + $retryBlock = new Netresearch_OPS_Block_RetryPayment(); + + $result = $retryBlock->getFormAction(); + + $this->assertInternalType('string', $result); + $this->assertContains('http', $result); + + } + + /** + * @test + */ + public function getCancelUrl() + { + $this->mockSessions(); + $retryBlock = new Netresearch_OPS_Block_RetryPayment(); + + $result = $retryBlock->getCancelUrl(); + + $this->assertInternalType('string', $result); + $this->assertContains('http', $result); + + } + + /** + * @test + */ + public function getOrderId() + { + $this->mockSessions(); + $retryBlock = new Netresearch_OPS_Block_RetryPayment(); + $orderId = '100000023'; + + Mage::app()->getRequest()->setParam('orderID', $orderId); + + $result = $retryBlock->getOrderId(); + + $this->assertContains($orderId, $result); + + } + + /** + * Helper Function to remove Session Errors + */ + protected function mockSessions() + { + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor()// This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'core/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor()// This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/System/Config/Form/Field/ImageTest.php b/app/code/community/Netresearch/OPS/Test/Block/System/Config/Form/Field/ImageTest.php new file mode 100644 index 0000000..590545e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/System/Config/Form/Field/ImageTest.php @@ -0,0 +1,73 @@ + + * @copyright 2016 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ + +/** + * Netresearch_OPS_Test_Block_System_Config_ImageTest + * + * @category OPS + * @package Netresearch_OPS + * @author Benjamin Heuer + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ +class Netresearch_OPS_Test_Block_System_Config_Form_Field_ImageTest + extends EcomDev_PHPUnit_Test_Case +{ + /** + * @test + */ + public function getElementHtml() + { + $blockMock = + $this->getBlockMock('ops/system_config_form_field_image', array('getHtmlId', '_getDeleteCheckbox'), false, + array(), '', false + ); + + $blockMock->expects($this->any()) + ->method('getHtmlId') + ->will($this->returnValue('112')); + $blockMock->expects($this->any()) + ->method('_getDeleteCheckbox') + ->will($this->returnValue('')); + + /** @var Netresearch_OPS_Block_System_Config_Form_Field_Image $imageBlock */ + $blockMock->setData('name', 'groups[ops_cc][fields][image][value]'); + + $result = $blockMock->getElementHtml(); + + $this->assertInternalType('string', $result); + $this->assertContains('ops/logos/ops_cc.png', $result); + + /* Fill the Block with an Url */ + $simpleXmlElement = new Varien_Simplexml_Element('ingenico/test'); + $blockMock->setData('value', 'default/img.png'); + $blockMock->setData('field_config', $simpleXmlElement); + $result = $blockMock->getElementHtml(); + $this->assertInternalType('string', $result); + $this->assertContains('ingenico/test', $result); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Block/System/Config/KwixoconfigurationTest.php b/app/code/community/Netresearch/OPS/Test/Block/System/Config/KwixoconfigurationTest.php new file mode 100644 index 0000000..f57a43a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/System/Config/KwixoconfigurationTest.php @@ -0,0 +1,24 @@ + + * @category Netresearch + * @package ${MODULENAME} + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Block_System_Config_KwixoconfigurationTest + extends EcomDev_PHPUnit_Test_Case +{ + + public function testRender() + { + $element = new Varien_Data_Form_Element_Text(); + $element->setLegend('I am legend'); + $block = new Netresearch_OPS_Block_System_Config_Kwixoconfiguration(); + $block->render($element); + $this->assertEquals('I am legend', $block->getData('fieldset_label')); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Block/System/Config/ModeTest.php b/app/code/community/Netresearch/OPS/Test/Block/System/Config/ModeTest.php new file mode 100644 index 0000000..618d24d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Block/System/Config/ModeTest.php @@ -0,0 +1,21 @@ +setForm(new Varien_Object()); + $block = new Netresearch_OPS_Block_System_Config_Mode(); + $html = $block->render($element); + $this->assertTrue(preg_match('/"); + + $params = array('Alias_AliasId' => '4711', 'Card_CVC' => '123', 'Alias_OrderId' => '0'); + $this->dispatch($routeToDispatch, $params); + $result = $this->getResponse()->getOutputBody(); + $this->assertEquals($result, ""); + + + + } + + public function testAcceptAliasActionException() + { + $routeToDispatch = $this->prepareAliasControllerTest(); + + $this->dispatch($routeToDispatch, array('Alias_OrderId' => '0')); + $result = $this->getResponse()->getOutputBody(); + $this->assertEquals($result, ""); + } + + + /** + * @loadFixture orders.yaml + */ + public function testGenerateHashAction() + { + + $fakeQuote = Mage::getModel('sales/order')->load(11); + $quoteMock = $this->getModelMock('sales/quote', array('load', 'save')); + $quoteMock->expects($this->any()) + ->method('load') + ->will($this->returnValue($fakeQuote)); + $this->replaceByMock('model', 'sales/quote', $quoteMock); + $params = array( + 'alias' => 4711, + 'storeId' => 1 + ); + + $configHelperMock = $this->getModelMock('ops/config', array('getAliasAcceptUrl', 'getAliasExceptionUrl')); + $configHelperMock->expects($this->any()) + ->method('getAliasAcceptUrl') + ->with(1) + ->will($this->returnValue(1)); + $configHelperMock->expects($this->any()) + ->method('getAliasExceptionUrl') + ->with(1) + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'ops/config', $configHelperMock); + + $this->dispatch('ops/alias/generateHash', $params); + $result = Mage::helper('core')->jsonDecode($this->getResponse()->getOutputBody()); + $this->assertArrayHasKey('hash', $result); + + $params = array( + 'alias' => 4712, + 'storeId' => 0 + ); + + $configHelperMock = $this->getModelMock('ops/config', array('getAliasAcceptUrl', 'getAliasExceptionUrl')); + $configHelperMock->expects($this->any()) + ->method('getAliasAcceptUrl') + ->with(0) + ->will($this->returnValue(1)); + $configHelperMock->expects($this->any()) + ->method('getAliasExceptionUrl') + ->with(0) + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'ops/config', $configHelperMock); + + $this->dispatch('ops/alias/generateHash', $params); + $result = Mage::helper('core')->jsonDecode($this->getResponse()->getOutputBody()); + $this->assertArrayHasKey('hash', $result); + + $params = array( + 'alias' => 4713, + 'storeId' => 1, + 'isAdmin' => 1, + 'brand' => 'visa' + ); + + $configHelperMock = $this->getModelMock('ops/config', array('getAliasAcceptUrl', 'getAliasExecptionUrl')); + $configHelperMock->expects($this->any()) + ->method('getAliasAcceptUrl') + ->with(0) + ->will($this->returnValue(1)); + $configHelperMock->expects($this->any()) + ->method('getAliasExceptionUrl') + ->with(0) + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'ops/config', $configHelperMock); + + $this->dispatch('ops/alias/generateHash', $params); + $result = Mage::helper('core')->jsonDecode($this->getResponse()->getOutputBody()); + $this->assertArrayHasKey('hash', $result); + + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Controller/AliasControllerTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Controller/AliasControllerTest/fixtures/orders.yaml new file mode 100644 index 0000000..30ca933 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Controller/AliasControllerTest/fixtures/orders.yaml @@ -0,0 +1,595 @@ +tables: + sales/order: + - entity_id: 11 + increment_id: 100000011 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 23 + + - entity_id: 12 + increment_id: 100000012 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 24 + + - entity_id: 13 + increment_id: 100000013 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 177.55 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'processing' + status: 'processing' + quote_id: 25 + + - entity_id: 14 + increment_id: 100000014 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 177.55 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 26 + + - entity_id: 15 + increment_id: 100000015 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 200.00 + currency: '$' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 27 + + - entity_id: 16 + increment_id: 100000016 + shipping_address_id: 43 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 28 + + - entity_id: 17 + increment_id: 100000017 + shipping_address_id: 43 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 29 + + - entity_id: 18 + increment_id: 100000018 + shipping_address_id: 43 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 30 + + - entity_id: 19 + increment_id: 100000019 + shipping_address_id: 50 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 43 + + - entity_id: 20 + increment_id: 100000020 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 42# + + - entity_id: 21 + increment_id: 100000021 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 22 + increment_id: 100000022 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 23 + increment_id: 100000023 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 24 + increment_id: 100000024 + store_id: 1 + shipping_address_id: 46 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 24 + increment_id: 100000024 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 25 + increment_id: 100000025 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 26 + increment_id: 100000026 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 27 + increment_id: 100000027 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 28 + increment_id: 100000028 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 29 + increment_id: 100000029 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 30 + increment_id: 100000030 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + sales/quote: + - entity_id: 1 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + - entity_id: 2 + base_grand_total: 119.00 + grand_total: 200.00 + currency: '$' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + - entity_id: 10 + store_id: 0 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 11 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + - entity_id: 23 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + - entity_id: 12 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + sales/quote_payment: + - payment_id: 1 + quote_id: 10 + updated_at: '1970-01-01' + method: 'ops_cc' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 2 + quote_id: 11 + updated_at: '1970-01-01' + method: 'ops_cc' + quote_id: 11 + + - payment_id: 3 + quote_id: 10 + updated_at: '1970-01-01' + method: 'ops_cc' + additional_information: a:7:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:3:"cvc";s:3:"123";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 4 + quote_id: 12 + updated_at: '1970-01-01' + method: 'ops_paypal' + additional_information: a:7:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:3:"cvc";s:3:"123";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + sales/order_payment: + - entity_id: 1 + parent_id: 11 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_bankTransfer' + + - entity_id: 2 + parent_id: 12 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:6:"status";s:1:"5";} + + - entity_id: 3 + parent_id: 13 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:6:"status";s:1:"0";} + + - entity_id: 4 + parent_id: 14 + additional_information: a:2:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";} + + - entity_id: 5 + parent_id: 15 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFD";} + method: 'ops_cc' + + - entity_id: 6 + parent_id: 16 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFS";} + + - entity_id: 7 + parent_id: 17 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFS";s:9:"canRefund";b:0;} + + - entity_id: 8 + parent_id: 18 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFD";s:9:"canRefund";b:1;} + + - entity_id: 9 + parent_id: 19 + method: 'ops_openInvoiceDe' + + - entity_id: 11 + parent_id: 20 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + + - entity_id: 12 + parent_id: 21 + additional_information: a:4:{s:2:"PM";s:16:"Direct Debits DE";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + method: 'ops_directDebit' + + - entity_id: 13 + parent_id: 22 + additional_information: a:4:{s:2:"PM";s:16:"Direct Debits NL";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + + - entity_id: 14 + parent_id: 23 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + + - entity_id: 42 + parent_id: 20 + + - entity_id: 15 + parent_id: 24 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_cc' + + - entity_id: 16 + parent_id: 25 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_cc' + + - entity_id: 17 + parent_id: 26 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_paypal' + + - entity_id: 18 + parent_id: 27 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_kwixoApresReception' + + - entity_id: 19 + parent_id: 28 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_kwixoComptant' + + - entity_id: 20 + parent_id: 29 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_kwixoCredit' + + - entity_id: 21 + parent_id: 30 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'checkmo' + + sales/shipment: + - entity_id: 1 + increment_id: 100000077 + order_id: 11 + shipping_address_id: 42 + + - entity_id: 2 + increment_id: 100000078 + order_id: 12 + shipping_address_id: 43 + + sales/order_address: + - entity_id: 42 + parent_id: 11 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + - entity_id: 43 + parent_id: 11 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + + - entity_id: 44 + parent_id: 12 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 45 + parent_id: 12 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + + - entity_id: 46 + parent_id: 24 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + + - entity_id: 47 + parent_id: 27 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_shipping: 1 + + - entity_id: 48 + parent_id: 28 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 49 + parent_id: 19 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 50 + parent_id: 19 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + sales/order_item: + - item_id: 11 + order_id: 11 + qty_ordered: 2.0000 + qty_shipped: 2.0000 + sku: 4711 + name: 'some great test item' + price: 19.99 + tax_amount: 1.99 + + - item_id: 12 + order_id: 11 + qty_ordered: 2.0000 + qty_shipped: 2.0000 + sku: 4711 + name: 'some great test item 2' + price: 19.99 + tax_amount: 1.99 + diff --git a/app/code/community/Netresearch/OPS/Test/Controller/ApiControllerTest.php b/app/code/community/Netresearch/OPS/Test/Controller/ApiControllerTest.php new file mode 100644 index 0000000..85093da --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Controller/ApiControllerTest.php @@ -0,0 +1,165 @@ +getHelperMock( + 'ops/payment', array('applyStateForOrder', 'shaCryptValidation') + ); + $paymentHelperMock->expects($this->any()) + ->method('applyStateForOrder') + ->will($this->returnValue($returnStatus)); + + $paymentHelperMock->expects($this->any()) + ->method('shaCryptValidation') + ->will($this->returnValue(true)); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + + $fakePayment = new Varien_Object(); + $fakePayment->setMethodInstance(Mage::getModel('ops/payment_cc')); + + $fakeOrder = new Varien_Object(); + $fakeOrder->setPayment($fakePayment); + $fakeOrder->setId(1); + if ($setStoreId) { + $fakeOrder->setStoreId(1); + } + $orderHelperMock = $this->getHelperMock('ops/order', array('getOrder')); + $orderHelperMock->expects($this->any()) + ->method('getOrder') + ->will($this->returnValue($fakeOrder)); + $this->replaceByMock('helper', 'ops/order', $orderHelperMock); + } + + private function getRequestParams() + { + return array( + 'orderID' => 1, + 'SHASIGN' => '12344', + ); + } + + public function testRedirectToSuccessRoute() + { + $this->setUpHelperMock( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT + ); + $modelMock = $this->getModelMock('core/store', array('getId')); + $modelMock->expects($this->any()) + ->method('getId()') + ->will($this->returnValue(null)); + $this->replaceByMock('model', 'core/store', $modelMock); + $this->dispatch('ops/api/postBack', $this->getRequestParams()); + $this->assertRedirectTo( + Mage::getModel('ops/config')->getAcceptRedirectRoute(), + array('_query' => $this->getRequestParams(), '_store' => 1) + ); + + } + + public function testRedirectToSuccessRouteWithOrderId() + { + $this->setUpHelperMock( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT + ); + $modelMock = $this->getModelMock('core/store', array('getId')); + $modelMock->expects($this->any()) + ->method('getId()') + ->will($this->returnValue(null)); + $requestParams = $this->getRequestParams(); + $requestParams['orderID'] = '#1000001'; + $this->dispatch('ops/api/postBack', $requestParams); + $this->assertRedirectTo( + Mage::getModel('ops/config')->getAcceptRedirectRoute(), + array('_query' => $requestParams, '_store' => 1) + ); + + } + + public function testRedirectToCancelRoute() + { + $this->setUpHelperMock( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_CANCEL + ); + $modelMock = $this->getModelMock('core/store', array('getId')); + $modelMock->expects($this->any()) + ->method('getId()') + ->will($this->returnValue(null)); + $this->dispatch('ops/api/postBack', $this->getRequestParams()); + $this->assertRedirectTo( + Mage::getModel('ops/config')->getCancelRedirectRoute(), + array('_query' => $this->getRequestParams(), '_store' => 1) + ); + } + + public function testRedirectToDeclineRoute() + { + $this->setUpHelperMock( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_DECLINE + ); + $modelMock = $this->getModelMock('core/store', array('getId')); + $modelMock->expects($this->any()) + ->method('getId()') + ->will($this->returnValue(null)); + $this->dispatch('ops/api/postBack', $this->getRequestParams()); + $this->assertRedirectTo( + Mage::getModel('ops/config')->getDeclineRedirectRoute(), + array('_query' => $this->getRequestParams(), '_store' => 1) + ); + } + + public function testRedirectToExceptionRoute() + { + $this->setUpHelperMock( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_EXCEPTION + ); + $modelMock = $this->getModelMock('core/store', array('getId')); + $modelMock->expects($this->any()) + ->method('getId()') + ->will($this->returnValue(null)); + $this->dispatch('ops/api/postBack', $this->getRequestParams()); + $this->assertRedirectTo( + Mage::getModel('ops/config')->getExceptionRedirectRoute(), + array('_query' => $this->getRequestParams(), '_store' => 1) + ); + } + + public function testExceptionReturnsStatus500() + { + $this->setUpHelperMock( + 'INVALID_STATUS' + ); + $modelMock = $this->getModelMock('core/store', array('getId')); + $modelMock->expects($this->any()) + ->method('getId()') + ->will($this->returnValue(null)); + $this->dispatch('ops/api/postBack', $this->getRequestParams()); + $this->assertResponseHttpCode(500); + $this->getResponse(); + } + + + public function testRedirectIfStoreIdDoesNotMatch() + { + $this->setUpHelperMock( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT, + false + ); + $modelMock = $this->getModelMock('core/store', array('getId')); + $modelMock->expects($this->any()) + ->method('getId()') + ->will($this->returnValue(9999)); + $this->replaceByMock('model', 'core/store', $modelMock); + $this->dispatch('ops/api/postBack', $this->getRequestParams()); + $this->assertRedirectTo( + 'ops/payment/accept', array('_query' => $this->getRequestParams(), '_store' => 1) + ); + + + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Controller/DeviceControllerTest.php b/app/code/community/Netresearch/OPS/Test/Controller/DeviceControllerTest.php new file mode 100644 index 0000000..c2086a0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Controller/DeviceControllerTest.php @@ -0,0 +1,66 @@ + + */ +?> + true, '_store' => 1); + $this->dispatch('ops/device/toggleConsent', $params); + $this->assertResponseBodyJson(); + $this->assertResponseBodyJsonMatch(array('consent' => false)); + + Mage::app()->getStore(1)->setConfig(Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH.'device_fingerprinting', 1); + $this->dispatch('ops/device/toggleConsent', $params); + $this->assertResponseBodyJson(); + $this->assertResponseBodyJsonMatch(array('consent' => true)); + } + + public function testConsentAction() + { + $this->dispatch('ops/device/consent'); + $this->assertResponseBodyJson(); + $this->assertResponseBodyJsonMatch(array('consent' => false)); + + $params = array('consent' => true, '_store' => 1); + Mage::app()->getStore(1)->setConfig(Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH.'device_fingerprinting', 1); + $this->dispatch('ops/device/toggleConsent', $params); + $this->assertResponseBodyJson(); + $this->assertResponseBodyJsonMatch(array('consent' => true)); + + $this->dispatch('ops/device/consent'); + $this->assertResponseBodyJson(); + $this->assertResponseBodyJsonMatch(array('consent' => true)); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest.php b/app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest.php new file mode 100644 index 0000000..58fbb07 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest.php @@ -0,0 +1,413 @@ + + * @author André Herrn + * @author Paul Siedler + */ +class Netresearch_OPS_Test_Controller_PaymentControllerTest + extends EcomDev_PHPUnit_Test_Case_Controller +{ + public function setUp() + { + parent::setUp(); + $helperMock = $this->getHelperMock( + 'ops/payment', array( + 'shaCryptValidation', + 'cancelOrder', + 'declineOrder', + 'handleException', + 'getSHAInSet', + 'refillCart' + ) + ); + $helperMock->expects($this->any()) + ->method('shaCryptValidation') + ->will($this->returnValue(true)); + + $this->replaceByMock('helper', 'ops/payment', $helperMock); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testAcceptAction() + { + $params = array( + 'orderID' => '#100000011' + ); + $this->dispatch('ops/payment/accept', $params); + $this->assertRedirectTo('checkout/onepage/success'); + + $params = array( + 'orderID' => '23' + ); + $this->dispatch('ops/payment/accept', $params); + $this->assertRedirectTo('checkout/onepage/success'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testExceptionAction() + { + $msg = 'Your order has been registered, but your payment is still marked as pending.'; + $msg.= ' Please have patience until the final status is known.'; + + $orderId = '11'; + $quoteId = '23'; + $incrementId = '#100000011'; + + + // assert order increment id parameter handling + $params = array('orderID' => $incrementId); + + $this->dispatch('ops/payment/exception', $params); + $this->assertRedirectTo('checkout/onepage/success'); + $this->assertEquals($orderId, Mage::getSingleton('checkout/session')->getLastOrderId()); + + $message = Mage::getSingleton('checkout/session')->getMessages()->getLastAddedMessage(); + $this->assertEquals('error', $message->getType()); + $this->assertEquals($msg, $message->getText()); + + + // assert entity id parameter handling + $params = array('orderID' => $quoteId); + + $this->dispatch('ops/payment/exception', $params); + $this->assertRedirectTo('checkout/onepage/success'); + $this->assertEquals($orderId, Mage::getSingleton('checkout/session')->getLastOrderId()); + + $message = Mage::getSingleton('checkout/session')->getMessages()->getLastAddedMessage(); + $this->assertEquals('error', $message->getType()); + $this->assertEquals($msg, $message->getText()); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testDeclineAction() + { + $routeToDispatch = 'ops/payment/decline'; + $params = array(); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('checkout/onepage'); + + + $params = array( + 'orderID' => '#100000011' + ); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('checkout/onepage'); + + $params = array( + 'orderID' => '23' + ); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('checkout/onepage'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testCancelAction() + { + $routeToDispatch = 'ops/payment/cancel'; + + $params = array( + 'orderID' => '#100000011' + ); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('checkout/cart'); + + $params = array( + 'orderID' => '23' + ); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('checkout/cart'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testContinueAction() + { + $routeToDispatch = 'ops/payment/continue'; + $params = array(); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('checkout/cart'); + + + $params = array( + 'orderID' => '#100000011' + ); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('checkout/cart'); + + $params = array( + 'orderID' => '23' + ); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('checkout/cart'); + + $params = array( + 'orderID' => '#100000011', + 'redirect' => 'catalog' + ); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('/'); + + $params = array( + 'orderID' => '23', + 'redirect' => 'catalog' + ); + $this->dispatch($routeToDispatch, $params); + $this->assertRedirectTo('/'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testRepayActionWithInvalidHash() + { + // test 1: hash not valid + $order = Mage::getModel('sales/order')->load(11); + $opsOrderId = Mage::helper('ops/order')->getOpsOrderId($order); + + $paymentHelperMock = $this->getHelperMock('ops/payment', array('shaCryptValidation')); + $paymentHelperMock->expects($this->any()) + ->method('shaCryptValidation') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + + + $params = array('orderID' => $opsOrderId, 'SHASIGN' => 'foo'); + $this->dispatch('ops/payment/retry', $params); + $this->assertRedirectTo('/'); + $message = Mage::getSingleton('core/session')->getMessages()->getLastAddedMessage(); + $this->assertNotNull($message); + $this->assertEquals($message->getText(), 'Hash not valid'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testRepayActionWithInvalidOrder() + { + + // test 1: hash valid, order can not be retried + // orderID 100000012 - status 5 + $order = Mage::getModel('sales/order')->load(12); + $opsOrderId = Mage::helper('ops/order')->getOpsOrderId($order); + + $paymentHelperMock = $this->getHelperMock('ops/payment', array('shaCryptValidation')); + $paymentHelperMock->expects($this->any()) + ->method('shaCryptValidation') + ->will($this->returnValue(true)); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + + $orderHelperMock = $this->getHelperMock('ops/order', array('getOrder')); + $orderHelperMock->expects($this->any()) + ->method('getOrder') + ->will($this->returnValue($order)); + $this->replaceByMock('helper', 'ops/order', $orderHelperMock); + + $params = array('orderID' => $opsOrderId, 'SHASIGN' => 'foo'); + $this->dispatch('ops/payment/retry', $params); + + $this->assertRedirectTo('/'); + + $message = Mage::getSingleton('core/session')->getMessages()->getLastAddedMessage(); + $this->assertNotNull($message); + $this->assertEquals( + $message->getText(), 'Not possible to reenter the payment details for order ' . $order->getIncrementId() + ); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testRepayActionWithSuccess() + { + // test 31: order is fine + // orderID 100000011 + + $order = Mage::getModel('sales/order')->load(11); + $opsOrderId = Mage::helper('ops/order')->getOpsOrderId($order); + + $paymentHelperMock = $this->getHelperMock('ops/payment', array('shaCryptValidation')); + $paymentHelperMock->expects($this->any()) + ->method('shaCryptValidation') + ->will($this->returnValue(true)); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + + $this->dispatch('ops/payment/retry', $paymentHelperMock->validateOrderForReuse($opsOrderId,1)); + + $this->assertLayoutLoaded(); + $this->assertLayoutHandleLoaded('ops_payment_retry'); + } + + /** + * @test + */ + public function placeformActionWithoutSuccess() + { + $this->dispatch('ops/payment/placeForm'); + $this->assertRedirectTo('checkout/cart'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + * @test + */ + public function placeformActionWithSuccess() + { + $quoteModelMock = $this->getModelMock('sales/quote', array('save')); + $quoteModelMock->expects($this->any()) + ->method('save') + ->will($this->returnSelf()); + + $checkoutSessionMock = $this->getModelMock('checkout/session', array('getLastRealOrderId', 'getQuote')); + $checkoutSessionMock->expects($this->any()) + ->method('getLastRealOrderId') + ->will($this->returnValue(100000013)); + $checkoutSessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($quoteModelMock)); + $this->replaceByMock('singleton', 'checkout/session', $checkoutSessionMock); + + $this->dispatch('ops/payment/placeForm'); + + $this->assertLayoutLoaded(); + $this->assertLayoutHandleLoaded('ops_payment_placeform'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + * @test + */ + public function updatePaymentAndPlaceFormActionWithException() + { + $orderId = 100000013; + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order'); + $order->loadByIncrementId($orderId); + + $orderHelperMock = $this->getHelperMock('ops/order', array('getOrder')); + $orderHelperMock->expects($this->any()) + ->method('getOrder') + ->will($this->returnValue($order)); + $this->replaceByMock('helper', 'ops/order', $orderHelperMock); + + + $this->dispatch('ops/payment/updatePaymentAndPlaceForm', array('orderID' => $orderId)); + $this->assertRedirectTo('checkout/cart'); + + /** @var Mage_Core_Model_Session $test */ + $test = Mage::getSingleton('core/session'); + $messages = $test->getMessages()->count(); + $this->assertEquals(1, $messages); + $test->getMessages()->clear(); + } + + /** + * @loadFixture orders.yaml + * @test + */ + public function updatePaymentAndPlaceFormActionWithSuccess() + { + $orderId = '#100000013'; + $params = array( + 'orderID' => $orderId, + 'payment' => array( + 'method' => 'ops_iDeal', + 'ops_iDeal' => array('info' => 'foo', 'info2' => 'bar') + ) + ); + + Mage::app()->getRequest()->setParams($params); + Mage::getConfig()->setNode('stores/default/payment/ops_iDeal/active', 1); + $this->mockSession('checkout/session', array()); + + $this->dispatch('ops/payment/updatePaymentAndPlaceForm'); + + $this->assertRedirectToUrlContains('placeForm'); + + /** @var Mage_Core_Model_Session $test */ + $test = Mage::getSingleton('core/session'); + $messages = $test->getMessages()->count(); + $this->assertEquals(0, $messages); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + * @test + */ + public function updatePaymentAndPlaceFormActionWithSuccessForDirectDebit() + { + $this->markTestIncomplete("DirectDebit needs a general rework - INGRC-34"); + + $orderId = 100000014; + Mage::app()->getRequest()->setParam('orderID', $orderId); + Mage::app()->getRequest()->setParam('payment', array('method' => 'ops_directDebit')); + + /** @var Mage_Sales_Model_Order $orderModelMock */ + $orderModelMock = $this->getModelMock('sales/order', array('save', 'place')); + $orderModelMock->expects($this->any()) + ->method('save') + ->will($this->returnSelf()); + $orderModelMock->expects($this->any()) + ->method('place') + ->will($this->returnSelf()); + + $orderHelperMock = $this->getHelperMock('ops/order', array('getOrder')); + $orderHelperMock->expects($this->any()) + ->method('getOrder') + ->will($this->returnValue($orderModelMock->loadByIncrementId($orderId))); + $this->replaceByMock('helper', 'ops/order', $orderHelperMock); + + $quoteModelMock = $this->getModelMock('sales/quote', array('save')); + $quoteModelMock->expects($this->any()) + ->method('save') + ->will($this->returnSelf()); + $this->replaceByMock('model', 'sales/quote', $quoteModelMock); + + $quotePaymentModelMock = $this->getModelMock('sales/quote_payment', array('save')); + $quotePaymentModelMock->expects($this->any()) + ->method('save') + ->will($this->returnSelf()); + $this->replaceByMock('model', 'sales/quote_payment', $quotePaymentModelMock); + + $this->dispatch('ops/payment/updatePaymentAndPlaceForm'); + $this->assertRedirectToUrlContains('onepage/success'); + + /** @var Mage_Core_Model_Session $test */ + $test = Mage::getSingleton('core/session'); + $messages = $test->getMessages()->count(); + $this->assertEquals(0, $messages); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest/fixtures/orders.yaml new file mode 100644 index 0000000..0766b5b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Controller/PaymentControllerTest/fixtures/orders.yaml @@ -0,0 +1,26 @@ +tables: + core/config_data: + - config_id: 500 + path: 'payment/ops_iDeal/active' + value: 1 + + sales/order: + - entity_id: 13 + increment_id: 100000013 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 177.55 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'processing' + status: 'processing' + quote_id: 25 + store_id: 0 + + sales/order_payment: + - entity_id: 3 + parent_id: 13 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:6:"status";s:1:"0";} + method: 'ops_bankTransfer' \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/AddressTest.php b/app/code/community/Netresearch/OPS/Test/Helper/AddressTest.php new file mode 100644 index 0000000..6fe0451 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/AddressTest.php @@ -0,0 +1,22 @@ +splitStreet($street); + $expected = $this->expected('auto')->getData(); + + $this->assertEquals($expected, $split); + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Helper/AddressTest/expectations/splitStreet.yaml b/app/code/community/Netresearch/OPS/Test/Helper/AddressTest/expectations/splitStreet.yaml new file mode 100644 index 0000000..8c81c10 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/AddressTest/expectations/splitStreet.yaml @@ -0,0 +1,209 @@ +thunstrasse_39: + street_name: "Thunstraße" + street_number: "39" + supplement: "" +nonnenstrasse: + street_name: "Nonnenstraße" + street_number: "" + supplement: "" +tulpenweg: + street_name: "Tulpenweg" + street_number: "" + supplement: "" +tulpenweg_: + street_name: "Tulpenweg" + street_number: "" + supplement: "" +nonnenstrasse_11c: + street_name: "Nonnenstraße" + street_number: "11c" + supplement: "" +nonnenstrasse11c: + street_name: "Nonnenstraße11c" + street_number: "" + supplement: "" +nonnenstrasse_44_46: + street_name: "Nonnenstraße" + street_number: "44-46" + supplement: "" +nonnenstrasse_44_46_haus_c: + street_name: "Nonnenstraße" + street_number: "44-46" + supplement: "Haus C" +leipziger_strasse_117_zimmer_321: + street_name: "Leipziger Straße" + street_number: "117" + supplement: "Zimmer 321" +lilienweg_14: + street_name: "Lilienweg" + street_number: "14" + supplement: "" +lilienweg_4: + street_name: "Lilienweg" + street_number: "4" + supplement: "" +richard_strauss_strasse_4: + street_name: "Richard Strauß Straße" + street_number: "4" + supplement: "" +mittelstrasse_6: + street_name: "Mittelstrasse" + street_number: "6" + supplement: "" +hauptstr_6: + street_name: "Hauptstr." + street_number: "6" + supplement: "" +alte_dorfstraße_4: + street_name: "Alte Dorfstraße" + street_number: "4" + supplement: "" +alfons_mitnacht_str_5: + street_name: "Alfons-Mitnacht Str." + street_number: "5" + supplement: "" +seilerweg_5: + street_name: "Seilerweg" + street_number: "5" + supplement: "" +aegirsvej_4: + street_name: "Ægirsvej" + street_number: "4" + supplement: "" +hilgartshausener_hauptstrasse_49: + street_name: "Hilgartshausener Hauptstraße" + street_number: "49" + supplement: "" +erich_weinert_strasse_87: + street_name: "erich-weinert-strasse" + street_number: "87" + supplement: "" +m4_8: + street_name: "M4" + street_number: "8" + supplement: "" +strasse_1: + street_name: "1.Straße" + street_number: "" + supplement: "" +wiesencenter_bayreuther_strasse_108_2_stock: + street_name: "Bayreuther Str." + street_number: "108" + supplement: "Wiesentcenter 2. Stock" +neue_strasse_28_1_stock: + street_name: "Neue Straße" + street_number: "28" + supplement: "1.Stock" +kirchengasse_7_1_stock_zi_4: + street_name: "Kirchengasse" + street_number: "7" + supplement: "1. Stock Zi.Nr. 4" +pallaswiesenstr_57_App_235: + street_name: "Pallaswiesenstr." + street_number: "57" + supplement: "App. 235" +neue_str_55_whg_3: + street_name: "Neue Str." + street_number: "55" + supplement: "Whg. 3" +apenrader_str_16_whg_3: + street_name: "Apenrader Str." + street_number: "16" + supplement: "Whg. 3" +d_6_2: + street_name: "D 6" + street_number: "2" + supplement: "" +r4_7: + street_name: "R4" + street_number: "7" + supplement: "" +lieblgasse_2_41_7_21: + street_name: "Lieblgasse" + street_number: "2/41/7/21" + supplement: "" +1752_oto_iwatsukiku: + street_name: "Oto" + street_number: "1752" + supplement: "Iwatsukiku" +3940_radio_road_unit_110: + street_name: "Radio Road" + street_number: "3940" + supplement: "Unit 110" +1101_madison_st_600: + street_name: "Madison St" + street_number: "1101" + supplement: "# 600" +1523_6th_ave: + street_name: "6th Ave" + street_number: "1523" + supplement: "" +81_quai_charles_de_gaulle: + street_name: "quai Charles de Gaulle" + street_number: "81" + supplement: "" +nafarroa_kalea_9: + street_name: "Nafarroa Kalea" + street_number: "9" + supplement: "" +av_libertad_53: + street_name: "Av. Libertad" + street_number: "53" + supplement: "" +vanzada_errepidea_1: + street_name: "vanzada Errepidea" + street_number: "1" + supplement: "" +88_century_blvd_pudong: + street_name: "Century Blvd" + street_number: "88" + supplement: "Pudong" +mannerheimintie_13A2: + street_name: "Mannerheimintie" + street_number: "13A2" + supplement: "" +mannerheimintie_13_A2: + street_name: "Mannerheimintie" + street_number: "13 A2" + supplement: "" +mannerheimintie_13_A_2: + street_name: "Mannerheimintie" + street_number: "13 A 2" + supplement: "" +7_place_de_l_hotel_de_ville: + street_name: "place de l'Hôtel de Ville" + street_number: "7" + supplement: "" +488_west_57_street: + street_name: "West 57 Street" + street_number: "488" + supplement: "" +574_e_10th_street: + street_name: "E 10th Street" + street_number: "574" + supplement: "" +neuhof_13_15: + street_name: "Neuhof" + street_number: "13 / 15" + supplement: "" +no_133_shangdong_st: + street_name: "Shangdong St." + street_number: "133" + supplement: "" +56_route_de_geneve: + street_name: "route de Genève" + street_number: "56" + supplement: "" +no_59_yangfangdian_xi: + street_name: "Yangfangdian Xi" + street_number: "59" + supplement: "" +514_3_daechi_dong: + street_name: "Daechi-Dong" + street_number: "514-3" + supplement: "" +san_millan_3_10: + street_name: "San Millán" + street_number: "3-10" + supplement: "" + diff --git a/app/code/community/Netresearch/OPS/Test/Helper/AddressTest/providers/splitStreet.yaml b/app/code/community/Netresearch/OPS/Test/Helper/AddressTest/providers/splitStreet.yaml new file mode 100644 index 0000000..1e511ff --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/AddressTest/providers/splitStreet.yaml @@ -0,0 +1,105 @@ +thunstrasse_39: + - "Thunstraße 39" +nonnenstrasse: + - "Nonnenstraße" +tulpenweg: + - "Tulpenweg" +tulpenweg_: + - "Tulpenweg" +nonnenstrasse_11c: + - "Nonnenstraße 11c" +nonnenstrasse11c: + - "Nonnenstraße11c" +nonnenstrasse_44_46: + - "Nonnenstraße 44-46" +nonnenstrasse_44_46_haus_c: + - "Nonnenstraße 44-46 Haus C" +leipziger_strasse_117_zimmer_321: + - "Leipziger Straße 117, Zimmer 321" +lilienweg_14: + - "Lilienweg 14" +lilienweg_4: + - "Lilienweg 4" +richard_strauss_strasse_4: + - "Richard Strauß Straße 4" +mittelstrasse_6: + - "Mittelstrasse 6" +hauptstr_6: + - "Hauptstr. 6" +alte_dorfstraße_4: + - "Alte Dorfstraße 4" +alfons_mitnacht_str_5: + - "Alfons-Mitnacht Str. 5" +seilerweg_5: + - "Seilerweg 5" +aegirsvej_4: + - "Ægirsvej 4" +hilgartshausener_hauptstrasse_49: + - "Hilgartshausener Hauptstraße 49" +erich_weinert_strasse_87: + - "erich-weinert-strasse 87" +m4_8: + - "M4 8" +strasse_1: + - "1.Straße" +wiesencenter_bayreuther_strasse_108_2_stock: + - "Wiesentcenter, Bayreuther Str. 108, 2. Stock" +neue_strasse_28_1_stock: + - "Neue Straße 28 / 1.Stock" +kirchengasse_7_1_stock_zi_4: + - "Kirchengasse 7, 1. Stock Zi.Nr. 4" +pallaswiesenstr_57_App_235: + - "Pallaswiesenstr. 57 App. 235" +neue_str_55_whg_3: + - "Neue Str. 55, Whg. 3" +apenrader_str_16_whg_3: + - "Apenrader Str. 16 / Whg. 3" +d_6_2: + - "D 6, 2" +r4_7: + - "R4, 7" +lieblgasse_2_41_7_21: + - "Lieblgasse 2/41/7/21" +1752_oto_iwatsukiku: + - "1752 Oto, Iwatsukiku" +3940_radio_road_unit_110: + - "3940 Radio Road, Unit 110" +1101_madison_st_600: + - "1101 Madison St # 600" +1523_6th_ave: + - "1523 6th Ave" +81_quai_charles_de_gaulle: + - "81, quai Charles de Gaulle" +nafarroa_kalea_9: + - "Nafarroa Kalea 9" +av_libertad_53: + - "Av. Libertad 53" +vanzada_errepidea_1: + - "vanzada Errepidea 1" +88_century_blvd_pudong: + - "88 Century Blvd, Pudong" +mannerheimintie_13A2: + - "Mannerheimintie 13A2" +mannerheimintie_13_A2: + - "Mannerheimintie 13 A2" +mannerheimintie_13_A_2: + - "Mannerheimintie 13 A 2" +7_place_de_l_hotel_de_ville: + - "7, place de l'Hôtel de Ville" +488_west_57_street: + - "488 West 57 Street" +574_e_10th_street: + - "574 E 10th Street" +neuhof_13_15: + - "Neuhof 13 / 15" +no_133_shangdong_st: + - "No. 133 Shangdong St." +56_route_de_geneve: + - "56, route de Genève" +no_59_yangfangdian_xi: + - "No. 59 Yangfangdian Xi" +514_3_daechi_dong: + - "514-3, Daechi-Dong" +san_millan_3_10: + - "San Millán, 3-10" + diff --git a/app/code/community/Netresearch/OPS/Test/Helper/AliasTest.php b/app/code/community/Netresearch/OPS/Test/Helper/AliasTest.php new file mode 100644 index 0000000..e6df1e6 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/AliasTest.php @@ -0,0 +1,763 @@ +_helper = Mage::helper('ops/alias'); + $this->store = Mage::app()->getStore(0)->load(0); + $this->mockSessions(); + } + + protected function mockSessions() + { + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor() // This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'checkout/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() // This one removes session_start and other methods usage + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + } + + protected function mockCheckoutOnepage($method) + { + $onepage = $this->getModelMock('checkout/type_onepage', array('getCheckoutMethod')); + $onepage->expects($this->any()) + ->method('getCheckoutMehtod') + ->will($this->returnValue($method)); + $this->replaceByMock('singleton', 'checkout/type_onepage', $onepage); + } + + + + public function testGetOpsCode() + { + $this->assertEquals(null, Mage::helper('ops/alias')->getOpsCode()); + } + + public function testGetOpsBrand() + { + $this->assertEquals(null, Mage::helper('ops/alias')->getOpsBrand()); + } + + public function testSaveAliasIfCustomerIsNotLoggedIn() + { + $this->mockCheckoutOnepage(Mage_Checkout_Model_Type_Onepage::METHOD_GUEST); + $quote = Mage::getModel('sales/quote'); + $this->assertEquals( + null, + $this->_helper->saveAlias( + array( + 'OrderID' => 4711, + 'StorePermanently' => 'N' + ) + ) + ); + } + + public function testSaveAliasIfCustomerIsLoggedIn() + { + $this->mockCheckoutOnepage(Mage_Checkout_Model_Type_Onepage::METHOD_CUSTOMER); + /** @var Mage_Sales_Model_Quote $quote */ + $quote = $this->getModelMock('sales/quote', array('save')); + $this->replaceByMock('model', 'sales/quote', $quote); + $quote->setBillingAddress($this->getAddressData()); + $quote->setShippingAddress($this->getAddressData()); + $quote->setId(4711); + $customer = Mage::getModel('customer/customer'); + $customer->setId(1); + $aliasData['Alias_OrderId'] = 4711; + $aliasData['Alias_AliasId'] = 4711; + $aliasData['Card_Brand'] = 'Visa'; + $aliasData['Card_CardNumber'] = 'xxxx0815'; + $aliasData['Card_ExpiryDate'] = '1212'; + $aliasData['Card_CardHolderName'] = 'Foo Baar'; + $aliasData['Alias_StorePermanently'] = 'Y'; + $payment = $this->getModelMock('sales/quote_payment', array('save')); + $payment->setMethod('CreditCard'); + $payment->setAdditionalInformation(array('saveOpsAlias' => '1')); +// $quote->setCustomer($customer); + $quote->setPayment($payment); + + $quoteMock = $this->getModelMock( + 'sales/quote', array('load', 'getPayment', 'getCustomer') + ); + $quoteMock->expects($this->any()) + ->method('load') + ->will($this->returnValue($quoteMock)); + $quoteMock->expects($this->any()) + ->method('getPayment') + ->will($this->returnValue($payment)); + $quoteMock->expects($this->any()) + ->method('getCustomer') + ->will($this->returnValue($customer)); + $this->replaceByMock('model', 'sales/quote', $quoteMock); + $alias = $this->_helper->saveAlias($aliasData); + $this->assertEquals('4711', $alias->getAlias()); + $this->assertEquals('Visa', $alias->getBrand()); + $this->assertEquals('xxxx0815', $alias->getPseudoAccountOrCcNo()); + $this->assertEquals('1212', $alias->getExpirationDate()); + $this->assertEquals( + 'da39a3ee5e6b4b0d3255bfef95601890afd80709', + $alias->getBillingAddressHash() + ); + $this->assertEquals( + 'da39a3ee5e6b4b0d3255bfef95601890afd80709', + $alias->getShippingAddressHash() + ); + $this->assertEquals('CreditCard', $alias->getPaymentMethod()); + $this->assertEquals(1, $alias->getCustomerId()); + + $oldAliasId = $alias->getId(); + $alias = $this->_helper->saveAlias($aliasData, $quote); + $this->assertEquals($oldAliasId, $alias->getId()); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testSaveAliasUpdate() + { + $this->mockCheckoutOnepage(Mage_Checkout_Model_Type_Onepage::METHOD_CUSTOMER); + $quote = $this->getModelMock('sales/quote', array('save')); + $this->replaceByMock('model', 'sales/quote', $quote); + $quote->setBillingAddress($this->getAddressData()); + $quote->setShippingAddress($this->getAddressData()); + $quote->setId(4711); + $customer = new Varien_Object(); + $customer->setId(1); + $aliasData['Alias_OrderId'] = 4711; + $aliasData['Alias_AliasId'] = 4711; + $aliasData['Card_Brand'] = 'Visa'; + $aliasData['Card_CardNumber'] = 'xxxx0815'; + $aliasData['Card_ExpiryDate'] = '1212'; + $aliasData['Card_CardHolderName'] = 'Foo Baar'; + $aliasData['Alias_StorePermanently'] = 'Y'; + $payment = $this->getModelMock('sales/quote_payment', array('save')); + $payment->setMethod('CreditCard'); + $payment->setAdditionalInformation(array('saveOpsAlias' => '1')); +// $quote->setCustomer($customer); + $quote->setPayment($payment); + + $quoteMock = $this->getModelMock( + 'sales/quote', array('load', 'getPayment', 'getCustomer') + ); + $quoteMock->expects($this->any()) + ->method('load') + ->will($this->returnValue($quoteMock)); + $quoteMock->expects($this->any()) + ->method('getPayment') + ->will($this->returnValue($payment)); + $quoteMock->expects($this->any()) + ->method('getCustomer') + ->will($this->returnValue($customer)); + $this->replaceByMock('model', 'sales/quote', $quoteMock); + $oldAlias = $this->_helper->saveAlias($aliasData); + $oldAlias->setState(Netresearch_OPS_Model_Alias_State::ACTIVE); + $oldAlias->save(); + + $aliasData['Alias_OrderId'] = 4711; + $aliasData['Alias_AliasId'] = 4711; + $aliasData['Card_Brand'] = 'Mastercard'; + $aliasData['Card_CardNumber'] = 'xxxx01111'; + $aliasData['Card_ExpiryDate'] = '1213'; + $aliasData['Card_CardHolderName'] = 'Max Power'; + $aliasData['Alias_StorePermanently'] = 'Y'; + + $updatedAlias = $this->_helper->saveAlias($aliasData); + + + + $this->assertEquals(4711, $updatedAlias->getAlias()); + $this->assertEquals('Mastercard', $updatedAlias->getBrand()); + $this->assertEquals('xxxx01111', $updatedAlias->getPseudoAccountOrCcNo()); + $this->assertEquals('1213', $updatedAlias->getExpirationDate()); + $this->assertEquals('Max Power', $updatedAlias->getCardHolder()); + + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testSaveNewAliasFromQuote() + { + $reflection_class = new ReflectionClass("Netresearch_OPS_Helper_Alias"); + $method = $reflection_class->getMethod("saveNewAliasFromQuote"); + $method->setAccessible(true); + + $quote = Mage::getModel('sales/quote')->load(10); + $aliasData = array( + 'Alias_AliasId' => 'TestAlias', + 'Card_ExpiryDate' => '12.12.0012', + 'Card_Brand' => 'Visa', + 'Card_CardNumber' => '12345678', + 'Card_CardHolderName' => 'Foo', + 'Alias_StorePermanently' => 'Y' + ); + $aliasHelper = Mage::helper('ops/alias'); + $alias = $method->invoke($aliasHelper, $quote, $aliasData); + $this->assertEquals('TestAlias', $alias->getAlias()); + $this->assertEquals('12.12.0012', $alias->getExpirationDate()); + $this->assertEquals('Foo', $alias->getCardHolder()); + $this->assertEquals(Netresearch_OPS_Model_Alias_State::PENDING, $alias->getState()); + + $aliasData = array( + 'Alias_AliasId' => 'TestAlias', + 'Card_ExpiryDate' => '12.12.0012', + 'Card_Brand' => 'Visa', + 'Card_CardNumber' => '12345678', + 'Alias_StorePermanently' => 'Y' + ); + $aliasHelper = Mage::helper('ops/alias'); + $alias = $method->invoke($aliasHelper, $quote, $aliasData); + $this->assertEquals('TestAlias', $alias->getAlias()); + $this->assertEquals('12.12.0012', $alias->getExpirationDate()); + $this->assertEquals('Foo', $alias->getCardHolder()); + $this->assertEquals(Netresearch_OPS_Model_Alias_State::PENDING, $alias->getState()); + + $aliasData = array( + 'Alias_AliasId' => 'TestAlias', + 'Card_ExpiryDate' => '12.12.0012', + 'Card_Brand' => 'Visa', + 'Card_CardNumber' => '12345678', + 'Card_CardHolderName' => '', + 'Alias_StorePermanently' => 'Y' + ); + $aliasHelper = Mage::helper('ops/alias'); + $alias = $method->invoke($aliasHelper, $quote, $aliasData); + $this->assertEquals('TestAlias', $alias->getAlias()); + $this->assertEquals('12.12.0012', $alias->getExpirationDate()); + $this->assertEquals('', $alias->getCardHolder()); + $this->assertEquals(Netresearch_OPS_Model_Alias_State::PENDING, $alias->getState()); + } + + + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testSaveNewAliasFromOrder() + { + $reflection_class = new ReflectionClass("Netresearch_OPS_Helper_Alias"); + $method = $reflection_class->getMethod("saveNewAliasFromOrder"); + $method->setAccessible(true); + + $order = Mage::getModel('sales/order')->load(11); + + + + $aliasData = array( + 'alias' => 'TestAlias', + 'brand' => 'Visa', + 'cardno' => '12345678', + 'cn' => 'Foo', + 'Alias_StorePermanently' => 'Y', + 'ed' => '12.12.0012' + ); + + + $aliasHelper = Mage::helper('ops/alias'); + $alias = $method->invoke($aliasHelper, $order, $aliasData); + $this->assertEquals('TestAlias', $alias->getAlias()); + $this->assertEquals('12.12.0012', $alias->getExpirationDate()); + $this->assertEquals('Foo', $alias->getCardHolder()); + $this->assertEquals(Netresearch_OPS_Model_Alias_State::ACTIVE, $alias->getState()); + + $aliasData = array( + 'alias' => 'TestAlias', + 'brand' => 'Visa', + 'cardno' => '12345678', + 'Alias_StorePermanently' => 'Y', + 'ed' => '12.12.0012', + 'cn' => null + ); + $aliasHelper = Mage::helper('ops/alias'); + $alias = $method->invoke($aliasHelper, $order, $aliasData); + $this->assertEquals('TestAlias', $alias->getAlias()); + $this->assertEquals('12.12.0012', $alias->getExpirationDate()); + $this->assertNull($alias->getCardHolder()); + $this->assertEquals(Netresearch_OPS_Model_Alias_State::ACTIVE, $alias->getState()); + + $aliasData = array( + 'alias' => 'TestAlias', + 'brand' => 'Visa', + 'cardno' => '12345678', + 'Alias_StorePermanently' => 'Y', + 'ed' => '12.12.0012', + 'cn' => '' + ); + $aliasHelper = Mage::helper('ops/alias'); + $alias = $method->invoke($aliasHelper, $order, $aliasData); + $this->assertEquals('TestAlias', $alias->getAlias()); + $this->assertEquals('12.12.0012', $alias->getExpirationDate()); + $this->assertEquals('', $alias->getCardHolder()); + $this->assertEquals(Netresearch_OPS_Model_Alias_State::ACTIVE, $alias->getState()); + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testDeleteAlias() + { + $reflection_class = new ReflectionClass("Netresearch_OPS_Helper_Alias"); + $method = $reflection_class->getMethod("deleteAlias"); + $method->setAccessible(true); + $aliasModel = Mage::getModel('ops/alias'); + $quote = Mage::getModel('sales/quote')->load(10); + $quote->setBillingAddress($this->getAddressData()); + $quote->setShippingAddress($this->getAddressData()); + $customer = Mage::getModel('customer/customer'); + $customer->setId(1); + + $quote->setCustomer($customer); + + $customerId = $quote->getCustomer()->getId(); + $aliasesForCustomer = $aliasCollection = $aliasModel->getCollection() + ->addFieldToFilter('customer_id', $customerId) + ->load(); + $oldAliasSize = count($aliasesForCustomer); + + $aliasData = array( + 'Alias_AliasId' => '4711', + 'Card_ExpiryDate' => '0117', + 'Card_Brand' => 'Visa', + 'Card_CardNumber' => 'xxxx1111' + ); + $aliasHelper = Mage::helper('ops/alias'); + $method->invoke($aliasHelper, $quote, $aliasData); + $newAliasForCustomer = $aliasCollection = $aliasModel->getCollection() + ->addFieldToFilter('customer_id', $customerId) + ->load(); + $newAliasSize = count($newAliasForCustomer); + $this->assertGreaterThan($newAliasSize, $oldAliasSize); + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testDeleteAliasDoesNotDeleteAliasIfAddressDoesNotMatch() + { + $reflection_class = new ReflectionClass("Netresearch_OPS_Helper_Alias"); + $method = $reflection_class->getMethod("deleteAlias"); + $method->setAccessible(true); + $aliasModel = Mage::getModel('ops/alias'); + $quote = Mage::getModel('sales/quote')->load(10); + $customer = Mage::getModel('customer/customer'); + $customer->setId(1); + + $quote->setCustomer($customer); + + $customerId = $quote->getCustomer()->getId(); + $aliasesForCustomer = $aliasCollection = $aliasModel->getCollection() + ->addFieldToFilter('customer_id', $customerId) + ->load(); + $oldAliasSize = count($aliasesForCustomer); + + $aliasData = array( + 'Alias_AliasId' => '4711', + 'Card_ExpiryDate' => '0117', + 'Card_Brand' => 'Visa', + 'Card_CardNumber' => 'xxxx1111' + ); + $aliasHelper = Mage::helper('ops/alias'); + $method->invoke($aliasHelper, $quote, $aliasData); + $newAliasForCustomer = $aliasCollection = $aliasModel->getCollection() + ->addFieldToFilter('customer_id', $customerId) + ->load(); + $newAliasSize = count($newAliasForCustomer); + $this->assertEquals($newAliasSize, $oldAliasSize); + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testGetAliasesForCustomer() + { + $quote = Mage::getModel('sales/quote')->load(10); + + $aliasesCollection = $this->_helper->getAliasesForCustomer(1); + $this->assertEquals(5, count($aliasesCollection)); + + $aliasesCollection = $this->_helper->getAliasesForCustomer(2); + $this->assertEquals(1, count($aliasesCollection)); + + $aliasesCollection = $this->_helper->getAliasesForCustomer(3); + $this->assertEquals(0, count($aliasesCollection)); + + $aliasesCollection = $this->_helper->getAliasesForCustomer( + null, $quote + ); + $this->assertEquals(0, count($aliasesCollection)); + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + */ + public function testIsAliasValidForAddresses() + { + $billingAddress = Mage::getModel('sales/quote_address'); + $shippingAddress = Mage::getModel('sales/quote_address'); + $this->assertFalse( + $this->_helper->isAliasValidForAddresses( + 1, '4711', $shippingAddress, $billingAddress + ) + ); + + $billingAddress = $this->getAddressData(); + $shippingAddress = $this->getAddressData(); + + $this->assertTrue( + $this->_helper->isAliasValidForAddresses( + 1, '4711', $shippingAddress, $billingAddress + ) + ); + + $this->assertFalse( + $this->_helper->isAliasValidForAddresses( + 2, '4711', $shippingAddress, $billingAddress + ) + ); + } + + protected function getAddressData() + { + $address = new Mage_Sales_Model_Quote_Address(); + $address->setFirstname('foo'); + $address->setLastname('bert'); + $address->setStreet1('bla street 1'); + $address->setZipcode('4711'); + $address->setCity('Cologne'); + $address->setCountry_id(1); + return $address; + } + + public function testGenerateAddressHash() + { + $address = $this->getAddressData(); + $this->assertEquals( + '1b9ecdf409e240717f04b7155712658ab09116bb', + $this->_helper->generateAddressHash($address) + ); + $address->setData('street', array('wuseldusel', 'foo')); + $this->assertEquals( + '260a9287b2964d3674f49f589d5e5fd7143041cf', + $this->_helper->generateAddressHash($address) + ); + } + + public function testFormatAliasCardNo() + { + $helper = Mage::helper('ops/alias'); + $cardNo = 'xxxxxxxxxxxx1111'; + $cardType = 'VISA'; + $this->assertEquals( + 'XXXX XXXX XXXX 1111', + $helper->formatAliasCardNo($cardType, $cardNo) + ); + + $cardNo = 'xxxxxxxxxxxx9999'; + $cardType = 'MasterCard'; + $this->assertEquals( + 'XXXX XXXX XXXX 9999', + $helper->formatAliasCardNo($cardType, $cardNo) + ); + + $cardNo = '3750-xxxxxx-03'; + $cardType = 'american express'; + $this->assertEquals( + '3750 XXXXXX 03', $helper->formatAliasCardNo($cardType, $cardNo) + ); + + $cardNo = '3750-xxxxxx-03'; + $cardType = 'DINERS CLUB'; + $this->assertEquals( + '3750 XXXXXX 03', $helper->formatAliasCardNo($cardType, $cardNo) + ); + + + $cardNo = '675941-XXXXXXXX-08'; + $cardType = 'MaestroUK'; + $this->assertEquals( + '675941 XXXXXXXX 08', $helper->formatAliasCardNo($cardType, $cardNo) + ); + + $cardNo = '675956-XXXXXXXX-54'; + $cardType = 'MaestroUK'; + $this->assertEquals( + '675956 XXXXXXXX 54', $helper->formatAliasCardNo($cardType, $cardNo) + ); + + $cardNo = '564182-XXXXXXXX-69'; + $cardType = 'MaestroUK'; + $this->assertEquals( + '564182 XXXXXXXX 69', $helper->formatAliasCardNo($cardType, $cardNo) + ); + + $cardNo = '3750-xxxxxx-03'; + $cardType = 'PostFinance Card'; + $this->assertEquals( + '3750-XXXXXX-03', $helper->formatAliasCardNo($cardType, $cardNo) + ); + } + + /** + * + * @loadFixture ../../../var/fixtures/orders.yaml + * @loadFixture ../../../var/fixtures/aliases.yaml + */ + public function testSetAliasToPayment() + { + $params = array(); + $quote = Mage::getModel('sales/quote')->load(11); + $helper = Mage::helper('ops/alias'); + $helper->setAliasToPayment($quote->getPayment(), $params); + $payment = $quote->getPayment(); + $this->assertArrayNotHasKey('alias', $payment->getAdditionalInformation()); + $this->assertArrayNotHasKey('cvc', $payment->getAdditionalInformation()); + + $params = array( + 'alias_aliasid' => '4711' + ); + $quote = Mage::getModel('sales/quote')->load(10); + $helper = Mage::helper('ops/alias'); + $helper->setAliasToPayment($quote->getPayment(), $params); + $payment = $quote->getPayment(); + + $this->assertArrayHasKey('alias', $payment->getAdditionalInformation()); + $this->assertEquals('4711', $payment->getAdditionalInformation('alias')); + $this->assertArrayNotHasKey('cvc', $payment->getAdditionalInformation()); + + $params = array( + 'alias_aliasid' => '4712', + 'card_cvc' => '123' + ); + + $dataHelperMock = $this->getHelperMock('ops/data', array('isAdminSession')); + $dataHelperMock->expects($this->any()) + ->method('isAdminSession') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/data', $dataHelperMock); + $quote = Mage::getModel('sales/quote')->load(10); + $helper = Mage::helper('ops/alias'); + $helper->setAliasToPayment($quote->getPayment(), $params); + $payment = $quote->getPayment(); + $this->assertEquals('4712', $payment->getAdditionalInformation('alias')); + $this->assertArrayHasKey('cvc', $payment->getAdditionalInformation()); + $this->assertEquals('123', $payment->getAdditionalInformation('cvc')); + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testSetCardHolder() + { + $reflection_class = new ReflectionClass("Netresearch_OPS_Helper_Alias"); + $method = $reflection_class->getMethod('setCardHolderToAlias'); + $method->setAccessible(true); + + $helperObject = Mage::helper('ops/alias'); + + $quote = Mage::getModel('sales/quote')->load(10); + $quote->setBillingAddress($this->getAddressData()); + $quote->setShippingAddress($this->getAddressData()); + + $oldAlias = Mage::getModel('ops/alias')->load(7); + $aliasData = array( + 'alias_aliasid' => '4712', + 'card_cvc' => '123', + 'Card_CardHolderName' => 'Max Muster' + ); + + $method->invoke($helperObject, $quote, $aliasData); + $updatedAlias = Mage::getModel('ops/alias')->load(7); + $this->assertEquals($aliasData['Card_CardHolderName'], $updatedAlias->getCardHolder()); + + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testSetAliasActive() + { + $order = Mage::getModel('sales/order')->load(11); + $quote = Mage::getModel('sales/quote')->load(10); + $quote->setBillingAddress($this->getAddressData()); + $quote->setShippingAddress($this->getAddressData()); + $payment = $this->getModelMock('sales/quote_payment', array('save')); + $payment->setAdditionalInformation('alias', '4714'); + $payment->setAdditionalInformation('userIsRegistering', false); + $orderPayment = $this->getModelMock('sales/order_payment', array('save')); + $orderPayment->setAdditionalInformation('alias', '4714'); + $orderPayment->setAdditionalInformation('userIsRegistering', false); + $quote->setPayment($payment); + $order->setPayment($orderPayment); + $customer = Mage::getModel('customer/customer'); + $customer->setId(1); + $quote->setCustomer($customer); + $billingAddressHash = Mage::helper('ops/alias')->generateAddressHash($quote->getBillingAddress()); + $shippingAddressHash = Mage::helper('ops/alias')->generateAddressHash($quote->getShippingAddress()); + $aliasesToDelete = Mage::getModel('ops/alias') + ->getCollection() + ->addFieldToFilter('customer_id', $quote->getCustomer()->getId()) + ->addFieldToFilter('billing_address_hash', $billingAddressHash) + ->addFieldToFilter('shipping_address_hash', $shippingAddressHash) + ->addFieldToFilter('state', Netresearch_OPS_Model_Alias_State::ACTIVE); + $oldAlias = $aliasesToDelete->getFirstItem(); + $helperMock = $this->getHelperMock('ops/alias', array('cleanUpAdditionalInformation')); + $helperMock->setAliasActive($quote, $order); + $aliasesToUpdate = Mage::getModel('ops/alias') + ->getCollection() + ->addFieldToFilter('customer_id', $quote->getCustomer()->getId()) + ->addFieldToFilter('billing_address_hash', $billingAddressHash) + ->addFieldToFilter('shipping_address_hash', $shippingAddressHash) + ->addFieldToFilter('alias', '4714') + ->addFieldToFilter('state', Netresearch_OPS_Model_Alias_State::ACTIVE) + ->setOrder('created_at', Varien_Data_Collection::SORT_ORDER_DESC); + $newAlias = $aliasesToUpdate->getFirstItem(); + + $testAlias = Mage::getModel('ops/alias')->load($oldAlias->getId()); + $this->assertEquals(null, $testAlias->getId()); + $this->assertEquals('active', $newAlias->getState()); + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testSetAliasActiveWhenUserRegisters() + { + $order = $this->getModelMock('sales/order', array('getPayment')); + $quote = $this->getModelMock('sales/quote', array('getPayment')); + $quote->setBillingAddress($this->getAddressData()); + $quote->setShippingAddress($this->getAddressData()); + $payment = $this->getModelMock('sales/quote_payment', array('save')); + $payment->setAdditionalInformation('alias', '4714'); + $payment->setAdditionalInformation('userIsRegistering', true); + $orderPayment = $this->getModelMock('sales/order_payment', array('save')); + $orderPayment->setAdditionalInformation('alias', '4714'); + $orderPayment->setAdditionalInformation('userIsRegistering', true); + $quote->expects($this->any()) + ->method('getPayment') + ->will($this->returnValue($payment)); + + $order->expects($this->any()) + ->method('getPayment') + ->will($this->returnValue($orderPayment)); + $customer = Mage::getModel('customer/customer'); + $customer->setId(1); + $quote->setCustomer($customer); + $helperMock = $this->getHelperMock('ops/alias', array('cleanUpAdditionalInformation', 'setAliasToActiveAfterUserRegisters')); + $helperMock->expects($this->once()) + ->method('setAliasToActiveAfterUserRegisters') + ->with($order, $quote); + + $helperMock->setAliasActive($quote, $order); + + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testSetAliasToActiveAfterUserRegisters() + { + $order = Mage::getModel('sales/order')->load(11); + $order->setCustomerId(123); + $quote = Mage::getModel('sales/quote')->load(10); + $quote->setBillingAddress($this->getAddressData()); + $quote->setShippingAddress($this->getAddressData()); + $quote->getPayment()->setAdditionalInformation('alias', '4714'); + $quote->getPayment()->setAdditionalInformation('opsAliasId', '11111'); + $quote->getPayment()->setAdditionalInformation('userIsRegistering', true); + $quote->setCheckoutMethod(Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER); + $quote->setStoreId(null); + $billingAddressHash = Mage::helper('ops/alias')->generateAddressHash($quote->getBillingAddress()); + $shippingAddressHash = Mage::helper('ops/alias')->generateAddressHash($quote->getShippingAddress()); + + $oldAlias = Mage::getModel('ops/alias')->getCollection() + ->addFieldToFilter('alias', $quote->getPayment()->getAdditionalInformation('alias')) + ->addFieldToFilter('billing_address_hash', $billingAddressHash) + ->addFieldToFilter('shipping_address_hash', $shippingAddressHash) + ->getFirstItem(); + + Mage::helper('ops/alias')->setAliasToActiveAfterUserRegisters($order, $quote); + $testAlias = Mage::getModel('ops/alias')->load($oldAlias->getId()); + $this->assertEquals(Netresearch_OPS_Model_Alias_State::ACTIVE, $testAlias->getState()); + $this->assertEquals(123, $testAlias->getCustomerId()); + } + + /** + * @loadFixture ../../../var/fixtures/aliases.yaml + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testCleanUpAdditionalInformation() + { + $quote = Mage::getModel('sales/quote')->load(11); + $payment = $quote->getPayment(); + $payment->setAdditionalInformation('cvc', 'cvctest'); + $payment->setAdditionalInformation('storedOPSId', 'storedOPSIdTest'); + + + + $this->assertTrue(array_key_exists('cvc', $payment->getAdditionalInformation())); + $this->assertTrue(array_key_exists('storedOPSId', $payment->getAdditionalInformation())); + + Mage::helper('ops/alias')->cleanUpAdditionalInformation($payment); + + $this->assertTrue(is_array($payment->getAdditionalInformation())); + $this->assertFalse(array_key_exists('cvc', $payment->getAdditionalInformation())); + $this->assertFalse(array_key_exists('storedOPSId', $payment->getAdditionalInformation())); + + $quote = Mage::getModel('sales/quote')->load(11); + $payment = $quote->getPayment(); + $payment->setAdditionalInformation('cvc', 'cvctest'); + $payment->setAdditionalInformation('storedOPSId', 'storedOPSIdTest'); + + + + $this->assertTrue(array_key_exists('cvc', $payment->getAdditionalInformation())); + $this->assertTrue(array_key_exists('storedOPSId', $payment->getAdditionalInformation())); + + Mage::helper('ops/alias')->cleanUpAdditionalInformation($payment, true); + + $this->assertTrue(is_array($payment->getAdditionalInformation())); + $this->assertFalse(array_key_exists('cvc', $payment->getAdditionalInformation())); + $this->assertTrue(array_key_exists('storedOPSId', $payment->getAdditionalInformation())); + } + + /** + */ + public function testCleanUpAdditionalInformationWithSave() + { + $payment = $this->getModelMock('sales/quote_payment', array('save')); + $payment->expects($this->once()) + ->method('save'); + + Mage::helper('ops/alias')->cleanUpAdditionalInformation($payment, true, true); + + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/AliasTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Helper/AliasTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Helper/ApiTest.php b/app/code/community/Netresearch/OPS/Test/Helper/ApiTest.php new file mode 100644 index 0000000..14047d9 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/ApiTest.php @@ -0,0 +1,52 @@ +getAcceptRedirectRoute(); + $this->assertEquals( + $successRoute, $helper->getRedirectRouteFromStatus( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT + ) + ); + + $cancelRoute = $configModel->getCancelRedirectRoute(); + $this->assertEquals( + $cancelRoute, $helper->getRedirectRouteFromStatus( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_CANCEL + ) + ); + + $declineRoute = $configModel->getDeclineRedirectRoute(); + $this->assertEquals( + $declineRoute, $helper->getRedirectRouteFromStatus( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_DECLINE + ) + ); + + $exceptionRoute = $configModel->getExceptionRedirectRoute(); + $this->assertEquals( + $exceptionRoute, $helper->getRedirectRouteFromStatus( + Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_EXCEPTION + ) + ); + + try { + $helper->getRedirectRouteFromStatus(''); + } catch (Exception $e) { + $this->assertEquals('invalid status provided', $e->getMessage()); + } + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/DataTest.php b/app/code/community/Netresearch/OPS/Test/Helper/DataTest.php new file mode 100644 index 0000000..d54cdcc --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/DataTest.php @@ -0,0 +1,170 @@ +helper = Mage::helper('ops'); + $this->store = Mage::app()->getStore(0)->load(0); + } + + /** + * @test + */ + public function getModuleVersionString() + { + $path = 'modules/Netresearch_OPS/version'; + + Mage::getConfig()->setNode($path, '12.03.01'); + $this->assertSame('OGmg120301', $this->helper->getModuleVersionString()); + + Mage::getConfig()->setNode($path, '12.06.12+383'); + $this->assertSame('OGmg120612', $this->helper->getModuleVersionString()); + + Mage::getConfig()->setNode($path, '12.09.12-beta2+808'); + $this->assertSame('OGmg120912', $this->helper->getModuleVersionString()); + + $this->store->resetConfig(); + } + + public function testCheckIfUserIsRegistering() + { + $quote = new Varien_Object(); + $quote->setCheckoutMethod(Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER); + $sessionMock = $this->getModelMock('checkout/session', array('getQuote', 'init', 'save')); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($quote)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + + + $this->assertTrue(Mage::helper('ops/data')->checkIfUserIsRegistering()); + + $quote->setCheckoutMethod(Mage_Sales_Model_Quote::CHECKOUT_METHOD_LOGIN_IN); + $this->assertTrue(Mage::helper('ops/data')->checkIfUserIsRegistering()); + + $quote->setCheckoutMethod(Mage_Sales_Model_Quote::CHECKOUT_METHOD_GUEST); + $this->assertFalse(Mage::helper('ops/data')->checkIfUserIsRegistering()); + + } + + public function testCheckIfUserIsNotRegistering() + { + $quote = new Varien_Object(); + $quote->setCheckoutMethod(Mage_Sales_Model_Quote::CHECKOUT_METHOD_REGISTER); + $sessionMock = $this->getModelMock('checkout/session', array('getQuote', 'init', 'save')); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($quote)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + + $this->assertTrue(Mage::helper('ops/data')->checkIfUserIsNotRegistering()); + + $quote->setCheckoutMethod(Mage_Sales_Model_Quote::CHECKOUT_METHOD_LOGIN_IN); + $this->assertFalse(Mage::helper('ops/data')->checkIfUserIsNotRegistering()); + + $quote->setCheckoutMethod(Mage_Sales_Model_Quote::CHECKOUT_METHOD_GUEST); + $this->assertFalse(Mage::helper('ops/data')->checkIfUserIsNotRegistering()); + } + + public function testClearMsg() + { + $helper = Mage::helper('ops/data'); + $testArray = array('cvc' => '1', 'CVC' => '2', 'test' => 'me'); + $testArray = $helper->clearMsg($testArray); + $this->assertFalse(array_key_exists('cvc', $testArray)); + $this->assertFalse(array_key_exists('CVC', $testArray)); + $this->assertTrue(array_key_exists('test', $testArray)); + $testString = '{"CVC":"123"}'; + $this->assertFalse(strpos($helper->clearMsg($testString), 'CVC')); + $testString = '{"CVC":"123","CN":"Some Name"}'; + $this->assertFalse(strpos($helper->clearMsg($testString), 'CVC')); + $testString = '{"cvc":"123","CN":"Some Name"}'; + $this->assertFalse(strpos($helper->clearMsg($testString), 'cvc')); + $this->assertTrue(false !== strpos($helper->clearMsg($testString), 'CN')); + + $testString = 'a:3:{s:5:"Alias";s:14:"10290855992990";s:3:"CVC";s:3:"777";s:2:"CN";s:13:"Homer Simpson";}'; + $this->assertFalse(strpos($helper->clearMsg($testString), 'CVC')); + $this->assertTrue(false !== strpos($helper->clearMsg($testString), 'Homer')); + } + + /** + * @test + */ + public function sendNoMail() + { + $documentMock = $this->getMockBuilder('Mage_Sales_Model_Order_Shipment') + ->setMethods(array('getEmailSent')) + ->getMock(); + $documentMock + ->expects($this->never()) + ->method('getEmailSent'); + + Mage::helper('ops/data')->sendTransactionalEmail($documentMock); + } + + /** + * @test + */ + public function sendOrderEmail() + { + $orderMock = $this->getModelMock( + 'sales/order', array( + 'getEmailSent', 'getCanSendNewEmailFlag', 'sendNewOrderEmail' + ) + ); + $orderMock + ->expects($this->exactly(3)) + ->method('getEmailSent') + ->willReturnOnConsecutiveCalls(true, false, false); + $orderMock + ->expects($this->exactly(2)) + ->method('getCanSendNewEmailFlag') + ->willReturnOnConsecutiveCalls(false, true); + $orderMock + ->expects($this->once()) + ->method('sendNewOrderEmail'); + $this->replaceByMock('model', 'sales/order', $orderMock); + + $order = Mage::getModel('sales/order'); + Mage::helper('ops/data')->sendTransactionalEmail($order); + Mage::helper('ops/data')->sendTransactionalEmail($order); + Mage::helper('ops/data')->sendTransactionalEmail($order); + } + + /** + * @test + */ + public function sendInvoiceEmail() + { + $configMock = $this->getModelMock('ops/config', array('getSendInvoice')); + $configMock + ->expects($this->exactly(2)) + ->method('getSendInvoice') + ->willReturnOnConsecutiveCalls(false, true); + $this->replaceByMock('model', 'ops/config', $configMock); + + $invoiceMock = $this->getModelMock( + 'sales/order_invoice', array( + 'getEmailSent', 'sendEmail' + ) + ); + $invoiceMock + ->expects($this->exactly(3)) + ->method('getEmailSent') + ->willReturnOnConsecutiveCalls(true, false, false); + $invoiceMock + ->expects($this->once()) + ->method('sendEmail'); + $this->replaceByMock('model', 'sales/order', $invoiceMock); + + $order = Mage::getModel('sales/order'); + Mage::helper('ops/data')->sendTransactionalEmail($order); + Mage::helper('ops/data')->sendTransactionalEmail($order); + Mage::helper('ops/data')->sendTransactionalEmail($order); + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest.php b/app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest.php new file mode 100644 index 0000000..73eac59 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest.php @@ -0,0 +1,144 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Helper_DirectDebitTest + extends EcomDev_PHPUnit_Test_Case +{ + + /** + * @return Netresearch_OPS_Helper_DirectDebit + */ + protected function getDirectDebitHelper() + { + return Mage::helper('ops/directDebit'); + } + + + public function testGetDataHelper() + { + $this->assertTrue( + $this->getDirectDebitHelper()->getDataHelper() instanceof + Netresearch_OPS_Helper_Data + ); + } + + public function testSetDataHelper() + { + $this->getDirectDebitHelper()->setDataHelper(Mage::helper('ops/data')); + + $this->assertTrue( + $this->getDirectDebitHelper()->getDataHelper() instanceof + Netresearch_OPS_Helper_Data + ); + } + + public function testGetQuoteHelper() + { + $this->assertTrue( + $this->getDirectDebitHelper()->getQuoteHelper() instanceof + Netresearch_OPS_Helper_Quote + ); + } + + public function testSetQuoteHelper() + { + $this->getDirectDebitHelper()->setQuoteHelper(Mage::helper('ops/quote')); + + $this->assertTrue( + $this->getDirectDebitHelper()->getQuoteHelper() instanceof + Netresearch_OPS_Helper_Quote + ); + } + + public function testGetOrderHelper() + { + $this->assertTrue( + $this->getDirectDebitHelper()->getOrderHelper() instanceof + Netresearch_OPS_Helper_Order + ); + } + + public function testSetOrderHelper() + { + $this->getDirectDebitHelper()->setOrderHelper(Mage::helper('ops/order')); + + $this->assertTrue( + $this->getDirectDebitHelper()->getOrderHelper() instanceof + Netresearch_OPS_Helper_Order + ); + } + + public function testGetCustomerHelper() + { + $this->assertTrue( + $this->getDirectDebitHelper()->getCustomerHelper() instanceof + Mage_Customer_Helper_Data + ); + } + + public function testSetCustomerHelper() + { + $this->getDirectDebitHelper()->setCustomerHelper(Mage::helper('customer/data')); + + $this->assertTrue( + $this->getDirectDebitHelper()->getCustomerHelper() instanceof + Mage_Customer_Helper_Data + ); + } + + public function testHandleAdminPayment() + { + $quote = Mage::getModel('sales/quote'); + $this->assertTrue( + $this->getDirectDebitHelper()->handleAdminPayment($quote, array()) + instanceof Netresearch_OPS_Helper_DirectDebit + ); + } + + /** + * @loadFixture orders.yaml + */ + public function testGetPaymentSpecificParamsForFrontend() + { + $this->mockSessions(); + $order = Mage::getModel('sales/order')->load(1); + $quote = Mage::getModel('sales/quote')->load(1); + $helper = $this->getDirectDebitHelper(); + $params = array( + 'country_id' => 'de', + 'CN' => 'Account Holder', + 'account' => '1234567', + 'bankcode' => '1234567' + ); + $result = $helper->getDirectLinkRequestParams($quote, $order, $params); + + $this->assertTrue((isset($result['BRAND'])) && $result['BRAND'] = "Direct Debits DE"); + $this->assertTrue((isset($result['PM'])) && $result['PM'] = "Direct Debits DE"); + $this->assertTrue((isset($result['ALIAS'])) && $result['ALIAS'] = "0000000012385139"); + } + + protected function mockSessions() + { + $sessionMock = $this->getModelMockBuilder('admin/session') + ->disableOriginalConstructor() + ->getMock(); + $this->replaceByMock('singleton', 'admin/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('core/session') + ->disableOriginalConstructor() + ->getMock(); + $this->replaceByMock('singleton', 'core/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest/fixtures/orders.yaml new file mode 100644 index 0000000..6c6259c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/DirectDebitTest/fixtures/orders.yaml @@ -0,0 +1,79 @@ +tables: + sales/order: + - entity_id: 1 + increment_id: 100000001 + shipping_address_id: 1 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 1 + store_id: 0 + + sales/quote: + - entity_id: 1 + orig_order_id: 1 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + + sales/quote_payment: + - payment_id: 1 + quote_id: 1 + updated_at: '1970-01-01' + method: 'ops_directDebit' + additional_information: a:5:{s:5:"alias";s:16:"0000000012385139";s:10:"country_id";s:2:"DE";s:10:"opsAliasId";s:2:"21";s:8:"CC_BRAND";s:16:"Direct Debits DE";s:5:"CC_CN";s:4:"asda";} + + + sales/order_payment: + - entity_id: 1 + parent_id: 1 + method: 'ops_directDebit' + + + sales/shipment: + - entity_id: 1 + increment_id: 100000001 + order_id: 1 + shipping_address_id: 42 + + sales/order_address: + - entity_id: 1 + parent_id: 1 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + - entity_id: 2 + parent_id: 1 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + diff --git a/app/code/community/Netresearch/OPS/Test/Helper/DirectLinkTest.php b/app/code/community/Netresearch/OPS/Test/Helper/DirectLinkTest.php new file mode 100644 index 0000000..9653e71 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/DirectLinkTest.php @@ -0,0 +1,562 @@ +_helper = Mage::helper('ops/directlink'); + $transaction = Mage::getModel('sales/order_payment_transaction'); + $transaction->setAdditionalInformation( + 'arrInfo', serialize( + array( + 'amount' => '184.90' + ) + ) + ); + $transaction->setIsClosed(0); + $this->_transaction = $transaction; + $this->_order = Mage::getModel('sales/order'); + $this->_order->setGrandTotal('184.90'); + $this->_order->setBaseGrandTotal('184.90'); + } + + public function testDeleteActions() + { + $this->assertFalse( + $this->_helper->isValidOpsRequest( + $this->_transaction, + $this->_order, + array('STATUS'=> Netresearch_OPS_Model_Status::PAYMENT_DELETED) + ) + ); + $this->assertFalse($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, array('STATUS'=> Netresearch_OPS_Model_Status::PAYMENT_DELETION_PENDING))); + $this->assertFalse($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, array('STATUS'=> Netresearch_OPS_Model_Status::PAYMENT_DELETION_UNCERTAIN))); + $this->assertFalse($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, array('STATUS'=> Netresearch_OPS_Model_Status::PAYMENT_DELETION_REFUSED))); + $this->assertFalse($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, array('STATUS'=> Netresearch_OPS_Model_Status::PAYMENT_DELETION_OK))); + $this->assertFalse($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, array('STATUS'=> Netresearch_OPS_Model_Status::DELETION_HANDLED_BY_MERCHANT))); + } + + public function testRefundActions() + { + + $opsRequest = array( + 'STATUS' => Netresearch_OPS_Model_Status::REFUNDED, + 'amount' => '184.90' + ); + $this->assertFalse($this->_helper->isValidOpsRequest(null, $this->_order, $opsRequest), 'Refund should not be possible without open transactions'); + $this->assertTrue($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, $opsRequest), 'Refund should be possible with open transactions'); + $opsRequest['amount'] = '14.90'; + $this->assertFalse($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, $opsRequest), 'Refund should NOT be possible because of differing amount'); + } + + public function testCancelActions() + { + $opsRequest = array( + 'STATUS' => Netresearch_OPS_Model_Status::AUTHORIZED_AND_CANCELLED, + 'amount' => '184.90' + ); + $this->assertFalse($this->_helper->isValidOpsRequest(null, $this->_order, $opsRequest), 'Cancel should not be possible without open transactions'); + $this->assertTrue($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, $opsRequest), 'Cancel should be possible with open transactions'); + $opsRequest['amount'] = '14.90'; + $this->assertFalse($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, $opsRequest), 'Cancel should NOT be possible because of differing amount'); + } + + public function testCaptureActions() + { + $opsRequest = array( + 'STATUS' => Netresearch_OPS_Model_Status::PAYMENT_REQUESTED, + 'amount' => '184.90' + ); + $this->assertTrue($this->_helper->isValidOpsRequest(null, $this->_order, $opsRequest), 'Capture should be possible because of no open transactions and matching amount'); + $opsRequest['amount'] = '14.90'; + $this->assertFalse($this->_helper->isValidOpsRequest($this->_transaction, $this->_order, $opsRequest), 'Capture should NOT be possible because of differing amount'); + } + + public function testCleanupParameters() + { + $expected = 123.45; + $result = $this->_helper->formatAmount('123.45'); + $this->assertEquals($expected, $result); + + $result = $this->_helper->formatAmount('\'123.45\''); + $this->assertEquals($expected, $result); + + $result = $this->_helper->formatAmount('"123.45"'); + $this->assertEquals($expected, $result); + + $expected = $this->_helper->formatAmount(0.3); + $result = $this->_helper->formatAmount(0.1 + 0.2); + $this->assertEquals($expected . '', $result . ''); + $this->assertEquals((float) $expected, (float) $result); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackCaptureSuccess() + { + $rssSession = $this->mockSession('rss/session')->disableOriginalConstructor(); + $this->replaceByMock('model', 'rss/session', $rssSession); + $adminSession = $this->mockSession('admin/session')->disableOriginalConstructor(); + $this->replaceByMock('model', 'admin/session', $adminSession); + $this->mockEmailHelper($this->once()); + + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $closure = function ($order, $params = array()) { + $order->getPayment()->setAdditionalInformation('status', 9); + return $order->getPayment(); + }; + + $captureHelper = $this->getHelperMock('ops/order_capture', array('acceptCapture')); + $captureHelper->expects($this->any()) + ->method('acceptCapture') + ->will($this->returnCallback($closure)); + $this->replaceByMock('helper', 'ops/order_capture', $captureHelper); + $params = array('STATUS' => Netresearch_OPS_Model_Status::PAYMENT_REQUESTED); + $directlinkHelperMock->processFeedback($order, $params); + $this->assertEquals(9, $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackRefundSuccess() + { + $this->mockEmailHelper($this->never()); + + $mock = $this->getModelMock('sales/order', array('getBillingAddress')); + $mock->expects($this->any()) + ->method('getBillingAddress') + ->will($this->returnValue(new Varien_Object())); + $this->replaceByMock('model', 'sales/order', $mock); + + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $params = array('STATUS' => Netresearch_OPS_Model_Status::REFUNDED, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + $this->assertEquals($params['STATUS'], $order->getPayment()->getAdditionalInformation('status')); + } + + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackRefundWithStatusEightyFiveSuccess() + { + $this->mockEmailHelper($this->never()); + + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + + + $closure = function ($order, $params = array()) { + $order->getPayment()->setAdditionalInformation( + 'status', + Netresearch_OPS_Model_Status::REFUND_PROCESSED_BY_MERCHANT + ); + return $order->getPayment(); + }; + + $refundHelper = $this->getHelperMock('ops/order_refund', array('createRefund')); + $refundHelper->expects($this->any()) + ->method('createRefund') + ->will($this->returnCallback($closure)); + $this->replaceByMock('helper', 'ops/order_refund', $refundHelper); + $params = array('STATUS' => Netresearch_OPS_Model_Status::REFUND_PROCESSED_BY_MERCHANT, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + $this->assertEquals($params['STATUS'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackPaymentWaiting() + { + $this->mockEmailHelper($this->once()); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $cntBefore = $order->getStatusHistoryCollection()->count(); + + + $closure = function ($order, $params = array()) { + $order->getPayment()->setAdditionalInformation( + 'status', + Netresearch_OPS_Model_Status::PAYMENT_PROCESSING + ); + return $order->getPayment(); + }; + + $params = array('STATUS' => Netresearch_OPS_Model_Status::PAYMENT_PROCESSING, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + $cntAfter = $order->getStatusHistoryCollection()->count(); + $this->assertTrue($cntBefore < $cntAfter); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackPaymentRefused() + { + // mail sending is triggered but getEmailSent takes effect + $this->mockEmailHelper($this->once()); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock( + 'ops/directlink', + array('isValidOpsRequest', 'closePaymentTransaction') + ); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $closure = function ($order, $params = array()) { + $order->getPayment()->setAdditionalInformation( + 'status', + Netresearch_OPS_Model_Status::PAYMENT_REFUSED + ); + return $order->getPayment(); + }; + + $params = array('STATUS' => Netresearch_OPS_Model_Status::PAYMENT_REFUSED, 'PAYID' => '4711'); + + $directlinkHelperMock->processFeedback($order, $params); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackRefundWaiting() + { + $this->mockEmailHelper($this->never()); + + $paymentMock = $this->getModelMock('core/resource_transaction', array('save')); + $this->replaceByMock('model', 'core/resource_transaction', $paymentMock); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + + $creditMemo = $this->getModelMock('sales/order_creditmemo', array('_beforeSave')); + $order->getPayment()->setCreatedCreditMemo($creditMemo); + /** @var Netresearch_OPS_Helper_Directlink $directlinkHelperMock */ + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $cntBefore = $order->getStatusHistoryCollection()->count(); + + + $closure = function ($order, $params = array()) { + $order->getPayment()->setAdditionalInformation( + 'status', + Netresearch_OPS_Model_Status::REFUND_PENDING + ); + return $order->getPayment(); + }; + + $refundHelper = $this->getHelperMock('ops/order_refund', array('createRefund')); + $refundHelper->expects($this->any()) + ->method('createRefund') + ->will($this->returnCallback($closure)); + $this->replaceByMock('helper', 'ops/order_refund', $refundHelper); + $params = array('STATUS' => Netresearch_OPS_Model_Status::REFUND_PENDING, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + $cntAfter = $order->getStatusHistoryCollection()->count(); + $this->assertTrue($cntBefore < $cntAfter); + + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackRefundRefused() + { + $this->mockEmailHelper($this->never()); + + $transMock = $this->getModelMock('core/resource_transaction', array('save')); + $this->replaceByMock('model', 'core/resource_transaction', $transMock); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock( + 'ops/directlink', + array('isValidOpsRequest', 'closePaymentTransaction') + ); + $creditMemo = $this->getModelMock('sales/order_creditmemo', array('_beforeSave')); + $order->getPayment()->setCreatedCreditMemo($creditMemo); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $closure = function ($order, $params = array()) { + $order->getPayment()->setAdditionalInformation( + 'status', + Netresearch_OPS_Model_Status::REFUND_PENDING + ); + return $order->getPayment(); + }; + + $refundHelper = $this->getHelperMock('ops/order_refund', array('createRefund')); + $refundHelper->expects($this->any()) + ->method('createRefund') + ->will($this->returnCallback($closure)); + $this->replaceByMock('helper', 'ops/order_refund', $refundHelper); + $params = array('STATUS' => Netresearch_OPS_Model_Status::REFUND_REFUSED, 'PAYID' => '4711'); + + $directlinkHelperMock->processFeedback($order, $params); + + $this->assertEquals(Netresearch_OPS_Model_Status::REFUND_REFUSED, $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackVoidSuccess() + { + $this->mockEmailHelper($this->never()); + $this->mockOrderConfig(); + + $transMock = $this->getModelMock('core/resource_transaction', array('save')); + $this->replaceByMock('model', 'core/resource_transaction', $transMock); + + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + + + $closure = function ($order, $params = array()) { + $order->getPayment()->setAdditionalInformation('status', 6); + return $order->getPayment(); + }; + + $voidHelper = $this->getHelperMock('ops/order_void', array('acceptVoid')); + $voidHelper->expects($this->any()) + ->method('acceptVoid') + ->will($this->returnCallback($closure)); + $this->replaceByMock('helper', 'ops/order_void', $voidHelper); + $params = array('STATUS' => Netresearch_OPS_Model_Status::AUTHORIZED_AND_CANCELLED, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + $this->assertEquals($params['STATUS'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackVoidWaiting() + { + $this->mockEmailHelper($this->never()); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + /** @var Netresearch_OPS_Helper_Directlink $directlinkHelperMock */ + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $params = array('STATUS' => Netresearch_OPS_Model_Status::DELETION_WAITING, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + $this->assertNotEmpty($order->getPayment()->getMessage()); + + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackVoidRefused() + { + $this->mockEmailHelper($this->never()); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock( + 'ops/directlink', + array('isValidOpsRequest') + ); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $closure = function ($order, $params = array()) { + $order->getPayment()->setAdditionalInformation( + 'status', + Netresearch_OPS_Model_Status::DELETION_REFUSED + ); + return $order->getPayment(); + }; + + $refundHelper = $this->getHelperMock('ops/order_void', array('acceptVoid')); + $refundHelper->expects($this->any()) + ->method('acceptVoid') + ->will($this->returnCallback($closure)); + $this->replaceByMock('helper', 'ops/order_void', $refundHelper); + $params = array('STATUS' => Netresearch_OPS_Model_Status::DELETION_REFUSED, 'PAYID' => '4711'); + + + $directlinkHelperMock->processFeedback($order, $params); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackAuthorizeChanged() + { + // mail sending is triggered but getEmailSent takes effect + $this->mockEmailHelper($this->once()); + $this->mockOrderConfig(); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $cntBefore = $order->getStatusHistoryCollection()->count(); + + $params = array('STATUS' => Netresearch_OPS_Model_Status::AUTHORIZED, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + $cntAfter = $order->getStatusHistoryCollection()->count(); + $this->assertTrue($cntBefore < $cntAfter); + + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testProcessFeedbackAuthorizeKwixoAccepted() + { + $this->mockEmailHelper($this->once()); + $this->mockOrderConfig(); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(27); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $cntBefore = $order->getStatusHistoryCollection()->count(); + + + $closure = function ($order, $params = array()) { + $order->getPayment()->setAdditionalInformation( + 'status', Netresearch_OPS_Model_Status::AUTHORIZED + ); + return $order->getPayment(); + }; + + $paymentHelper = $this->getHelperMock('ops/payment', array('acceptOrder')); + $paymentHelper->expects($this->any()) + ->method('acceptOrder') + ->will($this->returnCallback($closure)); + $this->replaceByMock('helper', 'ops/payment', $paymentHelper); + + $params = array('STATUS' => Netresearch_OPS_Model_Status::AUTHORIZED, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + $this->assertEquals($params['STATUS'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage Can not handle status 4711. + */ + public function testProcessFeedbackUnknownStatus() + { + $this->mockEmailHelper($this->never()); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + + $params = array('STATUS' => 4711, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage Ingenico ePayments status 0, the action failed. + */ + public function testProcessFeedbackInvalidStatus() + { + $helperMock = $this->getHelperMock('ops', array('isAdminSession', 'sendTransactionalEmail')); + $helperMock->expects($this->once()) + ->method('isAdminSession') + ->will($this->returnValue(false)); + $helperMock->expects($this->never()) + ->method('sendTransactionalEmail'); + $this->replaceByMock('helper', 'ops', $helperMock); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + $order->getPayment()->setAdditionalInformation('status', 500); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $params = array('STATUS' => Netresearch_OPS_Model_Status::INVALID_INCOMPLETE, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage Ingenico ePayments status 0, the action failed. + */ + public function testProcessFeedbackInvalidStatusAsAdmin() + { + $helperMock = $this->getHelperMock('ops', array('isAdminSession', 'sendTransactionalEmail')); + $helperMock->expects($this->once()) + ->method('isAdminSession') + ->will($this->returnValue(true)); + $helperMock->expects($this->never()) + ->method('sendTransactionalEmail'); + $this->replaceByMock('helper', 'ops', $helperMock); + + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load(11); + $order->getPayment()->setAdditionalInformation('status', 500); + $directlinkHelperMock = $this->getHelperMock('ops/directlink', array('isValidOpsRequest')); + $directlinkHelperMock->expects($this->any()) + ->method('isValidOpsRequest') + ->will($this->returnValue(true)); + + $params = array('STATUS' => Netresearch_OPS_Model_Status::INVALID_INCOMPLETE, 'PAYID' => '4711'); + $directlinkHelperMock->processFeedback($order, $params); + + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Helper/DirectLinkTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Helper/DirectLinkTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Helper/KwixoTest.php b/app/code/community/Netresearch/OPS/Test/Helper/KwixoTest.php new file mode 100644 index 0000000..346b750 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/KwixoTest.php @@ -0,0 +1,132 @@ +helperMustThrowException(array()); + } + + public function testValidateKwixoconfigurationMappingContainsInvalidId() + { + $this->helperMustThrowException(array('od' => 1)); + } + + public function testValidateKwixoconfigurationMappingContainsEmptyId() + { + $this->helperMustThrowException(array('id' => '')); + } + + public function testValidateKwixoconfigurationMappingContainsNonNumericId() + { + $this->helperMustThrowException(array('id' => 'abc')); + } + + public function testValidateKwixoconfigurationMappingContainsNegativeId() + { + $this->helperMustThrowException(array('id' => -1)); + } + + public function testValidateKwixoconfigurationMappingContainsNoKwixoCategory( + ) + { + $this->helperMustThrowException(array('id' => 1)); + } + + public function testValidateKwixoconfigurationMappingContainsInvalidKwixoCategory( + ) + { + $this->helperMustThrowException( + array('id' => 1, 'kwixoCategory_id' => 666) + ); + } + + public function testValidateKwixoconfigurationMappingContainsNoCategory() + { + $this->helperMustThrowException( + array('id' => 1, 'kwixoCategory_id' => 1) + ); + } + + public function testValidateKwixoconfigurationMappingContainsNonNumericCategory( + ) + { + $this->helperMustThrowException( + array('id' => 1, 'kwixoCategory_id' => 1, 'category_id' => 'abc') + ); + } + + public function testValidateKwixoconfigurationMappingContainsNegativeCategory( + ) + { + $this->helperMustThrowException( + array('id' => 1, 'kwixoCategory_id' => 1, 'category_id' => -1) + ); + } + + public function testValidateKwixoconfigurationMappingContainsNegativeCategory2( + ) + { + $helper = Mage::helper('ops/kwixo'); + $helper->validateKwixoconfigurationMapping( + array('id' => 1, 'kwixoCategory_id' => 1, 'category_id' => 1) + ); + } + + + protected function helperMustThrowException($invalidData) + { + $helper = Mage::helper('ops/kwixo'); + $this->setExpectedException('Mage_Core_Exception'); + $helper->validateKwixoconfigurationMapping($invalidData); + } + + /** + * @loadFixture category_mapping + */ + public function testSaveKwixoConfigurationMapping() + { + $helperMock = $this->getHelperMock( + 'ops/kwixo', array('validateKwixoconfigurationMapping') + ); + $helperMock->saveKwixoconfigurationMapping( + array('id' => 1, 'category_id' => 666, 'kwixoCategory_id' => 777) + ); + $model = Mage::getModel('ops/kwixo_category_mapping')->load(1); + $this->assertEquals(666, $model->getCategoryId()); + $this->assertEquals(777, $model->getKwixoCategoryId()); + } + + /** + * @loadFixture category_mapping + */ + public function testSaveKwixoConfigurationMappingForSubCategories() + { + $helperMock = $this->getHelperMock( + 'ops/kwixo', array('validateKwixoconfigurationMapping') + ); + + $categoryMock = $this->getModelMock('catalog/category', array('load', 'getAllChildren')); + $categoryMock->expects($this->any()) + ->method('getAllChildren') + ->will($this->returnValue(array(11))); + $categoryMock->expects($this->any()) + ->method('load') + ->will($this->returnValue($categoryMock)); + $this->replaceByMock('model', 'catalog/category', $categoryMock); + + $helperMock->saveKwixoconfigurationMapping( + array('id' => 1, 'category_id' => 666, + 'kwixoCategory_id' => 777, 'applysubcat' => true) + ); + + $model = Mage::getModel('ops/kwixo_category_mapping')->load(1); + $this->assertEquals(666, $model->getCategoryId()); + $this->assertEquals(777, $model->getKwixoCategoryId()); + $model = Mage::getModel('ops/kwixo_category_mapping')->load(2); + $this->assertEquals(777, $model->getKwixoCategoryId()); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/KwixoTest/fixtures/category_mapping.yaml b/app/code/community/Netresearch/OPS/Test/Helper/KwixoTest/fixtures/category_mapping.yaml new file mode 100644 index 0000000..e18a796 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/KwixoTest/fixtures/category_mapping.yaml @@ -0,0 +1,11 @@ +tables: + ops/kwixo_category_mapping: + - id: 1 + kwixo_category_id: 10 + category_id: 10 + - id: 2 + kwixo_category_id: 1 + category_id: 11 + - id: 3 + kwixo_category_id: 2 + category_id: 11 \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/MobileDetectTest.php b/app/code/community/Netresearch/OPS/Test/Helper/MobileDetectTest.php new file mode 100644 index 0000000..402d98f --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/MobileDetectTest.php @@ -0,0 +1,77 @@ +detectorMock = $this->getMockBuilder('Mobile_Detect') + ->setMethods(array('isMobile', 'isTablet')) + ->getMock(); + $this->helper = Mage::helper('ops/mobileDetect'); + $this->helper->setDetector($this->detectorMock); + } + + /** + * @test + */ + public function testGetDeviceTypeMobile() + { + $this->detectorMock + ->expects($this->once()) + ->method('isMobile') + ->willReturn(true); + + $this->detectorMock + ->expects($this->once()) + ->method('isTablet') + ->willReturn(false); + + $this->assertEquals(Netresearch_OPS_Helper_MobileDetect::DEVICE_TYPE_MOBILE, $this->helper->getDeviceType()); + } + + /** + * @test + */ + public function testGetDeviceTypeTablet() + { + $this->detectorMock + ->expects($this->once()) + ->method('isMobile') + ->willReturn(false); + + $this->detectorMock + ->expects($this->once()) + ->method('isTablet') + ->willReturn(true); + + $this->assertEquals(Netresearch_OPS_Helper_MobileDetect::DEVICE_TYPE_TABLET, $this->helper->getDeviceType()); + } + + /** + * @test + */ + public function testGetDeviceTypeComputer() + { + $this->detectorMock + ->expects($this->once()) + ->method('isMobile') + ->willReturn(false); + + $this->detectorMock + ->expects($this->once()) + ->method('isTablet') + ->willReturn(false); + + $this->assertEquals(Netresearch_OPS_Helper_MobileDetect::DEVICE_TYPE_COMPUTER, $this->helper->getDeviceType()); + } +} + + diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Order/CaptureTest.php b/app/code/community/Netresearch/OPS/Test/Helper/Order/CaptureTest.php new file mode 100644 index 0000000..e8fe078 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/Order/CaptureTest.php @@ -0,0 +1,82 @@ + "foo"); + Mage::app()->getRequest()->setParam('invoice', $invoice); + $payment = new Varien_Object(); + $order = Mage::getModel('sales/order')->load(11); + $payment->setOrder($order); + // order base_grand_total == grand_total == 119.00 + + $expected = array( + "items" => $invoice["items"], + "operation" => Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_PARTIAL, + "type" => "partial", + "amount" => 100.00 + ); + + $payment->setBaseAmountPaidOnline(0.00); + $amount = 100.00; + $this->assertEquals($expected, $helper->prepareOperation($payment, $amount)); + + } + + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testOperationFullAndTypePartial() + { + $helper = Mage::helper('ops/order_capture'); + $invoice = array("items" => "foo"); + Mage::app()->getRequest()->setParam('invoice', $invoice); + $payment = new Varien_Object(); + $order = Mage::getModel('sales/order')->load(11); + $payment->setOrder($order); + // order base_grand_total == grand_total == 119.00 + + $expected = array( + "items" => $invoice["items"], + "operation" => Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_FULL, + "type" => "partial", + "amount" => 100.00 + ); + + $payment->setBaseAmountPaidOnline(19.00); + $amount = 100.00; + $this->assertEquals($expected, $helper->prepareOperation($payment, $amount)); + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testOperationFullAndTypeFull() + { + $helper = Mage::helper('ops/order_capture'); + $invoice = array("items" => "foo"); + Mage::app()->getRequest()->setParam('invoice', $invoice); + $payment = new Varien_Object(); + $order = Mage::getModel('sales/order')->load(11); + $payment->setOrder($order); + // order base_grand_total == grand_total == 119.00 + + $expected = array( + "items" => $invoice["items"], + "operation" => Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_FULL, + "type" => "full", + "amount" => 119.00 + ); + + $payment->setBaseAmountPaidOnline(0.00); + $amount = 119.00; + $this->assertEquals($expected, $helper->prepareOperation($payment, $amount)); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Order/CaptureTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Helper/Order/CaptureTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Order/RefundTest.php b/app/code/community/Netresearch/OPS/Test/Helper/Order/RefundTest.php new file mode 100644 index 0000000..c81835b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/Order/RefundTest.php @@ -0,0 +1,141 @@ +load(11); + $payment->setOrder($order); + $payment->setBaseAmountRefundedOnline(0.00); + $amount = 119.00; + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, $helper->determineOperationCode($payment, $amount)); + + // complete refund should lead to RFS + $order = Mage::getModel('sales/order')->load(16); + $payment->setOrder($order); + $payment->setBaseAmountRefundedOnline(0.00); + $amount = 19.99; + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, $helper->determineOperationCode($payment, $amount)); + + // partial refund should lead to RFD + $order = Mage::getModel('sales/order')->load(11); + $payment->setOrder($order); + $payment->setBaseAmountRefundedOnline(0.00); + $amount = 100.00; + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL, $helper->determineOperationCode($payment, $amount)); + + // partial refund + new amount to refund should lead to RFS + $order = Mage::getModel('sales/order')->load(11); + $payment->setOrder($order); + $payment->setBaseAmountRefundedOnline(19.00); + $amount = 100.00; + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, $helper->determineOperationCode($payment, $amount)); + + // partial refund + new amount to refund should lead to RFS + $order = Mage::getModel('sales/order')->load(16); + $payment->setOrder($order); + $payment->setBaseAmountRefundedOnline(17.98); + $amount = 2.01; + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, $helper->determineOperationCode($payment, $amount)); + + // partial refund + new amount to refund should lead to RFS + $order = Mage::getModel('sales/order')->load(16); + $payment->setOrder($order); + $payment->setBaseAmountRefundedOnline(17.98); + $amount = 2.00; + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, $helper->determineOperationCode($payment, $amount)); + + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testOperationPartialAndTypePartial() + { + $helper = Mage::helper('ops/order_refund'); + $creditmemo = array("creditmemo" => array("items" => "foo")); + $helper->setCreditMemoRequestParams($creditmemo); + $payment = new Varien_Object(); + $order = Mage::getModel('sales/order')->load(11); + $payment->setOrder($order); + // order base_grand_total == grand_total == 119.00 + + $expected = array( + "items" => $creditmemo["creditmemo"]["items"], + "operation" => Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL, + "type" => "partial", + "amount" => 100.00 + ); + + $payment->setBaseAmountRefundedOnline(0.00); + $amount = 100.00; + $this->assertEquals($expected, $helper->prepareOperation($payment, $amount)); + + } + + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testOperationFullAndTypePartial() + { + $helper = Mage::helper('ops/order_refund'); + $creditmemo = array("creditmemo" => array("items" => "foo")); + $helper->setCreditMemoRequestParams($creditmemo); + $payment = new Varien_Object(); + $order = Mage::getModel('sales/order')->load(11); + $payment->setOrder($order); + // order base_grand_total == grand_total == 119.00 + + $expected = array( + "items" => $creditmemo["creditmemo"]["items"], + "operation" => Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, + "type" => "partial", + "amount" => 100.00 + ); + + $payment->setBaseAmountRefundedOnline(19.00); + $amount = 100.00; + $this->assertEquals($expected, $helper->prepareOperation($payment, $amount)); + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testOperationFullAndTypeFull() + { + $helper = Mage::helper('ops/order_refund'); + $creditmemo = array("creditmemo" => array("items" => "foo")); + $helper->setCreditMemoRequestParams($creditmemo); + $payment = new Varien_Object(); + $order = Mage::getModel('sales/order')->load(11); + $payment->setOrder($order); + // order base_grand_total == grand_total == 119.00 + + $expected = array( + "items" => $creditmemo["creditmemo"]["items"], + "operation" => Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, + "type" => "full", + "amount" => 119.00 + ); + + $payment->setBaseAmountRefundedOnline(0.00); + $amount = 119.00; + $this->assertEquals($expected, $helper->prepareOperation($payment, $amount)); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Order/RefundTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Helper/Order/RefundTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Helper/OrderTest.php b/app/code/community/Netresearch/OPS/Test/Helper/OrderTest.php new file mode 100644 index 0000000..37dd1c5 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/OrderTest.php @@ -0,0 +1,137 @@ +devPrefix = Mage::getModel('ops/config')->getConfigData( + 'devprefix' + ); + parent::setUp(); + } + + + /** + * @loadFixture order.yaml + */ + public function testGetOpsOrderId() + { + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $helper = Mage::helper('ops/order'); + + $store->setConfig( + 'payment_services/ops/redirectOrderReference', + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID + ); + + $order = Mage::getModel('sales/order')->load(1); + $delimiter = $helper::DELIMITER; + $this->assertEquals( + $this->devPrefix . $order->getQuoteId(), + $helper->getOpsOrderId($order) + ); + + + $store->setConfig( + 'payment_services/ops/redirectOrderReference', + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID + ); + + $order = Mage::getModel('sales/order')->load(2); + $this->assertEquals( + $this->devPrefix . $delimiter . $order->getIncrementId(), + $helper->getOpsOrderId($order) + ); + + $store->setConfig( + 'payment_services/ops/redirectOrderReference', + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID + ); + $order = Mage::getModel('sales/order')->load(3); + $this->assertEquals( + $this->devPrefix . $order->getQuoteId(), + $helper->getOpsOrderId($order) + ); + + $store->setConfig( + 'payment_services/ops/redirectOrderReference', + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID + ); + $order = Mage::getModel('sales/order')->load(3); + $this->assertEquals( + $this->devPrefix . $order->getQuoteId(), + $helper->getOpsOrderId($order, false) + ); + } + + + /** + * @loadFixture order.yaml + */ + public function testGetOrder() + { + $helper = Mage::helper('ops/order'); + // old behaviour: load order from quote + $opsOrderId = $this->devPrefix . '5'; + $order = $helper->getOrder($opsOrderId); + $this->assertEquals(4, $order->getId()); + + // new behaviour + $delimiter = $helper::DELIMITER; + $opsOrderId = $this->devPrefix . $delimiter . 2000; + $order = $helper->getOrder($opsOrderId); + $this->assertEquals(2, $order->getId()); + + + } + + + /** + * @loadFixture order.yaml + */ + public function testGetQuote() + { + $order = Mage::getModel('sales/order')->load(1); + $quote = Mage::helper('ops/order')->getQuote($order->getQuoteId()); + $this->assertTrue($quote instanceof Mage_Sales_Model_Quote); + $this->assertEquals(1, $quote->getId()); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testCheckIfAddressAreSameWithSameAddressData() + { + $order = Mage::getModel('sales/order')->load(11); + $this->assertTrue( + (bool)Mage::helper('ops/order')->checkIfAddressesAreSame($order) + ); + + $order = Mage::getModel('sales/order')->load(27); + $this->assertFalse( + (bool)Mage::helper('ops/order')->checkIfAddressesAreSame($order) + ); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testCheckIfAddressAreSameWithDifferentAddressData() + { + $order = Mage::getModel('sales/order')->load(12); + $this->assertFalse( + (bool)Mage::helper('ops/order')->checkIfAddressesAreSame($order) + ); + } + + public function testSetDataHelper() + { + $dataHelper = $this->getHelperMock('ops/data'); + $helper = Mage::helper('ops/order'); + $helper->setDataHelper($dataHelper); + $this->assertEquals($dataHelper, $helper->getDataHelper()); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Helper/OrderTest/fixtures/order.yaml b/app/code/community/Netresearch/OPS/Test/Helper/OrderTest/fixtures/order.yaml new file mode 100644 index 0000000..68de179 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/OrderTest/fixtures/order.yaml @@ -0,0 +1,42 @@ +tables: + sales/order: + - entity_id: 1 + increment_id: 1000 + quote_id: 1 + + - entity_id: 2 + increment_id: 2000 + quote_id: 2 + + - entity_id: 3 + quote_id: 3 + + - id: 4 + entity_id: 4 + increment_id: 4000 + quote_id: 5 + + - id: 5 + entity_id: 5 + increment_id: 1001 + quote_id: 5 + + + sales/quote: + - id: 1 + entity_id: 1 + parent_id: 1 + + - id: 5 + entity_id: 5 + parent_id: 1 + + sales/order_payment: + - parent_id: 4 + method: 'ops_test' + + - parent_id: 2 + method: 'ops_test' + + - parent_id: 5 + method: 'ops_test' \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Payment/DirectLink/RequestTest.php b/app/code/community/Netresearch/OPS/Test/Helper/Payment/DirectLink/RequestTest.php new file mode 100644 index 0000000..69e1af9 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/Payment/DirectLink/RequestTest.php @@ -0,0 +1,89 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Helper_Payment_DirectLink_RequestTest extends EcomDev_PHPUnit_Test_Case_Controller +{ + + /** + * @loadFixture ../../../../../var/fixtures/orders.yaml + */ + public function testGetBaseParams() + { + $order = Mage::getModel('sales/order')->load(11); + $quote = Mage::getModel('sales/quote')->load($order->getQuoteId()); + $quote->getPayment()->setMethod('ops_cc'); + $configMock = $this->getModelMock('ops/config', array('canSubmitExtraParameter')); + $configMock->expects($this->any()) + ->method('canSubmitExtraParameter') + ->will($this->returnValue(false)); + $paramHelper = Mage::helper('ops/creditcard'); + $paramHelper->setConfig($configMock); + $paramHelper->getRequestHelper()->setConfig($configMock); + $params = $paramHelper->getDirectLinkRequestParams($quote, $order); + foreach ($this->getOwnerParams() as $ownerParam) { + $this->assertArrayNotHasKey($ownerParam, $params); + } + foreach ($this->getShippingParams() as $shippingParam) { + $this->assertArrayNotHasKey($shippingParam, $params); + } + $this->assertArrayHasKey('RTIMEOUT', $params); + } + + /** + * @loadFixture ../../../../../var/fixtures/orders.yaml + */ + public function testGetExtraParams() + { + $order = Mage::getModel('sales/order')->load(11); + $quote = Mage::getModel('sales/quote'); + $quote->getPayment()->setMethod('ops_cc'); + $configMock = $this->getModelMock('ops/config', array('canSubmitExtraParameter')); + $configMock->expects($this->any()) + ->method('canSubmitExtraParameter') + ->will($this->returnValue(true)); + $paramHelper = Mage::helper('ops/creditcard'); + $paramHelper->setConfig($configMock); + $paramHelper->getRequestHelper()->setConfig($configMock); + $params = $paramHelper->getDirectLinkRequestParams($quote, $order); + foreach ($this->getOwnerParams() as $ownerParam) { + $this->assertArrayHasKey($ownerParam, $params); + } + foreach ($this->getShippingParams() as $shippingParam) { + $this->assertArrayHasKey($shippingParam, $params); + } + $this->assertArrayHasKey('RTIMEOUT', $params); + + } + + protected function getOwnerParams() + { + return $ownerParams = array( + 'OWNERADDRESS', + 'OWNERTOWN', + 'OWNERZIP', + 'OWNERTELNO', + 'OWNERCTY', + 'ECOM_BILLTO_POSTAL_POSTALCODE', + ); + } + + protected function getShippingParams() + { + $paramValues = array( + 'ECOM_SHIPTO_POSTAL_NAME_FIRST', + 'ECOM_SHIPTO_POSTAL_NAME_LAST', + 'ECOM_SHIPTO_POSTAL_STREET_LINE1', + 'ECOM_SHIPTO_POSTAL_STREET_LINE2', + 'ECOM_SHIPTO_POSTAL_COUNTRYCODE', + 'ECOM_SHIPTO_POSTAL_CITY', + 'ECOM_SHIPTO_POSTAL_POSTALCODE' + ); + + return $paramValues; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Payment/DirectLink/RequestTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Helper/Payment/DirectLink/RequestTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Payment/RequestTest.php b/app/code/community/Netresearch/OPS/Test/Helper/Payment/RequestTest.php new file mode 100644 index 0000000..5eec80b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/Payment/RequestTest.php @@ -0,0 +1,323 @@ +getRequestHelper(); + $configMock = $this->getModelMock('ops/config', array('canSubmitExtraParameter')); + $configMock->expects($this->any()) + ->method('canSubmitExtraParameter') + ->will($this->returnValue(true)); + $helper->setConfig($configMock); + $params = $helper->extractShipToParameters($address, Mage::getModel('sales/quote')); + $this->assertTrue(is_array($params)); + foreach ($this->getShipToArrayKeys() as $key) { + $this->assertArrayHasKey($key, $params); + } + + $address->setFirstname('Hans'); + $address->setLastname('Wurst'); + $address->setStreet('Nonnenstrasse 11d'); + $address->setCountry('DE'); + $address->setCity('Leipzig'); + $address->setPostcode('04229'); + $params = $this->getRequestHelper()->extractShipToParameters($address, Mage::getModel('sales/quote')); + $this->assertEquals('Hans', $params['ECOM_SHIPTO_POSTAL_NAME_FIRST']); + $this->assertEquals('Wurst', $params['ECOM_SHIPTO_POSTAL_NAME_LAST']); + $this->assertEquals('Nonnenstrasse' , $params['ECOM_SHIPTO_POSTAL_STREET_LINE1']); + $this->assertEquals('Nonnenstrasse' , $params['ECOM_SHIPTO_POSTAL_STREET_LINE1']); + $this->assertEquals('', $params['ECOM_SHIPTO_POSTAL_STREET_LINE2']); + $this->assertEquals('DE', $params['ECOM_SHIPTO_POSTAL_COUNTRYCODE']); + $this->assertEquals('Leipzig', $params['ECOM_SHIPTO_POSTAL_CITY']); + $this->assertEquals('04229', $params['ECOM_SHIPTO_POSTAL_POSTALCODE']); + $this->assertEquals('11d', $params['ECOM_SHIPTO_POSTAL_STREET_NUMBER']); + + } + + public function testGetIsoRegionCodeWithIsoRegionCode() + { + $address = Mage::getModel('customer/address'); + $address->setRegionCode('SN'); + $address->setCountry('DE'); + $this->assertEquals('SN', $this->getRequestHelper()->getIsoRegionCode($address)); + } + + public function testGetIsoRegionCodeWithIsoRegionCodeContainingTheCountryCode() + { + $address = Mage::getModel('customer/address'); + $address->setRegionCode('ES-AB'); + $address->setCountry('ES'); + $this->assertEquals('AB', $this->getRequestHelper()->getIsoRegionCode($address)); + } + + + public function testGetIsoRegionCodeWithGermanMageRegionCode() + { + $address = Mage::getModel('customer/address'); + $address->setRegionCode('SAS'); + $address->setCountry('DE'); + $this->assertEquals('SN', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('NDS'); + $this->assertEquals('NI', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('THE'); + $this->assertEquals('TH', $this->getRequestHelper()->getIsoRegionCode($address)); + } + + public function testGetIsoRegionCodeWithAustrianMageRegionCode() + { + $address = Mage::getModel('customer/address'); + $address->setRegionCode('WI'); + $address->setCountry('AT'); + $this->assertEquals('9', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('NO'); + $this->assertEquals('3', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('VB'); + $this->assertEquals('8', $this->getRequestHelper()->getIsoRegionCode($address)); + } + + public function testGetIsoRegionCodeWithSpanishMageRegionCode() + { + $address = Mage::getModel('customer/address'); + $address->setRegionCode('A Coruсa'); + $address->setCountry('ES'); + $this->assertEquals('C', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('Barcelona'); + $this->assertEquals('B', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('Madrid'); + $this->assertEquals('M', $this->getRequestHelper()->getIsoRegionCode($address)); + } + + public function testGetIsoRegionCodeWithFinnishMageRegionCode() + { + $address = Mage::getModel('customer/address'); + $address->setRegionCode('Lappi'); + $address->setCountry('FI'); + $this->assertEquals('10', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('Etelä-Savo'); + $this->assertEquals('04', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('Itä-Uusimaa'); + $this->assertEquals('19', $this->getRequestHelper()->getIsoRegionCode($address)); + } + + public function testGetIsoRegionCodeWithLatvianMageRegionCode() + { + $address = Mage::getModel('customer/address'); + $address->setRegionCode('Ādažu novads'); + $address->setCountry('LV'); + $this->assertEquals('LV', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('Engures novads'); + $this->assertEquals('029', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('Viļakas novads'); + $this->assertEquals('108', $this->getRequestHelper()->getIsoRegionCode($address)); + } + + public function testGetIsoRegionCodeWithUnknownRegionCode() + { + $address = Mage::getModel('customer/address'); + $address->setRegionCode('DEFG'); + $address->setCountry('AB'); + $this->assertEquals('AB', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('DEF'); + $this->assertEquals('DEF', $this->getRequestHelper()->getIsoRegionCode($address)); + $address->setRegionCode('DF'); + $this->assertEquals('DF', $this->getRequestHelper()->getIsoRegionCode($address)); + } + + public function testGetTemplateParamsIframeMode() + { + $config = $this->getModelMock('ops/config', array('getConfigData')); + $config->expects($this->any()) + ->method('getConfigData') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_IFRAME)); + $helper = Mage::helper('ops/payment_request'); + $helper->setConfig($config); + + $params = $helper->getTemplateParams(); + + $this->assertArrayHasKey('PARAMPLUS', $params); + $this->assertEquals('IFRAME=1', $params['PARAMPLUS']); + $this->assertArrayHasKey('TITLE', $params); + $this->assertArrayNotHasKey('TP', $params); + } + + public function testGetTemplateParamsNoMode() + { + $config = $this->getModelMock('ops/config', array('getConfigData')); + $config->expects($this->any()) + ->method('getConfigData') + ->will($this->returnValue(null)); + $helper = Mage::helper('ops/payment_request'); + $helper->setConfig($config); + + $params = $helper->getTemplateParams(); + + $this->assertArrayNotHasKey('PARAMPLUS', $params); + $this->assertArrayNotHasKey('TITLE', $params); + $this->assertArrayNotHasKey('TP', $params); + + } + + public function testGetTemplateParamsRedirectMode() + { + $config = $this->getModelMock('ops/config', array('getConfigData')); + $config->expects($this->any()) + ->method('getConfigData') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_REDIRECT)); + $helper = Mage::helper('ops/payment_request'); + $helper->setConfig($config); + + $params = $helper->getTemplateParams(); + $this->assertArrayNotHasKey('PARAMPLUS', $params); + $this->assertArrayHasKey('TITLE', $params); + $this->assertArrayNotHasKey('TP', $params); + } + + public function testExtractOrderItemParametersWithAllItems() + { + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + + // setup one item + $item = Mage::getModel('sales/order_item'); + $item->setId(1); + $item->setName('Item'); + $item->setBasePriceInclTax(10.00); + $item->setQtyOrdered(1); + $item->setTaxPercent(19.00); + $item->setProductType(Mage_Catalog_Model_Product_Type::TYPE_SIMPLE); + + $order = $this->getModelMock('sales/order', array('getAllItems')); + $order->expects($this->once()) + ->method('getAllItems') + ->will($this->returnValue(array($item))); + + // prepare discount item + $order->setBaseDiscountAmount(1.00) + ->setCouponRuleName('DISCOUNT'); + + //prepare shipping Item + $order->setShippingDescription('SHIPPING') + ->setBaseShippingInclTax(5.00) + ->setIsVirtual(0); + + $helper = Mage::helper('ops/payment_request'); + + $formFields = $helper->extractOrderItemParameters($order); + + $this->assertArrayHasKey('ITEMID1', $formFields); + $this->assertArrayHasKey('ITEMID2', $formFields); + $this->assertArrayHasKey('ITEMID3', $formFields); + } + + public function testExtractOrderItemParametersWithNoItems() + { + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + + // setup one item + $item = Mage::getModel('sales/order_item'); + $item->setProductType(Mage_Catalog_Model_Product_Type::TYPE_BUNDLE); + + $order = $this->getModelMock('sales/order', array('getAllItems')); + $order->expects($this->once()) + ->method('getAllItems') + ->will($this->returnValue(array($item))); + + // prepare discount item + $order->setBaseDiscountAmount(0.00); + + //prepare shipping Item + $order->setIsVirtual(true); + + $helper = Mage::helper('ops/payment_request'); + + $formFields = $helper->extractOrderItemParameters($order); + + $this->assertArrayNotHasKey('ITEMID1', $formFields); + $this->assertArrayNotHasKey('ITEMID2', $formFields); + $this->assertArrayNotHasKey('ITEMID3', $formFields); + } + + public function testGetTemplateParamsTemplateMode() + { + $config = $this->getModelMock('ops/config', array('getConfigData')); + $config->expects($this->any()) + ->method('getConfigData') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_TEMPLATE)); + $helper = Mage::helper('ops/payment_request'); + $helper->setConfig($config); + + $params = $helper->getTemplateParams(); + $this->assertArrayNotHasKey('PARAMPLUS', $params); + $this->assertArrayNotHasKey('TITLE', $params); + $this->assertArrayHasKey('TP', $params); + + } + + public function testGetTemplateParamsInternalTemplateMode() + { + $config = $this->getModelMock('ops/config', array('getConfigData')); + $config->expects($this->any()) + ->method('getConfigData') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_MAGENTO_INTERNAL)); + $helper = Mage::helper('ops/payment_request'); + $helper->setConfig($config); + + $params = $helper->getTemplateParams(); + $this->assertArrayNotHasKey('PARAMPLUS', $params); + $this->assertArrayNotHasKey('TITLE', $params); + $this->assertArrayHasKey('TP', $params); + $this->assertEquals($config->getPayPageTemplate(), $params['TP']); + } + + public function testGetMandatoryRequestFieldsWithFPActiveOff() + { + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_cc'); + $order = Mage::getModel('sales/order'); + $order->setStoreId(0)->setPayment($payment); + $subject = Mage::helper('ops/payment_request'); + $sessionMock = $this->mockSession('customer/session', array('getData')); + $sessionMock->expects($this->any()) + ->method('getData') + ->with(Netresearch_OPS_Model_Payment_Abstract::FINGERPRINT_CONSENT_SESSION_KEY) + ->will($this->returnValue(true)); + + $params = $subject->getMandatoryRequestFields($order); + + $this->assertEquals(0, $params['FP_ACTIV']); + + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Helper/PaymentTest.php b/app/code/community/Netresearch/OPS/Test/Helper/PaymentTest.php new file mode 100644 index 0000000..bb6711b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/PaymentTest.php @@ -0,0 +1,708 @@ +_helper = Mage::helper('ops/payment'); + $this->store = Mage::app()->getStore(0)->load(0); + $this->store->resetConfig(); + } + + public function testIsPaymentAuthorizeType() + { + $this->assertTrue( + $this->_helper->isPaymentAuthorizeType( + Netresearch_OPS_Model_Status::AUTHORIZED + ) + ); + $this->assertTrue( + $this->_helper->isPaymentAuthorizeType( + Netresearch_OPS_Model_Status::AUTHORIZATION_WAITING + ) + ); + $this->assertTrue( + $this->_helper->isPaymentAuthorizeType( + Netresearch_OPS_Model_Status::AUTHORIZED_UNKNOWN + ) + ); + $this->assertTrue( + $this->_helper->isPaymentAuthorizeType( + Netresearch_OPS_Model_Status::WAITING_CLIENT_PAYMENT + ) + ); + $this->assertFalse($this->_helper->isPaymentAuthorizeType(0)); + } + + public function testIsPaymentCaptureType() + { + $this->assertTrue( + $this->_helper->isPaymentCaptureType( + Netresearch_OPS_Model_Status::PAYMENT_REQUESTED + ) + ); + $this->assertTrue( + $this->_helper->isPaymentCaptureType( + Netresearch_OPS_Model_Status::PAYMENT_PROCESSING + ) + ); + $this->assertTrue( + $this->_helper->isPaymentCaptureType( + Netresearch_OPS_Model_Status::PAYMENT_UNCERTAIN + ) + ); + $this->assertFalse($this->_helper->isPaymentCaptureType(0)); + } + + /** + * send no invoice mail if it is denied by configuration + */ + public function testSendNoInvoiceToCustomerIfDenied() + { + $this->store->setConfig('payment_services/ops/send_invoice', 0); + $this->assertFalse(Mage::getModel('ops/config')->getSendInvoice()); + $sentInvoice = $this->getModelMock( + 'sales/order_invoice', array('getEmailSent', 'sendEmail') + ); + $sentInvoice->expects($this->any()) + ->method('getEmailSent') + ->will($this->returnValue(false)); + $sentInvoice->expects($this->never()) + ->method('sendEmail'); + $this->_helper->sendInvoiceToCustomer($sentInvoice); + } + + /** + * send no invoice mail if it was already sent + */ + public function testSendNoInvoiceToCustomerIfAlreadySent() + { + $this->store->setConfig('payment_services/ops/send_invoice', 1); + $this->assertTrue(Mage::getModel('ops/config')->getSendInvoice()); + $someInvoice = $this->getModelMock( + 'sales/order_invoice', array('getEmailSent', 'sendEmail') + ); + $someInvoice->expects($this->any()) + ->method('getEmailSent') + ->will($this->returnValue(true)); + $someInvoice->expects($this->never()) + ->method('sendEmail'); + $this->_helper->sendInvoiceToCustomer($someInvoice); + } + + /** + * send invoice mail + */ + public function testSendInvoiceToCustomerIfEnabled() + { + $this->store->setConfig('payment_services/ops/send_invoice', 1); + $this->assertTrue(Mage::getModel('ops/config')->getSendInvoice()); + $anotherInvoice = $this->getModelMock( + 'sales/order_invoice', array('getEmailSent', 'sendEmail') + ); + $anotherInvoice->expects($this->any()) + ->method('getEmailSent') + ->will($this->returnValue(false)); + $anotherInvoice->expects($this->once()) + ->method('sendEmail') + ->with($this->equalTo(true)); + $this->_helper->sendInvoiceToCustomer($anotherInvoice); + } + + public function testPrepareParamsAndSort() + { + $params = array( + 'CVC' => '123', + 'CARDNO' => '4111111111111111', + 'CN' => 'JohnSmith', + 'PSPID' => 'test1', + 'ED' => '1212', + 'ACCEPTURL' => 'https=//www.myshop.com/ok.html', + 'EXCEPTIONURL' => 'https=//www.myshop.com/nok.html', + 'BRAND' => 'VISA', + ); + $sortedParams = array( + 'ACCEPTURL' => array('key' => 'ACCEPTURL', + 'value' => 'https=//www.myshop.com/ok.html'), + 'BRAND' => array('key' => 'BRAND', 'value' => 'VISA'), + 'CARDNO' => array('key' => 'CARDNO', + 'value' => '4111111111111111'), + 'CN' => array('key' => 'CN', 'value' => 'JohnSmith'), + 'CVC' => array('key' => 'CVC', 'value' => '123'), + 'ED' => array('key' => 'ED', 'value' => '1212'), + 'EXCEPTIONURL' => array('key' => 'EXCEPTIONURL', + 'value' => 'https=//www.myshop.com/nok.html'), + 'PSPID' => array('key' => 'PSPID', 'value' => 'test1'), + ); + $secret = 'Mysecretsig1875!?'; + $shaInSet + = 'ACCEPTURL=https=//www.myshop.com/ok.htmlMysecretsig1875!?BRAND=VISAMysecretsig1875!?' + . 'CARDNO=4111111111111111Mysecretsig1875!?CN=JohnSmithMysecretsig1875!?CVC=123Mysecretsig1875!?' + . 'ED=1212Mysecretsig1875!?EXCEPTIONURL=https=//www.myshop.com/nok.htmlMysecretsig1875!?' + . 'PSPID=test1Mysecretsig1875!?'; + $key = 'a28dc9fe69b63fe81da92471fefa80aca3f4851a'; + $this->assertEquals( + $sortedParams, $this->_helper->prepareParamsAndSort($params) + ); + $this->assertEquals( + $shaInSet, $this->_helper->getSHAInSet($params, $secret) + ); + $this->assertEquals($key, $this->_helper->shaCrypt($shaInSet, $secret)); + } + + public function testHandleUnknownStatus() + { + $order = $this->getModelMock('sales/order', array('save')); + $order->expects($this->any()) + ->method('save') + ->will($this->returnValue(true)); + $order->setState( + Mage_Sales_Model_Order::STATE_NEW, + Mage_Sales_Model_Order::STATE_NEW + ); + $statusHistoryCount = $order->getStatusHistoryCollection()->count(); + Mage::helper('ops/payment')->handleUnknownStatus($order); + $this->assertEquals( + Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, $order->getState() + ); + $this->assertTrue( + $statusHistoryCount < $order->getStatusHistoryCollection()->count() + ); + $statusHistoryCount = $order->getStatusHistoryCollection()->count(); + $order->setState( + Mage_Sales_Model_Order::STATE_PROCESSING, + Mage_Sales_Model_Order::STATE_PROCESSING + ); + + Mage::helper('ops/payment')->handleUnknownStatus($order); + $this->assertEquals( + Mage_Sales_Model_Order::STATE_PROCESSING, $order->getState() + ); + $this->assertTrue( + $statusHistoryCount < $order->getStatusHistoryCollection()->count() + ); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testGetBaseGrandTotalFromSalesObject() + { + $helper = Mage::helper('ops/payment'); + $order = Mage::getModel('sales/order')->load(14); + $amount = $helper->getBaseGrandTotalFromSalesObject($order); + $this->assertEquals($order->getBaseGrandTotal(), $amount); + $order = Mage::getModel('sales/order')->load(15); + $amount = $helper->getBaseGrandTotalFromSalesObject($order); + $this->assertEquals($order->getBaseGrandTotal(), $amount); + $quote = Mage::getModel('sales/quote')->load(1); + $amount = $helper->getBaseGrandTotalFromSalesObject($quote); + $this->assertEquals($quote->getBaseGrandTotal(), $amount); + $quote = Mage::getModel('sales/quote')->load(2); + $amount = $helper->getBaseGrandTotalFromSalesObject($quote); + $this->assertEquals($quote->getBaseGrandTotal(), $amount); + $someOtherObject = new Varien_Object(); + $this->setExpectedException('Mage_Core_Exception'); + $helper->getBaseGrandTotalFromSalesObject($someOtherObject); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testSaveOpsRefundOperationCodeToPayment() + { + $order = Mage::getModel('sales/order')->load(11); + $payment = $order->getPayment(); + $helper = Mage::helper('ops/payment'); + + // no last refund operation code is set if an empty string is passed + $helper->saveOpsRefundOperationCodeToPayment($payment, ''); + $this->assertFalse( + array_key_exists( + 'lastRefundOperationCode', $payment->getAdditionalInformation() + ) + ); + + // no last refund operation code is set if it's no refund operation code + $helper->saveOpsRefundOperationCodeToPayment( + $payment, Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_FULL + ); + $this->assertFalse( + array_key_exists( + 'lastRefundOperationCode', $payment->getAdditionalInformation() + ) + ); + + // last ops refund code is present if a valid refund code is passed + $helper->saveOpsRefundOperationCodeToPayment( + $payment, Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL + ); + $this->assertTrue( + array_key_exists( + 'lastRefundOperationCode', $payment->getAdditionalInformation() + ) + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL, + $payment->getAdditionalInformation('lastRefundOperationCode') + ); + + // last ops refund code is present if a valid refund code is passed and will override a previous one + $helper->saveOpsRefundOperationCodeToPayment( + $payment, Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL + ); + $this->assertTrue( + array_key_exists( + 'lastRefundOperationCode', $payment->getAdditionalInformation() + ) + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_FULL, + $payment->getAdditionalInformation('lastRefundOperationCode') + ); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testSetCanRefundToPayment() + { + + $helper = Mage::helper('ops/payment'); + $order = Mage::getModel('sales/order')->load(11); + $payment = $order->getPayment(); + $helper->setCanRefundToPayment($payment); + $this->assertFalse( + array_key_exists('canRefund', $payment->getAdditionalInformation()) + ); + + $order = Mage::getModel('sales/order')->load(15); + $payment = $order->getPayment(); + $helper->setCanRefundToPayment($payment); + $this->assertTrue($payment->getAdditionalInformation('canRefund')); + + $order = Mage::getModel('sales/order')->load(16); + $payment = $order->getPayment(); + $helper->setCanRefundToPayment($payment); + $this->assertFalse($payment->getAdditionalInformation('canRefund')); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + * + */ + public function testSetPaymentTransactionInformation() + { + $dataMock = $this->getHelperMock('ops/data', array('isAdminSession')); + $dataMock->expects($this->any()) + ->method('isAdminSession') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/data', $dataMock); + + $order = Mage::getModel('sales/order')->load(15); + $reflectionClass = new ReflectionClass( + get_class( + Mage::helper('ops/payment') + ) + ); + $method = $reflectionClass->getMethod( + 'setPaymentTransactionInformation' + ); + $method->setAccessible(true); + $paymentHelper = Mage::helper('ops/payment'); + $params = array( + 'PAYID' => '0815', + 'STATUS' => 9 + ); + $method->invoke( + $paymentHelper, $order->getPayment(), $params, 'accept' + ); + $this->assertEquals( + '0815', $order->getPayment()->getAdditionalInformation('paymentId') + ); + $this->assertEquals( + 9, $order->getPayment()->getAdditionalInformation('status') + ); + + $params = array( + 'PAYID' => '0815', + 'STATUS' => 9, + 'ACCEPTANCE' => '' + ); + $method->invoke( + $paymentHelper, $order->getPayment(), $params, 'accept' + ); + $this->assertEquals( + '0815', $order->getPayment()->getAdditionalInformation('paymentId') + ); + $this->assertEquals( + '', $order->getPayment()->getAdditionalInformation('acceptance') + ); + + $params = array( + 'PAYID' => '0815', + 'STATUS' => 9, + 'ACCEPTANCE' => 'Akzeptanz' + ); + $method->invoke( + $paymentHelper, $order->getPayment(), $params, 'accept' + ); + $this->assertEquals( + '0815', $order->getPayment()->getAdditionalInformation('paymentId') + ); + $this->assertEquals( + 'Akzeptanz', + $order->getPayment()->getAdditionalInformation('acceptance') + ); + + $params = array( + 'PAYID' => '0815', + 'STATUS' => 9, + 'ACCEPTANCE' => 'Akzeptanz', + 'HTML_ANSWER' => '3D Secure', + 'BRAND' => 'Brand' + ); + $method->invoke( + $paymentHelper, $order->getPayment(), $params, 'accept' + ); + $this->assertEquals( + '0815', $order->getPayment()->getAdditionalInformation('paymentId') + ); + $this->assertEquals( + 'Akzeptanz', + $order->getPayment()->getAdditionalInformation('acceptance') + ); + $this->assertEquals( + '3D Secure', + $order->getPayment()->getAdditionalInformation('HTML_ANSWER') + ); + $this->assertEquals( + 'Brand', + $order->getPayment()->getAdditionalInformation('CC_BRAND') + ); + } + + /** + * @param int $opsStatus Incoming postBack status + * @param bool $sendMail Indicates whether opsStatus should trigger order confirmation mail + * @param string $feedbackStatus Indicates the route that the customer should get redirected to + * + * @loadFixture ../../../var/fixtures/orders.yaml + * @dataProvider applyStateForOrderProvider + */ + public function testApplyStateForOrder($opsStatus, $sendMail, $feedbackStatus) + { + $this->mockEmailHelper($this->exactly(intval($sendMail))); + $this->mockOrderConfig(); + + $helperMock = $this->getHelperMock('ops', array('isAdminSession', 'sendTransactionalEmail')); + $helperMock->expects($this->any()) + ->method('isAdminSession') + ->will($this->returnValue(false)); + $helperMock->expects($this->any()) + ->method('sendTransactionalEmail') + ->will($this->returnArgument(0)); + $this->replaceByMock('helper', 'ops', $helperMock); + + /** @var Netresearch_OPS_Helper_Payment $paymenthelperMock */ + $paymenthelperMock = $this->getHelperMock( + 'ops/payment', array( + 'acceptOrder', 'waitOrder', 'declineOrder', 'cancelOrder', 'handleException', + ) + ); + + $order = Mage::getModel('sales/order')->load(19); + $this->assertEquals( + $feedbackStatus, + $paymenthelperMock->applyStateForOrder($order, array('STATUS' => $opsStatus)) + ); + } + + public function applyStateForOrderProvider() + { + return array( + // assertion for WAITING_FOR_IDENTIFICATION = 46 + array( + $opsStatus = Netresearch_OPS_Model_Status::WAITING_FOR_IDENTIFICATION, + $sendMail = false, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT, + ), + // assertion for AUTHORIZED = 5 + array( + $opsStatus = Netresearch_OPS_Model_Status::AUTHORIZED, + $sendMail = true, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT, + ), + // assertion for AUTHORIZED_WAITING_EXTERNAL_RESULT = 50 + array( + $opsStatus = Netresearch_OPS_Model_Status::AUTHORIZED_WAITING_EXTERNAL_RESULT, + $sendMail = true, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT, + ), + // assertion for AUTHORIZATION_WAITING = 51 + array( + $opsStatus = Netresearch_OPS_Model_Status::AUTHORIZATION_WAITING, + $sendMail = true, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT, + ), + // assertion for AUTHORIZED_UNKNOWN = 52 + array( + $opsStatus = Netresearch_OPS_Model_Status::AUTHORIZED_UNKNOWN, + $sendMail = true, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_EXCEPTION, + ), + // assertion for WAITING_CLIENT_PAYMENT = 41 + array( + $opsStatus = Netresearch_OPS_Model_Status::WAITING_CLIENT_PAYMENT, + $sendMail = true, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT, + ), + // assertion for PAYMENT_REQUESTED = 9 + array( + $opsStatus = Netresearch_OPS_Model_Status::PAYMENT_REQUESTED, + $sendMail = true, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT, + ), + // assertion for PAYMENT_PROCESSING = 91 + array( + $opsStatus = Netresearch_OPS_Model_Status::PAYMENT_PROCESSING, + $sendMail = true, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_ACCEPT, + ), + // assertion for AUTHORISATION_DECLINED = 2 + array( + $opsStatus = Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED, + $sendMail = true, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_DECLINE, + ), + // assertion for PAYMENT_REFUSED = 93 + array( + $opsStatus = Netresearch_OPS_Model_Status::PAYMENT_REFUSED, + $sendMail = true, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_DECLINE, + ), + // assertion for CANCELED_BY_CUSTOMER = 1 + array( + $opsStatus = Netresearch_OPS_Model_Status::CANCELED_BY_CUSTOMER, + $sendMail = false, + $feedbackStatus = Netresearch_OPS_Model_Status_Feedback::OPS_ORDER_FEEDBACK_STATUS_CANCEL, + ), + ); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testForceAuthorize() + { + $helper = Mage::helper('ops/payment'); + $reflectionClass = new ReflectionClass(get_class($helper)); + $method = $reflectionClass->getMethod("forceAuthorize"); + $method->setAccessible(true); + + $order = Mage::getModel('sales/order')->load(11); + $this->assertFalse($method->invoke($helper, $order)); + + $order = Mage::getModel('sales/order')->load(27); + $this->assertTrue($method->invoke($helper, $order)); + + $order = Mage::getModel('sales/order')->load(28); + $this->assertTrue($method->invoke($helper, $order)); + + // $order = Mage::getModel('sales/order')->load(29); + // $this->assertTrue($method->invoke($helper, $order)); + } + + public function testCheckIfCCisInCheckoutMethodsFalse() + { + $testMethod = $this->getProtectedMethod($this->_helper, 'checkIfCCisInCheckoutMethods'); + $paymentMethods = new Varien_Object(); + $paymentMethods->setCode('ops_iDeal'); + $this->assertFalse($testMethod->invoke($this->_helper, array($paymentMethods))); + + + } + + public function testCheckIfCCisInCheckoutMethodsTrue() + { + $testMethod = $this->getProtectedMethod($this->_helper, 'checkIfCCisInCheckoutMethods'); + $paymentMethods = new Varien_Object(); + $paymentMethods->setCode('ops_cc'); + $this->assertTrue($testMethod->invoke($this->_helper, array($paymentMethods))); + + + } + + public function testAddCCForZeroAmountCheckout() + { + $block = new Mage_Payment_Block_Form_Container(); + $method = new Varien_Object(); + $method->setCode('ops_ideal'); + $block->setData('methods', array($method)); + $quote = Mage::getModel('sales/quote'); + $block->setQuote($quote); + + $featureModelMock = $this->getModelMock( + 'ops/payment_features_zeroAmountAuth', array('isCCAndZeroAmountAuthAllowed') + ); + $featureModelMock->expects($this->any()) + ->method('isCCAndZeroAmountAuthAllowed') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/payment_features_zeroAmountAuth', $featureModelMock); + + $this->_helper->addCCForZeroAmountCheckout($block); + + $methods = $block->getMethods(); + $this->assertTrue($methods[1] instanceof Netresearch_OPS_Model_Payment_Cc); + $this->assertFalse($methods[0] instanceof Netresearch_OPS_Model_Payment_Cc); + + } + + protected function getProtectedMethod($class, $method) + { + $reflection_class = new ReflectionClass(get_class($class)); + $method = $reflection_class->getMethod($method); + $method->setAccessible(true); + + return $method; + } + + public function testIsInlinePaymentWithOrderIdIsTrueForInlineCcWithOrderId() + { + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference')); + + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID)); + $this->replaceByMock('singleton', 'ops/config', $configMock); + + $ccMock = $this->getModelMock('ops/payment_cc', array('hasBrandAliasInterfaceSupport')); + $ccMock->expects($this->once()) + ->method('hasBrandAliasInterfaceSupport') + ->will($this->returnValue(true)); + + + $payment = $this->getModelMock('payment/info', array('getMethodInstance')); + $payment->expects($this->any()) + ->method('getMethodInstance') + ->will($this->returnValue($ccMock)); + + $this->assertTrue(Mage::helper('ops/payment')->isInlinePaymentWithOrderId($payment)); + } + + public function testIsInlinePaymentWithOrderIdIsFalseForRedirectCcWithOrderId() + { + $ccMock = $this->getModelMock( + 'ops/payment_cc', array('getConfigPaymentAction', 'hasBrandAliasInterfaceSupport') + ); + $ccMock->expects($this->any()) + ->method('getConfigPaymentAction') + ->will($this->returnValue('authorize')); + $ccMock->expects($this->once()) + ->method('hasBrandAliasInterfaceSupport') + ->will($this->returnValue(false)); + + + $payment = $this->getModelMock('payment/info', array('getMethodInstance')); + $payment->expects($this->any()) + ->method('getMethodInstance') + ->will($this->returnValue($ccMock)); + + $this->assertFalse(Mage::helper('ops/payment')->isInlinePaymentWithOrderId($payment)); + } + + public function testIsInlinePaymentWithOrderIdIsFalseIfQuoteIdIsConfigured() + { + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference')); + + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID)); + $this->replaceByMock('singleton', 'ops/config', $configMock); + + $ccMock = $this->getModelMock('ops/payment_cc', array('hasBrandAliasInterfaceSupport', 'getConfig')); + $ccMock->expects($this->once()) + ->method('hasBrandAliasInterfaceSupport') + ->will($this->returnValue(true)); + + + $payment = $this->getModelMock('payment/info', array('getMethodInstance')); + $payment->expects($this->any()) + ->method('getMethodInstance') + ->will($this->returnValue($ccMock)); + + $this->assertFalse(Mage::helper('ops/payment')->isInlinePaymentWithOrderId($payment)); + } + + public function testIsInlinePaymentWithOrderIdIsFalseIfQuoteIdIsConfiguredForDirectDebit() + { + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference')); + + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID)); + $this->replaceByMock('singleton', 'ops/config', $configMock); + + $payment = $this->getModelMock('payment/info', array('getMethodInstance')); + $payment->expects($this->any()) + ->method('getMethodInstance') + ->will($this->returnValue(Mage::getModel('ops/payment_directDebit'))); + + $this->assertFalse(Mage::helper('ops/payment')->isInlinePaymentWithOrderId($payment)); + } + + public function testIsInlinePaymentWithOrderIdIsTrueIfOrderIdIsConfiguredForDirectDebit() + { + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference')); + + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID)); + $this->replaceByMock('singleton', 'ops/config', $configMock); + + + $payment = $this->getModelMock('payment/info', array('getMethodInstance')); + $payment->expects($this->any()) + ->method('getMethodInstance') + ->will($this->returnValue(Mage::getModel('ops/payment_directDebit'))); + + $this->assertTrue(Mage::helper('ops/payment')->isInlinePaymentWithOrderId($payment)); + } + + public function testIsInlinePaymentWithQuoteId() + { + $directDebitMock = $this->getModelMock('ops/payment_directDebit', array('getConfigPaymentAction')); + $directDebitMock->expects($this->once()) + ->method('getConfigPaymentAction') + ->will($this->returnValue('')); + + $payment = $this->getModelMock('payment/info', array('getMethodInstance')); + $payment->expects($this->any()) + ->method('getMethodInstance') + ->will($this->returnValue($directDebitMock)); + + $this->assertTrue(Mage::helper('ops/payment')->isInlinePaymentWithQuoteId($payment)); + } + + public function testSetInvoicesToPaid() + { + /** @var Mage_Sales_Model_Resource_Order_Invoice_Collection $invoiceCollection */ + $invoiceCollection = $this->getResourceModelMock('sales/order_invoice_collection', array('save')); + $invoiceCollection->addItem(Mage::getModel('sales/order_invoice')); + $order = $this->getModelMock('sales/order', array('save', 'getInvoiceCollection')); + $order->expects($this->any()) + ->method('getInvoiceCollection') + ->will($this->returnValue($invoiceCollection)); + Mage::helper('ops/payment')->setInvoicesToPaid($order); + foreach ($order->getInvoiceCollection() as $invoice) { + $this->assertEquals(Mage_Sales_Model_Order_Invoice::STATE_PAID, $invoice->getState()); + } + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Helper/PaymentTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Helper/PaymentTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Helper/QuoteTest.php b/app/code/community/Netresearch/OPS/Test/Helper/QuoteTest.php new file mode 100644 index 0000000..8a655ea --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/QuoteTest.php @@ -0,0 +1,176 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +class Netresearch_OPS_Test_Helper_QuoteTest extends EcomDev_PHPUnit_Test_Case +{ + + public function testGetDataHelper() + { + $this->assertTrue(Mage::helper('ops/quote')->getDataHelper() instanceof Netresearch_OPS_Helper_Data); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testCleanUpOldPaymentInformation() + { + $payment = Mage::getModel('sales/quote_payment')->load(3); + $this->assertArrayHasKey('cvc', $payment->getAdditionalInformation()); + Mage::helper('ops/quote')->cleanUpOldPaymentInformation(); + $payment = Mage::getModel('sales/quote_payment')->load(3); + $this->assertArrayNotHasKey( + 'cvc', $payment->getAdditionalInformation() + ); + } + + public function testGetQuoteCurrency() + { + $quote = Mage::getModel('sales/quote'); + $this->assertEquals( + Mage::app()->getStore($quote->getStoreId())->getBaseCurrencyCode(), + Mage::helper('ops/quote')->getQuoteCurrency($quote) + ); + $forcedCurrency = new Varien_Object(); + $forcedCurrency->setCode('USD'); + $quote->setForcedCurrency($forcedCurrency); + $this->assertEquals( + 'USD', Mage::helper('ops/quote')->getQuoteCurrency($quote) + ); + } + + public function testGetPaymentActionForAuthorize() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $order->setPayment($payment); + $modelMock = $this->getModelMock( + 'ops/config', array('getPaymentAction') + ); + $modelMock->expects($this->any()) + ->method('getPaymentAction') + ->will( + $this->returnValue( + 'Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_ACTION' + ) + ); + $this->replaceByMock('model', 'ops/config', $modelMock); + $helper = Mage::helper('ops/quote'); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_ACTION, + $helper->getPaymentAction($order) + ); + $order->getPayment()->setAdditionalInformation( + 'PM', 'Direct Debits DE' + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_ACTION, + $helper->getPaymentAction($order) + ); + $order->getPayment()->setAdditionalInformation( + 'PM', 'Direct Debits AT' + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_ACTION, + $helper->getPaymentAction($order) + ); + $order->getPayment()->setAdditionalInformation( + 'PM', 'Direct Debits NL' + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_ACTION, + $helper->getPaymentAction($order) + ); + } + + public function testGetPaymentActionForAuthorizeCapture() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $order->setPayment($payment); + $modelMock = $this->getModelMock( + 'ops/config', array('getPaymentAction') + ); + $modelMock->expects($this->any()) + ->method('getPaymentAction') + ->will($this->returnValue('authorize_capture')); + $this->replaceByMock('model', 'ops/config', $modelMock); + $helper = Mage::helper('ops/quote'); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION, + $helper->getPaymentAction($order) + ); + $order->getPayment()->setAdditionalInformation( + 'PM', 'Direct Debits DE' + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION, + $helper->getPaymentAction($order) + ); + $order->getPayment()->setAdditionalInformation( + 'PM', 'Direct Debits AT' + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION, + $helper->getPaymentAction($order) + ); + $order->getPayment()->setAdditionalInformation( + 'PM', 'Direct Debits NL' + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_DIRECTDEBIT_NL, + $helper->getPaymentAction($order) + ); + } + + + public function testGetQuoteWithAdminSession() + { + $fakeQuote = $this->getModelMock('sales/quote'); + $this->replaceByMock('model', 'sales/quote', $fakeQuote); + + $sessionMock = $this->getModelMockBuilder('adminhtml/session_quote') + ->disableOriginalConstructor() // This one removes session_start and other methods usage + ->setMethods(null) // Enables original methods usage, because by default it overrides all methods + ->getMock(); + $sessionMock->setData('quote', $fakeQuote); + $this->replaceByMock('singleton', 'adminhtml/session_quote', $sessionMock); + $helper = Mage::helper('ops/quote'); + $this->assertEquals($fakeQuote, $helper->getQuote()); + } + + public function testGetQuoteWithCheckoutSession() + { + Mage::app()->setCurrentStore(1); + + $fakeQuote = $this->getModelMock('sales/quote', array('setStoreId')); + $fakeQuote->expects($this->any()) + ->method('setStoreId') + ->will($this->returnValue($fakeQuote)); + $this->replaceByMock('model', 'sales/quote', $fakeQuote); + + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $sessionMock->setData('quote', $fakeQuote); + $this->replaceByMock('singleton', 'checkout/session', $sessionMock); + /** @var Netresearch_OPS_Helper_Quote $helper */ + $helper = Mage::helper('ops/quote'); + $this->assertEquals($fakeQuote, $helper->getQuote()); + } + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/QuoteTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Helper/QuoteTest/fixtures/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/QuoteTest/fixtures/.gitkeep @@ -0,0 +1 @@ + diff --git a/app/code/community/Netresearch/OPS/Test/Helper/SubscriptionTest.php b/app/code/community/Netresearch/OPS/Test/Helper/SubscriptionTest.php new file mode 100644 index 0000000..11bc23a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/SubscriptionTest.php @@ -0,0 +1,116 @@ + + */ +?> +setId(123) + ->setTrialPeriodUnit(true); + $id = Netresearch_OPS_Helper_Subscription::SUBSCRIPTION_PREFIX . '123'; + $this->assertEquals($id, $subject->generateSubscriptionId($profile)); + $id .= Netresearch_OPS_Helper_Subscription::TRIAL_SUFFIX; + $this->assertEquals($id, $subject->generateSubscriptionId($profile, true)); + } + + public function testIsSubscriptionFeedback() + { + $subject = Mage::helper('ops/subscription'); + $id = Netresearch_OPS_Helper_Subscription::SUBSCRIPTION_PREFIX . '123' + . Netresearch_OPS_Helper_Subscription::TRIAL_SUFFIX; + $params = array( + 'orderID' => $id + ); + $this->assertTrue($subject->isSubscriptionFeedback($params)); + $params['orderID'] = '#10123123120'; + $this->assertFalse($subject->isSubscriptionFeedback($params)); + + } + + /** + * @loadFixture profile.yaml + */ + public function testGetProfileForSubscription() + { + $subject = Mage::helper('ops/subscription'); + + $id = $subject::SUBSCRIPTION_PREFIX . 1; + + $profile = $subject->getProfileForSubscription($id); + $this->assertEquals(1, $profile->getId()); + + $id = $subject::SUBSCRIPTION_PREFIX . 1 . $subject::TRIAL_SUFFIX; + + $profile = $subject->getProfileForSubscription($id); + $this->assertEquals(1, $profile->getId()); + } + /** + * @loadFixture profile.yaml + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage Could find no subscription for id 2 + */ + public function testGetProfileForSubscriptionWithException() + { + $subject = Mage::helper('ops/subscription'); + + $id = $subject::SUBSCRIPTION_PREFIX . 2; + + $profile = $subject->getProfileForSubscription($id); + $this->assertEquals(1, $profile->getId()); + } + + public function testIsTrialFeedback() + { + $subject = Mage::helper('ops/subscription'); + $id = Netresearch_OPS_Helper_Subscription::SUBSCRIPTION_PREFIX . '123'; + $this->assertFalse($subject->isTrialFeedback($id)); + $id .= Netresearch_OPS_Helper_Subscription::TRIAL_SUFFIX; + $this->assertTrue($subject->isTrialFeedback($id)); + + } + + public function testGetBillingDayForPeriodUnit() + { + $subject = Mage::helper('ops/subscription'); + + $this->assertEquals(1, $subject->getBillingDayForPeriodUnit('abc')); + $this->assertEquals(1, $subject->getBillingDayForPeriodUnit(Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_WEEK)); + $this->assertEquals(15, $subject->getBillingDayForPeriodUnit(Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_MONTH)); + } + + +} + diff --git a/app/code/community/Netresearch/OPS/Test/Helper/SubscriptionTest/fixtures/profile.yaml b/app/code/community/Netresearch/OPS/Test/Helper/SubscriptionTest/fixtures/profile.yaml new file mode 100644 index 0000000..dc4f2ed --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/SubscriptionTest/fixtures/profile.yaml @@ -0,0 +1,11 @@ +tables: + customer/entity: + - entity_id: 1 + + sales/recurring_profile: + - profile_id: 1 + state: 'active' + customer_id: 1 + store_id: 1 + method_code: 'ops_recurring_cc' + reference_id: 'SUB-1' diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Validation/Checkout/StepTest.php b/app/code/community/Netresearch/OPS/Test/Helper/Validation/Checkout/StepTest.php new file mode 100644 index 0000000..62f319b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/Validation/Checkout/StepTest.php @@ -0,0 +1,44 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + + + +class Netresearch_OPS_Test_Helper_Validation_Checkout_StepTest extends EcomDev_PHPUnit_Test_Case +{ + + protected $stepHelper = null; + + public function setUp() + { + parent::setUp(); + $this->stepHelper = Mage::helper('ops/validation_checkout_step'); + } + + public function testHelperReturnsNoStep() + { + $this->assertEquals('', $this->stepHelper->getStep(array())); + $this->assertEquals('', $this->stepHelper->getStep(array('SOME_OTHER_FIELD'))); + } + + public function testHelperReturnsBillingStep() + { + $expectedStep = Netresearch_OPS_Helper_Validation_Checkout_Step::BILLING_STEP; + $this->assertEquals($expectedStep, $this->stepHelper->getStep(array('OWNERADDRESS'))); + $this->assertEquals($expectedStep, $this->stepHelper->getStep(array('OWNERADDRESS', 'SOME_OTHER_FIELD'))); + $this->assertEquals($expectedStep, $this->stepHelper->getStep(array('ECOM_SHIPTO_POSTAL_STATE', 'SOME_OTHER_FIELD', 'CN'))); + } + + public function testHelperReturnsShippingStep() + { + $expectedStep = Netresearch_OPS_Helper_Validation_Checkout_Step::SHIPPING_STEP; + $this->assertEquals($expectedStep, $this->stepHelper->getStep(array('ECOM_SHIPTO_POSTAL_STATE'))); + $this->assertEquals($expectedStep, $this->stepHelper->getStep(array('ECOM_SHIPTO_POSTAL_STATE', 'SOME_OTHER_FIELD'))); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Validation/ResultTest.php b/app/code/community/Netresearch/OPS/Test/Helper/Validation/ResultTest.php new file mode 100644 index 0000000..03557a5 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/Validation/ResultTest.php @@ -0,0 +1,94 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Helper_Validation_ResultTest extends EcomDev_PHPUnit_Test_Case +{ + + protected $validationResultHelper = null; + + public function setUp() + { + parent::setUp(); + Mage::unregister('_helper/ops/validation_result'); + $this->validationResultHelper = Mage::helper('ops/validation_result'); + } + + public function testBaseErroneousFields() + { + $quote = Mage::getModel('sales/quote'); + $messages = array('foo', 'bar'); + $result = $this->validationResultHelper->getValidationFailedResult($messages, $quote); + $this->assertArrayHasKey('error', $result); + $this->assertArrayHasKey('goto_section', $result); + $this->assertArrayHasKey('opsError', $result); + $this->assertArrayHasKey('fields', $result); + } + + public function testGetValidationFailedResultWithFieldMapping() + { + $quote = Mage::getModel('sales/quote'); + $configMock = $this->getModelMock('ops/config', array('getFrontendFieldMapping')); + $configMock->expects($this->once()) + ->method('getFrontendFieldMapping') + ->will($this->returnValue(array('foo' => 'bar'))); + $this->validationResultHelper->setConfig($configMock); + $this->validationResultHelper->setFormBlock(Mage::app()->getLayout()->createBlock('ops/form')); + $messages = array('foo' => 'bar'); + $result = $this->validationResultHelper->getValidationFailedResult($messages, $quote); + $this->assertArrayHasKey('fields', $result); + $this->assertArrayHasKey('bar', $result['fields']); + } + + /** + * @loadFixture quotes + */ + public function testGetValidationFailedResultWithExistingAddress() + { + $quote = Mage::getModel('sales/quote')->load(1); + $checkoutStepHelperMock = $this->getHelperMock('ops/validation_checkout_step', array('getStep')); + $checkoutStepHelperMock->expects($this->exactly(2)) + ->method('getStep') + ->will($this->onConsecutiveCalls('billing', 'shipping')); + + $configMock = $this->getModelMock('ops/config', array('getFrontendFieldMapping')); + $configMock->expects($this->exactly(2)) + ->method('getFrontendFieldMapping') + ->will($this->returnValue(array('foo' => 'bar'))); + $this->validationResultHelper->setConfig($configMock); + + $messages = array('foo' => 'bar'); + $this->validationResultHelper->setCheckoutStepHelper($checkoutStepHelperMock); + $this->validationResultHelper->setFormBlock(Mage::app()->getLayout()->createBlock('ops/form')); + $result = $this->validationResultHelper->getValidationFailedResult($messages, $quote); + $this->assertArrayHasKey('fields', $result); + $this->assertArrayHasKey('billing-address-select', $result['fields']); + + $result = $this->validationResultHelper->getValidationFailedResult($messages, $quote); + $this->assertArrayHasKey('fields', $result); + $this->assertArrayHasKey('shipping-address-select', $result['fields']); + } + + public function testCleanResult() + { + $quote = Mage::getModel('sales/quote'); + $messages = array('foo' => 'bar'); + $prevResult = array('update_section' => 'foo'); + + $configMock = $this->getModelMock('ops/config', array('getFrontendFieldMapping')); + $configMock->expects($this->once()) + ->method('getFrontendFieldMapping') + ->will($this->returnValue(array('foo' => 'bar'))); + $this->validationResultHelper->setConfig($configMock); + $this->validationResultHelper->setFormBlock(Mage::app()->getLayout()->createBlock('ops/form')); + + $this->validationResultHelper->setResult($prevResult); + $result = $this->validationResultHelper->getValidationFailedResult($messages, $quote); + $this->assertArrayNotHasKey('update_section', $result); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/Validation/ResultTest/fixtures/quotes.yaml b/app/code/community/Netresearch/OPS/Test/Helper/Validation/ResultTest/fixtures/quotes.yaml new file mode 100644 index 0000000..1e3415c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/Validation/ResultTest/fixtures/quotes.yaml @@ -0,0 +1,42 @@ +tables: + sales/quote: + - entity_id: 1 + increment_id: 100000001 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + sales/quote_address: + - entity_id: 42 + quote_id: 1 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: '12345678' + + - entity_id: 43 + quote_id: 1 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: '12345678' \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Helper/VersionTest.php b/app/code/community/Netresearch/OPS/Test/Helper/VersionTest.php new file mode 100644 index 0000000..a27aeb8 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Helper/VersionTest.php @@ -0,0 +1,92 @@ +helper = Mage::helper('ops/version'); + } + + public function testGetVersionForEditionEE() + { + $helperMethod = $this->getProtectedMethod($this->helper, 'getVersionForEdition'); + $this->assertEquals( + Netresearch_OPS_Helper_Version::CAN_USE_APPLICABLE_FOR_QUOTE_EE_MINOR, + $helperMethod->invoke($this->helper, 'Enterprise') + ); + } + + public function testGetVersionForEditionCE() + { + $helperMethod = $this->getProtectedMethod($this->helper, 'getVersionForEdition'); + $this->assertEquals( + Netresearch_OPS_Helper_Version::CAN_USE_APPLICABLE_FOR_QUOTE_CE_MINOR, + $helperMethod->invoke($this->helper, 'Community') + ); + } + + public function testGetVersionForEditionDefaultCE() + { + $helperMethod = $this->getProtectedMethod($this->helper, 'getVersionForEdition'); + $this->assertEquals( + Netresearch_OPS_Helper_Version::CAN_USE_APPLICABLE_FOR_QUOTE_CE_MINOR, + $helperMethod->invoke($this->helper, null) + ); + } + + public function testCanUseApplicableForQuoteForEE() + { + $helperMock = $this->getHelperMock('ops/version', array('getVersionInfo')); + $helperMock->expects($this->any()) + ->method('getVersionInfo') + ->will($this->returnValue(array('minor' => '14'))); + + $this->assertTrue($helperMock->canUseApplicableForQuote('Enterprise')); + + $helperMock = $this->getHelperMock('ops/version', array('getVersionInfo')); + $helperMock->expects($this->any()) + ->method('getVersionInfo') + ->will($this->returnValue(array('minor' => '13'))); + + $this->assertFalse($helperMock->canUseApplicableForQuote('Enterprise')); + } + + public function testCanUseApplicableForQuoteForCE() + { + $helperMock = $this->getHelperMock('ops/version', array('getVersionInfo')); + $helperMock->expects($this->any()) + ->method('getVersionInfo') + ->will($this->returnValue(array('minor' => '8'))); + + $this->assertTrue($helperMock->canUseApplicableForQuote('Community')); + + $helperMock = $this->getHelperMock('ops/version', array('getVersionInfo')); + $helperMock->expects($this->any()) + ->method('getVersionInfo') + ->will($this->returnValue(array('minor' => '7'))); + + $this->assertFalse($helperMock->canUseApplicableForQuote('Community')); + + + } + + public function testGetVersionInfo() + { + $helperMethod = $this->getProtectedMethod($this->helper, 'getVersionInfo'); + $this->assertEquals(Mage::getVersionInfo(), $helperMethod->invoke($this->helper)); + + } + + protected function getProtectedMethod($class, $method) + { + $reflection_class = new ReflectionClass(get_class($class)); + $method = $reflection_class->getMethod($method); + $method->setAccessible(true); + return $method; + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Model/AliasTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Model/AliasTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Model/Api/DirectLinkShaTest.php b/app/code/community/Netresearch/OPS/Test/Model/Api/DirectLinkShaTest.php new file mode 100644 index 0000000..2c440c1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Api/DirectLinkShaTest.php @@ -0,0 +1,44 @@ +_model = Mage::getModel('ops/api_directlink'); + $this->_shaKey = 'ksdf239sdnkvs2e9'; + } + + public function testShaGenerationWithoutSpecialChars() + { + $params = array( + 'ALIAS' => 'foo' + ); + $expected = $params; + $expected['SHASIGN'] = '44194456a31b8ea1de461612b19f7255732438d5'; + $this->assertEquals($expected, $this->_model->getEncodedParametersWithHash($params, $this->_shaKey, 0)); + } + + public function testShaGenerationWithSpecialChars() + { + $params = array( + 'AMOUNT' => '36980', + 'CARDNO' => '257354109BLZ86010090', + 'CN' => 'André Herrn', + 'CURRENCY' => 'EUR', + 'ED' => '9999', + 'OPERATION' => 'SAL', + 'ORDERID' => '20190', + 'PM' => 'Direct Debits DE', + 'PSPID' => 'NRMAGENTO', + 'PSWD' => 'magento1', + 'USERID' => 'NRMAGENTO1API', + ); + $expected = $params; + $expected['SHASIGN'] = 'eb95f7d66879e9801fdbdf75095ce23147202c30'; + $result = $this->_model->getEncodedParametersWithHash($params, $this->_shaKey, 0); + $this->assertEquals($expected, $result); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Api/DirectLinkTest.php b/app/code/community/Netresearch/OPS/Test/Model/Api/DirectLinkTest.php new file mode 100644 index 0000000..f843684 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Api/DirectLinkTest.php @@ -0,0 +1,253 @@ +_model = Mage::getModel('ops/api_directlink'); + } + + public function testCallInvalidUrl() + { + $response = $this->_model->call(array(), 'http://localhost'); + $this->assertInternalType('string', $response); + } + + public function testType() + { + $this->assertInstanceOf('Netresearch_OPS_Model_Api_DirectLink', $this->_model); + } + + public function testXmlParser() + { + $xmlExample = + ' + + '; + $arrParams = $this->_model->getParamArrFromXmlString($xmlExample); + $this->assertEquals("1121212", $arrParams['orderID']); + $this->assertEquals("232", $arrParams['PAYID']); + $this->assertEquals("0", $arrParams['PAYIDSUB']); + $this->assertEquals("5", $arrParams['NCSTATUS']); + $this->assertEquals("50001111", $arrParams['NCERROR']); + $this->assertEquals("Some of the data entered is incorrect. Please retry.", $arrParams['NCERRORPLUS']); + $this->assertEquals("", $arrParams['ACCEPTANCE']); + $this->assertEquals("0", $arrParams['STATUS']); + $this->assertEquals("", $arrParams['amount']); + $this->assertEquals("", $arrParams['currency']); + } + + public function testXmlParserWithHtmlAnswer() + { + $xmlExample = + ' + + PGZvcm0gbmFtZT0iZG93bmxvYWRmb3JtM0QiIGFjdGlvbj0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9UZXN0XzNEX0FDUy5hc3AiIG1ldGhvZD0icG9zdCI+DQo8Tk9TQ1JJUFQ+DQpKYXZhU2NyaXB0IGlzIGN1cnJlbnRseSBkaXNhYmxlZCBvciBpcyBub3Qgc3VwcG9ydGVkIGJ5IHlvdXIgYnJvd3Nlci48YnI+DQpQbGVhc2UgY2xpY2sgb24gdGhlICZxdW90O0NvbnRpbnVlJnF1b3Q7IGJ1dHRvbiB0byBjb250aW51ZSB0aGUgcHJvY2Vzc2luZyBvZiB5b3VyIDMtRCBzZWN1cmUgdHJhbnNhY3Rpb24uPGJyPg0KPGlucHV0IGNsYXNzPSJuY29sIiB0eXBlPSJzdWJtaXQiIHZhbHVlPSJDb250aW51ZSIgaWQ9InN1Ym1pdDEiIG5hbWU9InN1Ym1pdDEiIC8+DQo8L05PU0NSSVBUPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ1NSRktFWSIgdmFsdWU9IjA0MzA0MzI4NkE0M0ZDM0YyRDhFMDFCOUM2MzYwRTA1Qzg5NkZEMzYiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJDU1JGVFMiIHZhbHVlPSIyMDExMDkxMjE2MjQ1MCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNTUkZTUCIgdmFsdWU9Ii9uY29sL3Rlc3Qvb3JkZXJkaXJlY3QuYXNwIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iUGFSZXEiIHZhbHVlPSI8P3htbCB2ZXJzaW9uPSZxdW90OzEuMCZxdW90Oz8+PFRocmVlRFNlY3VyZT48TWVzc2FnZSBpZD0mcXVvdDsxMjMmcXVvdDs+PFBBUmVxPjx2ZXJzaW9uPjEuMDI8L3ZlcnNpb24+PE1lcmNoYW50PjxtZXJJRD5OUk1BR0VOVE8zPC9tZXJJRD48bmFtZT5OZXRyZXNlYXJjaCBHbWJIICZhbXA7YW1wOyBDby5LRzwvbmFtZT48dXJsPmh0dHA6Ly93d3cubmV0cmVzZWFyY2guZGU8L3VybD48L01lcmNoYW50PjxQdXJjaGFzZT48eGlkPjExNjI4OTgzPC94aWQ+PGFtb3VudD4xMjY4Ljc1PC9hbW91bnQ+PHB1cmNoQW1vdW50PjEyNjguNzU8L3B1cmNoQW1vdW50PjxjdXJyZW5jeT5FVVI8L2N1cnJlbmN5PjwvUHVyY2hhc2U+PENIPjxhY2N0SUQ+NDAwMDAwWFhYWFhYMDAwMjwvYWNjdElEPjxleHBpcnk+MDExNjwvZXhwaXJ5PjxzZWxCcmFuZD48L3NlbEJyYW5kPjwvQ0g+PC9QQVJlcT48L01lc3NhZ2U+PC9UaHJlZURTZWN1cmU+DQoiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJUZXJtVXJsIiB2YWx1ZT0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9vcmRlcl9BM0RTLmFzcCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9Ik1EIiB2YWx1ZT0iTUFJTldQVEVTVDAwMDAxMTYyODk4MzAxKjEwNzUyNjIiIC8+DQo8L2Zvcm0+DQo8Zm9ybSBtZXRob2Q9InBvc3QiIGFjdGlvbj0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9vcmRlcl9hZ3JlZS5hc3AiIG5hbWU9InVwbG9hZEZvcm0zRCI+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJDU1JGS0VZIiB2YWx1ZT0iMDZGM0MzMUQ2RkI1MzIzODg4NjhFRjlGNTA5RUNGNzlBQzIwRDJGMyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNTUkZUUyIgdmFsdWU9IjIwMTEwOTEyMTYyNDUwIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ1NSRlNQIiB2YWx1ZT0iL25jb2wvdGVzdC9vcmRlcmRpcmVjdC5hc3AiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJicmFuZGluZyIgdmFsdWU9Ik9nb25lIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0icGF5aWQiIHZhbHVlPSIxMTYyODk4MyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InN0b3JlYWxpYXMiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJoYXNoX3BhcmFtIiB2YWx1ZT0iOTFBMzA1MjFEMEI0QTA1MEFBRDkzRDM5RDY2RkEyM0Y5OEIzRDQ4RCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InhpZF8zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InN0YXR1c18zRCIgdmFsdWU9IlhYIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iZWNpXzNEIiB2YWx1ZT0iNyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImNhcmRudW1iZXIiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJFY29tX1BheW1lbnRfQ2FyZF9WZXJpZmljYXRpb24iIHZhbHVlPSIqMTA3NTI2MiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNWQ0ZsYWciIHZhbHVlPSIxIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iY2F2dl8zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImNhdnZhbGdvcml0aG1fM0QiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJzaWduYXR1cmVPS18zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9Imhhc2hfcGFyYW1fM0QiIHZhbHVlPSIwMzAzREZDMkI1OTM0MjZCQTExRkQ5RjJBNkQ0NDk5ODEwN0JGN0YzIiAvPg0KPC9mb3JtPg0KPFNDUklQVCBMQU5HVUFHRT0iSmF2YXNjcmlwdCIgRk9SPSJ3aW5kb3ciIEVWRU5UPSJvbkxvYWQiPg0KdmFyIHBvcHVwV2luOw0KdmFyIHN1Ym1pdHBvcHVwV2luID0gMDsNCg0KZnVuY3Rpb24gTG9hZFBvcHVwKCkgew0KCWlmIChzZWxmLm5hbWUgPT0gbnVsbCkJew0KCQlzZWxmLm5hbWUgPSAib2dvbmVNYWluIjsNCgl9DQoJcG9wdXBXaW4gPSB3aW5kb3cub3BlbignYWJvdXQ6YmxhbmsnLCAncG9wdXBXaW4nLCAnaGVpZ2h0PTQwMCwgd2lkdGg9MzkwLCBzdGF0dXM9eWVzLCBkZXBlbmRlbnQ9bm8sIHNjcm9sbGJhcnM9eWVzLCByZXNpemFibGU9bm8nKTsNCglpZiAocG9wdXBXaW4gIT0gbnVsbCkgew0KCQlpZiAgKCFwb3B1cFdpbiB8fCBwb3B1cFdpbi5jbG9zZWQpIHsNCgkJCXJldHVybiAxOw0KCQl9IGVsc2Ugew0KCQkJaWYgKCFwb3B1cFdpbi5vcGVuZXIgfHwgcG9wdXBXaW4ub3BlbmVyID09IG51bGwpIHsNCgkJCQlwb3B1cFdpbi5vcGVuZXIgPSBzZWxmOw0KCQkJfQ0KCQkJc2VsZi5kb2N1bWVudC5mb3Jtcy5kb3dubG9hZGZvcm0zRC50YXJnZXQgPSAncG9wdXBXaW4nOw0KCQkJaWYgKHN1Ym1pdHBvcHVwV2luID09IDEpIHsNCgkJCQlzZWxmLmRvY3VtZW50LmZvcm1zLmRvd25sb2FkZm9ybTNELnN1Ym1pdCgpOw0KCQkJfQ0KCQkJcG9wdXBXaW4uZm9jdXMoKTsNCgkJCXJldHVybiAwOw0KCQl9DQoJfSBlbHNlIHsNCgkJcmV0dXJuIDE7DQoJfQ0KfQ0KCXNlbGYuZG9jdW1lbnQuZm9ybXMuZG93bmxvYWRmb3JtM0Quc3VibWl0KCk7DQovLy0tPg0KPC9TQ1JJUFQ+DQo= + '; + $arrParams = $this->_model->getParamArrFromXmlString($xmlExample); + $this->assertEquals("1121212", $arrParams['orderID']); + $this->assertEquals("232", $arrParams['PAYID']); + $this->assertEquals("PGZvcm0gbmFtZT0iZG93bmxvYWRmb3JtM0QiIGFjdGlvbj0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9UZXN0XzNEX0FDUy5hc3AiIG1ldGhvZD0icG9zdCI+DQo8Tk9TQ1JJUFQ+DQpKYXZhU2NyaXB0IGlzIGN1cnJlbnRseSBkaXNhYmxlZCBvciBpcyBub3Qgc3VwcG9ydGVkIGJ5IHlvdXIgYnJvd3Nlci48YnI+DQpQbGVhc2UgY2xpY2sgb24gdGhlICZxdW90O0NvbnRpbnVlJnF1b3Q7IGJ1dHRvbiB0byBjb250aW51ZSB0aGUgcHJvY2Vzc2luZyBvZiB5b3VyIDMtRCBzZWN1cmUgdHJhbnNhY3Rpb24uPGJyPg0KPGlucHV0IGNsYXNzPSJuY29sIiB0eXBlPSJzdWJtaXQiIHZhbHVlPSJDb250aW51ZSIgaWQ9InN1Ym1pdDEiIG5hbWU9InN1Ym1pdDEiIC8+DQo8L05PU0NSSVBUPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ1NSRktFWSIgdmFsdWU9IjA0MzA0MzI4NkE0M0ZDM0YyRDhFMDFCOUM2MzYwRTA1Qzg5NkZEMzYiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJDU1JGVFMiIHZhbHVlPSIyMDExMDkxMjE2MjQ1MCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNTUkZTUCIgdmFsdWU9Ii9uY29sL3Rlc3Qvb3JkZXJkaXJlY3QuYXNwIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iUGFSZXEiIHZhbHVlPSI8P3htbCB2ZXJzaW9uPSZxdW90OzEuMCZxdW90Oz8+PFRocmVlRFNlY3VyZT48TWVzc2FnZSBpZD0mcXVvdDsxMjMmcXVvdDs+PFBBUmVxPjx2ZXJzaW9uPjEuMDI8L3ZlcnNpb24+PE1lcmNoYW50PjxtZXJJRD5OUk1BR0VOVE8zPC9tZXJJRD48bmFtZT5OZXRyZXNlYXJjaCBHbWJIICZhbXA7YW1wOyBDby5LRzwvbmFtZT48dXJsPmh0dHA6Ly93d3cubmV0cmVzZWFyY2guZGU8L3VybD48L01lcmNoYW50PjxQdXJjaGFzZT48eGlkPjExNjI4OTgzPC94aWQ+PGFtb3VudD4xMjY4Ljc1PC9hbW91bnQ+PHB1cmNoQW1vdW50PjEyNjguNzU8L3B1cmNoQW1vdW50PjxjdXJyZW5jeT5FVVI8L2N1cnJlbmN5PjwvUHVyY2hhc2U+PENIPjxhY2N0SUQ+NDAwMDAwWFhYWFhYMDAwMjwvYWNjdElEPjxleHBpcnk+MDExNjwvZXhwaXJ5PjxzZWxCcmFuZD48L3NlbEJyYW5kPjwvQ0g+PC9QQVJlcT48L01lc3NhZ2U+PC9UaHJlZURTZWN1cmU+DQoiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJUZXJtVXJsIiB2YWx1ZT0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9vcmRlcl9BM0RTLmFzcCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9Ik1EIiB2YWx1ZT0iTUFJTldQVEVTVDAwMDAxMTYyODk4MzAxKjEwNzUyNjIiIC8+DQo8L2Zvcm0+DQo8Zm9ybSBtZXRob2Q9InBvc3QiIGFjdGlvbj0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9vcmRlcl9hZ3JlZS5hc3AiIG5hbWU9InVwbG9hZEZvcm0zRCI+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJDU1JGS0VZIiB2YWx1ZT0iMDZGM0MzMUQ2RkI1MzIzODg4NjhFRjlGNTA5RUNGNzlBQzIwRDJGMyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNTUkZUUyIgdmFsdWU9IjIwMTEwOTEyMTYyNDUwIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ1NSRlNQIiB2YWx1ZT0iL25jb2wvdGVzdC9vcmRlcmRpcmVjdC5hc3AiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJicmFuZGluZyIgdmFsdWU9Ik9nb25lIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0icGF5aWQiIHZhbHVlPSIxMTYyODk4MyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InN0b3JlYWxpYXMiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJoYXNoX3BhcmFtIiB2YWx1ZT0iOTFBMzA1MjFEMEI0QTA1MEFBRDkzRDM5RDY2RkEyM0Y5OEIzRDQ4RCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InhpZF8zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InN0YXR1c18zRCIgdmFsdWU9IlhYIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iZWNpXzNEIiB2YWx1ZT0iNyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImNhcmRudW1iZXIiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJFY29tX1BheW1lbnRfQ2FyZF9WZXJpZmljYXRpb24iIHZhbHVlPSIqMTA3NTI2MiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNWQ0ZsYWciIHZhbHVlPSIxIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iY2F2dl8zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImNhdnZhbGdvcml0aG1fM0QiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJzaWduYXR1cmVPS18zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9Imhhc2hfcGFyYW1fM0QiIHZhbHVlPSIwMzAzREZDMkI1OTM0MjZCQTExRkQ5RjJBNkQ0NDk5ODEwN0JGN0YzIiAvPg0KPC9mb3JtPg0KPFNDUklQVCBMQU5HVUFHRT0iSmF2YXNjcmlwdCIgRk9SPSJ3aW5kb3ciIEVWRU5UPSJvbkxvYWQiPg0KdmFyIHBvcHVwV2luOw0KdmFyIHN1Ym1pdHBvcHVwV2luID0gMDsNCg0KZnVuY3Rpb24gTG9hZFBvcHVwKCkgew0KCWlmIChzZWxmLm5hbWUgPT0gbnVsbCkJew0KCQlzZWxmLm5hbWUgPSAib2dvbmVNYWluIjsNCgl9DQoJcG9wdXBXaW4gPSB3aW5kb3cub3BlbignYWJvdXQ6YmxhbmsnLCAncG9wdXBXaW4nLCAnaGVpZ2h0PTQwMCwgd2lkdGg9MzkwLCBzdGF0dXM9eWVzLCBkZXBlbmRlbnQ9bm8sIHNjcm9sbGJhcnM9eWVzLCByZXNpemFibGU9bm8nKTsNCglpZiAocG9wdXBXaW4gIT0gbnVsbCkgew0KCQlpZiAgKCFwb3B1cFdpbiB8fCBwb3B1cFdpbi5jbG9zZWQpIHsNCgkJCXJldHVybiAxOw0KCQl9IGVsc2Ugew0KCQkJaWYgKCFwb3B1cFdpbi5vcGVuZXIgfHwgcG9wdXBXaW4ub3BlbmVyID09IG51bGwpIHsNCgkJCQlwb3B1cFdpbi5vcGVuZXIgPSBzZWxmOw0KCQkJfQ0KCQkJc2VsZi5kb2N1bWVudC5mb3Jtcy5kb3dubG9hZGZvcm0zRC50YXJnZXQgPSAncG9wdXBXaW4nOw0KCQkJaWYgKHN1Ym1pdHBvcHVwV2luID09IDEpIHsNCgkJCQlzZWxmLmRvY3VtZW50LmZvcm1zLmRvd25sb2FkZm9ybTNELnN1Ym1pdCgpOw0KCQkJfQ0KCQkJcG9wdXBXaW4uZm9jdXMoKTsNCgkJCXJldHVybiAwOw0KCQl9DQoJfSBlbHNlIHsNCgkJcmV0dXJuIDE7DQoJfQ0KfQ0KCXNlbGYuZG9jdW1lbnQuZm9ybXMuZG93bmxvYWRmb3JtM0Quc3VibWl0KCk7DQovLy0tPg0KPC9TQ1JJUFQ+DQo=", $arrParams['HTML_ANSWER']); + } + + + public function testWithNoXmlResponse() + { + $xmlExample = ''; + $this->assertTrue($this->setExpectedException('PHPUnit_Framework_ExpectationFailedException')); + $arrParams = $this->_model->getParamArrFromXmlString($xmlExample); + $xmlExample = 'N'; + $arrParams = $this->_model->getParamArrFromXmlString($xmlExample); + } + + /** + * @expectedException Mage_Core_Exception + */ + public function testIsInvalidResponseWithEmptyData() + { + $this->_model->checkResponse(null); + } + + public function testIsValidResponse() + { + $this->assertTrue(is_null($this->_model->checkResponse(array('NCERROR' => 0)))); + } + + public function testGetResponseParamsWithValidCallResponse() + { + $xmlExample = + ' + + PGZvcm0gbmFtZT0iZG93bmxvYWRmb3JtM0QiIGFjdGlvbj0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9UZXN0XzNEX0FDUy5hc3AiIG1ldGhvZD0icG9zdCI+DQo8Tk9TQ1JJUFQ+DQpKYXZhU2NyaXB0IGlzIGN1cnJlbnRseSBkaXNhYmxlZCBvciBpcyBub3Qgc3VwcG9ydGVkIGJ5IHlvdXIgYnJvd3Nlci48YnI+DQpQbGVhc2UgY2xpY2sgb24gdGhlICZxdW90O0NvbnRpbnVlJnF1b3Q7IGJ1dHRvbiB0byBjb250aW51ZSB0aGUgcHJvY2Vzc2luZyBvZiB5b3VyIDMtRCBzZWN1cmUgdHJhbnNhY3Rpb24uPGJyPg0KPGlucHV0IGNsYXNzPSJuY29sIiB0eXBlPSJzdWJtaXQiIHZhbHVlPSJDb250aW51ZSIgaWQ9InN1Ym1pdDEiIG5hbWU9InN1Ym1pdDEiIC8+DQo8L05PU0NSSVBUPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ1NSRktFWSIgdmFsdWU9IjA0MzA0MzI4NkE0M0ZDM0YyRDhFMDFCOUM2MzYwRTA1Qzg5NkZEMzYiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJDU1JGVFMiIHZhbHVlPSIyMDExMDkxMjE2MjQ1MCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNTUkZTUCIgdmFsdWU9Ii9uY29sL3Rlc3Qvb3JkZXJkaXJlY3QuYXNwIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iUGFSZXEiIHZhbHVlPSI8P3htbCB2ZXJzaW9uPSZxdW90OzEuMCZxdW90Oz8+PFRocmVlRFNlY3VyZT48TWVzc2FnZSBpZD0mcXVvdDsxMjMmcXVvdDs+PFBBUmVxPjx2ZXJzaW9uPjEuMDI8L3ZlcnNpb24+PE1lcmNoYW50PjxtZXJJRD5OUk1BR0VOVE8zPC9tZXJJRD48bmFtZT5OZXRyZXNlYXJjaCBHbWJIICZhbXA7YW1wOyBDby5LRzwvbmFtZT48dXJsPmh0dHA6Ly93d3cubmV0cmVzZWFyY2guZGU8L3VybD48L01lcmNoYW50PjxQdXJjaGFzZT48eGlkPjExNjI4OTgzPC94aWQ+PGFtb3VudD4xMjY4Ljc1PC9hbW91bnQ+PHB1cmNoQW1vdW50PjEyNjguNzU8L3B1cmNoQW1vdW50PjxjdXJyZW5jeT5FVVI8L2N1cnJlbmN5PjwvUHVyY2hhc2U+PENIPjxhY2N0SUQ+NDAwMDAwWFhYWFhYMDAwMjwvYWNjdElEPjxleHBpcnk+MDExNjwvZXhwaXJ5PjxzZWxCcmFuZD48L3NlbEJyYW5kPjwvQ0g+PC9QQVJlcT48L01lc3NhZ2U+PC9UaHJlZURTZWN1cmU+DQoiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJUZXJtVXJsIiB2YWx1ZT0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9vcmRlcl9BM0RTLmFzcCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9Ik1EIiB2YWx1ZT0iTUFJTldQVEVTVDAwMDAxMTYyODk4MzAxKjEwNzUyNjIiIC8+DQo8L2Zvcm0+DQo8Zm9ybSBtZXRob2Q9InBvc3QiIGFjdGlvbj0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9vcmRlcl9hZ3JlZS5hc3AiIG5hbWU9InVwbG9hZEZvcm0zRCI+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJDU1JGS0VZIiB2YWx1ZT0iMDZGM0MzMUQ2RkI1MzIzODg4NjhFRjlGNTA5RUNGNzlBQzIwRDJGMyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNTUkZUUyIgdmFsdWU9IjIwMTEwOTEyMTYyNDUwIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ1NSRlNQIiB2YWx1ZT0iL25jb2wvdGVzdC9vcmRlcmRpcmVjdC5hc3AiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJicmFuZGluZyIgdmFsdWU9Ik9nb25lIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0icGF5aWQiIHZhbHVlPSIxMTYyODk4MyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InN0b3JlYWxpYXMiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJoYXNoX3BhcmFtIiB2YWx1ZT0iOTFBMzA1MjFEMEI0QTA1MEFBRDkzRDM5RDY2RkEyM0Y5OEIzRDQ4RCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InhpZF8zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InN0YXR1c18zRCIgdmFsdWU9IlhYIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iZWNpXzNEIiB2YWx1ZT0iNyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImNhcmRudW1iZXIiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJFY29tX1BheW1lbnRfQ2FyZF9WZXJpZmljYXRpb24iIHZhbHVlPSIqMTA3NTI2MiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNWQ0ZsYWciIHZhbHVlPSIxIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iY2F2dl8zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImNhdnZhbGdvcml0aG1fM0QiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJzaWduYXR1cmVPS18zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9Imhhc2hfcGFyYW1fM0QiIHZhbHVlPSIwMzAzREZDMkI1OTM0MjZCQTExRkQ5RjJBNkQ0NDk5ODEwN0JGN0YzIiAvPg0KPC9mb3JtPg0KPFNDUklQVCBMQU5HVUFHRT0iSmF2YXNjcmlwdCIgRk9SPSJ3aW5kb3ciIEVWRU5UPSJvbkxvYWQiPg0KdmFyIHBvcHVwV2luOw0KdmFyIHN1Ym1pdHBvcHVwV2luID0gMDsNCg0KZnVuY3Rpb24gTG9hZFBvcHVwKCkgew0KCWlmIChzZWxmLm5hbWUgPT0gbnVsbCkJew0KCQlzZWxmLm5hbWUgPSAib2dvbmVNYWluIjsNCgl9DQoJcG9wdXBXaW4gPSB3aW5kb3cub3BlbignYWJvdXQ6YmxhbmsnLCAncG9wdXBXaW4nLCAnaGVpZ2h0PTQwMCwgd2lkdGg9MzkwLCBzdGF0dXM9eWVzLCBkZXBlbmRlbnQ9bm8sIHNjcm9sbGJhcnM9eWVzLCByZXNpemFibGU9bm8nKTsNCglpZiAocG9wdXBXaW4gIT0gbnVsbCkgew0KCQlpZiAgKCFwb3B1cFdpbiB8fCBwb3B1cFdpbi5jbG9zZWQpIHsNCgkJCXJldHVybiAxOw0KCQl9IGVsc2Ugew0KCQkJaWYgKCFwb3B1cFdpbi5vcGVuZXIgfHwgcG9wdXBXaW4ub3BlbmVyID09IG51bGwpIHsNCgkJCQlwb3B1cFdpbi5vcGVuZXIgPSBzZWxmOw0KCQkJfQ0KCQkJc2VsZi5kb2N1bWVudC5mb3Jtcy5kb3dubG9hZGZvcm0zRC50YXJnZXQgPSAncG9wdXBXaW4nOw0KCQkJaWYgKHN1Ym1pdHBvcHVwV2luID09IDEpIHsNCgkJCQlzZWxmLmRvY3VtZW50LmZvcm1zLmRvd25sb2FkZm9ybTNELnN1Ym1pdCgpOw0KCQkJfQ0KCQkJcG9wdXBXaW4uZm9jdXMoKTsNCgkJCXJldHVybiAwOw0KCQl9DQoJfSBlbHNlIHsNCgkJcmV0dXJuIDE7DQoJfQ0KfQ0KCXNlbGYuZG9jdW1lbnQuZm9ybXMuZG93bmxvYWRmb3JtM0Quc3VibWl0KCk7DQovLy0tPg0KPC9TQ1JJUFQ+DQo= + '; + $directLinkMock = $this->getModelMock('ops/api_directlink', array('call')); + $directLinkMock->expects($this->any()) + ->method('call') + ->will($this->returnValue($xmlExample)); + + $class = new ReflectionClass(get_class(Mage::getModel('ops/api_directlink'))); + $method = $class->getMethod('getResponseParams'); + $method->setAccessible(true); + $result = $method->invokeArgs($directLinkMock, array(array('foo' => '111', 'ORDERID' => 4711), 'bar')); + $this->assertTrue(is_array($result)); + $this->assertTrue(array_key_exists('orderID', $result)); + $this->assertTrue(array_key_exists('PAYID', $result)); + $this->assertTrue(array_key_exists('PAYIDSUB', $result)); + $this->assertTrue(array_key_exists('NCSTATUS', $result)); + $this->assertTrue(array_key_exists('NCERROR', $result)); + $this->assertTrue(array_key_exists('NCERROR', $result)); + $this->assertTrue(array_key_exists('NCERRORPLUS', $result)); + $this->assertTrue(array_key_exists('ACCEPTANCE', $result)); + $this->assertTrue(array_key_exists('STATUS', $result)); + $this->assertTrue(array_key_exists('amount', $result)); + $this->assertTrue(array_key_exists('currency', $result)); + $this->assertTrue(array_key_exists('HTML_ANSWER', $result)); + } + + public function testGetResponseParamsWithValidCallButWrongEncodedResponse() + { + $xmlExample = + ' + + PGZvcm0gbmFtZT0iZG93bmxvYWRmb3JtM0QiIGFjdGlvbj0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9UZXN0XzNEX0FDUy5hc3AiIG1ldGhvZD0icG9zdCI+DQo8Tk9TQ1JJUFQ+DQpKYXZhU2NyaXB0IGlzIGN1cnJlbnRseSBkaXNhYmxlZCBvciBpcyBub3Qgc3VwcG9ydGVkIGJ5IHlvdXIgYnJvd3Nlci48YnI+DQpQbGVhc2UgY2xpY2sgb24gdGhlICZxdW90O0NvbnRpbnVlJnF1b3Q7IGJ1dHRvbiB0byBjb250aW51ZSB0aGUgcHJvY2Vzc2luZyBvZiB5b3VyIDMtRCBzZWN1cmUgdHJhbnNhY3Rpb24uPGJyPg0KPGlucHV0IGNsYXNzPSJuY29sIiB0eXBlPSJzdWJtaXQiIHZhbHVlPSJDb250aW51ZSIgaWQ9InN1Ym1pdDEiIG5hbWU9InN1Ym1pdDEiIC8+DQo8L05PU0NSSVBUPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ1NSRktFWSIgdmFsdWU9IjA0MzA0MzI4NkE0M0ZDM0YyRDhFMDFCOUM2MzYwRTA1Qzg5NkZEMzYiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJDU1JGVFMiIHZhbHVlPSIyMDExMDkxMjE2MjQ1MCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNTUkZTUCIgdmFsdWU9Ii9uY29sL3Rlc3Qvb3JkZXJkaXJlY3QuYXNwIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iUGFSZXEiIHZhbHVlPSI8P3htbCB2ZXJzaW9uPSZxdW90OzEuMCZxdW90Oz8+PFRocmVlRFNlY3VyZT48TWVzc2FnZSBpZD0mcXVvdDsxMjMmcXVvdDs+PFBBUmVxPjx2ZXJzaW9uPjEuMDI8L3ZlcnNpb24+PE1lcmNoYW50PjxtZXJJRD5OUk1BR0VOVE8zPC9tZXJJRD48bmFtZT5OZXRyZXNlYXJjaCBHbWJIICZhbXA7YW1wOyBDby5LRzwvbmFtZT48dXJsPmh0dHA6Ly93d3cubmV0cmVzZWFyY2guZGU8L3VybD48L01lcmNoYW50PjxQdXJjaGFzZT48eGlkPjExNjI4OTgzPC94aWQ+PGFtb3VudD4xMjY4Ljc1PC9hbW91bnQ+PHB1cmNoQW1vdW50PjEyNjguNzU8L3B1cmNoQW1vdW50PjxjdXJyZW5jeT5FVVI8L2N1cnJlbmN5PjwvUHVyY2hhc2U+PENIPjxhY2N0SUQ+NDAwMDAwWFhYWFhYMDAwMjwvYWNjdElEPjxleHBpcnk+MDExNjwvZXhwaXJ5PjxzZWxCcmFuZD48L3NlbEJyYW5kPjwvQ0g+PC9QQVJlcT48L01lc3NhZ2U+PC9UaHJlZURTZWN1cmU+DQoiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJUZXJtVXJsIiB2YWx1ZT0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9vcmRlcl9BM0RTLmFzcCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9Ik1EIiB2YWx1ZT0iTUFJTldQVEVTVDAwMDAxMTYyODk4MzAxKjEwNzUyNjIiIC8+DQo8L2Zvcm0+DQo8Zm9ybSBtZXRob2Q9InBvc3QiIGFjdGlvbj0iaHR0cHM6Ly9zZWN1cmUub2dvbmUuY29tL25jb2wvdGVzdC9vcmRlcl9hZ3JlZS5hc3AiIG5hbWU9InVwbG9hZEZvcm0zRCI+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJDU1JGS0VZIiB2YWx1ZT0iMDZGM0MzMUQ2RkI1MzIzODg4NjhFRjlGNTA5RUNGNzlBQzIwRDJGMyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNTUkZUUyIgdmFsdWU9IjIwMTEwOTEyMTYyNDUwIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iQ1NSRlNQIiB2YWx1ZT0iL25jb2wvdGVzdC9vcmRlcmRpcmVjdC5hc3AiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJicmFuZGluZyIgdmFsdWU9Ik9nb25lIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0icGF5aWQiIHZhbHVlPSIxMTYyODk4MyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InN0b3JlYWxpYXMiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJoYXNoX3BhcmFtIiB2YWx1ZT0iOTFBMzA1MjFEMEI0QTA1MEFBRDkzRDM5RDY2RkEyM0Y5OEIzRDQ4RCIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InhpZF8zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InN0YXR1c18zRCIgdmFsdWU9IlhYIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iZWNpXzNEIiB2YWx1ZT0iNyIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImNhcmRudW1iZXIiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJFY29tX1BheW1lbnRfQ2FyZF9WZXJpZmljYXRpb24iIHZhbHVlPSIqMTA3NTI2MiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9IkNWQ0ZsYWciIHZhbHVlPSIxIiAvPg0KPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iY2F2dl8zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImNhdnZhbGdvcml0aG1fM0QiIHZhbHVlPSIiIC8+DQo8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJzaWduYXR1cmVPS18zRCIgdmFsdWU9IiIgLz4NCjxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9Imhhc2hfcGFyYW1fM0QiIHZhbHVlPSIwMzAzREZDMkI1OTM0MjZCQTExRkQ5RjJBNkQ0NDk5ODEwN0JGN0YzIiAvPg0KPC9mb3JtPg0KPFNDUklQVCBMQU5HVUFHRT0iSmF2YXNjcmlwdCIgRk9SPSJ3aW5kb3ciIEVWRU5UPSJvbkxvYWQiPg0KdmFyIHBvcHVwV2luOw0KdmFyIHN1Ym1pdHBvcHVwV2luID0gMDsNCg0KZnVuY3Rpb24gTG9hZFBvcHVwKCkgew0KCWlmIChzZWxmLm5hbWUgPT0gbnVsbCkJew0KCQlzZWxmLm5hbWUgPSAib2dvbmVNYWluIjsNCgl9DQoJcG9wdXBXaW4gPSB3aW5kb3cub3BlbignYWJvdXQ6YmxhbmsnLCAncG9wdXBXaW4nLCAnaGVpZ2h0PTQwMCwgd2lkdGg9MzkwLCBzdGF0dXM9eWVzLCBkZXBlbmRlbnQ9bm8sIHNjcm9sbGJhcnM9eWVzLCByZXNpemFibGU9bm8nKTsNCglpZiAocG9wdXBXaW4gIT0gbnVsbCkgew0KCQlpZiAgKCFwb3B1cFdpbiB8fCBwb3B1cFdpbi5jbG9zZWQpIHsNCgkJCXJldHVybiAxOw0KCQl9IGVsc2Ugew0KCQkJaWYgKCFwb3B1cFdpbi5vcGVuZXIgfHwgcG9wdXBXaW4ub3BlbmVyID09IG51bGwpIHsNCgkJCQlwb3B1cFdpbi5vcGVuZXIgPSBzZWxmOw0KCQkJfQ0KCQkJc2VsZi5kb2N1bWVudC5mb3Jtcy5kb3dubG9hZGZvcm0zRC50YXJnZXQgPSAncG9wdXBXaW4nOw0KCQkJaWYgKHN1Ym1pdHBvcHVwV2luID09IDEpIHsNCgkJCQlzZWxmLmRvY3VtZW50LmZvcm1zLmRvd25sb2FkZm9ybTNELnN1Ym1pdCgpOw0KCQkJfQ0KCQkJcG9wdXBXaW4uZm9jdXMoKTsNCgkJCXJldHVybiAwOw0KCQl9DQoJfSBlbHNlIHsNCgkJcmV0dXJuIDE7DQoJfQ0KfQ0KCXNlbGYuZG9jdW1lbnQuZm9ybXMuZG93bmxvYWRmb3JtM0Quc3VibWl0KCk7DQovLy0tPg0KPC9TQ1JJUFQ+DQo= + '; + $xmlExample = utf8_decode($xmlExample); + $directLinkMock = $this->getModelMock('ops/api_directlink', array('call')); + $directLinkMock->expects($this->any()) + ->method('call') + ->will($this->returnValue($xmlExample)); + + $class = new ReflectionClass(get_class(Mage::getModel('ops/api_directlink'))); + $method = $class->getMethod('getResponseParams'); + $method->setAccessible(true); + $result = $method->invokeArgs($directLinkMock, array(array('foo' => '111', 'ORDERID' => 4711), 'bar')); + $this->assertTrue(is_array($result)); + $this->assertTrue(array_key_exists('orderID', $result)); + $this->assertTrue(array_key_exists('PAYID', $result)); + $this->assertTrue(array_key_exists('PAYIDSUB', $result)); + $this->assertTrue(array_key_exists('NCSTATUS', $result)); + $this->assertTrue(array_key_exists('NCERROR', $result)); + $this->assertTrue(array_key_exists('NCERROR', $result)); + $this->assertTrue(array_key_exists('NCERRORPLUS', $result)); + $this->assertTrue(array_key_exists('ACCEPTANCE', $result)); + $this->assertTrue(array_key_exists('STATUS', $result)); + $this->assertTrue(array_key_exists('amount', $result)); + $this->assertTrue(array_key_exists('currency', $result)); + $this->assertTrue(array_key_exists('HTML_ANSWER', $result)); + $this->assertTrue(array_key_exists('CN', $result)); + $this->assertEquals('Max Mueller', $result['CN']); + } + + /** + * @expectedException Mage_Core_Exception + */ + public function testGetResponseParamsWithRetryCountExceeded() + { + $maxAllowedRetryCount = Netresearch_OPS_Model_Api_DirectLink::MAX_RETRY_COUNT; + $class = new ReflectionClass(get_class(Mage::getModel('ops/api_directlink'))); + $method = $class->getMethod('getResponseParams'); + $method->setAccessible(true); + $result = $method->invokeArgs(Mage::getModel('ops/api_directlink'), array(array('foo' => '123', 'ORDERID' => '4711'), 'bar', ++$maxAllowedRetryCount)); + } + + /** + * @expectedException Mage_Core_Exception + */ + public function testGetResponseParamsWithInvalidResponse() + { + $xmlExample = ''; + $directLinkMock = $this->getModelMock('ops/api_directlink', array('call')); + $directLinkMock->expects($this->any()) + ->method('call') + ->will($this->returnValue($xmlExample)); + + $class = new ReflectionClass(get_class(Mage::getModel('ops/api_directlink'))); + $method = $class->getMethod('getResponseParams'); + $method->setAccessible(true); + $result = $method->invokeArgs($directLinkMock, array(array('foo' => '12334', 'ORDERID' => 4711), 'bar')); + } + + /** + * @expectedException Mage_Core_Exception + */ + public function testCheckResponseNcErrorSet() + { + $responseParams = array('NCERROR' => '50001111'); + $this->_model->checkResponse($responseParams); + } + + /** + * @expectedException Mage_Core_Exception + */ + public function testCheckResponseNcErrorEmpty() + { + $responseParams = array('NCERROR' => '1', 'NCERRORPLUS' => 'Error occured'); + $this->_model->checkResponse($responseParams); + } + + public function testCheckResponseNrErrorWithStatus() + { + $responseParams = array('NCERROR' => '1', 'STATUS' => 4); + try { + $this->_model->checkResponse($responseParams); + } + catch (Exception $e) { + $this->fail('An unexpected exception has been raised.'); + } + } + + public function testPerformRequest() + { + $result = array('NCERROR' => 0); + $directLinkMock = $this->getModelMock('ops/api_directlink', array('getResponseParams')); + $directLinkMock->expects($this->any()) + ->method('getResponseParams') + ->will($this->returnValue($result)); + $this->assertEquals($result, $directLinkMock->performRequest(array('foo'), 'bla')); + + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Capture/Additional/OpenInvoiceNlTest.php b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Capture/Additional/OpenInvoiceNlTest.php new file mode 100644 index 0000000..9eb424c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Capture/Additional/OpenInvoiceNlTest.php @@ -0,0 +1,291 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + + +class Netresearch_OPS_Test_Model_Backend_Operation_Capture_Additional_OpenInvoiceNlTest + extends EcomDev_PHPUnit_Test_Case +{ + + protected $openInvoiceNlModel = null; + + public function setUp() + { + parent::setUp(); + $this->openInvoiceNlModel = Mage::getModel('ops/backend_operation_capture_additional_openInvoiceNl'); + $this->mockSessions(); + } + + public function testExtractAdditionalParamsWithoutShipping1() + { + $itemsContainer = Mage::getModel('sales/order_invoice'); + $orderItem = Mage::getModel('sales/order_item'); + $orderItem->setId(1); + $orderItem->setQtyOrdered(2); + $item = Mage::getModel('sales/order_invoice_item'); + $item->setOrderItemId(1); + $item->setOrderItem($orderItem); + $item->setName('Item'); + $item->setBasePriceInclTax(19.99); + $item->setQty(2); + $item->setTaxPercent(19); + $itemsContainer->addItem($item); + $payment = Mage::getModel('sales/order_payment')->setMethod('ops_openInvoiceNl'); + $payment->setInvoice($itemsContainer); + $result = $this->openInvoiceNlModel->extractAdditionalParams($payment); + $this->assertTrue(is_array($result)); + $this->assertTrue(0 < count($result)); + $this->assertArrayHasKey('ITEMID1', $result); + $this->assertEquals(1, $result['ITEMID1']); + $this->assertArrayHasKey('ITEMNAME1', $result); + $this->assertEquals('Item', $result['ITEMNAME1']); + $this->assertArrayHasKey('ITEMPRICE1', $result); + $this->assertEquals(1999, $result['ITEMPRICE1']); + $this->assertArrayHasKey('ITEMVATCODE1', $result); + $this->assertEquals('19%', $result['ITEMVATCODE1']); + $this->assertArrayHasKey('TAXINCLUDED1', $result); + $this->assertEquals(1, $result['TAXINCLUDED1']); + } + + public function testExtractAdditionalParamsWithoutShippingButWithConfigurable() + { + $itemsContainer = Mage::getModel('sales/order_invoice'); + $orderItem = Mage::getModel('sales/order_item'); + $orderItem->setId(1); + $orderItem->setQtyOrdered(2); + $orderItem->setProductType(Mage_Catalog_Model_Product_Type::TYPE_CONFIGURABLE); + $item = Mage::getModel('sales/order_invoice_item'); + $item->setOrderItemId(1); + $item->setOrderItem($orderItem); + $item->setName('Item'); + $item->setBasePriceInclTax(19.99); + $item->setQty(2); + $item->setTaxPercent(19); + $itemsContainer->addItem($item); + $item = Mage::getModel('sales/order_invoice_item'); + $simpleOrderItem = Mage::getModel('sales/order_item'); + $simpleOrderItem->setId(1); + $simpleOrderItem->setQtyOrdered(2); + $simpleOrderItem->setParentItemId(1); + $simpleOrderItem->setParentItem($orderItem); + $item->setOrderItemId(2); + $item->setOrderItem($simpleOrderItem); + $item->setName('Item'); + $item->setBasePriceInclTax(19.99); + $item->setQty(2); + $item->setTaxPercent(19); + $itemsContainer->addItem($item); + $payment = Mage::getModel('sales/order_payment')->setMethod('ops_openInvoiceNl'); + $payment->setInvoice($itemsContainer); + $result = $this->openInvoiceNlModel->extractAdditionalParams($payment); + $this->assertTrue(is_array($result)); + $this->assertTrue(0 < count($result)); + $this->assertArrayHasKey('ITEMID1', $result); + $this->assertEquals(1, $result['ITEMID1']); + $this->assertArrayHasKey('ITEMNAME1', $result); + $this->assertEquals('Item', $result['ITEMNAME1']); + $this->assertArrayHasKey('ITEMPRICE1', $result); + $this->assertEquals(1999, $result['ITEMPRICE1']); + $this->assertArrayHasKey('ITEMVATCODE1', $result); + $this->assertEquals('19%', $result['ITEMVATCODE1']); + $this->assertArrayHasKey('TAXINCLUDED1', $result); + $this->assertEquals(1, $result['TAXINCLUDED1']); + } + + public function testExtractAdditionalParamsWithoutShippingButWithTwoNormalInvoiceItems() + { + $itemsContainer = Mage::getModel('sales/order_invoice'); + $orderItem = Mage::getModel('sales/order_item'); + $orderItem->setId(1); + $orderItem->setQtyOrdered(2); + $item = Mage::getModel('sales/order_invoice_item'); + $item->setOrderItemId(1); + $item->setOrderItem($orderItem); + $item->setName('Item'); + $item->setBasePriceInclTax(19.99); + $item->setQty(2); + $item->setTaxPercent(19); + $itemsContainer->addItem($item); + $item = Mage::getModel('sales/order_invoice_item'); + $orderItem = Mage::getModel('sales/order_item'); + $orderItem->setId(1); + $orderItem->setQtyOrdered(2); + $item->setOrderItemId(2); + $item->setOrderItem($orderItem); + $item->setName('Item'); + $item->setBasePriceInclTax(19.99); + $item->setQty(2); + $item->setTaxPercent(19); + $itemsContainer->addItem($item); + $payment = Mage::getModel('sales/order_payment')->setMethod('ops_openInvoiceNl'); + $payment->setInvoice($itemsContainer); + $result = $this->openInvoiceNlModel->extractAdditionalParams($payment); + $this->assertTrue(is_array($result)); + $this->assertTrue(0 < count($result)); + $this->assertArrayHasKey('ITEMID1', $result); + $this->assertEquals(1, $result['ITEMID1']); + $this->assertArrayHasKey('ITEMNAME1', $result); + $this->assertEquals('Item', $result['ITEMNAME1']); + $this->assertArrayHasKey('ITEMPRICE1', $result); + $this->assertEquals(1999, $result['ITEMPRICE1']); + $this->assertArrayHasKey('ITEMVATCODE1', $result); + $this->assertEquals('19%', $result['ITEMVATCODE1']); + $this->assertArrayHasKey('TAXINCLUDED1', $result); + $this->assertEquals(1, $result['TAXINCLUDED1']); + + $this->assertArrayHasKey('ITEMID2', $result); + $this->assertEquals(1, $result['ITEMID2']); + $this->assertArrayHasKey('ITEMNAME2', $result); + $this->assertEquals('Item', $result['ITEMNAME2']); + $this->assertArrayHasKey('ITEMPRICE2', $result); + $this->assertEquals(1999, $result['ITEMPRICE2']); + $this->assertArrayHasKey('ITEMVATCODE2', $result); + $this->assertEquals('19%', $result['ITEMVATCODE2']); + $this->assertArrayHasKey('TAXINCLUDED2', $result); + $this->assertEquals(1, $result['TAXINCLUDED2']); + } + + public function testExtractAdditionalParamsWithShipping() + { + $itemsContainer = Mage::getModel('sales/order_invoice'); + $orderItem = Mage::getModel('sales/order_item'); + $orderItem->setId(1); + $orderItem->setQtyOrdered(2); + $item = Mage::getModel('sales/order_invoice_item'); + $item->setOrderItemId(1); + $item->setOrderItem($orderItem); + $item->setName('Item'); + $item->setBasePriceInclTax(19.99); + $item->setQty(2); + $item->setTaxPercent(19); + $itemsContainer->addItem($item); + $itemsContainer->setBaseShippingInclTax(10.00); + $order = Mage::getModel('sales/order'); + $order->setShippingDescription('foo'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_openInvoiceNl'); + $order->setPayment($payment); + $itemsContainer->setOrder($order); + $payment->setInvoice($itemsContainer); + $result = $this->openInvoiceNlModel->extractAdditionalParams($payment); + $this->assertTrue(is_array($result)); + $this->assertTrue(0 < count($result)); + $this->assertArrayHasKey('ITEMID1', $result); + $this->assertEquals(1, $result['ITEMID1']); + $this->assertArrayHasKey('ITEMNAME1', $result); + $this->assertEquals('Item', $result['ITEMNAME1']); + $this->assertArrayHasKey('ITEMPRICE1', $result); + $this->assertEquals(1999, $result['ITEMPRICE1']); + $this->assertArrayHasKey('ITEMVATCODE1', $result); + $this->assertEquals('19%', $result['ITEMVATCODE1']); + $this->assertArrayHasKey('TAXINCLUDED1', $result); + $this->assertEquals(1, $result['TAXINCLUDED1']); + $this->assertArrayHasKey('ITEMID2', $result); + $this->assertEquals('SHIPPING', $result['ITEMID2']); + $this->assertArrayHasKey('ITEMNAME2', $result); + $this->assertEquals(substr($order->getShippingDescription(), 0, 30), $result['ITEMNAME2']); + $this->assertArrayHasKey('ITEMPRICE2', $result); + $this->assertEquals(Mage::helper('ops/data')->getAmount(10.00), $result['ITEMPRICE2']); + $this->assertArrayHasKey('ITEMQUANT2', $result); + $this->assertEquals(1, $result['ITEMQUANT2']); + $this->assertArrayHasKey('ITEMVATCODE2', $result); + $this->assertEquals(floatval($payment->getMethodInstance()->getShippingTaxRate($order)).'%', $result['ITEMVATCODE2']); + $this->assertArrayHasKey('TAXINCLUDED2', $result); + $this->assertEquals(1, $result['TAXINCLUDED2']); + } + + public function testExtractAdditionalParamsWithShippingAndDiscount() + { + $itemsContainer = Mage::getModel('sales/order_invoice'); + $orderItem = Mage::getModel('sales/order_item'); + $orderItem->setId(1); + $orderItem->setQtyOrdered(2); + $item = Mage::getModel('sales/order_invoice_item'); + $item->setOrderItemId(1); + $item->setOrderItem($orderItem); + $item->setName('Item'); + $item->setBasePriceInclTax(19.99); + $item->setQty(2); + $item->setTaxPercent(19); + $itemsContainer->addItem($item); + $itemsContainer->setBaseShippingInclTax(10.00); + $itemsContainer->setBaseDiscountAmount(-10.00); + $order = Mage::getModel('sales/order'); + $order->setShippingDescription('foo'); + $order->setCouponRuleName('Foo'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_openInvoiceNl'); + $order->setPayment($payment); + $itemsContainer->setOrder($order); + $payment->setInvoice($itemsContainer); + $result = $this->openInvoiceNlModel->extractAdditionalParams($payment); + $this->assertTrue(is_array($result)); + $this->assertTrue(0 < count($result)); + $this->assertArrayHasKey('ITEMID1', $result); + $this->assertEquals(1, $result['ITEMID1']); + $this->assertArrayHasKey('ITEMNAME1', $result); + $this->assertEquals('Item', $result['ITEMNAME1']); + $this->assertArrayHasKey('ITEMPRICE1', $result); + $this->assertEquals(1999, $result['ITEMPRICE1']); + $this->assertArrayHasKey('ITEMVATCODE1', $result); + $this->assertEquals('19%', $result['ITEMVATCODE1']); + $this->assertArrayHasKey('TAXINCLUDED1', $result); + $this->assertEquals(1, $result['TAXINCLUDED1']); + + $this->assertArrayHasKey('ITEMID2', $result); + $this->assertEquals('DISCOUNT', $result['ITEMID2']); + $this->assertArrayHasKey('ITEMNAME2', $result); + $this->assertEquals('Foo', $result['ITEMNAME2']); + $this->assertArrayHasKey('ITEMPRICE2', $result); + $this->assertEquals(Mage::helper('ops/data')->getAmount(-10.00), $result['ITEMPRICE2']); + $this->assertArrayHasKey('ITEMQUANT2', $result); + $this->assertEquals(1, $result['ITEMQUANT2']); + $this->assertArrayHasKey('ITEMVATCODE2', $result); + $this->assertEquals(floatval($payment->getMethodInstance()->getShippingTaxRate($order)).'%', $result['ITEMVATCODE2']); + $this->assertArrayHasKey('TAXINCLUDED2', $result); + $this->assertEquals(1, $result['TAXINCLUDED2']); + + $this->assertArrayHasKey('ITEMID3', $result); + $this->assertEquals('SHIPPING', $result['ITEMID3']); + $this->assertArrayHasKey('ITEMNAME3', $result); + $this->assertEquals(substr($order->getShippingDescription(), 0, 30), $result['ITEMNAME3']); + $this->assertArrayHasKey('ITEMPRICE3', $result); + $this->assertEquals(Mage::helper('ops/data')->getAmount(10.00), $result['ITEMPRICE3']); + $this->assertArrayHasKey('ITEMQUANT3', $result); + $this->assertEquals(1, $result['ITEMQUANT3']); + $this->assertArrayHasKey('ITEMVATCODE3', $result); + $this->assertEquals(floatval($payment->getMethodInstance()->getShippingTaxRate($order)).'%', $result['ITEMVATCODE3']); + $this->assertArrayHasKey('TAXINCLUDED3', $result); + $this->assertEquals(1, $result['TAXINCLUDED3']); + } + + protected function mockSessions() + { + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'checkout/session', $sessionMock); + $sessionMock = $this->getModelMockBuilder('adminhtml/session_quote') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'adminhtml/session_quote', $sessionMock); + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + } + + public function tearDown() + { + parent::tearDown(); + $this->openInvoiceNlModel = null; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Capture/ParameterTest.php b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Capture/ParameterTest.php new file mode 100644 index 0000000..e488fa7 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Capture/ParameterTest.php @@ -0,0 +1,70 @@ + + * @category Netresearch_OPS + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Model_Backend_Operation_Capture_ParameterTest extends EcomDev_PHPUnit_Test_Case +{ + + public function testGetRequestParams() + { + $sessionMock = $this->mockSession('customer/session'); + $sessionMock->disableOriginalConstructor(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + + $fakePayment = Mage::getModel('sales/order_payment'); + $fakePayment->setOrder(Mage::getModel('sales/order')); + $fakePayment->setAdditionalInformation(array('paymentId' => '4711')); + $arrInfo = array('operation' => 'capture'); + $amount = 10; + $opsPaymentMethod = Mage::getModel('ops/payment_abstract'); + + $captureParameterModel = Mage::getModel('ops/backend_operation_capture_parameter'); + $requestParams = $captureParameterModel->getRequestParams($opsPaymentMethod, $fakePayment, $amount, $arrInfo); + $this->assertArrayHasKey('AMOUNT', $requestParams); + $this->assertArrayHasKey('PAYID', $requestParams); + $this->assertArrayHasKey('OPERATION', $requestParams); + $this->assertArrayHasKey('CURRENCY', $requestParams); + + $this->assertEquals(1000, $requestParams['AMOUNT']); + $this->assertEquals(4711, $requestParams['PAYID']); + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_PARTIAL, $requestParams['OPERATION']); + $this->assertEquals( + Mage::app()->getStore($fakePayment->getOrder()->getStoreId())->getBaseCurrencyCode(), + $requestParams['CURRENCY'] + ); + } + + public function testGetRequestParamsWithAdditionalParameters() + { + $sessionMock = $this->mockSession('customer/session'); + $sessionMock->disableOriginalConstructor(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + $fakePayment = Mage::getModel('sales/order_payment'); + $fakePayment->setOrder(Mage::getModel('sales/order')); + $fakePayment->setAdditionalInformation(array('paymentId' => '4711')); + $fakeInvoice = Mage::getModel('sales/order_invoice'); + $fakePayment->setInvoice($fakeInvoice); + $arrInfo = array('operation' => 'capture'); + $amount = 10; + $opsPaymentMethod = Mage::getModel('ops/payment_openInvoiceNl'); + $captureParameterModel = Mage::getModel('ops/backend_operation_capture_parameter'); + $requestParams = $captureParameterModel->getRequestParams($opsPaymentMethod, $fakePayment, $amount, $arrInfo); + $this->assertArrayHasKey('AMOUNT', $requestParams); + $this->assertArrayHasKey('PAYID', $requestParams); + $this->assertArrayHasKey('OPERATION', $requestParams); + $this->assertArrayHasKey('CURRENCY', $requestParams); + + $this->assertEquals(1000, $requestParams['AMOUNT']); + $this->assertEquals(4711, $requestParams['PAYID']); + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_PARTIAL, $requestParams['OPERATION']); + $this->assertEquals( + Mage::app()->getStore($fakePayment->getOrder()->getStoreId())->getBaseCurrencyCode(), + $requestParams['CURRENCY'] + ); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/ParameterTest.php b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/ParameterTest.php new file mode 100644 index 0000000..75e7e1e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/ParameterTest.php @@ -0,0 +1,52 @@ + + * @category Netresearch_OPS + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +class Netresearch_OPS_Test_Model_Backend_Operation_ParameterTest extends EcomDev_PHPUnit_Test_Case +{ + + /** + * @expectedException Mage_Core_Exception + */ + public function testGetParameterForWillThrowException() + { + $fakePayment = new Varien_Object(); + $arrInfo = array(); + $amount = 0; + $opsPaymentMethod = Mage::getModel('ops/payment_abstract'); + Mage::getModel('ops/backend_operation_parameter')->getParameterFor( + 'NOT SUPPORTED OPERATION TYPE', $opsPaymentMethod, $fakePayment, $amount, $arrInfo + ); + } + + public function testGetParameterForCaptureWillReturnArray() + { + $fakePayment = Mage::getModel('sales/order_payment'); + $fakePayment->setOrder(Mage::getModel('sales/order')); + $fakePayment->setAdditionalInformation(array('paymentId' => '4711')); + $arrInfo = array('operation' => Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_PARTIAL); + $amount = 10; + $opsPaymentMethod = Mage::getModel('ops/payment_abstract'); + $requestParams = Mage::getModel('ops/backend_operation_parameter')->getParameterFor( + Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_TRANSACTION_TYPE, + $opsPaymentMethod, + $fakePayment, + $amount, + $arrInfo + ); + $this->assertArrayHasKey('AMOUNT', $requestParams); + $this->assertArrayHasKey('PAYID', $requestParams); + $this->assertArrayHasKey('OPERATION', $requestParams); + $this->assertArrayHasKey('CURRENCY', $requestParams); + + $this->assertEquals(1000, $requestParams['AMOUNT']); + $this->assertEquals(4711, $requestParams['PAYID']); + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_PARTIAL, $requestParams['OPERATION']); + $this->assertEquals(Mage::app()->getStore($fakePayment->getOrder()->getStoreId())->getBaseCurrencyCode(), $requestParams['CURRENCY']); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Refund/Additional/OpenInvoiceNlTest.php b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Refund/Additional/OpenInvoiceNlTest.php new file mode 100644 index 0000000..4f9f8d0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Refund/Additional/OpenInvoiceNlTest.php @@ -0,0 +1,248 @@ +openInvoiceNlModel = Mage::getModel('ops/backend_operation_refund_additional_openInvoiceNl'); + $creditMemo = Mage::getModel('sales/order_creditmemo'); + $invoice = Mage::getModel('sales/order_invoice'); + // add first item to invoice + + + $orderItem = Mage::getModel('sales/order_item'); + $orderItem->setId(1); + $orderItem->setQtyOrdered(2); + $orderItem->setTaxPercent(19); + + $item = Mage::getModel('sales/order_invoice_item'); + $item->setOrderItemId(1); + $item->setOrderItem($orderItem); + $item->setName('Item 1'); + $item->setBasePriceInclTax(42.99); + $item->setQty(2); + $item->setTaxPercent(19); + + $orderItem->setQtyInvoiced(2); + $creditMemoItem = Mage::getModel('sales/order_creditmemo_item'); + $creditMemoItem->setOrderItemId(1); + $creditMemoItem->setOrderItem($orderItem); + $creditMemoItem->setName('Item 1'); + $creditMemoItem->setBasePriceInclTax(42.99); + $creditMemoItem->setQty(2); + + $creditMemo->addItem($creditMemoItem); + $invoice->addItem($item); + + // add second item to invoice + $orderItem = Mage::getModel('sales/order_item'); + $orderItem->setId(2); + $orderItem->setQtyOrdered(2); + $orderItem->setTaxPercent(7); + + $item = Mage::getModel('sales/order_invoice_item'); + $item->setOrderItemId(2); + $item->setOrderItem($orderItem); + $item->setName('Item 2'); + $item->setBasePriceInclTax(19.99); + $item->setQty(2); + $item->setTaxPercent(7); + + $orderItem->setQtyInvoiced(2); + $creditMemoItem = Mage::getModel('sales/order_creditmemo_item'); + $creditMemoItem->setOrderItemId(2); + $creditMemoItem->setOrderItem($orderItem); + $creditMemoItem->setName('Item 2'); + $creditMemoItem->setBasePriceInclTax(19.99); + $creditMemoItem->setQty(2); + + $creditMemo->addItem($creditMemoItem); + $invoice->addItem($item); + // add shipping and discount + $invoice->setBaseShippingInclTax(10.00); + $order = Mage::getModel('sales/order'); + $order->setShippingDescription('SuperFunShipping'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_openInvoiceNl'); + $order->setPayment($payment); + $invoice->setOrder($order); + $payment->setInvoice($invoice); + $payment->setCreditmemo($creditMemo); + $this->testPayment = $payment; + + $sessionMock = $this->getModelMockBuilder('adminhtml/session_quote') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'adminhtml/session_quote', $sessionMock); + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'checkout/session', $sessionMock); + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + } + + + public function testExtractWithoutShippingAndAdjustments() + { + $params = array( + + 'shipping_amount' => 0, + 'adjustment_positive' => 0, + 'adjustment_negative' => 0 + + + ); + $this->testPayment->getCreditmemo()->addData($params); + $this->mockRefundHelper($params); + $result = $this->openInvoiceNlModel->extractAdditionalParams($this->testPayment); + // refunded item + $this->assertTrue(is_array($result)); + $this->assertTrue(0 < count($result)); + $this->assertArrayHasKey('ITEMID1', $result); + $this->assertEquals(1, $result['ITEMID1']); + $this->assertArrayHasKey('ITEMNAME1', $result); + $this->assertEquals('Item 1', $result['ITEMNAME1']); + $this->assertArrayHasKey('ITEMPRICE1', $result); + $this->assertEquals(4299, $result['ITEMPRICE1']); + $this->assertArrayHasKey('ITEMVATCODE1', $result); + $this->assertEquals('19%', $result['ITEMVATCODE1']); + $this->assertArrayHasKey('TAXINCLUDED1', $result); + $this->assertEquals(1, $result['TAXINCLUDED1']); + $this->assertArrayHasKey('ITEMQUANT1', $result); + $this->assertEquals(2, $result['ITEMQUANT1']); + // 'ignored item' + $this->assertArrayHasKey('ITEMID2', $result); + $this->assertEquals(2, $result['ITEMID2']); + $this->assertArrayHasKey('ITEMNAME2', $result); + $this->assertEquals('Item 2', $result['ITEMNAME2']); + $this->assertArrayHasKey('ITEMPRICE2', $result); + $this->assertEquals(1999, $result['ITEMPRICE2']); + $this->assertArrayHasKey('ITEMVATCODE2', $result); + $this->assertEquals('7%', $result['ITEMVATCODE2']); + $this->assertArrayHasKey('TAXINCLUDED2', $result); + $this->assertEquals(1, $result['TAXINCLUDED2']); + $this->assertArrayHasKey('ITEMQUANT2', $result); + $this->assertEquals(2, $result['ITEMQUANT2']); + // amount + $this->assertArrayHasKey('AMOUNT', $result); + $this->assertEquals(12596, $result['AMOUNT']); + } + + + public function testWithShippingAndAllAdjustments() + { + $params = array( + + 'base_shipping_incl_tax' => 5, + 'base_adjustment_positive' => 5, + 'base_adjustment_negative' => 10 + + + ); + $this->mockRefundHelper($params); + $this->testPayment->getCreditmemo()->addData($params); + + $result = $this->openInvoiceNlModel->extractAdditionalParams($this->testPayment); + // Test our items + $this->assertTrue(is_array($result)); + $this->assertTrue(0 < count($result)); + $this->assertArrayHasKey('ITEMID1', $result); + $this->assertEquals(1, $result['ITEMID1']); + $this->assertArrayHasKey('ITEMNAME1', $result); + $this->assertEquals('Item 1', $result['ITEMNAME1']); + $this->assertArrayHasKey('ITEMPRICE1', $result); + $this->assertEquals(4299, $result['ITEMPRICE1']); + $this->assertArrayHasKey('ITEMVATCODE1', $result); + $this->assertEquals('19%', $result['ITEMVATCODE1']); + $this->assertArrayHasKey('TAXINCLUDED1', $result); + $this->assertEquals(1, $result['TAXINCLUDED1']); + $this->assertArrayHasKey('ITEMQUANT1', $result); + $this->assertEquals(2, $result['ITEMQUANT1']); + $this->assertArrayHasKey('ITEMID2', $result); + $this->assertEquals(2, $result['ITEMID2']); + $this->assertArrayHasKey('ITEMNAME2', $result); + $this->assertEquals('Item 2', $result['ITEMNAME2']); + $this->assertArrayHasKey('ITEMPRICE2', $result); + $this->assertEquals(1999, $result['ITEMPRICE2']); + $this->assertArrayHasKey('ITEMVATCODE2', $result); + $this->assertEquals('7%', $result['ITEMVATCODE2']); + $this->assertArrayHasKey('TAXINCLUDED2', $result); + $this->assertEquals(1, $result['TAXINCLUDED2']); + $this->assertArrayHasKey('ITEMQUANT2', $result); + $this->assertEquals(2, $result['ITEMQUANT2']); + + // shipping + $this->assertArrayHasKey('ITEMID3', $result); + $this->assertEquals('SHIPPING', $result['ITEMID3']); + $this->assertArrayHasKey('ITEMNAME3', $result); + $this->assertEquals('SuperFunShipping', $result['ITEMNAME3']); + $this->assertArrayHasKey('ITEMPRICE3', $result); + // note, that this is the refunded amount, not the actual shipping cost of the invoice + $this->assertEquals(500, $result['ITEMPRICE3']); + $this->assertArrayHasKey('ITEMVATCODE3', $result); + $this->assertEquals('0%', $result['ITEMVATCODE3']); + $this->assertArrayHasKey('TAXINCLUDED3', $result); + $this->assertEquals(1, $result['TAXINCLUDED3']); + $this->assertArrayHasKey('ITEMQUANT3', $result); + $this->assertEquals(1, $result['ITEMQUANT3']); + // adjustment refund + $this->assertArrayHasKey('ITEMID4', $result); + $this->assertEquals('ADJUSTREFUND', $result['ITEMID4']); + $this->assertArrayHasKey('ITEMNAME3', $result); + $this->assertEquals('Adjustment Refund', $result['ITEMNAME4']); + $this->assertArrayHasKey('ITEMPRICE4', $result); + $this->assertEquals(500, $result['ITEMPRICE4']); + $this->assertArrayHasKey('ITEMVATCODE4', $result); + $this->assertEquals('0%', $result['ITEMVATCODE4']); + $this->assertArrayHasKey('TAXINCLUDED4', $result); + $this->assertEquals(1, $result['TAXINCLUDED4']); + $this->assertArrayHasKey('ITEMQUANT4', $result); + $this->assertEquals(1, $result['ITEMQUANT4']); + // adjustment fee + $this->assertArrayHasKey('ITEMID5', $result); + $this->assertEquals('ADJUSTFEE', $result['ITEMID5']); + $this->assertArrayHasKey('ITEMNAME5', $result); + $this->assertEquals('Adjustment Fee', $result['ITEMNAME5']); + $this->assertArrayHasKey('ITEMPRICE5', $result); + $this->assertEquals(-1000, $result['ITEMPRICE5']); + $this->assertArrayHasKey('ITEMVATCODE5', $result); + $this->assertEquals('0%', $result['ITEMVATCODE5']); + $this->assertArrayHasKey('TAXINCLUDED5', $result); + $this->assertEquals(1, $result['TAXINCLUDED5']); + $this->assertArrayHasKey('ITEMQUANT5', $result); + $this->assertEquals(1, $result['ITEMQUANT5']); + // amount: 5+5+(-10) + $this->assertArrayHasKey('AMOUNT', $result); + $this->assertEquals(12596, $result['AMOUNT']); + } + + protected function mockRefundHelper($params) + { + $helperMock = $this->getHelperMock( + 'ops/order_refund', array('getCreditMemoRequestParams', 'createRefundTransaction') + ); + $helperMock->expects($this->any()) + ->method('getCreditMemoRequestParams') + ->will($this->returnValue($params)); + $this->replaceByMock('helper', 'ops/order_refund', $helperMock); + } + + + public function tearDown() + { + parent::tearDown(); + $this->openInvoiceNlModel = null; + $this->testPayment = null; + } +} + \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Refund/ParameterTest.php b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Refund/ParameterTest.php new file mode 100644 index 0000000..4624e60 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Backend/Operation/Refund/ParameterTest.php @@ -0,0 +1,91 @@ +setCreditmemo($creditMemo); + $fakePayment->setOrder(Mage::getModel('sales/order')); + $fakePayment->setAdditionalInformation(array('paymentId' => '4711')); + $arrInfo = array( + 'operation' => 'refund', + 'invoice_id' => 2 + ); + $amount = 10; + $opsPaymentMethod = Mage::getModel('ops/payment_abstract'); + + $captureParameterModel = Mage::getModel('ops/backend_operation_refund_parameter'); + $requestParams = $captureParameterModel->getRequestParams($opsPaymentMethod, $fakePayment, $amount, $arrInfo); + $this->assertArrayHasKey('AMOUNT', $requestParams); + $this->assertArrayHasKey('PAYID', $requestParams); + $this->assertArrayHasKey('OPERATION', $requestParams); + $this->assertArrayHasKey('CURRENCY', $requestParams); + + $this->assertEquals(1000, $requestParams['AMOUNT']); + $this->assertEquals(4711, $requestParams['PAYID']); + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL, $requestParams['OPERATION']); + $this->assertEquals(Mage::app()->getStore($fakePayment->getOrder()->getStoreId())->getBaseCurrencyCode(), $requestParams['CURRENCY']); + } + + public function testGetRequestParamsWithAdditionalParameters() + { + $customerSession = $this->getModelMockBuilder('customer/session')->disableOriginalConstructor()->getMock(); + $this->replaceByMock('model', 'customer/session', $customerSession); + + $fakePayment = Mage::getModel('sales/order_payment'); + $creditMemo = Mage::getModel('sales/order_creditmemo'); + $creditMemo->setBaseShippingInclTax(10); + $fakePayment->setCreditmemo($creditMemo); + $fakePayment->setOrder(Mage::getModel('sales/order')->setBaseGrandTotal(100)); + $fakePayment->setAdditionalInformation(array('paymentId' => '4711')); + $fakeInvoice = Mage::getModel('sales/order_invoice'); + $fakePayment->setInvoice($fakeInvoice); + $arrInfo = array( + 'operation' => 'refund', + 'invoice_id' => 2 + ); + $opsPaymentMethod = Mage::getModel('ops/payment_openInvoiceNl'); + $captureParameterModel = Mage::getModel('ops/backend_operation_refund_parameter'); + $this->mockRefundHelper(); + $requestParams = $captureParameterModel->getRequestParams($opsPaymentMethod, $fakePayment, 0, $arrInfo); + $this->assertArrayHasKey('AMOUNT', $requestParams); + $this->assertArrayHasKey('PAYID', $requestParams); + $this->assertArrayHasKey('OPERATION', $requestParams); + $this->assertArrayHasKey('CURRENCY', $requestParams); + + $this->assertEquals(1000, $requestParams['AMOUNT']); + $this->assertEquals(4711, $requestParams['PAYID']); + $this->assertEquals(Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL, $requestParams['OPERATION']); + $this->assertEquals(Mage::app()->getStore($fakePayment->getOrder()->getStoreId())->getBaseCurrencyCode(), $requestParams['CURRENCY']); + } + + protected function mockRefundHelper() + { + $helperMock = $this->getHelperMock('ops/order_refund', array('getCreditMemoRequestParams', 'createRefundTransaction')); + $params = array( + 'creditmemo' => array( + 'items' => array( + 1 => array( + 'qty' => 0 + ), + 2 => array( + 'qty' => 0 + ) + ), + 'shipping_amount' => 0, + 'adjustment_positive' => 10, + 'adjustment_negative' => 0 + + ) + ); + $helperMock->expects($this->any()) + ->method('getCreditMemoRequestParams') + ->will($this->returnValue($params)); + $this->replaceByMock('helper', 'ops/order_refund', $helperMock); + } + +} + \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/ConfigTest.php b/app/code/community/Netresearch/OPS/Test/Model/ConfigTest.php new file mode 100644 index 0000000..77dae8c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/ConfigTest.php @@ -0,0 +1,611 @@ +_model = Mage::getModel('ops/config'); + } + + + public function testType() + { + $this->assertInstanceOf('Netresearch_OPS_Model_Config', $this->_model); + } + + public function testGetIntersolveBrands() + { + $path = 'payment/ops_interSolve/brands'; + + + Mage::getConfig()->saveConfig($path, serialize(array())); + Mage::getConfig()->cleanCache(); + $this->assertTrue(is_array($this->_model->getIntersolveBrands(null))); + + $this->assertEquals( + sizeof(unserialize(Mage::getStoreConfig('payment/ops_interSolve/brands', 0))), + sizeof($this->_model->getIntersolveBrands(0)) + ); + + + $newVouchers = array( + array('brand' => '1234', 'value' => '1234'), + array('brand' => '5678', 'value' => '5678'), + array('brand' => '9012', 'value' => '9012'), + ); + + $store = Mage::app()->getStore(0)->load(0); + $store->setConfig($path, serialize($newVouchers)); + $this->assertEquals( + sizeof($newVouchers), + sizeof($this->_model->getIntersolveBrands(null)) + ); + } + + public function testGetInlinePaymentCcTypes() + { + $sourceModel = Mage::getModel( + 'ops/source_cc_aliasInterfaceEnabledTypes' + ); + + $pathRedirectAll = 'payment/ops_cc/redirect_all'; + $pathSpecific = 'payment/ops_cc/inline_types'; + $store = Mage::app()->getStore(0)->load(0); + + $store->resetConfig(); + $store->setConfig($pathRedirectAll, 0); + $store->setConfig($pathSpecific, 'MasterCard,VISA'); + $this->assertEquals( + array('MasterCard', 'VISA'), + $this->_model->getInlinePaymentCcTypes('ops_cc') + ); + + $store->resetConfig(); + $store->setConfig($pathRedirectAll, 1); + $store->setConfig($pathSpecific, 'MasterCard,VISA'); + $this->assertEquals(array(), $this->_model->getInlinePaymentCcTypes('ops_cc')); + + $store->resetConfig(); + } + + public function testGetGenerateHashUrl() + { + $urlModel = $this->getModelMock('core/url', array('getUrl')); + $urlModel->expects($this->any()) + ->method('getUrl') + ->with( + 'ops/alias/generatehash', + array('_secure' => false, '_nosid' => true) + ); + $this->replaceByMock('model', 'core/url', $urlModel); + $this->_model->getGenerateHashUrl(); + + $urlModel = $this->getModelMock('core/url', array('getUrl')); + $urlModel->expects($this->any()) + ->method('getUrl') + ->with( + 'ops/alias/generatehash', + array('_secure' => false, '_nosid' => true, '_store' => 1) + ); + $this->replaceByMock('model', 'core/url', $urlModel); + $this->_model->getGenerateHashUrl(1); + } + + public function testGetAliasAcceptUrl() + { + $urlModel = $this->getModelMock('core/url', array('getUrl')); + $urlModel->expects($this->any()) + ->method('getUrl') + ->with( + 'ops/alias/accept', + array('_secure' => false, '_nosid' => true) + ); + $this->replaceByMock('model', 'core/url', $urlModel); + $this->_model->getAliasAcceptUrl(); + + $urlModel = $this->getModelMock('core/url', array('getUrl')); + $urlModel->expects($this->any()) + ->method('getUrl') + ->with( + 'ops/alias/accept', + array('_secure' => false, '_nosid' => true, '_store' => 1) + ); + $this->replaceByMock('model', 'core/url', $urlModel); + $this->_model->getAliasAcceptUrl(1); + } + + public function testGetAliasExceptionUrl() + { + $urlModel = $this->getModelMock('core/url', array('getUrl')); + $urlModel->expects($this->any()) + ->method('getUrl') + ->with( + 'ops/alias/exception', + array('_secure' => false, '_nosid' => true) + ); + $this->replaceByMock('model', 'core/url', $urlModel); + $this->_model->getAliasExceptionUrl(); + + $urlModel = $this->getModelMock('core/url', array('getUrl')); + $urlModel->expects($this->any()) + ->method('getUrl') + ->with( + 'ops/alias/exception', + array('_secure' => false, '_nosid' => true, '_store' => 1) + ); + $this->replaceByMock('model', 'core/url', $urlModel); + $this->_model->getAliasExceptionUrl(1); + } + + public function testGetCcSaveAliasUrl() + { + $urlModel = $this->getModelMock('core/url', array('getUrl')); + $urlModel->expects($this->any()) + ->method('getUrl') + ->with('ops/alias/save', array('_secure' => false)); + $this->replaceByMock('model', 'core/url', $urlModel); + $this->_model->getCcSaveAliasUrl(); + + $urlModel = $this->getModelMock('core/url', array('getUrl')); + $urlModel->expects($this->any()) + ->method('getUrl') + ->with( + 'ops/alias/save', + array('_secure' => false, '_store' => 1) + ); + $this->replaceByMock('model', 'core/url', $urlModel); + $this->_model->getCcSaveAliasUrl(1); + } + + public function testIsAliasInfoBlockEnabled() + { + $path = 'payment/ops_cc/show_alias_manager_info_for_guests'; + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig($path, 0); + $this->assertFalse($this->_model->isAliasInfoBlockEnabled()); + + $store->resetConfig(); + $store->setConfig($path, 1); + $this->assertTrue($this->_model->isAliasInfoBlockEnabled()); + } + + + public function testObserveCreditMemoCreation() + { + $this->assertEventObserverDefined( + 'adminhtml', + 'core_block_abstract_to_html_after', + 'ops/observer', + 'showWarningForClosedTransactions' + ); + } + + public function testAppendCheckboxToRefundForm() + { + $this->assertEventObserverDefined( + 'adminhtml', + 'core_block_abstract_to_html_after', + 'ops/observer', + 'appendCheckBoxToRefundForm' + ); + } + + public function testGetOrderReference() + { + $store = Mage::app()->getStore(0)->load(0); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID, + $this->_model->getOrderReference() + ); + + $store->setConfig( + 'payment_services/ops/redirectOrderReference', + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID, + $this->_model->getOrderReference() + ); + } + + public function testGetShowQuoteIdInOrderGrid() + { + $store = Mage::app()->getStore(0)->load(0); + $this->assertEquals(1, $this->_model->getShowQuoteIdInOrderGrid()); + + $store->setConfig('payment_services/ops/showQuoteIdInOrderGrid', 0); + $this->assertEquals(0, $this->_model->getShowQuoteIdInOrderGrid()); + } + + public function testIsAliasManagerEnabled() + { + $path = 'payment/ops_cc/active_alias'; + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig($path, 0); + $this->assertFalse($this->_model->isAliasManagerEnabled('ops_cc')); + + $store->resetConfig(); + $store->setConfig($path, 1); + $this->assertTrue($this->_model->isAliasManagerEnabled('ops_cc')); + + } + + + public function getAliasUsageForNewAlias() + { + $path = 'payment/ops_cc/alias_usage_for_new_alias'; + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig($path, 'my text goes here'); + + $this->assertEquals('my text goes here', $this->_model->getAliasUsageForNewAlias('ops_cc', 0)); + + $store->resetConfig(); + $store->setConfig($path, 'my text goes here two'); + $this->assertNotEquals('my text goes here', $this->_model->getAliasUsageForNewAlias('ops_cc', 0)); + } + + + public function getAliasUsageForExistingAlias() + { + $path = 'payment/ops_cc/alias_usage_for_existing_alias'; + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig($path, 'my text goes here'); + + $this->assertEquals('my text goes here', $this->_model->getAliasUsageForNewAlias('ops_cc', 0)); + + $store->resetConfig(); + $store->setConfig($path, 'my text goes here two'); + $this->assertNotEquals('my text goes here', $this->_model->getAliasUsageForNewAlias('ops_cc', 0)); + } + + public function testGetAcceptRedirectLocation() + { + $this->assertEquals( + Netresearch_OPS_Model_Config::OPS_CONTROLLER_ROUTE_PAYMENT + . 'accept', + $this->_model->getAcceptRedirectRoute() + ); + } + + public function testGetCancelRedirectLocation() + { + $this->assertEquals( + Netresearch_OPS_Model_Config::OPS_CONTROLLER_ROUTE_PAYMENT + . 'cancel', + $this->_model->getCancelRedirectRoute() + ); + } + + public function testGetDeclineRedirectLocation() + { + $this->assertEquals( + Netresearch_OPS_Model_Config::OPS_CONTROLLER_ROUTE_PAYMENT + . 'decline', + $this->_model->getDeclineRedirectRoute() + ); + } + + public function testGetExceptionRedirectLocation() + { + $this->assertEquals( + Netresearch_OPS_Model_Config::OPS_CONTROLLER_ROUTE_PAYMENT + . 'exception', + $this->_model->getExceptionRedirectRoute() + ); + } + + /** + * asserts that the event for clearing old data for the payment methods is set up properly + */ + public function testClearMethodBeforeImportEventExists() + { + $this->assertEventObserverDefined( + 'global', + 'sales_quote_payment_import_data_before', + 'ops/observer', + 'clearPaymentMethodFromQuote' + ); + } + + + /** + * asserts that the event for clearing old data for the payment methods is set up properly + */ + public function testSalesOrderPaymentCapture() + { + $this->assertEventObserverDefined( + 'adminhtml', + 'sales_order_payment_capture', + 'ops/observer', + 'salesOrderPaymentCapture' + ); + } + + public function testGetMethodsRequiringAdditionalParametersFor() + { + $capturePms = Mage::getModel('ops/config')->getMethodsRequiringAdditionalParametersFor('capture'); + $this->assertTrue(is_array($capturePms)); + $this->assertTrue(0 < count($capturePms)); + $this->assertTrue(array_key_exists('OpenInvoiceNl', $capturePms)); + $this->assertTrue(array_key_exists('OpenInvoiceNl', $capturePms)); + $this->assertEquals('Netresearch_OPS_Model_Payment_OpenInvoiceNl', $capturePms['OpenInvoiceNl']); + } + + public function testDisableCaptureForZeroAmountInvoiceEventExists() + { + $this->assertEventObserverDefined( + 'adminhtml', + 'core_block_abstract_prepare_layout_before', + 'ops/observer', + 'disableCaptureForZeroAmountInvoice' + ); + } + + public function testGetIdealIssuers() + { + $issuers = $this->_model->getIDealIssuers(); + $this->assertTrue(is_array($issuers)); + $this->assertTrue(array_key_exists('ABNANL2A', $issuers)); + $this->assertEquals('ABN AMRO', $issuers['ABNANL2A']); + + $this->assertTrue(array_key_exists('RABONL2U', $issuers)); + $this->assertEquals('Rabobank', $issuers['RABONL2U']); + + $this->assertTrue(array_key_exists('INGBNL2A', $issuers)); + $this->assertEquals('ING', $issuers['INGBNL2A']); + + $this->assertTrue(array_key_exists('SNSBNL2A', $issuers)); + $this->assertEquals('SNS Bank', $issuers['SNSBNL2A']); + + $this->assertTrue(array_key_exists('RBRBNL21', $issuers)); + $this->assertEquals('Regio Bank', $issuers['RBRBNL21']); + + $this->assertTrue(array_key_exists('ASNBNL21', $issuers)); + $this->assertEquals('ASN Bank', $issuers['ASNBNL21']); + + $this->assertTrue(array_key_exists('TRIONL2U', $issuers)); + $this->assertEquals('Triodos Bank', $issuers['TRIONL2U']); + + $this->assertTrue(array_key_exists('FVLBNL22', $issuers)); + $this->assertEquals('Van Lanschot Bankiers', $issuers['FVLBNL22']); + + $this->assertTrue(array_key_exists('KNABNL2H', $issuers)); + $this->assertEquals('Knab Bank', $issuers['KNABNL2H']); + } + + public function testAddCcPaymentMethodEventExists() + { + $this->assertEventObserverDefined( + 'global', + 'core_block_abstract_prepare_layout_before', + 'ops/observer', + 'addCcPaymentMethod' + ); + } + + + public function testCanSubmitExtraParameters() + { + $this->assertTrue($this->_model->canSubmitExtraParameter()); + $path = 'payment_services/ops/submitExtraParameters'; + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig($path, 0); + $this->assertFalse($this->_model->isAliasManagerEnabled('ops_cc')); + } + + + public function testGetParameterLengths() + { + $fieldLengths = $this->_model->getParameterLengths(); + $this->assertEquals($this->validFieldLengths(), $fieldLengths); + } + + + protected function validFieldLengths() + { + return array( + 'ECOM_SHIPTO_POSTAL_NAME_FIRST' => 50, + 'ECOM_SHIPTO_POSTAL_NAME_LAST' => 50, + 'ECOM_SHIPTO_POSTAL_STREET_LINE1' => 35, + 'ECOM_SHIPTO_POSTAL_STREET_LINE2' => 35, + 'ECOM_SHIPTO_POSTAL_STREET_LINE3' => 35, + 'ECOM_SHIPTO_POSTAL_COUNTRYCODE' => 2, + 'ECOM_SHIPTO_POSTAL_COUNTY' => 25, + 'ECOM_SHIPTO_POSTAL_POSTALCODE' => 10, + 'ECOM_SHIPTO_POSTAL_CITY' => 25, + 'ECOM_SHIPTO_POSTAL_STREET_NUMBER' => 10, + 'CN' => 35, + 'OWNERZIP' => 10, + 'OWNERCTY' => 2, + 'OWNERTOWN' => 40, + 'OWNERTELNO' => 30, + 'OWNERADDRESS' => 35, + 'ECOM_BILLTO_POSTAL_POSTALCODE' => 10, + 'ECOM_BILLTO_POSTAL_NAME_FIRST' => 50, + 'ECOM_BILLTO_POSTAL_NAME_LAST' => 50, + 'ECOM_SHIPTO_POSTAL_STATE' => 35 + ); + + } + + public function testGetInlineOrderReference() + { + $store = Mage::app()->getStore(0)->load(0); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID, + $this->_model->getInlineOrderReference() + ); + + $store->setConfig( + 'payment_services/ops/inlineOrderReference', + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID + ); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID, + $this->_model->getInlineOrderReference() + ); + } + + public function testSetOrderStateDirectLinkExists() + { + $this->assertEventObserverDefined( + 'global', + 'sales_order_payment_place_end', + 'ops/observer', + 'setOrderStateDirectLink' + ); + } + + public function testGetFrontendGatewayPath() + { + $this->setMode(Netresearch_OPS_Model_Source_Mode::CUSTOM); + $expectedResult = Mage::getStoreConfig('payment_services/ops/frontend_gateway'); + $this->assertEquals($expectedResult, $this->_model->getFrontendGatewayPath(0)); + $this->setMode(Netresearch_OPS_Model_Source_Mode::TEST); + $this->assertContains('test', $this->_model->getFrontendGatewayPath(0)); + } + + public function testGetDirectLinkGatewayPath() + { + $this->setMode(Netresearch_OPS_Model_Source_Mode::CUSTOM); + $expectedResult = Mage::getStoreConfig('payment_services/ops/directlink_gateway'); + $this->assertEquals($expectedResult, $this->_model->getDirectLinkGatewayPath(0)); + $this->setMode(Netresearch_OPS_Model_Source_Mode::TEST); + $this->assertContains('test', $this->_model->getDirectLinkGatewayPath(0)); + } + + public function testGetDirectLinkGatewayOrderPath() + { + $this->setMode(Netresearch_OPS_Model_Source_Mode::CUSTOM); + $expectedResult = Mage::getStoreConfig('payment_services/ops/directlink_gateway_order'); + $this->assertEquals($expectedResult, $this->_model->getDirectLinkGatewayOrderPath(0)); + $this->setMode(Netresearch_OPS_Model_Source_Mode::TEST); + $this->assertContains('test', $this->_model->getDirectLinkGatewayOrderPath(0)); + } + + public function testGetAliasGatewayUrl() + { + $this->setMode(Netresearch_OPS_Model_Source_Mode::CUSTOM); + $expectedResult = Mage::getStoreConfig('payment_services/ops/ops_alias_gateway'); + $this->assertEquals($expectedResult, $this->_model->getAliasGatewayUrl(0)); + + // test with standard alias gateway + Mage::app()->getStore(0)->setConfig( + Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH . 'ops_alias_gateway_test', '' + ); + $this->setMode(Netresearch_OPS_Model_Source_Mode::TEST); + $this->assertContains('ncol/test', $this->_model->getAliasGatewayUrl(0)); + + Mage::app()->getStore(0)->setConfig( + Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH . 'ops_alias_gateway_test', 'abc' + ); + $this->assertEquals('abc', $this->_model->getAliasGatewayUrl(0)); + + $this->setMode(Netresearch_OPS_Model_Source_Mode::PROD); + $this->assertNotContains('ncol/prod', $this->_model->getAliasGatewayUrl(0)); + } + + public function testGetDirectLinkMaintenanceApiPath() + { + $this->setMode(Netresearch_OPS_Model_Source_Mode::CUSTOM); + $expectedResult = Mage::getStoreConfig('payment_services/ops/directlink_maintenance_api'); + $this->assertEquals($expectedResult, $this->_model->getDirectLinkMaintenanceApiPath(0)); + $this->setMode(Netresearch_OPS_Model_Source_Mode::TEST); + $this->assertContains('test', $this->_model->getDirectLinkMaintenanceApiPath(0)); + } + + public function testGetMode() + { + $this->setMode(Netresearch_OPS_Model_Source_Mode::CUSTOM); + $this->assertEquals($this->_model->getMode(0), Netresearch_OPS_Model_Source_Mode::CUSTOM); + } + + protected function setMode($mode, $storeId = 0) + { + Mage::app()->getStore($storeId)->setConfig(Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH . 'mode', $mode); + } + + public function testGetResendPaymentInfoTemplate() + { + $config = Mage::getModel('ops/config'); + $this->assertEquals($config->getResendPaymentInfoTemplate(), 'payment_services_ops_resendPaymentInfo_template'); + } + + + public function testGetPayPerMailTemplate() + { + $config = Mage::getModel('ops/config'); + $this->assertEquals($config->getPayPerMailTemplate(), 'payment_services_ops_payPerMail_template'); + } + + + public function testGetResendPaymentInfoIdentity() + { + $config = Mage::getModel('ops/config'); + $this->assertEquals($config->getResendPaymentInfoIdentity(), 'sales'); + } + + public function testGetOpsBaseUrl() + { + $this->setMode(Netresearch_OPS_Model_Source_Mode::TEST); + $this->assertEquals('https://secure.domain.tld/ncol/test', $this->_model->getOpsBaseUrl(0)); + $this->setMode(Netresearch_OPS_Model_Source_Mode::PROD); + $this->assertEquals('https://secure.domain.tld/ncol/prod', $this->_model->getOpsBaseUrl(0)); + $this->setMode(Netresearch_OPS_Model_Source_Mode::CUSTOM); + $this->assertEmpty($this->_model->getOpsBaseUrl(0)); + } + + public function testGetAllRecurringCcTypes() + { + /** @var Netresearch_OPS_Model_Config $config */ + $config = Mage::getModel('ops/config'); + $ccTypes = $config->getAllRecurringCcTypes(); + $this->assertEquals( + $ccTypes, array('American Express', 'Diners Club', 'MaestroUK', 'MasterCard', 'VISA', 'JCB') + ); + } + + public function testGetAcceptedRecurringCcTypes() + { + /** @var Netresearch_OPS_Model_Config $config */ + $config = Mage::getModel('ops/config'); + $ccTypes = $config->getAcceptedRecurringCcTypes(); + $this->assertEquals( + $ccTypes, array('American Express', 'Diners Club', 'MaestroUK', 'MasterCard', 'VISA', 'JCB') + ); + } + + public function testGetDeviceFingerPrinting() + { + $config = Mage::getModel('ops/config'); + // default false + $this->assertFalse($config->getDeviceFingerPrinting(0)); + Mage::app()->getStore(0)->setConfig( + Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH . 'device_fingerprinting', 1 + ); + $this->assertTrue($config->getDeviceFingerPrinting(0)); + } + + public function testGetTransActionTimeout() + { + $config = Mage::getModel('ops/config'); + // default false + Mage::app()->getStore(0)->setConfig(Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH . 'ops_rtimeout', 0); + $this->assertEquals(0, $config->getTransActionTimeout(0)); + Mage::app()->getStore(0)->setConfig(Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH . 'ops_rtimeout', 45); + $this->assertEquals(45, $config->getTransActionTimeout(0)); + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Model/File/Download.php b/app/code/community/Netresearch/OPS/Test/Model/File/Download.php new file mode 100644 index 0000000..a6e1afa --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/File/Download.php @@ -0,0 +1,37 @@ +testFile = Mage::helper('ops/data')->getLogPath(); + if(!file_exists($this->testFile)){ + $file = fopen($this->testFile, 'c'); + fclose($file); + } + + } + /** + * @expectedException Exception + */ + public function testFailingGetFile() + { + $model = Mage::getModel('ops/file_download'); + $path = 'abc'; + $model->getFile($path); + } + + public function testSuccessGetFile() + { + $model = Mage::getModel('ops/file_download'); + if(filesize($this->testFile) > $model::ONE_MEGABYTE){ + $this->assertEquals(0, strpos(basename($model->getFile($this->testFile)), 'tempFile')); + }else{ + $this->assertEquals($model->getFile($this->testFile), $this->testFile); + } + + } + +} + \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Kwixo/Category/MappingTest.php b/app/code/community/Netresearch/OPS/Test/Model/Kwixo/Category/MappingTest.php new file mode 100644 index 0000000..a30514a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Kwixo/Category/MappingTest.php @@ -0,0 +1,28 @@ +assertEquals( + 10, + Mage::getModel('ops/kwixo_category_mapping')->loadByCategoryId(10) + ->getKwixoCategoryId() + ); + $this->assertEquals( + 1, + Mage::getModel('ops/kwixo_category_mapping')->loadByCategoryId(11) + ->getKwixoCategoryId() + ); + $this->assertEquals( + null, + Mage::getModel('ops/kwixo_category_mapping')->loadByCategoryId(12) + ->getKwixoCategoryId() + ); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Kwixo/Category/MappingTest/fixtures/category_mapping.yaml b/app/code/community/Netresearch/OPS/Test/Model/Kwixo/Category/MappingTest/fixtures/category_mapping.yaml new file mode 100644 index 0000000..e18a796 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Kwixo/Category/MappingTest/fixtures/category_mapping.yaml @@ -0,0 +1,11 @@ +tables: + ops/kwixo_category_mapping: + - id: 1 + kwixo_category_id: 10 + category_id: 10 + - id: 2 + kwixo_category_id: 1 + category_id: 11 + - id: 3 + kwixo_category_id: 2 + category_id: 11 \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Mysql4/Alias/AliasTest.php b/app/code/community/Netresearch/OPS/Test/Model/Mysql4/Alias/AliasTest.php new file mode 100644 index 0000000..51b888b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Mysql4/Alias/AliasTest.php @@ -0,0 +1,9 @@ +assertTypeOf('Netresearch_OPS_Model_Mysql4_Alias_Collection', Mage::getModel('ops/mysql4_alias')->getCollection()); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Mysql4/AliasTest.php b/app/code/community/Netresearch/OPS/Test/Model/Mysql4/AliasTest.php new file mode 100644 index 0000000..eb97365 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Mysql4/AliasTest.php @@ -0,0 +1,11 @@ +assertInstanceOf( + 'Netresearch_OPS_Model_Mysql4_Alias', + Mage::getModel('ops/mysql4_alias') + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/ObserverTest.php b/app/code/community/Netresearch/OPS/Test/Model/ObserverTest.php new file mode 100644 index 0000000..67697b8 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/ObserverTest.php @@ -0,0 +1,1111 @@ +_model = Mage::getModel('ops/observer'); + } + + public function testType() + { + $this->assertInstanceOf('Netresearch_OPS_Model_Observer', $this->_model); + } + + public function testPerformDirectLinkRequestWithUnknownResponse() + { + $quote = $this->getModelMock('sales/quote', array('save')); + $aliasHelperMock = $this->getHelperMock('ops/alias', array('setAliasActive')); + $this->replaceByMock('helper', 'ops/alias', $aliasHelperMock); + $payment = $this->getModelMock('sales/quote_payment', array('save')); + $payment->expects($this->any()) + ->method('save') + ->will($this->returnValue(null)); + $this->replaceByMock('model', 'sales/quote_payment', $payment); + $quote->setPayment($payment); + $response = null; + $directLinkMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkMock->expects($this->any()) + ->method('performRequest') + ->will($this->returnValue($response)); + $this->replaceByMock('model', 'ops/api_directlink', $directLinkMock); + $observer = Mage::getModel('ops/observer'); + $observer->performDirectLinkRequest($quote, array()); + $this->assertFalse($this->setExpectedException('PHPUnit_Framework_ExpectationFailedException')); + $this->assertTrue(array_key_exists('ops_response', $quote->getPayment()->getAdditionalInformation())); + } + + public function testPerformDirectLinkRequestWithInvalidResponse() + { + $quote = new Varien_Object(); + $aliasHelperMock = $this->getHelperMock('ops/alias', array('setAliasActive')); + $this->replaceByMock('helper', 'ops/alias', $aliasHelperMock); + $payment = $this->getModelMock('sales/quote_payment', array('save')); + $payment->expects($this->any()) + ->method('save') + ->will($this->returnValue(null)); + $this->replaceByMock('model', 'sales/quote_payment', $payment); + $quote->setPayment($payment); + $response = ''; + $directLinkMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkMock->expects($this->any()) + ->method('performRequest') + ->will($this->returnValue($response)); + $this->replaceByMock('model', 'ops/api_directlink', $directLinkMock); + $observer = Mage::getModel('ops/observer'); + $this->assertTrue($this->setExpectedException('PHPUnit_Framework_ExpectationFailedException')); + $observer->performDirectLinkRequest($quote, array()); + $this->assertFalse(array_key_exists('ops_response', $quote->getPayment()->getAdditionalInformation())); + } + + public function testPerformDirectLinkRequestWithValidResponse() + { + $quote = new Varien_Object(); + $aliasHelperMock = $this->getHelperMock('ops/alias', array('setAliasActive')); + $this->replaceByMock('helper', 'ops/alias', $aliasHelperMock); + $payment = $this->getModelMock('sales/quote_payment', array('save')); + $payment->expects($this->any()) + ->method('save') + ->will($this->returnValue(null)); + $this->replaceByMock('model', 'sales/quote_payment', $payment); + $quote->setPayment($payment); + $response = array('STATUS' => Netresearch_OPS_Model_Status::AUTHORIZED); + $directLinkMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkMock->expects($this->any()) + ->method('performRequest') + ->will($this->returnValue($response)); + $this->replaceByMock('model', 'ops/api_directlink', $directLinkMock); + $observer = Mage::getModel('ops/observer'); + $this->assertFalse($this->setExpectedException('PHPUnit_Framework_ExpectationFailedException')); + $observer->performDirectLinkRequest($quote, array()); + $this->assertTrue(array_key_exists('ops_response', $quote->getPayment()->getAdditionalInformation())); + } + + public function testPerformDirectLinkRequestWithValidResponseButInvalidStatus() + { + $quote = new Varien_Object(); + $aliasHelperMock = $this->getHelperMock('ops/alias', array('setAliasActive')); + $this->replaceByMock('helper', 'ops/alias', $aliasHelperMock); + $payment = $this->getModelMock('sales/quote_payment', array('save')); + $payment->expects($this->any()) + ->method('save') + ->will($this->returnValue(null)); + $this->replaceByMock('model', 'sales/quote_payment', $payment); + $quote->setPayment($payment); + $response = array('STATUS' => Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED); + $directLinkMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkMock->expects($this->any()) + ->method('performRequest') + ->will($this->returnValue($response)); + $this->replaceByMock('model', 'ops/api_directlink', $directLinkMock); + $observer = Mage::getModel('ops/observer'); + $this->assertTrue($this->setExpectedException('PHPUnit_Framework_ExpectationFailedException')); + $observer->performDirectLinkRequest($quote, array()); + $this->assertFalse(array_key_exists('ops_response', $quote->getPayment()->getAdditionalInformation())); + } + + public function testShowWarningForClosedTransactions() + { + Mage::register('current_creditmemo', null); + $transport = new Varien_Object(); + $transport->setHtml('Foo'); + $observer = Mage::getModel('ops/observer'); + $event = new Varien_Object(); + $event->setBlock(''); + $this->assertEquals('', $observer->showWarningForClosedTransactions($event)); + + $order = new Varien_Object(); + $payment = new Varien_Object(); + $methodInstance = Mage::getModel('ops/payment_cc'); + $payment->setMethodInstance($methodInstance); + $order->setPayment($payment); + $invoice = new Varien_Object(); + $invoice->setTransactionId(1); + $creditMemo = $this->getModelMock( + 'sales/order_creditmemo', + array('getOrder', 'getInvoice', 'canRefund', 'getOrderId') + ); + $creditMemo->expects($this->any()) + ->method('getOrder') + ->will($this->returnValue($order)); + $creditMemo->expects($this->any()) + ->method('getInvoice') + ->will($this->returnValue($invoice)); + $creditMemo->expects($this->any()) + ->method('canRefund') + ->will($this->returnValue(false)); + $creditMemo->expects($this->any()) + ->method('getOrderId') + ->will($this->returnValue(1)); + Mage::register('current_creditmemo', $creditMemo); + $block = Mage::app()->getLayout()->getBlockSingleton('adminhtml/sales_order_creditmemo_create'); + + $blockMock = $this->getBlockMock( + 'ops/adminhtml_sales_order_creditmemo_closedTransaction_warning', + array('renderView') + ); + $blockMock->expects($this->once()) + ->method('renderView') + ->will($this->returnValue('warning')); + $this->replaceByMock('block', 'ops/adminhtml_sales_order_creditmemo_closedTransaction_warning', $blockMock); + $event->setBlock($block); + $event->setTransport($transport); + $html = $observer->showWarningForClosedTransactions($event); + $this->assertEquals('warningFoo', $html); + $this->assertNotEquals('Barwarning', $html); + + Mage::unregister('current_creditmemo'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testConfirmAliasPayment() + { + $quote = Mage::getModel('sales/quote')->load(23); + $order = Mage::getModel('sales/order')->load(11); + $payment = $quote->getPayment(); +// $payment->expects($this->any()) +// ->method('getMethodInstance') +// ->will($this->returnValue(Mage::getModel('ops/payment_cc'))); + $payment->setAdditionalInformation(array('cvc' => '123', 'alias' => '99')); + $payment->setMethod('ops_cc'); + $quote->setPayment($payment); + $requestParams = $this->getRequestParamsWithAlias($quote, $order); + + $helperMock = $this->getHelperMock('ops/data', array('isAdminSession')); + $helperMock->expects($this->any()) + ->method('isAdminSession') + ->will($this->returnValue(true)); + $this->replaceByMock('helper', 'ops/data', $helperMock); + + $quoteHelperMock = $this->getHelperMock('ops/quote', array('getQuoteCurrency')); + $quoteHelperMock->expects($this->any()) + ->method('getQuoteCurrency') + ->will($this->returnValue('USD')); + $this->replaceByMock('helper', 'ops/quote', $quoteHelperMock); + + $observerMock = $this->getModelMock( + 'ops/observer', + array('performDirectLinkRequest', 'invokeRequestParamValidation') + ); + $observerMock->expects($this->any()) + ->method('performDirectLinkRequest') + ->will($this->returnValue('WuselDusel')); + + $orderHelperMock = $this->getHelperMock('ops/order', array('checkIfAddressesAreSame')); + $orderHelperMock->expects($this->any()) + ->method('checkIfAddressesAreSame') + ->will($this->returnValue(1)); + $this->replaceByMock('helper', 'ops/order', $orderHelperMock); + + $customerSessionMock = $this->getModelMock('customer/session', array('isLoggedIn')); + $customerSessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $customerSessionMock); + + $configModelMock = $this->getModelMock( + 'ops/config', array( + 'get3dSecureIsActive', + 'getAcceptUrl', + 'getDeclineUrl', + 'getExceptionUrl' + ) + ); + + $configModelMock->expects($this->any()) + ->method('get3dSecureIsActive') + ->will($this->returnValue(true)); + $configModelMock->expects($this->any()) + ->method('getAcceptUrl') + ->will($this->returnValue('www.abc.com')); + $configModelMock->expects($this->any()) + ->method('getDeclineUrl') + ->will($this->returnValue('www.abcd.com')); + $configModelMock->expects($this->any()) + ->method('getExceptionUrl') + ->will($this->returnValue('www.abcde.com')); + $this->replaceByMock('model', 'ops/config', $configModelMock); + + $aliashelperMock = $this->getHelperMock('ops/alias', array('getAlias', 'cleanUpAdditionalInformation')); + $aliashelperMock->expects($this->any()) + ->method('getAlias') + ->with($quote) + ->will($this->returnValue('99')); + $this->replaceByMock('helper', 'ops/alias', $aliashelperMock); + $this->assertEquals('WuselDusel', $observerMock->confirmAliasPayment($order, $quote)); + + $helperMock = $this->getHelperMock('ops/data', array('isAdminSession')); + $helperMock->expects($this->any()) + ->method('isAdminSession') + ->will($this->returnValue(true)); + $this->replaceByMock('helper', 'ops/data', $helperMock); + + $observerMock = $this->getModelMock( + 'ops/observer', + array('performDirectLinkRequest', 'invokeRequestParamValidation') + ); + + $requestParams = $this->getRequestParamsWithoutAlias($quote, $order); + $observerMock->expects($this->any()) + ->method('performDirectLinkRequest') + ->will($this->returnValue('wrong')); + $this->assertEquals('wrong', $observerMock->confirmAliasPayment($order, $quote)); + + + $observerMock = $this->getModelMock('ops/observer', array('performDirectLinkRequest')); + $validatorMock = $this->getModelMock('ops/validator_parameter_validator', array('isValid')); + $validatorMock->expects($this->once()) + ->method('isValid') + ->will($this->returnValue(false)); + $validatorFactoryMock = $this->getModelMock('ops/validator_parameter_factory', array('getValidatorFor')); + $validatorFactoryMock->expects($this->once()) + ->method('getValidatorFor') + ->will($this->returnValue($validatorMock)); + $this->replaceByMock('model', 'ops/validator_parameter_factory', $validatorFactoryMock); + try { + $observerMock->confirmAliasPayment($order, $quote); + } catch (Exception $e) { + $this->assertTrue($e instanceof Mage_Core_Exception); + } + } + + + private function getRequestParamsWithAlias($quote, $order) + { + return array( + 'ALIAS' => '99', + 'AMOUNT' => 0.0, + 'CURRENCY' => 'USD', + 'OPERATION' => 'RES', + 'ORDERID' => Mage::getSingleton('ops/config')->getConfigData('devprefix') + . $order->getQuoteId(), + 'EMAIL' => 'hubertus.von.fuerstenberg@trash-mail.com', + 'OWNERADDRESS' => utf8_decode('An der Tabaksmühle 3a'), + 'OWNERZIP' => '04229', + 'OWNERTELNO' => null, + 'OWNERCTY' => 'DE', + 'ADDMATCH' => 1, + + 'RTIMEOUT' => 45, + 'CREDITDEBIT' => 'C', + 'ECOM_SHIPTO_POSTAL_POSTALCODE' => '04229', + 'ECOM_BILLTO_POSTAL_POSTALCODE' => '04229', + 'CVC' => '123', + 'REMOTE_ADDR' => 'NONE', + 'CUID' => null, + 'ECI' => Netresearch_OPS_Model_Eci_Values::MANUALLY_KEYED_FROM_MOTO, + 'OWNERTOWN' => 'Leipzig', + 'ORIG' => Mage::helper('ops/data')->getModuleVersionString(), + 'ECOM_SHIPTO_POSTAL_NAME_FIRST' => 'Hubertus', + 'ECOM_SHIPTO_POSTAL_NAME_LAST' => utf8_decode('Fürstenberg'), + 'ECOM_SHIPTO_POSTAL_STREET_LINE1' => utf8_decode('An der Tabaksmühle 3a'), + 'ECOM_SHIPTO_POSTAL_STREET_LINE2' => '', + 'ECOM_SHIPTO_POSTAL_STREET_LINE3' => '', + 'ECOM_SHIPTO_POSTAL_COUNTRYCODE' => 'DE', + 'ECOM_SHIPTO_POSTAL_CITY' => 'Leipzig', + 'ECOM_SHIPTO_POSTAL_STATE' => '', + ); + } + + private function getRequestParamsWithoutAlias($quote, $order) + { + return array( + 'ALIAS' => '99', + 'AMOUNT' => 0.0, + 'CURRENCY' => 'USD', + 'OPERATION' => 'RES', + 'ORDERID' => Mage::getSingleton('ops/config')->getConfigData('devprefix') + . $order->getQuoteId(), + 'EMAIL' => 'hubertus.von.fuerstenberg@trash-mail.com', + 'OWNERADDRESS' => utf8_decode('An der Tabaksmühle 3a'), + 'OWNERZIP' => '04229', + 'OWNERTELNO' => null, + 'OWNERCTY' => 'DE', + 'ADDMATCH' => 1, + 'RTIMEOUT' => 45, + 'CREDITDEBIT' => 'C', + 'ECOM_SHIPTO_POSTAL_POSTALCODE' => '04229', + 'ECOM_BILLTO_POSTAL_POSTALCODE' => '04229', + 'CVC' => '123', + 'REMOTE_ADDR' => 'NONE', + 'OWNERTOWN' => 'Leipzig', + 'ORIG' => Mage::helper('ops/data')->getModuleVersionString(), + 'ECOM_SHIPTO_POSTAL_NAME_FIRST' => 'Hubertus', + 'ECOM_SHIPTO_POSTAL_NAME_LAST' => utf8_decode('Fürstenberg'), + 'ECOM_SHIPTO_POSTAL_STREET_LINE1' => utf8_decode('An der Tabaksmühle 3a'), + 'ECOM_SHIPTO_POSTAL_STREET_LINE2' => '', + 'ECOM_SHIPTO_POSTAL_STREET_LINE3' => '', + 'ECOM_SHIPTO_POSTAL_COUNTRYCODE' => 'DE', + 'ECOM_SHIPTO_POSTAL_CITY' => 'Leipzig', + 'ECI' => 1, + 'CUID' => null, + 'ECOM_SHIPTO_POSTAL_STATE' => '', + ); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testConfirmDdPayment() + { + $quote = Mage::getModel('sales/quote')->load(10); + $order = Mage::getModel('sales/order')->load(11); + + $observerMock = $this->getModelMock( + 'ops/observer', + array('performDirectLinkRequest', 'invokeRequestParamValidation') + ); + + $requestParams = array( + 'AMOUNT' => 0.0, + 'CARDNO' => '12335BLZ12345566', + 'CN' => utf8_decode('Hubertus zu Fürstenberg'), + 'CURRENCY' => 'USD', + 'ED' => '9999', + 'OPERATION' => 'RES', + 'ORDERID' => Mage::getSingleton('ops/config')->getConfigData('devprefix') + . $quote->getId(), + 'PM' => 'Direct Debits DE', + 'OWNERADDRESS' => utf8_decode('An der Tabaksmühle 3a'), + 'OWNERZIP' => '04229', + 'OWNERTELNO' => null, + 'OWNERCTY' => 'DE', + 'ADDMATCH' => 1, + 'ECOM_SHIPTO_POSTAL_POSTALCODE' => '04229', + 'ECOM_BILLTO_POSTAL_POSTALCODE' => '04229', + 'CUID' => null, + 'BRAND' => 'Direct Debits DE', + 'ECI' => Netresearch_OPS_Model_Eci_Values::MANUALLY_KEYED_FROM_MOTO, + 'OWNERTOWN' => 'Leipzig', + ); + + $directDebitHelperMock = $this->getHelperMock('ops/directDebit', array('getDirectLinkRequestParams')); + $directDebitHelperMock->expects($this->any()) + ->method('getDirectLinkRequestParams') + ->will($this->returnValue($requestParams)); + $this->replaceByMock('helper', 'ops/directDebit', $directDebitHelperMock); + + $observerMock->expects($this->any()) + ->method('performDirectLinkRequest') + ->with($quote, $requestParams, 1) + ->will($this->returnValue('MOTO')); + $this->assertEquals('MOTO', $observerMock->confirmDdPayment($order, $quote)); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testConfirmDdPaymentWithNoECI() + { + $quote = Mage::getModel('sales/quote')->load(10); + $order = Mage::getModel('sales/order')->load(11); + + + $observerMock = $this->getModelMock( + 'ops/observer', + array('performDirectLinkRequest', 'invokeRequestParamValidation') + ); + + + $requestParams = array( + 'AMOUNT' => 0.0, + 'CARDNO' => '12335BLZ12345566', + 'CN' => utf8_decode('Hubertus zu Fürstenberg'), + 'CURRENCY' => 'USD', + 'ED' => '9999', + 'OPERATION' => 'RES', + 'ORDERID' => Mage::getSingleton('ops/config')->getConfigData('devprefix') + . $quote->getId(), + 'PM' => 'Direct Debits DE', + 'OWNERADDRESS' => utf8_decode('An der Tabaksmühle 3a'), + 'OWNERZIP' => '04229', + 'OWNERTELNO' => null, + 'OWNERCTY' => 'DE', + 'ADDMATCH' => 1, + 'ECOM_SHIPTO_POSTAL_POSTALCODE' => '04229', + 'ECOM_BILLTO_POSTAL_POSTALCODE' => '04229', + 'CUID' => null, + 'OWNERTOWN' => 'Leipzig', + 'BRAND' => 'Direct Debits DE' + ); + + $directDebitHelperMock = $this->getHelperMock('ops/directDebit', array('getDirectLinkRequestParams')); + $directDebitHelperMock->expects($this->any()) + ->method('getDirectLinkRequestParams') + ->will($this->returnValue($requestParams)); + $this->replaceByMock('helper', 'ops/directDebit', $directDebitHelperMock); + + + $observerMock->expects($this->any()) + ->method('performDirectLinkRequest') + ->with($quote, $requestParams, 1) + ->will($this->returnValue('ECOM')); + + $this->assertEquals('ECOM', $observerMock->confirmDdPayment($order, $quote)); + } + + /** + * tests that the payment method is cleared before importing new data + * following conditions must be met in order to get the method gets cleared: + * 1. event must be sales_quote_payment_import_data_before + * 2. payment must be an instance of Mage_Sales_Quote_Payment + * + */ + public function testClearPaymentMethodFromQuote() + { + $observer = Mage::getModel('ops/observer'); + $event = new Varien_Event_Observer(); + $payment = Mage::getModel('sales/quote_payment'); + $payment->setMethod('not relevant'); + $eventData = new Varien_Event(); + $event->setEvent($eventData); + $event->getEvent()->setData('payment', $payment); + + // method is not cleared because of wring event + $observer->clearPaymentMethodFromQuote($event); + $this->assertEquals('not relevant', $event->getEvent()->getPayment()->getMethod()); + + // method is cleared + $event->setEventName('sales_quote_payment_import_data_before'); + $observer->clearPaymentMethodFromQuote($event); + $this->assertEquals(null, $event->getEvent()->getPayment()->getMethod()); + + // method is not cleared because the payment is not a Mage_Sales_Quote_Payment + $payment = new Varien_Object(); + $payment->setMethod('not relevant'); + $event->getEvent()->setData('payment', $payment); + $observer->clearPaymentMethodFromQuote($event); + $this->assertEquals('not relevant', $event->getEvent()->getPayment()->getMethod()); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testaddStatusUpdateButtonToOrderView() + { + $adminSessionMock = $this->getModelMock('admin/session', array('isAllowed', 'init', 'save')); + $adminSessionMock->expects($this->any()) + ->method('isAllowed') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'admin/session', $adminSessionMock); + + $order = Mage::getModel('sales/order')->load(11); + Mage::register('sales_order', $order); + $block = Mage::app()->getLayout()->getBlockSingleton('adminhtml/sales_order_view'); + $event = new Varien_Event_Observer(); + $event->setBlock($block); + $observer = Mage::getModel('ops/observer'); + $observer->addStatusUpdateButtonToOrderView($event); + $buttons = $block->getButtonsHtml(); + $this->assertContains(Mage::helper('ops/data')->__('Refresh payment status'), $buttons); + $this->assertContains('setLocation(\'' . $block->getUrl('adminhtml/opsstatus/update') . '\')', $buttons); + Mage::unregister('sales_order'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testAddStatusUpdateButtonToOrderViewWillNotAddButton() + { + $adminSessionMock = $this->getModelMock('admin/session', array('isAllowed', 'init', 'save')); + $adminSessionMock->expects($this->any()) + ->method('isAllowed') + ->will($this->returnValue(false)); + $this->replaceByMock('model', 'admin/session', $adminSessionMock); + + $order = Mage::getModel('sales/order')->load(11); + Mage::register('sales_order', $order); + $block = Mage::app()->getLayout()->getBlockSingleton('adminhtml/sales_order_view'); + $event = new Varien_Event_Observer(); + $event->setBlock($block); + $observer = Mage::getModel('ops/observer'); + $observer->addStatusUpdateButtonToOrderView($event); + $buttons = $block->getButtonsHtml(); + $this->assertNotContains(Mage::helper('ops/data')->__('Refresh payment status'), $buttons); + $this->assertNotContains('setLocation(\'' . $block->getUrl('adminhtml/opsstatus/update') . '\')', $buttons); + Mage::unregister('sales_order'); + + $adminSessionMock = $this->getModelMock('admin/session', array('isAllowed', 'init', 'save')); + $adminSessionMock->expects($this->any()) + ->method('isAllowed') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'admin/session', $adminSessionMock); + + $order = Mage::getModel('sales/order')->load(30); + Mage::register('sales_order', $order); + $block = Mage::app()->getLayout()->getBlockSingleton('adminhtml/sales_order_view'); + $event = new Varien_Event_Observer(); + $event->setBlock($block); + $observer = Mage::getModel('ops/observer'); + $observer->addStatusUpdateButtonToOrderView($event); + $buttons = $block->getButtonsHtml(); + $this->assertNotContains(Mage::helper('ops/data')->__('Refresh payment status'), $buttons); + $this->assertNotContains('setLocation(\'' . $block->getUrl('adminhtml/opsstatus/update') . '\')', $buttons); + Mage::unregister('sales_order'); + } + + public function testAddCcPaymentMethod() + { + $observer = Mage::getModel('ops/observer'); + $event = new Varien_Event_Observer(); + $block = new Mage_Payment_Block_Form_Container(); + $quote = Mage::getModel('sales/quote'); + $block->setQuote($quote); + $eventData = new Varien_Event(); + $event->setEvent($eventData); + $event->getEvent()->setData('block', $block); + + $versionHelperMock = $this->getHelperMock('ops/version', array('canUseApplicableForQuote')); + $versionHelperMock->expects($this->any()) + ->method('canUseApplicableForQuote') + ->will($this->returnvalue(false)); + $this->replaceByMock('helper', 'ops/version', $versionHelperMock); + + $paymentHelperMock = $this->getHelperMock('ops/payment', array('addCCForZeroAmountCheckout')); + $paymentHelperMock->expects($this->once()) + ->method('addCCForZeroAmountCheckout') + ->will($this->returnValue($paymentHelperMock)); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + + $observer->addCcPaymentMethod($event); + } + + public function testDisableCaptureForZeroAmountInvoice() + { + $ccPaymentObject = Mage::getModel('ops/payment_cc'); + $block = new Mage_Adminhtml_Block_Sales_Order_Invoice_Create_Items(); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethodInstance($ccPaymentObject); + $order = Mage::getModel('sales/order'); + $order->setPayment($payment); + $invoice = Mage::getModel('sales/order_invoice'); + $invoice->setBaseGrandTotal(0.00); + $invoice->setOrder($order); + $observer = Mage::getModel('ops/observer'); + $event = new Varien_Event_Observer(); + Mage::register('current_invoice', $invoice, true); + $eventData = new Varien_Event(); + $event->setEvent($eventData); + $event->getEvent()->setData('block', $block); + + $this->assertTrue($ccPaymentObject->canCapture()); + $observer->disableCaptureForZeroAmountInvoice($event); + $this->assertFalse($ccPaymentObject->canCapture()); + + // clean up invoice from registry after test + Mage::unregister('current_invoice'); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testCheckoutTypeOnepageSaveOrderBefore() + { + $event = new Varien_Event_Observer(); + $order = Mage::getModel('sales/order')->load(11); + $quotePayment = Mage::getModel('sales/quote_payment')->load(4); + $pmMock = $this->getModelMock('ops/payment_bankTransfer', array('getFormFields')); + $quotePayment->setMethodInstance($pmMock); + $quote = $this->getModelMock('sales/quote', array('getPayment')); + $quote->expects($this->any()) + ->method('getPayment') + ->will($this->returnValue($quotePayment)); + + $quotePayment->setQuote($quote); + $quote->setPayment($quotePayment); + $this->replaceByMock('model', 'ops/payment_bankTransfer', $pmMock); + $event->setOrder($order); + $event->setQuote($quote); + $observerMock = $this->getModelMock('ops/observer', array('invokeRequestParamValidation')); + $observerMock->expects($this->once()) + ->method('invokeRequestParamValidation'); + $observerMock->checkoutTypeOnepageSaveOrderBefore($event); + } + + /** + * @loadFixture ../../../var/fixtures/orders.yaml + */ + public function testControllerActionCheckoutOnepagePostdispatch() + { + $fakeController = new Varien_Object(); + $fakeResponse = new Varien_Object(); + $quotePayment = Mage::getModel('sales/quote_payment')->load(4); + $pmMock = $this->getModelMock('ops/payment_bankTransfer', array('isAvailable')); + $pmMock->expects($this->any()) + ->method('isAvailable') + ->will($this->returnValue(true)); + $quote = $this->getModelMock('sales/quote', array('getPayment')); + $quote->expects($this->any()) + ->method('getPayment') + ->will($this->returnValue($quotePayment)); + $quotePayment->setQuote($quote); + $quote->setPayment($quotePayment); + $this->replaceByMock('model', 'ops/payment_bankTransfer', $pmMock); + + + $fakeOnePage = new Varien_Object(); + $fakeOnePage->setQuote($quote); + + $observerMock = $this->getModelMock('ops/observer', array('getOnepage')); + $observerMock->expects($this->once()) + ->method('getOnepage') + ->will($this->returnValue($fakeOnePage)); + $helperMock = $this->getHelperMock('ops/payment_request', array('getOwnerParams', 'extractShipToParameters')); + $helperMock->expects($this->once()) + ->method('getOwnerParams') + ->will($this->returnValue(array())); + $helperMock->expects($this->once()) + ->method('extractShipToParameters') + ->will($this->returnValue(array())); + $this->replaceByMock('helper', 'ops/payment_request', $helperMock); + $validatorMock = $this->getModelMock('ops/validator_parameter_validator', array('isValid', 'getMessages')); + $validatorMock->expects($this->once()) + ->method('isValid') + ->will($this->returnValue(false)); + $validatorMock->expects($this->any()) + ->method('getMessages') + ->will($this->returnValue(array('Foo' => 'Not Valid'))); + $this->replaceByMock('model', 'ops/validator_parameter_validator', $validatorMock); + $fakeResponse->setBody( + Mage::helper('core/data')->jsonEncode(array('error' => false, 'update_section' => 'foo')) + ); + $fakeController->setResponse($fakeResponse); + $event = new Varien_Event_Observer(); + $event->setControllerAction($fakeController); + $observerMock->controllerActionCheckoutOnepagePostdispatch($event); + $result = Mage::helper('core/data')->jsonDecode($fakeResponse->getBody()); + $this->assertArrayHasKey('error', $result); + $this->assertArrayHasKey('goto_section', $result); + $this->assertArrayHasKey('fields', $result); + $this->assertArrayNotHasKey('update_section', $result); + } + + public function testSalesOrderPaymentCapture() + { + $opsObserver = Mage::getModel('ops/observer'); + $event = new Varien_Event_Observer(); + $fakePayment = Mage::getModel('sales/order_payment'); + $fakePayment->setMethod('checkmo'); + $invoice = Mage::getModel('sales/order_invoice'); + $event->setPayment($fakePayment); + $event->setInvoice($invoice); + $opsObserver->salesOrderPaymentCapture($event); + $this->assertNull($fakePayment->getInvoice()); + + $fakePayment = Mage::getModel('sales/order_payment'); + $fakePayment->setMethod('ops_cc'); + $event->setPayment($fakePayment); + $event->setInvoice($invoice); + $opsObserver->salesOrderPaymentCapture($event); + $this->assertEquals($invoice, $fakePayment->getInvoice()); + + } + + public function testSetOrderStateForDirectLinkDoesNotChangeState() + { + $order = $this->getOrderForDirectDebitNlTest(); + $event = $this->getEventForDirectDebitNlTests($order, 'payment/method_cc'); + $observer = Mage::getModel('ops/observer'); + $observer->setOrderStateDirectLink($event); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getStatus()); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getState()); + } + + + public function testSetOrderStateForDirectLinkDoesNotChangeStateDueToHelper() + { + $order = $this->getOrderForDirectDebitNlTest(); + $event = $this->getEventForDirectDebitNlTests($order, 'ops/payment_directDebit'); + $this->registerPaymentHelperMockForDirectDebitNlTests(false); + $observer = Mage::getModel('ops/observer'); + $observer->setOrderStateDirectLink($event); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getStatus()); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getState()); + } + + public function testSetOrderStateForDirectLinkDoesNotChangeStateDueToMissingInfos() + { + $order = $this->getOrderForDirectDebitNlTest(); + $event = $this->getEventForDirectDebitNlTests($order, 'ops/payment_directDebit'); + $this->registerPaymentHelperMockForDirectDebitNlTests(true); + $observer = Mage::getModel('ops/observer'); + $observer->setOrderStateDirectLink($event); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getStatus()); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getState()); + } + + public function testSetOrderStateForDirectLinkDoesNotChangeStateDueToMissingPm() + { + $order = $this->getOrderForDirectDebitNlTest(); + $event = $this->getEventForDirectDebitNlTests($order, 'ops/payment_directDebit', array('STATUS' => 51)); + $this->registerPaymentHelperMockForDirectDebitNlTests(true); + $observer = Mage::getModel('ops/observer'); + $observer->setOrderStateDirectLink($event); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getStatus()); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getState()); + } + + public function testSetOrderStateForDirectLinkDoesNotChangeStateDueToMissingStatus() + { + $order = $this->getOrderForDirectDebitNlTest(); + $event = $this->getEventForDirectDebitNlTests( + $order, 'ops/payment_directDebit', + array('PM' => 'Direct Debits NL') + ); + $this->registerPaymentHelperMockForDirectDebitNlTests(true); + $observer = Mage::getModel('ops/observer'); + $observer->setOrderStateDirectLink($event); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getStatus()); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getState()); + } + + public function testSetOrderStateForDirectLinkDoesNotChangeStateDueToWrongPm() + { + $order = $this->getOrderForDirectDebitNlTest(); + $event = $this->getEventForDirectDebitNlTests( + $order, 'ops/payment_directDebit', + array('PM' => 'Direct Debits DE', 'STATUS' => 51) + ); + $this->registerPaymentHelperMockForDirectDebitNlTests(true); + $observer = Mage::getModel('ops/observer'); + $observer->setOrderStateDirectLink($event); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getStatus()); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getState()); + } + + public function testSetOrderStateForDirectLinkDoesNotChangeStateDueToWrongStatus() + { + $order = $this->getOrderForDirectDebitNlTest(); + $event = $this->getEventForDirectDebitNlTests( + $order, 'ops/payment_directDebit', + array('PM' => 'Direct Debits NL', 'STATUS' => 5) + ); + $this->registerPaymentHelperMockForDirectDebitNlTests(true); + $observer = Mage::getModel('ops/observer'); + $observer->setOrderStateDirectLink($event); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getStatus()); + $this->assertEquals(Mage_sales_Model_Order::STATE_PROCESSING, $order->getState()); + } + + public function testSetOrderStateForDirectLinkDoesChangeState() + { + $order = $this->getOrderForDirectDebitNlTest(); + $event = $this->getEventForDirectDebitNlTests( + $order, 'ops/payment_directDebit', + array('PM' => 'Direct Debits NL', + 'status' => Netresearch_OPS_Model_Status::AUTHORIZED) + ); + $this->registerPaymentHelperMockForDirectDebitNlTests(true); + $observer = Mage::getModel('ops/observer'); + $observer->setOrderStateDirectLink($event); + $this->assertEquals(Mage_sales_Model_Order::STATE_PENDING_PAYMENT, $order->getStatus()); + $this->assertEquals(Mage_sales_Model_Order::STATE_PENDING_PAYMENT, $order->getState()); + } + + /** + * @return array + */ + protected function getEventForDirectDebitNlTests($order, $paymentMethod, array $addInfos = array()) + { + $event = new Varien_Event_Observer(); + $payment = $this->getModelMock('payment/info', array('getMethodInstance', 'getOrder')); + $payment->expects($this->any()) + ->method('getMethodInstance') + ->will($this->returnValue(Mage::getModel($paymentMethod))); + $payment->expects($this->any()) + ->method('getOrder') + ->will($this->returnValue($order)); + $payment->setAdditionalInformation($addInfos); + $event->setPayment($payment); + + return $event; + } + + /** + * @return PHPUnit_Framework_MockObject_MockObject + */ + protected function getOrderForDirectDebitNlTest() + { + $order = $this->getModelMock('sales/order', array('save',)); + $order->setStatus(Mage_sales_Model_Order::STATE_PROCESSING); + $order->setState(Mage_sales_Model_Order::STATE_PROCESSING); + + return $order; + } + + protected function registerPaymentHelperMockForDirectDebitNlTests($isInlinePaymentWithOrderIdRetVal = true) + { + $paymentHelperMock = $this->getHelperMock('ops/payment', array('isInlinePayment')); + $paymentHelperMock->expects($this->once()) + ->method('isInlinePayment') + ->will($this->returnValue($isInlinePaymentWithOrderIdRetVal)); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + } + + public function testUpdateRecurringProfileButtons() + { + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setMethodCode(Netresearch_OPS_Model_Payment_Recurring_Cc::CODE) + ->setState(Mage_Sales_Model_Recurring_Profile::STATE_ACTIVE) + ->setId(1); + + $this->replaceRegistry('current_recurring_profile', $profile); + + /** @var $block Mage_Sales_Block_Adminhtml_Recurring_Profile_View */ + $block = Mage::app()->getLayout()->createBlock('sales/adminhtml_recurring_profile_view'); + $block->setLayout(Mage::app()->getLayout()); + $observer = new Varien_Event_Observer(); + $event = new Varien_Event(); + $event->setData('block', $block); + $observer->setEvent($event); + + $subject = Mage::getModel('ops/observer'); + $subject->updateRecurringProfileButtons($observer); + $html = $block->getButtonsHtml('header'); + + $this->assertContains( + 'Suspending the subscription here will not actually cancel the subscription on Ingenico ePayments side.', + $html + ); + } + + public function overrideRecurringProfileState() + { + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setMethodCode(Netresearch_OPS_Model_Payment_Recurring_Cc::CODE) + ->setState('abc') + ->setNewState('def') + ->setOverrideState(true); + + $event = new Varien_Event_Observer(); + $event->setObject($profile); + + $observer = Mage::getModel('ops/observer'); + $observer->overrideRecurringProfileState($event); + + $this->assertEquals($profile->getNewState(), $profile->getState()); + $this->assertEquals('def', $profile->getState()); + } + + /** + * @test + */ + public function sendTransactionalEmailsNoOps() + { + // no emails on third party payments + $helperMock = $this->getHelperMock('ops/data', array('sendTransactionalEmail')); + $helperMock + ->expects($this->never()) + ->method('sendTransactionalEmail') + ->with($this->isInstanceOf('Mage_Sales_Model_Order')); + $this->replaceByMock('helper', 'ops/data', $helperMock); + + $payment = new Varien_Object( + array( + 'method' => Mage::getModel('payment/method_checkmo')->getCode(), + ) + ); + $order = new Varien_Object( + array( + 'payment' => $payment, + ) + ); + + $observerMock = $this->getMockBuilder('Varien_Event_Observer') + ->setMethods(array('getOrder')) + ->getMock(); + $observerMock + ->expects($this->once()) + ->method('getOrder') + ->willReturn($order); + + Mage::getModel('ops/observer')->sendTransactionalEmails($observerMock); + } + + /** + * @test + */ + public function sendTransactionalEmailsGatewayPayment() + { + // no emails on ingenico redirect payments + $helperMock = $this->getHelperMock('ops/data', array('sendTransactionalEmail')); + $helperMock + ->expects($this->never()) + ->method('sendTransactionalEmail') + ->with($this->isInstanceOf('Mage_Sales_Model_Order')); + $this->replaceByMock('helper', 'ops/data', $helperMock); + + $payment = new Varien_Object( + array( + 'method_instance' => Mage::getModel('ops/payment_bankTransfer'), + 'order_place_redirect_url' => 'foo', + ) + ); + $order = new Varien_Object( + array( + 'payment' => $payment, + ) + ); + $quote = new Varien_Object( + array( + 'payment' => $payment, + ) + ); + + $observerMock = $this->getMockBuilder('Varien_Event_Observer') + ->setMethods(array('getOrder', 'getQuote')) + ->getMock(); + $observerMock + ->expects($this->once()) + ->method('getOrder') + ->willReturn($order); + $observerMock + ->expects($this->once()) + ->method('getQuote') + ->willReturn($quote); + + Mage::getModel('ops/observer')->sendTransactionalEmails($observerMock); + } + + /** + * @test + */ + public function sendTransactionalEmailsException() + { + // exception not really testable. + // just assert that second call to sendTransactionalEmail is not invoked + $helperMock = $this->getHelperMock('ops/data', array('sendTransactionalEmail')); + $helperMock + ->expects($this->exactly(1)) + ->method('sendTransactionalEmail') + ->withConsecutive( + $this->isInstanceOf('Mage_Sales_Model_Order'), + $this->isInstanceOf('Mage_Sales_Model_Order_Invoice') + ) + ->willThrowException(new Exception('no mails foo')); + $this->replaceByMock('helper', 'ops/data', $helperMock); + + + $payment = new Varien_Object( + array( + 'method_instance' => Mage::getModel('ops/payment_bankTransfer'), + ) + ); + $quote = new Varien_Object( + array( + 'payment' => $payment, + ) + ); + $orderMock = $this->getModelMock('sales/order', array('getPayment')); + $orderMock + ->expects($this->once()) + ->method('getPayment') + ->willReturn($payment); + + $observerMock = $this->getMockBuilder('Varien_Event_Observer') + ->setMethods(array('getOrder', 'getQuote')) + ->getMock(); + $observerMock + ->expects($this->once()) + ->method('getOrder') + ->willReturn($orderMock); + $observerMock + ->expects($this->once()) + ->method('getQuote') + ->willReturn($quote); + + Mage::getModel('ops/observer')->sendTransactionalEmails($observerMock); + } + + /** + * @test + */ + public function sendTransactionalEmailsSuccess() + { + $helperMock = $this->getHelperMock('ops/data', array('sendTransactionalEmail')); + $helperMock + ->expects($this->exactly(2)) + ->method('sendTransactionalEmail') + ->withConsecutive( + $this->isInstanceOf('Mage_Sales_Model_Order'), + $this->isInstanceOf('Mage_Sales_Model_Order_Invoice') + ); + $this->replaceByMock('helper', 'ops/data', $helperMock); + + // invoice + $invoice = Mage::getModel('sales/order_invoice'); + + // order payment + $payment = new Varien_Object( + array( + 'method_instance' => Mage::getModel('ops/payment_cc'), + 'created_invoice' => $invoice, + ) + ); + + // order + $orderMock = $this->getModelMock('sales/order', array('getPayment')); + $orderMock + ->expects($this->exactly(2)) + ->method('getPayment') + ->willReturn($payment); + $this->replaceByMock('model', 'sales/order', $orderMock); + $order = Mage::getModel('sales/order'); + + // quote + $quote = new Varien_Object( + array( + 'payment' => $payment, + ) + ); + + $observerMock = $this->getMockBuilder('Varien_Event_Observer') + ->setMethods(array('getOrder', 'getQuote')) + ->getMock(); + $observerMock->expects($this->once())->method('getOrder')->willReturn($order); + $observerMock->expects($this->once())->method('getQuote')->willReturn($quote); + + Mage::getModel('ops/observer')->sendTransactionalEmails($observerMock); + } + + public function testSendPayPerMailInfoSuccess() + { + // order payment + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethodInstance(Mage::getModel('ops/payment_payPerMail')); + + $order = Mage::getModel('sales/order'); + $order->setPayment($payment); + + $observerMock = $this->getMockBuilder('Varien_Event_Observer') + ->setMethods(['getOrder']) + ->getMock() + ; + $observerMock->expects($this->once())->method('getOrder')->willReturn($order); + Mage::getModel('ops/observer')->sendPayPerMailInfo($observerMock); + } + + public function testSendPayPerMailInfoFailure() + { + // order payment + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethodInstance(Mage::getModel('ops/payment_cc')); + + $order = Mage::getModel('sales/order'); + $order->setPayment($payment); + + $observerMock = $this->getMockBuilder('Varien_Event_Observer') + ->setMethods(['getOrder']) + ->getMock() + ; + $observerMock->expects($this->once())->method('getOrder')->willReturn($order); + Mage::getModel('ops/observer')->sendPayPerMailInfo($observerMock); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/ObserverTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Model/ObserverTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractCaptureTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractCaptureTest.php new file mode 100644 index 0000000..bf577a6 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractCaptureTest.php @@ -0,0 +1,276 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Model_Payment_AbstractCaptureTest + extends EcomDev_PHPUnit_Test_Case_Controller +{ + + protected $testObject = null; + + /** + * set up a clean environment for the tests + */ + public function setUp() + { + parent::setUp(); + $this->testObject = Mage::getModel('ops/payment_abstract'); + } + + public function tearDown() + { + parent::tearDown(); + Mage::unregister('ops_auto_capture'); + } + + public function testCaptureWithAutoCapture() + { + Mage::register('ops_auto_capture', true); + $payment = new Varien_Object(); + $amount = null; + $this->testObject->capture($payment, $amount); + } + + public function testCaptureWithPreviousCaptureRequestLeadToRedirect() + { + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $amount = 1; + $captureHelperMock = $this->getHelperMock('ops/order_capture', array('prepareOperation')); + $this->replaceByMock('helper', 'ops/order_capture', $captureHelperMock); + $this->mockDirectlinkHelperCheckDirectLinkTransact( + $this->returnValue(true), + $this->equalTo(Netresearch_OPS_Model_Payment_Abstract::OPS_CAPTURE_TRANSACTION_TYPE), + $this->equalTo($testOrder->getId()) + ); + + $message = Mage::helper('ops/data')->__( + 'You already sent a capture request. Please wait until the capture request is acknowledged.' + ); + $this->mockDataHelperMockRedirectNoticed($testOrder->getId(), $message); + + $this->testObject->capture($testPayment, $amount); + } + + public function testCaptureWithPreviousVoidRequestLeadToRedirect() + { + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $amount = 1; + $captureHelperMock = $this->getHelperMock('ops/order_capture', array('prepareOperation')); + $this->replaceByMock('helper', 'ops/order_capture', $captureHelperMock); + $this->mockDirectlinkHelperCheckDirectLinkTransact($this->onConsecutiveCalls(false, true)); + $message = Mage::helper('ops/data')->__( + 'There is one void request waiting. Please wait until this request is acknowledged.' + ); + $this->mockDataHelperMockRedirectNoticed($testOrder->getId(), $message); + + $this->testObject->capture($testPayment, $amount); + } + + public function testCaptureWillPerformRequest() + { +// $this->markTestIncomplete(); + + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $testPayment->setAdditionalInformation('paymentId', 'payID'); + $amount = 10; + $this->mockOrderCaptureHelper(); + $requestParams = $this->getCaptureRequestParams($amount, $testPayment); + $testOpsResponse = $this->returnValue( + array('STATUS' => Netresearch_OPS_Model_Status::PAYMENT_PROCESSED_BY_MERCHANT) + ); + $this->mockApiDirectLink($requestParams, $testOpsResponse); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('saveOpsStatusToPayment')); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + $this->testObject->setInfoInstance($testPayment); + $this->testObject->capture($testPayment, $amount); + } + + + public function testCaptureWillPerformRequestWithPaymentProcessing() + { +// $this->markTestIncomplete(); + + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $amount = 10; + $this->mockDirectlinkHelperCheckDirectLinkTransact($this->returnValue(false)); + + $this->mockOrderCaptureHelper(array('operation' => 'SAS', 'type' => 'capture')); + $requestParams = $this->getCaptureRequestParams($amount, $testPayment); + $testOpsResponse = $this->returnValue( + array( + 'STATUS' => Netresearch_OPS_Model_Status::PAYMENT_PROCESSING, + 'PAYID' => 4711, + 'PAYIDSUB' => 1 + ) + ); + $this->mockApiDirectLink($requestParams, $testOpsResponse); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('saveOpsStatusToPayment')); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + $this->testObject->setInfoInstance($testPayment); + $this->testObject->capture($testPayment, $amount); + } + + /** + * @expectedException Mage_Core_Exception + */ + public function testCaptureWillPerformRequestWithInvalidResponseLeadToException() + { + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $testPayment->setAdditionalInformation('paymentId', 'payID'); + $amount = 10; + $this->mockOrderCaptureHelper(); + $requestParams = $this->getCaptureRequestParams($amount, $testPayment); + $testOpsResponse = $this->returnValue(array('STATUS' => 320)); + $this->mockApiDirectLink($requestParams, $testOpsResponse); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('saveOpsStatusToPayment')); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + + $statusUpdateMock = $this->getModelMock('ops/status_update', array('updateStatusFor')); + $this->replaceByMock('model', 'ops/status_update', $statusUpdateMock); + $this->testObject->setInfoInstance($testPayment); + $this->testObject->capture($testPayment, $amount); + } + + /** + * @expectedException Mage_Core_Exception + */ + public function testCaptureWillPerformRequestWithExceptionWillThrowException() + { + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $amount = 10; + $this->mockOrderCaptureHelper(); + $requestParams = $this->getCaptureRequestParams($amount, $testPayment); + $testOpsResponse = $this->throwException(new Exception('foo')); + $this->mockApiDirectLink($requestParams, $testOpsResponse); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('saveOpsStatusToPayment')); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + $statusUpdateMock = $this->getModelMock('ops/status_update', array('updateStatusFor')); + $this->replaceByMock('model', 'ops/status_update', $statusUpdateMock); + + $this->testObject->setInfoInstance($testPayment); + $this->testObject->capture($testPayment, $amount); + } + + protected function mockOrderCaptureHelper($returnValue = array('operation' => 'SAS')) + { + $captureHelperMock = $this->getHelperMock('ops/order_capture', array('prepareOperation')); + $captureHelperMock->expects($this->once()) + ->method('prepareOperation') + ->will($this->returnValue($returnValue)); + $this->replaceByMock('helper', 'ops/order_capture', $captureHelperMock); + } + + /** + * @param $firstArg + * @param $secondArg + */ + protected function mockDataHelperMockRedirectNoticed($firstArg, $secondArg, $returnValue = true) + { + $dataHelperMock = $this->getHelperMock('ops/data', array('redirectNoticed')); + $dataHelperMock->expects($this->once()) + ->method('redirectNoticed') + ->with($this->equalTo($firstArg), $this->equalTo($secondArg)) + ->will($this->returnValue($returnValue)); + + $this->replaceByMock('helper', 'ops/data', $dataHelperMock); + } + + /** + * @return array + */ + protected function preparePayment($order, $method = 'ops_iDeal') + { + $payment = Mage::getModel('sales/order_payment'); + $payment->setOrder($order); + $payment->setMethod($method); + $payment->setAdditionalInformation('paymentId', 'payID'); + + $order->setPayment($payment); + return $payment; + } + + /** + * @param $order + * + * @return PHPUnit_Framework_MockObject_MockObject + */ + protected function getOrderMock() + { + $order = $this->getModelMock('sales/order', array('save', 'load', '_beforeSave')); + $order->expects($this->any()) + ->method('load') + ->will($this->returnValue($order)); + $order->setId(1); + $this->replaceByMock('model', 'sales/order', $order); + + return $order; + } + + /** + * @param $amount + * @param $testPayment + * + * @return array + */ + protected function getCaptureRequestParams($amount, $testPayment) + { + $requestParams = array( + 'AMOUNT' => Mage::helper('ops/data')->getAmount($amount), + 'PAYID' => $testPayment->getAdditionalInformation('paymentId'), + 'OPERATION' => Mage::helper('ops/order_capture')->determineOperationCode($testPayment, $amount), + 'CURRENCY' => Mage::app()->getStore()->getBaseCurrencyCode() + ); + + return $requestParams; + } + + /** + * @param $requestParams + * @param $testOpsResponse + */ + protected function mockApiDirectLink($requestParams, $testOpsResponse) + { + $apiDirectLinkMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $apiDirectLinkMock->expects($this->once()) + ->method('performRequest') + ->with( + $this->equalTo($requestParams), + $this->equalTo(Mage::getModel('ops/config')->getDirectLinkGatewayPath()), + $this->equalTo(null) + ) + ->will($testOpsResponse); + $this->replaceByMock('model', 'ops/api_directlink', $apiDirectLinkMock); + } + + protected function mockDirectlinkHelperCheckDirectLinkTransact($will, $arg1 = null, $arg2 = null) + { + + $helperMock = $this->getHelperMock('ops/directlink', array('checkExistingTransact', 'directLinkTransact')); + $helperMock->expects($this->any()) + ->method('checkExistingTransact') + ->with($this->getConstraintForArg($arg1), $this->getConstraintForArg($arg2)) + ->will($will); + $this->replaceByMock('helper', 'ops/directlink', $helperMock); + } + + protected function getConstraintForArg($arg) + { + if (null === $arg) { + $arg = $this->anything(); + } + + return $arg; + } + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractRefundTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractRefundTest.php new file mode 100644 index 0000000..d7cc4bb --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractRefundTest.php @@ -0,0 +1,214 @@ +testObject = $this->getModelMock('ops/payment_cc', array('canRefund')); + $this->testObject->expects($this->any()) + ->method('canRefund') + ->will($this->returnValue(true)); + $this->mockRefundHelper(); + $this->mockDataHelper(); + $this->mockPaymentHelper(); + $sessionMock = $this->getModelMockBuilder('core/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'core/session', $sessionMock); + } + + public function tearDown() + { + parent::tearDown(); + Mage::unregister('ops_auto_creditmemo'); + } + + /** + * + */ + public function testRefundWillPerformRequestWithRefundPending() + { + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $testPayment->setAdditionalInformation('paymentId', 'payID'); + $amount = 10; + $requestParams = $this->getRequestParams($amount, $testPayment); + $testOpsResponse = $this->returnValue( + array('STATUS' => Netresearch_OPS_Model_Status::REFUND_UNCERTAIN) + ); + $this->mockApiDirectLink($requestParams, $testOpsResponse); + $this->testObject->setInfoInstance($testPayment); + $this->testObject->refund($testPayment, $amount); + } + + /** + * + */ + public function testRefundWillPerformRequestWithRefundProcessed() + { + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $testPayment->setAdditionalInformation('paymentId', 'payID'); + $amount = 10; + $requestParams = $this->getRequestParams($amount, $testPayment); + $testOpsResponse = $this->returnValue( + array('STATUS' => Netresearch_OPS_Model_Status::REFUND_PROCESSED_BY_MERCHANT) + ); + $this->mockApiDirectLink($requestParams, $testOpsResponse); + $this->testObject->setInfoInstance($testPayment); + $this->testObject->refund($testPayment, $amount); + + } + + /** + * @expectedException Mage_Core_Exception + */ + public function testRefundWillPerformRequestWithInvalidResponseLeadToException() + { + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $testPayment->setAdditionalInformation('paymentId', 'payID'); + $amount = 10; + $requestParams = $this->getRequestParams($amount, $testPayment); + $testOpsResponse = $this->returnValue( + array('STATUS' => 500) + ); + $this->mockApiDirectLink($requestParams, $testOpsResponse); + + $statusUpdateMock = $this->getModelMock('ops/status_update', array('updateStatusFor')); + $this->replaceByMock('model', 'ops/status_update', $statusUpdateMock); + $this->testObject->setInfoInstance($testPayment); + $this->testObject->refund($testPayment, $amount); + } + + /** + * @expectedException Mage_Core_Exception + */ + public function testCaptureWillPerformRequestWithExceptionWillThrowException() + { + $this->mockRefundHelper(); + $testOrder = $this->getOrderMock(); + $testPayment = $this->preparePayment($testOrder); + $testPayment->setAdditionalInformation('paymentId', 'payID'); + $amount = 10; + $requestParams = $this->getRequestParams($amount, $testPayment); + $testOpsResponse = $this->throwException(new Exception('foo')); + $this->mockApiDirectLink($requestParams, $testOpsResponse); + + $statusUpdateMock = $this->getModelMock('ops/status_update', array('updateStatusFor')); + $this->replaceByMock('model', 'ops/status_update', $statusUpdateMock); + $this->testObject->setInfoInstance($testPayment); + $this->testObject->refund($testPayment, $amount); + } + + /** + * @param $amount + * @param $testPayment + */ + protected function getRequestParams($amount, $testPayment) + { + $requestParams = array( + 'AMOUNT' => Mage::helper('ops/data')->getAmount($amount), + 'PAYID' => $testPayment->getAdditionalInformation('paymentId'), + 'OPERATION' => Netresearch_OPS_Model_Payment_Abstract::OPS_REFUND_PARTIAL, + 'CURRENCY' => Mage::app()->getStore()->getBaseCurrencyCode() + ); + + return $requestParams; + } + + /** + * @param $requestParams + * @param $testOpsResponse + */ + protected function mockApiDirectLink($requestParams, $testOpsResponse) + { + $apiDirectLinkMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $apiDirectLinkMock->expects($this->once()) + ->method('performRequest') + ->with( + $this->equalTo($requestParams), + $this->equalTo(Mage::getModel('ops/config')->getDirectLinkGatewayPath()), + $this->equalTo(null) + ) + ->will($testOpsResponse); + $this->replaceByMock('model', 'ops/api_directlink', $apiDirectLinkMock); + } + + /** + * @return array + */ + protected function preparePayment($order, $method = 'ops_cc') + { + $payment = Mage::getModel('sales/order_payment'); + $payment->setOrder($order); + $order->setPayment($payment); + $payment->setAdditionalInformation('paymentId', 'payID'); + $payment->setMethod($method); + + return $payment; + } + + /** + * @param $order + * + * @return PHPUnit_Framework_MockObject_MockObject + */ + protected function getOrderMock() + { + $order = $this->getModelMock('sales/order', array('save', 'load', '_beforeSave')); + $order->expects($this->any()) + ->method('load') + ->will($this->returnValue($order)); + $order->setId(1); + $this->replaceByMock('model', 'sales/order', $order); + + return $order; + } + + protected function mockRefundHelper() + { + $helperMock = $this->getHelperMock('ops/order_refund', array('getCreditMemoRequestParams', 'createRefundTransaction')); + $params = array( + 'creditmemo' => array( + 'items' => array( + 1 => array( + 'qty' => 0 + ), + 2 => array( + 'qty' => 0 + ) + ), + 'shipping_amount' => 0, + 'adjustment_positive' => 10, + 'adjustment_negative' => 0 + + ) + ); + $helperMock->expects($this->any()) + ->method('getCreditMemoRequestParams') + ->will($this->returnValue($params)); + $this->replaceByMock('helper', 'ops/order_refund', $helperMock); + } + + protected function mockDataHelper() + { + $helperMock = $this->getHelperMock('ops/data', array('redirect')); + $this->replaceByMock('helper', 'ops/data', $helperMock); + } + + protected function mockPaymentHelper() + { + $helperMock = $this->getHelperMock('ops/payment', array('saveOpsStatusToPayment', 'saveOpsRefundOperationCodeToPayment')); + $this->replaceByMock('helper', 'ops/payment', $helperMock); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest.php new file mode 100644 index 0000000..af92dd0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest.php @@ -0,0 +1,890 @@ +model = Mage::getModel('ops/payment_abstract'); + } + + public function testCaptureWithZeroAmount() + { + $paymentObject = new Varien_Object(); + $directLinkHelperMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkHelperMock->expects($this->never()) + ->method('performRequest'); + $this->assertTrue($this->model->capture($paymentObject, 0.00) instanceof Netresearch_OPS_Model_Payment_Abstract); + } + + /** + * @test + */ + public function _getOrderDescriptionShorterThen100Chars() + { + $items = array( + new Varien_Object( + array( + 'parent_item' => false, + 'name' => 'abc' + ) + ), + new Varien_Object( + array( + 'parent_item' => true, + 'name' => 'def' + ) + ), + new Varien_Object( + array( + 'parent_item' => false, + 'name' => 'ghi' + ) + ), + new Varien_Object( + array( + 'parent_item' => false, + 'name' => 'Dubbelwerkende cilinder Boring ø70 Stang ø40 3/8' + ) + ), + new Varien_Object( + array( + 'parent_item' => false, + 'name' => '0123456789012345678901234567890123456789' + ) + ), + ); + + $order = $this->getModelMock('sales/order', array('getAllItems')); + $order->expects($this->any()) + ->method('getAllItems') + ->will($this->returnValue($items)); + + $result = Mage::getModel('ops/payment_abstract')->_getOrderDescription($order); + $this->assertEquals( + 'abc, ghi, Dubbelwerkende cilinder Boring ø70 Stang ø40 3/8, 0123456789012345678901234567890123456789', + $result + ); + } + + /** + * @test + */ + public function _getOrderDescriptionLongerThen100Chars() + { + $items = array( + new Varien_Object( + array( + 'parent_item' => false, + 'name' => '1bcdefghij abcdefghij abcdefghij abcdefghij abcdefghi1' //54 chars + ) + ), + new Varien_Object( + array( + 'parent_item' => false, + 'name' => '2bcdefghij abcdefghij abcdefghij abcdefghij' //54 chars + ) + ) + ); + + $order = $this->getModelMock('sales/order', array('getAllItems')); + $order->expects($this->any()) + ->method('getAllItems') + ->will($this->returnValue($items)); + + $result = Mage::getModel('ops/payment_abstract')->_getOrderDescription($order); + $this->assertEquals( + '1bcdefghij abcdefghij abcdefghij abcdefghij abcdefghi1, 2bcdefghij abcdefghij abcdefghij abcdefghij', + $result + ); + } + + /** + * @test + */ + public function _getOrderDescriptionLongerThen100CharsOneItem() + { + $items = array( + new Varien_Object( + array( + 'parent_item' => false, + 'name' => '1bcdefghij abcdefghij abcdefghij abcdefghij abcdefghi1 '. + '2bcdefghij abcdefghij abcdefghij abcdefghij a' + ) + ) + ); + + $order = $this->getModelMock('sales/order', array('getAllItems')); + $order->expects($this->any()) + ->method('getAllItems') + ->will($this->returnValue($items)); + + $result = Mage::getModel('ops/payment_abstract')->_getOrderDescription($order); + $this->assertEquals( + '1bcdefghij abcdefghij abcdefghij abcdefghij abcdefghi1 2bcdefghij abcdefghij abcdefghij abcdefghij a', + $result + ); + } + + /** + * check if payment method BankTransfer returns correct BRAND and PM values + * + * @loadExpectation paymentMethods + * @test + */ + public function shouldReturnCorrectBrandAndPMValuesForBankTransfer() + { + $method = Mage::getModel('ops/payment_bankTransfer'); + + $payment = $this->getModelMock('sales/quote_payment', array('getId')); + $payment->expects($this->any()) + ->method('getId') + ->will($this->returnValue('1')); + $this->replaceByMock('model', 'sales/quote_payment', $payment); + + $method = Mage::getModel('ops/payment_bankTransfer'); + $method->setInfoInstance($payment); + try { + $method->assignData(array('country_id' => 'DE')); + } catch (Mage_Core_Exception $e) { + if ('Cannot retrieve the payment information object instance.' + != $e->getMessage() + ) { + throw $e; + } + } + $this->assertEquals( + $this->expected('ops_bankTransferDe')->getBrand(), + $method->getOpsBrand(null) + ); + $reflectedMethod = new ReflectionMethod($method, 'getOpsCode'); + $reflectedMethod->setAccessible(true); + $this->assertEquals( + $this->expected('ops_bankTransferDe')->getPm(), + $reflectedMethod->invoke($method) + ); + } + + /** + * @test + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testCanCancelManually() + { + $opsAbstractPayment = new Netresearch_OPS_Model_Payment_Abstract(); + + //Check for successful can cancel (pending_payment and payment status 0) + $order = Mage::getModel("sales/order")->load(11); + $this->assertTrue($opsAbstractPayment->canCancelManually($order)); + + //Check for successful cancel (pending_payment and payment status null/not existing) + $order = Mage::getModel("sales/order")->load(14); + $this->assertTrue($opsAbstractPayment->canCancelManually($order)); + + //Check for denied can cancel (pending_payment and payment status 5) + $order = Mage::getModel("sales/order")->load(12); + $this->assertFalse($opsAbstractPayment->canCancelManually($order)); + + //Check for denied can cancel (processing and payment status 0) + $order = Mage::getModel("sales/order")->load(13); + $this->assertTrue($opsAbstractPayment->canCancelManually($order)); + } + + + public function testGetCloseTransactionFromCreditMemoData() + { + $reflection_class + = new ReflectionClass("Netresearch_OPS_Model_Payment_Abstract"); + + //Then we need to get the method we wish to test and + //make it accessible + $method = $reflection_class->getMethod( + "getCloseTransactionFromCreditMemoData" + ); + $method->setAccessible(true); + + //We need to create an empty object to pass to + //ReflectionMethod invoke method followed by our + //test parameters + $paymentModel = new Netresearch_OPS_Model_Payment_Abstract(null); + + $this->assertFalse($method->invoke($paymentModel, array())); + $this->assertFalse( + $method->invoke( + $paymentModel, array('ops_close_transaction' => 'OFF') + ) + ); + $this->assertFalse( + $method->invoke( + $paymentModel, array('ops_close_transaction' => 'off') + ) + ); + $this->assertFalse( + $method->invoke($paymentModel, array('ops_close_transaction' => '')) + ); + $this->assertFalse( + $method->invoke($paymentModel, array('ops_close_transaction' => 1)) + ); + + $this->assertTrue( + $method->invoke( + $paymentModel, array('ops_close_transaction' => 'on') + ) + ); + $this->assertTrue( + $method->invoke( + $paymentModel, array('ops_close_transaction' => 'ON') + ) + ); + } + + + /** + * @test + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testGetMethodDependendFormFields() + { + $order = Mage::getModel('sales/order')->load(11); + $sessionMock = $this->getModelMock('checkout/session', array('getQuote')); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($order)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + + + $sessionMock = $this->getModelMock('customer/session', array('isLoggedIn')); + $sessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $sessionMock); + + + $configMock = $this->getModelMock('ops/config', array('canSubmitExtraParameter')); + $configMock->expects($this->any()) + ->method('canSubmitExtraParameter') + ->will($this->returnValue(true)); + $paymentModel = Mage::getModel('ops/payment_abstract'); + $paymentModel->setConfig($configMock); + $formFields = $paymentModel->getFormFields($order, array()); + + $this->assertTrue(array_key_exists('CN', $formFields)); + $this->assertTrue(array_key_exists('OWNERZIP', $formFields)); + $this->assertTrue(array_key_exists('OWNERCTY', $formFields)); + $this->assertTrue(array_key_exists('OWNERTOWN', $formFields)); + $this->assertTrue(array_key_exists('COM', $formFields)); + $this->assertTrue(array_key_exists('OWNERTELNO', $formFields)); + $this->assertTrue(array_key_exists('OWNERADDRESS', $formFields)); + $this->assertTrue(array_key_exists('BRAND', $formFields)); + $this->assertTrue(array_key_exists('ADDMATCH', $formFields)); + $this->assertTrue(array_key_exists('ECOM_SHIPTO_POSTAL_POSTALCODE', $formFields)); + $this->assertTrue(array_key_exists('ECOM_BILLTO_POSTAL_POSTALCODE', $formFields)); + $this->assertTrue(array_key_exists('CUID', $formFields)); + + $order = Mage::getModel('sales/order')->load(27); + + $formFields = $paymentModel->getFormFields($order, array()); + $this->assertTrue(array_key_exists('ECOM_SHIPTO_POSTAL_POSTALCODE', $formFields)); + + $order = Mage::getModel('sales/order')->load(11); + $configMock = $this->getModelMock('ops/config', array('canSubmitExtraParameter')); + $configMock->expects($this->any()) + ->method('canSubmitExtraParameter') + ->will($this->returnValue(false)); + $paymentModel = Mage::getModel('ops/payment_abstract'); + $paymentModel->setConfig($configMock); + $params = $paymentModel->getMethodDependendFormFields($order, array()); + foreach ($this->getOwnerParams() as $ownerParam) { + if ($ownerParam == 'OWNERZIP') continue; + $this->assertArrayNotHasKey($ownerParam, $params); + } + foreach ($this->getShippingParams() as $shippingParam) { + $this->assertArrayNotHasKey($shippingParam, $params); + } + + $order = Mage::getModel('sales/order')->load(19); + + $configMock = $this->getModelMock('ops/config', array('canSubmitExtraParameter')); + $configMock->expects($this->any()) + ->method('canSubmitExtraParameter') + ->will($this->returnValue(true)); + $paymentModel = Mage::getModel('ops/payment_openInvoiceNl'); + $paymentModel->setConfig($configMock); + $params = $paymentModel->getMethodDependendFormFields($order); + foreach ($this->getOwnerParams() as $ownerParam) { + if ($ownerParam == 'OWNERZIP' || $ownerParam == 'ADDMATCH' || $ownerParam == 'ECOM_BILLTO_POSTAL_POSTALCODE') continue; + $this->assertArrayHasKey($ownerParam, $params); + } + foreach ($this->getShippingParams() as $shippingParam) { + if ($shippingParam == 'ECOM_SHIPTO_POSTAL_STREET_LINE2') continue; + $this->assertArrayHasKey($shippingParam, $params); + } + + + } + + public function testGetFormFieldsEmptyWithNonExistingOrder() + { + $paymentModel = Mage::getModel('ops/payment_abstract'); + $this->assertTrue( + is_array($paymentModel->getFormFields(null, array())) + ); + $this->assertEquals( + 0, count($paymentModel->getFormFields(null, array())) + ); + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testGetFormFieldsWithEmptyOrderPassedButExistingOrder() + { + $order = Mage::getModel('sales/order')->load(11); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethodInstance(Mage::getModel('ops/payment_cc')); + $order->setPayment($payment); + $paymentModel = $this->getModelMock( + 'ops/payment_abstract', + array('getMethodDependendFormFields', 'getOrder') + ); + $paymentModel->expects($this->any()) + ->method('getOrder') + ->will($this->returnValue($order)); + $formFields = $paymentModel->getFormFields($order, array()); + $this->assertArrayHasKey('PSPID', $formFields); + $this->assertArrayHasKey('SHASIGN', $formFields); + } + + public function testGetFormFields() + { + $paymentModel = $this->getModelMock( + 'ops/payment_abstract', array('getMethodDependendFormFields') + ); + $configMock = $this->getModelMock('ops/config', array('getPSPID')); + $configMock->expects($this->once()) + ->method('getPSPID') + ->will($this->returnValue('NRMAGENTO')); + $this->replaceByMock('singleton', 'ops/config', $configMock); + $this->replaceByMock('model', 'ops/config', $configMock); + $helperMock = $this->getHelperMock('ops/payment', array('getShaSign')); + $helperMock->expects($this->any()) + ->method('getSHASign') + ->with( + $this->anything(), + $this->anything(), + null + ) + ->will($this->returnValue('SHA123')); + $this->replaceByMock('helper', 'ops/payment', $helperMock); + $requestMock = $this->getHelperMock('ops/payment_request', array('getConfig')); + $requestMock->expects($this->any()) + ->method('getConfig') + ->will($this->returnValue($configMock)); + $this->replaceByMock('helper', 'ops/payment_request', $requestMock); + + $order = Mage::getModel('sales/order'); + $address = Mage::getModel('sales/order_address'); + $order->setBillingAddress($address); + $order->setShippingAddress($address); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethodInstance(Mage::getModel('ops/payment_cc')); + $order->setPayment($payment); + $formFields = $paymentModel->getFormFields($order, array()); + $this->assertArrayHasKey('PSPID', $formFields); + $this->assertArrayHasKey('SHASIGN', $formFields); + $this->assertArrayHasKey('ACCEPTURL', $formFields); + $this->assertArrayHasKey('DECLINEURL', $formFields); + $this->assertArrayHasKey('EXCEPTIONURL', $formFields); + $this->assertArrayHasKey('CANCELURL', $formFields); + $this->assertEquals('NRMAGENTO', $formFields['PSPID']); + $this->assertEquals('2d9f92d6f3955847ab2db427be75fe7eb0cde045', $formFields['SHASIGN']); + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testGetFormFieldsWithFormDependendFormFields() + { + $paymentModel = $this->getModelMock( + 'ops/payment_abstract', array('getMethodDependendFormFields') + ); + $paymentModel->expects($this->any()) + ->method('getMethodDependendFormFields') + ->will($this->returnValue(array('foo' => 'bla'))); + $configMock = $this->getModelMock('ops/config', array('getPSPID')); + $configMock->expects($this->once()) + ->method('getPSPID') + ->with(null) + ->will($this->returnValue('NRMAGENTO')); + $this->replaceByMock('model', 'ops/config', $configMock); + $helperMock = $this->getHelperMock('ops/payment', array('getShaSign')); + $helperMock->expects($this->any()) + ->method('getSHASign') + ->with( + $this->anything(), + $this->anything(), + null + ) + ->will($this->returnValue('SHA123')); + $this->replaceByMock('helper', 'ops/payment', $helperMock); + $requestMock = $this->getHelperMock('ops/payment_request', array('getConfig')); + $requestMock->expects($this->any()) + ->method('getConfig') + ->will($this->returnValue($configMock)); + $this->replaceByMock('helper', 'ops/payment_request', $requestMock); + + $order = Mage::getModel('sales/order')->load(15); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethodInstance(Mage::getModel('ops/payment_cc')); + $order->setPayment($payment); + $formFields = $paymentModel->getFormFields($order, array()); + $this->assertArrayHasKey('PSPID', $formFields); + $this->assertArrayHasKey('SHASIGN', $formFields); + $this->assertArrayHasKey('foo', $formFields); + $this->assertEquals('NRMAGENTO', $formFields['PSPID']); + $this->assertEquals( + '2d9f92d6f3955847ab2db427be75fe7eb0cde045', $formFields['SHASIGN'] + ); + $this->assertEquals('bla', $formFields['foo']); + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testGetFormFieldsWithStoreId() + { + $paymentModel = $this->getModelMock( + 'ops/payment_abstract', array('getMethodDependendFormFields') + ); + $configMock = $this->getModelMock( + 'ops/config', array('getPSPID', 'getSHASign') + ); + $configMock->expects($this->once()) + ->method('getPSPID') + ->with(1) + ->will($this->returnValue('NRMAGENTO5')); + $this->replaceByMock('model', 'ops/config', $configMock); + + $helperMock = $this->getHelperMock('ops/payment', array('getShaSign')); + + $helperMock->expects($this->any()) + ->method('getSHASign') + ->with( + $this->anything(), + $this->anything(), + 1 + ) + ->will($this->returnValue('SHA987')); + $this->replaceByMock('helper', 'ops/payment', $helperMock); + $requestMock = $this->getHelperMock('ops/payment_request', array('getConfig')); + $requestMock->expects($this->any()) + ->method('getConfig') + ->will($this->returnValue($configMock)); + $this->replaceByMock('helper', 'ops/payment_request', $requestMock); + + $order = Mage::getModel('sales/order')->load(15); + $payment = Mage::getModel('sales/order_payment'); + $order->setStoreId(1); + $payment->setMethodInstance(Mage::getModel('ops/payment_cc')); + $order->setPayment($payment); + $formFields = $paymentModel->getFormFields($order, array()); + $this->assertArrayHasKey('PSPID', $formFields); + $this->assertArrayHasKey('SHASIGN', $formFields); + $this->assertEquals('NRMAGENTO5', $formFields['PSPID']); + $this->assertEquals( + '0f119cdea2f8ddc0c852bab4765f12d3913982fc', $formFields['SHASIGN'] + ); + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testVoidWithExistingVoidTransactionLeadsToRedirect() + { + $helperMock = $this->getHelperMock('ops/directlink', array('checkExistingTransact')); + $helperMock + ->expects($this->any()) + ->method('checkExistingTransact') + ->with(Netresearch_OPS_Model_Payment_Abstract::OPS_VOID_TRANSACTION_TYPE, 11) + ->will($this->returnValue(true)); + $this->replaceByMock('helper', 'ops/directlink', $helperMock); + $order = Mage::getModel('sales/order')->load(11); + $order->getPayment()->setAdditionalInformation('status', 5); + $paymentModel = $this->getModelMock( + 'ops/payment_abstract', + array('canVoid') + ); + $paymentModel->expects($this->any()) + ->method('canVoid') + ->will($this->returnValue(true)); + + + $dataHelperMock = $this->getHelperMock('ops/data', array('redirect')); + $this->replaceByMock('helper', 'ops/data', $dataHelperMock); + + Mage::getSingleton('admin/session')->getMessages(true); + $noticeCountBefore = sizeof( + Mage::getSingleton('admin/session')->getItemsByType('error') + ); + $paymentModel->void($order->getPayment()); + $notices = Mage::getSingleton('admin/session')->getMessages()->getItemsByType( + 'error' + ); + $noticeCountAfter = sizeof($notices); + $this->assertGreaterThan($noticeCountBefore, $noticeCountAfter); + $this->assertEquals( + $dataHelperMock->__('You already sent a void request. Please wait until the void request will be acknowledged.'), + current($notices)->getText() + ); + + + + } + + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testVoidFailsWhenRequestThrowsException() + { + /** @var Netresearch_OPS_Model_Payment_Abstract $paymentModel */ + $paymentModel = $this->getModelMock( + 'ops/payment_abstract', + array('canVoid') + ); + $paymentModel->expects($this->any()) + ->method('canVoid') + ->will($this->returnValue(true)); + $helperMock = $this->getHelperMock('ops/directlink', array('checkExistingTransact')); + $helperMock + ->expects($this->any()) + ->method('checkExistingTransact') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/directlink', $helperMock); + + $apiClientMock = $this->getModelMock( + 'ops/api_directlink', array('performRequest') + ); + $exception = new Exception('Fake Request failed'); + $apiClientMock->expects($this->any()) + ->method('performRequest') + ->will( + $this->throwException($exception) + ); + $this->replaceByMock('model', 'ops/api_directlink', $apiClientMock); + $order = Mage::getModel('sales/order')->load(11); + $order->getPayment()->setAdditionalInformation('status', 5); + try { + $paymentModel->void($order->getPayment()); + } catch (Exception $e) { + $this->assertEquals('Fake Request failed', $e->getMessage()); + } + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testVoidFailsWhenStatusIsUnknown() + { + $paymentModel = $this->getModelMock( + 'ops/payment_abstract', + array('canVoid') + ); + $paymentModel->expects($this->any()) + ->method('canVoid') + ->will($this->returnValue(true)); + $helperMock = $this->getHelperMock('ops/directlink', array('checkExistingTransact')); + $helperMock + ->expects($this->any()) + ->method('checkExistingTransact') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/directlink', $helperMock); + + $apiClientMock = $this->getModelMock( + 'ops/api_directlink', array('performRequest') + ); + $apiClientMock->expects($this->any()) + ->method('performRequest') + ->will( + $this->returnValue( + array( + 'STATUS' => 666 + ) + ) + ); + $this->replaceByMock('model', 'ops/api_directlink', $apiClientMock); + $order = Mage::getModel('sales/order')->load(11); + $order->getPayment()->setAdditionalInformation('status', 5); + try { + $paymentModel->void($order->getPayment()); + } catch (Exception $e) { + $this->assertEquals(5, $order->getPayment()->getAdditionalInformation('status')); + $helper = Mage::helper('ops/data'); + $this->assertEquals($helper->__('Can not handle status %s.', 666), $e->getMessage()); + } + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testVoidWithStatusVoidWaiting() + { + $txMock = $this->getModelMock('sales/order_payment_transaction', array('save')); + $this->replaceByMock('model', 'sales/order_payment_transaction', $txMock); + + $dataHelperMock = $this->getHelperMock('ops/data', array('redirect')); + $this->replaceByMock('helper', 'ops/data', $dataHelperMock); + + $paymentModel = $this->getModelMock( + 'ops/payment_abstract', + array('canVoid') + ); + + + $paymentModel->expects($this->any()) + ->method('canVoid') + ->will($this->returnValue(true)); + $helperMock = $this->getHelperMock('ops/directlink', array('checkExistingTransact')); + $helperMock + ->expects($this->any()) + ->method('checkExistingTransact') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/directlink', $helperMock); + + $apiClientMock = $this->getModelMock( + 'ops/api_directlink', array('performRequest') + ); + $apiClientMock->expects($this->any()) + ->method('performRequest') + ->will( + $this->returnValue( + array( + 'STATUS' => Netresearch_OPS_Model_Status::DELETION_WAITING, + 'PAYID' => '4711', + 'PAYIDSUB' => '0815' + ) + ) + ); + $this->replaceByMock('model', 'ops/api_directlink', $apiClientMock); + $order = Mage::getModel('sales/order')->load(11); + $order->getPayment()->setAdditionalInformation('status', 5); + + $paymentModel->setInfoInstance($order->getPayment()); + $paymentModel->void($order->getPayment()); + $this->assertTrue($order->getPayment()->hasMessage()); + $this->assertNotEmpty($order->getPayment()->getMessage()); + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testVoidWithStatusVoidAccepted() + { + $dataHelperMock = $this->getHelperMock('ops/data', array('redirect')); + $this->replaceByMock('helper', 'ops/data', $dataHelperMock); + + $paymentModel = $this->getModelMock( + 'ops/payment_abstract', + array('canVoid') + ); + + + $txMock = $this->getModelMock('sales/order_payment_transaction', array('save')); + $this->replaceByMock('model', 'sales/order_payment_transaction', $txMock); + + $paymentModel->expects($this->any()) + ->method('canVoid') + ->will($this->returnValue(true)); + $helperMock = $this->getHelperMock('ops/directlink', array('checkExistingTransact')); + $helperMock + ->expects($this->any()) + ->method('checkExistingTransact') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/directlink', $helperMock); + + $apiClientMock = $this->getModelMock( + 'ops/api_directlink', array('performRequest') + ); + $apiClientMock->expects($this->any()) + ->method('performRequest') + ->will( + $this->returnValue( + array( + 'STATUS' => Netresearch_OPS_Model_Status::AUTHORIZED_AND_CANCELLED, + 'PAYID' => '4711', + 'PAYIDSUB' => '0815' + ) + ) + ); + $this->replaceByMock('model', 'ops/api_directlink', $apiClientMock); + $order = Mage::getModel('sales/order')->load(11); + $order->getPayment()->setAdditionalInformation('status', 5); + $paymentModel->setInfoInstance($order->getPayment()); + $paymentModel->void($order->getPayment()); + + $this->assertEquals( + Netresearch_OPS_Model_Status::AUTHORIZED_AND_CANCELLED, + $order->getPayment()->getAdditionalInformation('status') + ); + + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testGetOpsHtmlAnswer() + { + $fakeQuote = new Varien_Object(); + $fakeQuote->setId(42); + + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor() // This one removes session_start and other methods usage + ->setMethods(array('getQuote')) + ->getMock(); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($fakeQuote)); + $this->replaceByMock('singleton', 'checkout/session', $sessionMock); + + $this->assertEquals('HTML', Mage::getModel('ops/payment_abstract')->getOpsHtmlAnswer()); + + $fakeQuote = new Varien_Object(); + $fakeQuote->setId(null); + $sessionMock = $this->getModelMockBuilder('checkout/session') + ->disableOriginalConstructor() // This one removes session_start and other methods usage + ->setMethods(array('getQuote', 'getLastRealOrderId')) + ->getMock(); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($fakeQuote)); + + $sessionMock->expects($this->any()) + ->method('getLastRealOrderId') + ->will($this->returnValue('100000020')); + + $this->replaceByMock('singleton', 'checkout/session', $sessionMock); + $this->assertEquals('HTML', Mage::getModel('ops/payment_abstract')->getOpsHtmlAnswer()); + + $order = Mage::getModel('sales/order')->load(20); + $this->assertEquals('HTML', Mage::getModel('ops/payment_abstract')->getOpsHtmlAnswer($order->getPayment())); + } + + /** + * @loadFixture orders.yaml + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage The payment review action is unavailable. + */ + public function testAcceptPaymentNotSupportedState() + { + $payment = Mage::getModel('payment/info'); + $payment->setAdditionalInformation('status', 99); + Mage::getModel('ops/payment_abstract')->acceptPayment($payment); + } + + + /** + * @loadFixture orders.yaml + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage The order can not be accepted via Magento. For the actual status of the payment check the Ingenico ePayments backend. + */ + public function testAcceptPaymentSupportedState() + { + $order = Mage::getModel('sales/order')->load(25); + $order->getPayment()->setAdditionalInformation('status', 57); + + $result = Mage::getModel('ops/payment_abstract')->acceptPayment($order->getPayment()); + } + + /** + * @loadFixture orders.yaml + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage The payment review action is unavailable. + */ + public function testDenyPaymentNotSupportedState() + { + $payment = Mage::getModel('payment/info'); + $payment->setAdditionalInformation('status', 99); + Mage::getModel('ops/payment_abstract')->denyPayment($payment); + } + + + /** + * @loadFixture orders.yaml + */ + public function testDenyPaymentSupportedState() + { + $order = Mage::getModel('sales/order')->load(25); + $order->getPayment()->setAdditionalInformation('status', 57); + + $result = Mage::getModel('ops/payment_abstract')->denyPayment($order->getPayment()); + $this->assertTrue($result); + } + + + /** + * @loadFixture orders.yaml + */ + public function testCanReviewPaymentFalse() + { + $order = Mage::getModel('sales/order')->load(25); + $order->getPayment()->setAdditionalInformation('status', 5); + $this->assertFalse(Mage::getModel('ops/payment_abstract')->canReviewPayment($order->getPayment())); + } + + /** + * @loadFixture orders.yaml + */ + public function testCanReviewPaymentTrue() + { + $order = Mage::getModel('sales/order')->load(25); + $order->getPayment()->setAdditionalInformation('status', 57); + $this->assertTrue(Mage::getModel('ops/payment_abstract')->canReviewPayment($order->getPayment())); + } + + public function testGetFrontendGateWay() + { + $gateway = Mage::getModel('ops/config')->getFrontendGatewayPath(); + $payment = Mage::getModel('ops/payment_cc'); + $url = $payment->getFrontendGateWay(); + $this->assertTrue(strpos($url, '_utf8') >= 0); + $this->assertEquals($gateway, $url); + } + + public function testSetEncoding() + { + $payment = Mage::getModel('ops/payment_cc'); + $payment->setEncoding('test_foo'); + + $this->assertEquals('test_foo', $payment->getEncoding()); + } + + protected function getOwnerParams() + { + return $ownerParams = array( + 'OWNERADDRESS', + 'OWNERTOWN', + 'OWNERZIP', + 'OWNERTELNO', + 'OWNERCTY', + 'ADDMATCH', + 'ECOM_BILLTO_POSTAL_POSTALCODE', + ); + } + + protected function getShippingParams() + { + $paramValues = array( + 'ECOM_SHIPTO_POSTAL_NAME_FIRST', + 'ECOM_SHIPTO_POSTAL_NAME_LAST', + 'ECOM_SHIPTO_POSTAL_STREET_LINE1', + 'ECOM_SHIPTO_POSTAL_STREET_LINE2', + 'ECOM_SHIPTO_POSTAL_COUNTRYCODE', + 'ECOM_SHIPTO_POSTAL_CITY', + 'ECOM_SHIPTO_POSTAL_POSTALCODE' + ); + + return $paramValues; + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/expectations/paymentMethods.yaml b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/expectations/paymentMethods.yaml new file mode 100644 index 0000000..e35461a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/expectations/paymentMethods.yaml @@ -0,0 +1,279 @@ +ops_3XCB: + pm: 3XCB + brand: 3XCB +ops_CreditCard: + pm: CreditCard +  brand: AIRPLUS +ops_CreditCard: + pm: CreditCard + brand: American Express +ops_CreditCard: + pm: CreditCard +  brand: Aurore +ops_CreditCard: + pm: CreditCard +  brand: CB +ops_CreditCard: + pm: CreditCard +  brand: Cofinoga +ops_CreditCard: + pm: CreditCard +  brand: Dankort +ops_CreditCard: + pm: CreditCard +  brand: Diners +ops_CreditCard: + pm: CreditCard +  brand: JCB +ops_Creditcard: + pm: Creditcard +  brand: Laser +ops_CreditCard: + pm: CreditCard +  brand: MaestroUK +ops_CreditCard: + pm: CreditCard +  brand: MasterCard +ops_CreditCard: + pm: CreditCard +  brand: UATP +ops_CreditCard: + pm: CreditCard +  brand: VISA +ops_BCMC: + pm: CreditCard +  brand: BCMC +ops_CreditCard: + pm: CreditCard + brand: Maestro +ops_PostFinance: + pm: PostFinance Card + brand: PostFinance Card  +ops_CreditCard: + pm: CreditCard + brand: PRIVILEGE +ops_UNEUROCOM: + pm: UNEUROCOM + brand: UNEUROCOM +ops_AmazonCheckout: + pm: Amazon Checkout + brand: Amazon Checkout +ops_Belfius: + pm: Belfius Direct Net + brand: Belfius Direct Net +ops_cashticket: + pm: cashticket + brand: cashticket +ops_cbcOnline: + pm: CBC Online + brand: CBC Online +ops_directEbanking: + pm: DirectEbanking + brand: Sofort Uberweisung +ops_directEbankingAT: + pm: DirectEbankingAT + brand: DirectEbankingAT +ops_directEbankingBE: + pm: DirectEbankingBE + brand: DirectEbankingBE  +ops_directEbankingCH: + pm: DirectEbankingCH + brand: DirectEbankingCH  +ops_directEbankingDE: + pm: DirectEbankingDE + brand: DirectEbankingDE  +ops_directEbankingFR: + pm: DirectEbankingFR + brand: DirectEbankingFR  +ops_directEbankingGB: + pm: DirectEbankingGB + brand: DirectEbankingGB  +ops_directEbankingIT: + pm: DirectEbankingIT + brand: DirectEbankingIT  +ops_directEbankingNL: + pm: DirectEbankingNL + brand: DirectEbankingNL  +ops_EBS_AXIS: + pm: EBS_AXIS + brand: EBS_AXIS +ops_EBS_BC: + pm: EBS_BC + brand: EBS_BC +ops_EBS_CB: + pm: EBS_CB + brand: EBS_CB +ops_EBS_CORP: + pm: EBS_CORP + brand: EBS_CORP +ops_EBS_DC: + pm: EBS_DC + brand: EBS_DC +ops_EBS_FED: + pm: EBS_FED + brand: EBS_FED +ops_EBS_HDFC: + pm: EBS_HDFC + brand: EBS_HDFC +ops_EBS_HYDERABAD: + pm: EBS_HYDERABAD + brand: EBS_HYDERABAD +ops_EBS_IB: + pm: EBS_IB + brand: EBS_IB +ops_EBS_ICASH: + pm: EBS_ICASH + brand: EBS_ICASH +ops_EBS_ICICI: + pm: EBS_ICICI + brand: EBS_ICICI +ops_EBS_INDIA: + pm: EBS_INDIA + brand: EBS_INDIA +ops_EBS_ITZ: + pm: EBS_ITZ + brand: EBS_ITZ +ops_EBS_JK: + pm: EBS_JK + brand: EBS_JK +ops_EBS_KARNATAKA: + pm: EBS_KARNATAKA + brand: EBS_KARNATAKA +ops_EBS_KOTAK: + pm: EBS_KOTAK + brand: EBS_KOTAK +ops_EBS_MYSORE: + pm: EBS_MYSORE + brand: EBS_MYSORE +ops_EBS_TRAVANCORE: + pm: EBS_TRAVANCORE + brand: EBS_TRAVANCORE +ops_eDankort: + pm: eDankort + brand: eDankort +ops_eps: + pm: EPS + brand: EPS +ops_FidorPay: + pm: FidorPay + brand: FidorPay +ops_fortisPayButton: + pm: Fortis Pay Button + brand: Fortis Pay Button +ops_giroPay: + pm: giropay + brand: giropay +ops_Masterpass: + pm: Masterpass + brand: Masterpass +ops_iDeal: + pm: iDEAL + brand: iDEAL +ops_ingHomePay: + pm: ING HomePay + brand: ING HomePay +ops_kbcOnline: + pm: KBC Online + brand: KBC Online +ops_paysafecard: + pm: paysafecard + brand: paysafecard +ops_postFinanceEFinance: + pm: PostFinance e-finance + brand: PostFinance e-finance +ops_directDebitsAt: + pm: Direct Debits AT + brand: Direct Debits AT +ops_directDebitsDe: + pm: Direct Debits DE + brand: Direct Debits DE +ops_directDebitsNl: + pm: Direct Debits NL + brand: Direct Debits NL +ops_bankTransfer: + pm: "Bank transfer" + brand: "Bank transfer" +ops_bankTransferBe: + pm: Bank transfer BE + brand: Bank transfer BE +ops_bankTransferDe: + pm: Bank transfer DE + brand: Bank transfer DE +ops_bankTransferFr: + pm: Bank transfer FR + brand: Bank transfer FR +ops_bankTransferNl: + pm: Bank transfer NL + brand: Bank transfer NL +ops_installmentsDe: + pm: Installments DE + brand: Installments DE +ops_installmentsDk: + pm: Installments DK + brand: Installments DK +ops_installmentsFi: + pm: Installments FI + brand: Installments FI +ops_installmentsNl: + pm: Installments NL + brand: Installments NL +ops_installmentsNo: + pm: Installments NO + brand: Installments NO +ops_installmentsSe: + pm: Installments SE + brand: Installments SE +ops_openInvoiceDe: + pm: Open Invoice DE + brand: Open Invoice DE +ops_openInvoiceDk: + pm: Open Invoice DK + brand: Open Invoice DK +ops_openInvoiceFi: + pm: Open Invoice FI + brand: Open Invoice FI +ops_openInvoiceNl: + pm: Open Invoice NL + brand: Open Invoice NL +ops_openInvoiceAT: + pm: Open Invoice AT + brand: Open Invoice AT +ops_openInvoiceNo: + pm: Open Invoice NO + brand: Open Invoice NO +ops_openInvoiceSe: + pm: Open Invoice SE + brand: Open Invoice SE +ops_cod: + pm: Payment on Delivery + brand: Payment on Delivery +ops_interSolve: + pm: InterSolve + brand: InterSolve +ops_pingPing: + pm: PingPing + brand: PingPing +ops_tunz: + pm: TUNZ + brand: TUNZ +ops_cashEX: + pm: cashEX + brand: cashEX +ops_cashU: + pm: cashU + brand: cashU +ops_cashUDirect: + pm: cashU Direct + brand: cashU Direct +ops_Payble: + pm: Payble + brand: Payble +ops_paypal: + pm: PAYPAL + brand: PAYPAL +ops_wallie: + pm: Wallie + brand: Wallie +ops_belfiusDirectNet: + pm: Belfius Direct Net + brand: Belfius Direct Net diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/fixtures/orders.yaml new file mode 100644 index 0000000..fc9499c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/AbstractTest/fixtures/orders.yaml @@ -0,0 +1,135 @@ +tables: + sales/order: + - entity_id: 25 + increment_id: 100000025 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 10 + + - entity_id: 26 + increment_id: 100000026 + shipping_address_id: 43 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'payment_review' + status: 'payment_review' + quote_id: 26 + + - entity_id: 27 + increment_id: 100000027 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 27 + + + + sales/quote: + - entity_id: 25 + parent_id: 25 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 26 + parent_id: 26 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 27 + parent_id: 27 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + + + sales/quote_payment: + - payment_id: 25 + quote_id: 25 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + + - payment_id: 26 + quote_id: 26 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + + - payment_id: 27 + quote_id: 27 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:9:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + + + + sales/order_payment: + - entity_id: 25 + parent_id: 25 + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + method: 'ops_iDeal' + + - entity_id: 26 + parent_id: 26 + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + method: 'ops_iDeal' + + - entity_id: 27 + parent_id: 27 + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + method: 'ops_iDeal' + + + + sales/order_address: + - entity_id: 42 + parent_id: 25 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + - entity_id: 43 + parent_id: 26 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + + diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/BancontactTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/BancontactTest.php new file mode 100644 index 0000000..01dce0c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/BancontactTest.php @@ -0,0 +1,81 @@ +model = Mage::getModel('ops/payment_bancontact'); + $this->model->setInfoInstance(Mage::getModel('payment/info')); + } + + public function testCanCapturePartial() + { + $this->assertTrue($this->model->canCapturePartial()); + } + + public function testGetOpsCode() + { + $this->assertEquals('CreditCard', $this->model->getOpsCode()); + } + + public function testGetOpsBrand() + { + $this->assertEquals('BCMC', $this->model->getOpsBrand()); + } + + /** + * @loadFixture ../../../../var/fixtures/orders.yaml + */ + public function testGetMethodDependendFormFields() + { + $order = Mage::getModel('sales/order')->load(32); + + $sessionMock = $this->getModelMockBuilder('core/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'core/session', $sessionMock); + + $sessionMock = $this->getModelMockBuilder('customer/session') + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + + $this->model->getInfoInstance()->setAdditionalInformation('DEVICE', Netresearch_OPS_Helper_MobileDetect::DEVICE_TYPE_MOBILE); + $formFields = $this->model->getMethodDependendFormFields($order, null); + $this->assertEquals(Netresearch_OPS_Helper_MobileDetect::DEVICE_TYPE_MOBILE, $formFields['DEVICE']); + } + + + public function testAssignData() + { + $infoInstance = Mage::getModel('sales/quote_payment'); + + $helperMock = new Varien_Object( + array('device_type' => Netresearch_OPS_Helper_MobileDetect::DEVICE_TYPE_MOBILE) + ); + + $paymentInstance = Mage::getModel('ops/payment_bancontact'); + $paymentInstance->setMobileDetectHelper($helperMock) + ->setInfoInstance($infoInstance); + + $paymentInstance->assignData(array()); + $additionalInformation = $infoInstance->getData('additional_information'); + + $this->assertEquals(Netresearch_OPS_Helper_MobileDetect::DEVICE_TYPE_MOBILE, $additionalInformation['DEVICE']); + } + + /** + * @Test + */ + public function testGetMobileDetectHelper() + { + $this->assertTrue($this->model->getMobileDetectHelper() instanceof Netresearch_OPS_Helper_MobileDetect); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/BancontactTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Model/Payment/BancontactTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/CcTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/CcTest.php new file mode 100644 index 0000000..1b85e06 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/CcTest.php @@ -0,0 +1,213 @@ +_model = Mage::getModel('ops/payment_cc'); + $this->_payment = ObjectHandler::getObject('quoteBeforeSaveOrder')->getPayment(); + } + + public function testBrand() + { + $this->_payment->setAdditionalInformation('CC_BRAND', 'VISA'); + $this->assertEquals('VISA', $this->_model->getOpsBrand($this->_payment), 'VISA should have brand VISA'); + $this->assertEquals('CreditCard', $this->_model->getOpsCode($this->_payment), 'VISA should be a CreditCard'); + $this->assertTrue($this->_model->hasBrandAliasInterfaceSupport($this->_payment), 'VISA should support alias interface'); + + $this->_payment->setAdditionalInformation('CC_BRAND', 'UNEUROCOM'); + $this->assertEquals('UNEUROCOM', $this->_model->getOpsBrand($this->_payment), 'UNEUROCOM should have brand UNEUROCOM'); + $this->assertEquals('UNEUROCOM', $this->_model->getOpsCode($this->_payment), 'UNEUROCOM should have code UNEUROCOM'); + $this->assertFalse($this->_model->hasBrandAliasInterfaceSupport($this->_payment), 'UNEUROCOM should NOT support alias interface'); + + $this->_payment->setAdditionalInformation('CC_BRAND', 'PostFinance card'); + $this->assertEquals('PostFinance card', $this->_model->getOpsBrand($this->_payment), 'PostFinance Card should have brand "PostFinance card"'); + $this->assertEquals('PostFinance Card', $this->_model->getOpsCode($this->_payment), 'PostFinance Card should have code "PostFinance Card"'); + $this->assertFalse($this->_model->hasBrandAliasInterfaceSupport($this->_payment), 'PostFinance Card should NOT support alias interface'); + + $this->_payment->setAdditionalInformation('CC_BRAND', 'PRIVILEGE'); + $this->assertEquals('PRIVILEGE', $this->_model->getOpsBrand($this->_payment), 'PRIVILEGE should have brand PRIVILEGE'); + $this->assertEquals('CreditCard', $this->_model->getOpsCode($this->_payment), 'PRIVILEGE should be a CreditCard'); + $this->assertFalse($this->_model->hasBrandAliasInterfaceSupport($this->_payment), 'PRIVILEGE should NOT support alias interface'); + } + + public function testOrderPlaceRedirectUrl() + { + $this->_model->setInfoInstance($this->_payment); + $this->_payment->setAdditionalInformation('CC_BRAND', 'VISA'); + $this->assertFalse($this->_model->getOrderPlaceRedirectUrl($this->_payment), 'VISA should NOT require a redirect after checkout'); + + $this->_payment->setAdditionalInformation('CC_BRAND', 'VISA'); + $this->_payment->setAdditionalInformation('HTML_ANSWER', 'BASE64ENCODEDSTRING'); + $this->assertInternalType('string', $this->_model->getOrderPlaceRedirectUrl($this->_payment), 'If Brand is VIA and HTML_ANSWER isset, a redirect should happen after checkout'); + + $this->_payment->setAdditionalInformation('CC_BRAND', 'PRIVILEGE'); + $this->assertInternalType('string', $this->_model->getOrderPlaceRedirectUrl($this->_payment), 'PRIVILEGE should require a redirect after checkout'); + + } + + public function testIsZeroAmountAuthorizationAllowed() + { + $model = new Netresearch_OPS_Model_Payment_Cc(); + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig('payment_services/ops/payment_action', Netresearch_OPS_Model_Payment_Abstract::ACTION_AUTHORIZE_CAPTURE); + $store->setConfig('payment/ops_cc/zero_amount_checkout', 0); + $this->assertFalse($model->isZeroAmountAuthorizationAllowed()); + + $store->resetConfig(); + $store->setConfig('payment_services/ops/payment_action', Netresearch_OPS_Model_Payment_Abstract::ACTION_AUTHORIZE_CAPTURE); + $store->setConfig('payment/ops_cc/zero_amount_checkout', 1); + $this->assertFalse($model->isZeroAmountAuthorizationAllowed()); + + $store->resetConfig(); + $store->setConfig('payment_services/ops/payment_action', Netresearch_OPS_Model_Payment_Abstract::ACTION_AUTHORIZE); + $store->setConfig('payment/ops_cc/zero_amount_checkout', 0); + $this->assertFalse($model->isZeroAmountAuthorizationAllowed()); + } + + public function testZeroAmountAuthAllowed() + { + $model = new Netresearch_OPS_Model_Payment_Cc(); + $store = Mage::app()->getStore(0)->load(0); + $configMock = $this->getModelMock('ops/config', array('getConfigData')); + $configMock->expects($this->once()) + ->method('getConfigData') + ->with('payment_action') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::ACTION_AUTHORIZE)); + $this->replaceByMock('model', 'ops/config', $configMock); + $store->setConfig('payment/ops_cc/zero_amount_checkout', 1); + $this->assertTrue($model->isZeroAmountAuthorizationAllowed()); + } + + + + public function testIsApplicableToQuoteTrue() + { + $helperMock = $this->getHelperMock('ops/version', array('canUseApplicableForQuote')); + $helperMock->expects($this->any()) + ->method('canUseApplicableForQuote') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/version', $helperMock); + + $quote = Mage::getModel('sales/quote'); + $quote->setBaseGrandTotal(0.0); + $this->assertTrue($this->_model->isApplicableToQuote($quote, 1)); + + } + + public function testIsApplicableToQuoteTrueWithZeroAmount() + { + $versionInfo = Mage::getVersionInfo(); + if ((array_key_exists('minor', $versionInfo)) + && (Mage::getEdition() === Mage::EDITION_COMMUNITY && $versionInfo['minor'] > '7') + || (Mage::getEdition() === Mage::EDITION_ENTERPRISE && $versionInfo['minor'] > '13') + ) + { + $store = Mage::app()->getStore(0)->load(0); + $configMock = $this->getModelMock('ops/config', array('getConfigData')); + $configMock->expects($this->once()) + ->method('getConfigData') + ->with('payment_action') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::ACTION_AUTHORIZE)); + $this->replaceByMock('model', 'ops/config', $configMock); + $store->setConfig('payment/ops_cc/zero_amount_checkout', 1); + + + $helperMock = $this->getHelperMock('ops/version', array('canUseApplicableForQuote')); + $helperMock->expects($this->any()) + ->method('canUseApplicableForQuote') + ->will($this->returnValue(true)); + $this->replaceByMock('helper', 'ops/version', $helperMock); + + $quote = $this->getModelMock('sales/quote', array('getItemsCount', 'getBaseGrandTotal', 'isNominal')); + $quote->expects($this->once()) + ->method('getItemsCount') + ->will($this->returnValue(1)); + $quote->expects($this->any()) + ->method('getBaseGrandTotal') + ->will($this->returnValue(0.0)); + $quote->expects($this->any()) + ->method('isNominal') + ->will($this->returnValue(false)); + $this->assertTrue($this->_model->isApplicableToQuote($quote, 128)); + } + } + + public function testIsApplicableToQuoteFeatureModelTrue() + { + $versionInfo = Mage::getVersionInfo(); + if ((array_key_exists('minor', $versionInfo)) + && (Mage::getEdition() === Mage::EDITION_COMMUNITY && $versionInfo['minor'] > '7') + || (Mage::getEdition() === Mage::EDITION_ENTERPRISE && $versionInfo['minor'] > '13') + ) + { + $featureModelMock = $this->getModelMock('ops/payment_features_zeroAmountAuth', array('isCCAndZeroAmountAuthAllowed')); + $featureModelMock->expects($this->any()) + ->method('isCCAndZeroAmountAuthAllowed') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/payment_features_zeroAmountAuth', $featureModelMock); + + $helperMock = $this->getHelperMock('ops/version', array('canUseApplicableForQuote')); + + $helperMock->expects($this->any()) + ->method('canUseApplicableForQuote') + ->will($this->returnValue(true)); + $this->replaceByMock('helper', 'ops/version', $helperMock); + + $quote = Mage::getModel('sales/quote'); + $this->assertTrue($this->_model->isApplicableToQuote($quote, '1')); + } + } + + public function testGetFeatureModel() + { + $this->assertTrue($this->_model->getFeatureModel() instanceof Netresearch_OPS_Model_Payment_Features_ZeroAmountAuth); + } + + public function testSetCanCapture() + { + $ccPaymentObject = Mage::getModel('ops/payment_cc'); + $this->assertTrue($ccPaymentObject->canCapture()); + + $ccPaymentObject->setCanCapture(false); + $this->assertFalse($ccPaymentObject->canCapture()); + } + + public function testGetOpsBrand() + { + $payment = Mage::getModel('sales/quote_payment'); + $payment->setAdditionalInformation('CC_BRAND', 'VISA'); + $quote = $this->getModelMock('sales/quote', array('getPayment')); + $quote->expects($this->any()) + ->method('getPayment') + ->will($this->returnValue($payment)); + $quote->setId(1); + $checkoutSessionMock = $this->getModelMock('checkout/session', array('getQuote', 'init', 'save')); + $checkoutSessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($quote)); + $this->replaceByMock('singleton', 'checkout/session', $checkoutSessionMock); + $this->assertEquals('VISA', $this->_model->getOpsBrand(null)); + + } + + public function testIsAvailable() + { + $helperMock = $this->getHelperMock('ops/data', array('isAdminSession')); + $helperMock->expects($this->once()) + ->method('isAdminSession') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'ops/data', $helperMock); + $quote = Mage::getModel('sales/quote'); + $quote->setItemsCount(0); + $this->assertFalse($this->_model->isAvailable($quote)); + $quote->setItemsCount(500); + $this->assertFalse($this->_model->isAvailable($quote)); + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/ChinaUnionPayTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/ChinaUnionPayTest.php new file mode 100644 index 0000000..06e8a75 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/ChinaUnionPayTest.php @@ -0,0 +1,47 @@ +model = Mage::getModel('ops/payment_chinaUnionPay'); + $this->model->setInfoInstance(Mage::getModel('payment/info')); + } + + /** + * assure that CUP can not capture partial, because invoice is always created on feedback in this case + */ + public function testCanCapturePartial() + { + $this->assertFalse($this->model->canCapturePartial()); + } + + public function testGetOpsCode() + { + $this->assertEquals('PAYDOL_UPOP', $this->model->getOpsCode()); + } + + public function testGetOpsBrand() + { + $this->assertEquals('UnionPay', $this->model->getOpsBrand()); + } + + + public function testCanRefundPartialPerInvoice() + { + $this->assertFalse($this->model->canRefundPartialPerInvoice()); + } + + public function testGetPaymentAction() + { + $this->assertEquals( + Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE, + $this->model->getPaymentAction() + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/DebitcardTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/DebitcardTest.php new file mode 100644 index 0000000..4b8ad20 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/DebitcardTest.php @@ -0,0 +1,53 @@ + + */ +?> +model = Mage::getModel('ops/payment_debitcard'); + } + + public function testGetOpsCode() + { + $this->assertEquals('CreditCard', $this->model->getOpsCode()); + } + + public function testGetRequestParamsHelper() + { + $this->assertTrue($this->model->getRequestParamsHelper() instanceof Netresearch_OPS_Helper_Debitcard); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/DirectDebitTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/DirectDebitTest.php new file mode 100644 index 0000000..cad5875 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/DirectDebitTest.php @@ -0,0 +1,23 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Model_Payment_DirectDebitTest + extends EcomDev_PHPUnit_Test_Case +{ + + public function testGetOrderPlaceRedirectUrl() + { + $this->assertFalse( + Mage::getModel('ops/payment_directDebit')->getOrderPlaceRedirectUrl( + ) + ); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/DirectEbankingTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/DirectEbankingTest.php new file mode 100644 index 0000000..a552aa4 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/DirectEbankingTest.php @@ -0,0 +1,19 @@ + 'Sofort Uberweisung', + ); + $payment = Mage::getModel('sales/order_payment'); + $infoInstance = new Varien_Object(); + + $method = Mage::getModel('ops/payment_directEbanking'); + $method->setInfoInstance(Mage::getModel('sales/quote_payment')); + $method = $method->assignData($data); + $this->assertEquals($method->getOpsBrand(), 'DirectEbanking'); + $this->assertEquals($method->getOpsCode(), 'DirectEbanking'); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/DirectLinkTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/DirectLinkTest.php new file mode 100644 index 0000000..15d964f --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/DirectLinkTest.php @@ -0,0 +1,475 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Model_Payment_DirectLinkTest extends EcomDev_PHPUnit_Test_Case +{ + + protected $testObjects = array(); + + + public function setUp() + { + parent::setUp(); + $payment = Mage::getModel('sales/order_payment'); + $payment->setAdditionalInformation('CC_BRAND', 'VISA') + ->setMethod('ops_cc'); + $this->testObjects[] = Mage::getModel('ops/payment_cc')->setInfoInstance($payment); + $payment2 = clone($payment); + $payment2->setMethod('ops_directDebit'); + $this->testObjects[] = Mage::getModel('ops/payment_directDebit')->setInfoInstance($payment2); + } + + + public function testInstanceOfAbstractPayment() + { + foreach ($this->testObjects as $testObject) { + $this->assertTrue($testObject instanceof Netresearch_OPS_Model_Payment_Abstract); + } + } + + + public function testGetConfigPaymentActionReturnsMageAuthorizeWithOrderIdAsMerchRef() + { + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference', 'getPaymentAction', 'getInlinePaymentCcTypes')); + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID)); + $configMock->expects($this->any()) + ->method('getPaymentAction') + ->will($this->returnValue(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE)); + $configMock->expects($this->any()) + ->method('getInlinePaymentCcTypes') + ->will($this->returnValue(array('VISA'))); + foreach ($this->testObjects as $testObject) { + $testObject->setConfig($configMock); + $this->assertEquals(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE, $testObject->getConfigPaymentAction()); + } + } + + public function testGetConfigPaymentActionReturnsAuthorizeStringWithQuoteIdAsMerchRef() + { + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference', 'getPaymentAction')); + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID)); + $configMock->expects($this->any()) + ->method('getPaymentAction') + ->will($this->returnValue(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE)); + foreach ($this->testObjects as $testObject) { + $testObject->setConfig($configMock); + $this->assertEquals(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE, $testObject->getConfigPaymentAction()); + } + } + + + public function testGetConfigPaymentActionReturnsMageAuthorizeCaptureWithOrderIdAsMerchRef() + { + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference', 'getPaymentAction', 'getInlinePaymentCcTypes')); + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID)); + $configMock->expects($this->any()) + ->method('getPaymentAction') + ->will($this->returnValue(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE)); + $configMock->expects($this->any()) + ->method('getInlinePaymentCcTypes') + ->will($this->returnValue(array('VISA'))); + + foreach ($this->testObjects as $testObject) { + $testObject->setConfig($configMock); + $this->assertEquals(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE, $testObject->getConfigPaymentAction()); + } + } + + public function testGetConfigPaymentActionReturnsAuthorizeCaptureStringForDirectSaleWithQuoteIdAsMerchRef() + { + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference', 'getPaymentAction')); + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID)); + $configMock->expects($this->any()) + ->method('getPaymentAction') + ->will($this->returnValue(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE)); + foreach ($this->testObjects as $testObject) { + $testObject->setConfig($configMock); + $this->assertEquals(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE, $testObject->getConfigPaymentAction()); + } + } + + public function testIsInitializeNeededReturnsFalse() + { + + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference')); + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID)); + foreach ($this->testObjects as $testObject) { + $testObject->setConfig($configMock); + $this->assertFalse($testObject->isInitializeNeeded()); + } + } + + public function testAuthorize() + { + $configMock = $this->getConfigMockWithOrderId(); + $fakeOrder = $this->getFakeOrder(); + $payment = $this->getFakePayment($fakeOrder); + + $fakeQuote = $this->getFakeQuote(); + $quoteHelperMock = $this->getQuoteHelperMock($fakeQuote); + + $testMock = $this->getModelMock('ops/payment_directDebit', array('confirmPayment')); + + $dataHelperMock = $this->getHelperMock('ops/data', array('isAdminSession')); + $dataHelperMock->expects($this->once()) + ->method('isAdminSession') + ->will($this->returnValue(true)); + $testMock->setDataHelper($dataHelperMock); + + $testMock->setQuoteHelper($quoteHelperMock); + $testMock->expects($this->once()) + ->method('confirmPayment') + ->with($fakeOrder, $fakeQuote, $payment); + $testMock->setConfig($configMock); + $testMock->authorize($payment, 100); + } + + public function testAuthorizeWithInlineCc() + { + $configMock = $this->getConfigMockWithOrderId(); + $fakeOrder = $this->getFakeOrder(); + $payment = $this->getFakePayment($fakeOrder); + $fakeCc = $this->getModelMock('ops/payment_cc', array('hasBrandAliasInterfaceSupport')); + $fakeCc->expects($this->once()) + ->method('hasBrandAliasInterfaceSupport') + ->will($this->returnValue(true)); +// $fakeCc->expects($this->once()) +// ->method('getInfoInstance') +// ->will($this->returnValue($payment)); + $payment->setMethodInstance($fakeCc); + $fakeCc->setInfoInstance($payment); + $fakeQuote = $this->getFakeQuote(); + $quoteHelperMock = $this->getQuoteHelperMock($fakeQuote); + + $testMock = $this->getModelMock('ops/payment_cc', array('confirmPayment', 'hasBrandAliasInterfaceSupport')); + $testMock->setQuoteHelper($quoteHelperMock); + $testMock->expects($this->once()) + ->method('confirmPayment') + ->with($fakeOrder, $fakeQuote, $payment); + $testMock->expects($this->any()) + ->method('hasBrandAliasInterfaceSupport') + ->will($this->returnValue(true)); + ; + $testMock->setInfoInstance($payment); + $testMock->setConfig($configMock); + $testMock->authorize($payment, 100); + } + + + public function testConfirmPaymentWithResponse() + { + $configMock = $this->getConfigMockWithOrderId(); + /** @var Mage_Sales_Model_Order $fakeOrder */ + $fakeOrder = $this->getFakeOrder(); + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $this->getFakePayment($fakeOrder); + + $fakeQuote = $this->getFakeQuote(); + $quoteHelperMock = $this->getQuoteHelperMock($fakeQuote); + /** @var Netresearch_OPS_Model_Payment_DirectDebit $testMock */ + $testMock = $this->getModelMock('ops/payment_directDebit', array('handleAdminPayment', 'performPreDirectLinkCallActions', 'performPostDirectLinkCallActions')); + $testMock->setInfoInstance($payment); + $dataHelperMock = $this->getHelperMock('ops/data', array('isAdminSession')); + $dataHelperMock->expects($this->once()) + ->method('isAdminSession') + ->will($this->returnValue(true)); + $testMock->setDataHelper($dataHelperMock); + + $requestParams = array('ORDERID' => '123'); + $requestParamsHelperMock = $this->getHelperMock('ops/directDebit', array('getDirectLinkRequestParams')); + + $requestParamsHelperMock->expects($this->once()) + ->method('getDirectLinkRequestParams') + ->with($fakeQuote, $fakeOrder, $payment) + ->will($this->returnValue($requestParams)); + $testMock->setRequestParamsHelper($requestParamsHelperMock); + + $response = array('PAYID' => 4711, 'ORDERID' => '123', 'STATUS' => 5); + $directLinkHelperMock = $this->getHelperMock('ops/directlink', array('performDirectLinkRequest')); + $directLinkHelperMock->expects($this->once()) + ->method('performDirectLinkRequest') + ->with($fakeQuote, $requestParams, 0) + ->will($this->returnValue($response)); + $testMock->setDirectLinkHelper($directLinkHelperMock); + $testMock->setQuoteHelper($quoteHelperMock); + + $testMock->setConfig($configMock); + $testMock->authorize($payment, 100); + + $this->assertEquals(5, $payment->getAdditionalInformation('status')); + $this->assertNotEmpty($fakeOrder->getAllStatusHistory()); + } + + public function testConfirmPaymentWithInvalidResponse() + { + $configMock = $this->getConfigMockWithOrderId(); + $fakeOrder = $this->getFakeOrder(); + $payment = $this->getFakePayment($fakeOrder); + + $fakeQuote = $this->getFakeQuote(); + $quoteHelperMock = $this->getQuoteHelperMock($fakeQuote); + $testMock = $this->getModelMock('ops/payment_directDebit', array('handleAdminPayment', 'performPreDirectLinkCallActions', 'performPostDirectLinkCallActions')); + + $dataHelperMock = $this->getHelperMock('ops/data', array('isAdminSession')); + $dataHelperMock->expects($this->once()) + ->method('isAdminSession') + ->will($this->returnValue(true)); + $testMock->setDataHelper($dataHelperMock); + + $requestParams = array('ORDERID' => '123'); + $requestParamsHelperMock = $this->getHelperMock('ops/directDebit', array('getDirectLinkRequestParams')); + + $requestParamsHelperMock->expects($this->once()) + ->method('getDirectLinkRequestParams') + ->with($fakeQuote, $fakeOrder, $payment) + ->will($this->returnValue($requestParams)); + $testMock->setRequestParamsHelper($requestParamsHelperMock); + + $response = array(); + $directLinkHelperMock = $this->getHelperMock('ops/directlink', array('performDirectLinkRequest')); + $directLinkHelperMock->expects($this->once()) + ->method('performDirectLinkRequest') + ->with($fakeQuote, $requestParams, 0) + ->will($this->returnValue($response)); + $testMock->setDirectLinkHelper($directLinkHelperMock); + $testMock->setQuoteHelper($quoteHelperMock); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('handleUnknownStatus')); + $paymentHelperMock->expects($this->once()) + ->method('handleUnknownStatus') + ->with($fakeOrder); + $testMock->setPaymentHelper($paymentHelperMock); + $testMock->setConfig($configMock); + $testMock->authorize($payment, 100); + + } + + + /** + * @expectedException Mage_Core_Exception + */ + public function testConfirmPaymentWithException() + { + $configMock = $this->getConfigMockWithOrderId(); + $fakeOrder = $this->getFakeOrder(); + $payment = $this->getFakePayment($fakeOrder); + + $fakeQuote = $this->getFakeQuote(); + $quoteHelperMock = $this->getQuoteHelperMock($fakeQuote); + $testMock = $this->getModelMock('ops/payment_directDebit', array('handleAdminPayment', 'performPreDirectLinkCallActions', 'performPostDirectLinkCallActions', 'getOnepage')); + + $dataHelperMock = $this->getHelperMock('ops/data', array('isAdminSession')); + $dataHelperMock->expects($this->once()) + ->method('isAdminSession') + ->will($this->returnValue(true)); + $testMock->setDataHelper($dataHelperMock); + + $fakeOnepage = new Varien_Object(); + $fakeOnepage->setCheckout(new Varien_Object()); + $testMock->expects($this->once()) + ->method('getOnepage') + ->will($this->returnValue($fakeOnepage)); + + $requestParams = array('ORDERID' => '123'); + $requestParamsHelperMock = $this->getHelperMock('ops/directDebit', array('getDirectLinkRequestParams')); + + $requestParamsHelperMock->expects($this->once()) + ->method('getDirectLinkRequestParams') + ->with($fakeQuote, $fakeOrder, $payment) + ->will($this->returnValue($requestParams)); + $testMock->setRequestParamsHelper($requestParamsHelperMock); + + $fakeValidator = $this->getModelMock('ops/validator_parameter_length', array('isValid')); + $fakeValidator->expects($this->once()) + ->method('isValid') + ->will($this->returnValue(false)); + + $validationMock = $this->getModelMock('ops/validator_parameter_factory', array('getValidatorFor')); + $validationMock->expects($this->once()) + ->method('getValidatorFor') + ->will($this->returnValue($fakeValidator)); + + $testMock->setValidationFactory($validationMock); + $testMock->setQuoteHelper($quoteHelperMock); + + $testMock->setConfig($configMock); + $testMock->authorize($payment, 100); + + } + + + public function testGetDefaultHelpers() + { + foreach ($this->testObjects as $testObject) { + $this->assertTrue($testObject->getQuoteHelper() instanceof Netresearch_OPS_Helper_Quote); + $this->assertTrue($testObject->getDirectLinkHelper() instanceof Netresearch_OPS_Helper_Directlink); + $this->assertTrue($testObject->getPaymentHelper() instanceof Netresearch_OPS_Helper_Payment); + } + } + + + /** + * @return PHPUnit_Framework_MockObject_MockObject + */ + protected function getConfigMockWithOrderId() + { + $configMock = $this->getModelMock('ops/config', array('getInlineOrderReference', 'getPaymentAction')); + $configMock->expects($this->any()) + ->method('getInlineOrderReference') + ->will($this->returnValue(Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID)); + $configMock->expects($this->any()) + ->method('getPaymentAction') + ->will($this->returnValue(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE)); + return $configMock; + } + + /** + * @return PHPUnit_Framework_MockObject_MockObject + */ + protected function getFakeOrder() + { + $fakeOrder = $this->getModelMock('sales/order', array('save', '_beforeSave')); + $fakeOrder->setState(Mage_Sales_Model_Order::STATE_NEW); + return $fakeOrder; + } + + /** + * @param $fakeOrder + * + * @return Varien_Object + */ + protected function getFakePayment($fakeOrder) + { + $payment = $this->getModelMock('sales/order_payment', array('save')); + $payment->setMethodInstance(Mage::getModel('ops/payment_directDebit')); + $payment->setOrder($fakeOrder); + $fakeOrder->setPayment($payment); + return $payment; + } + + /** + * @return PHPUnit_Framework_MockObject_MockObject + */ + protected function getFakeQuote() + { + $fakeQuote = $this->getModelMock('sales/quote', array('save')); + return $fakeQuote; + } + + /** + * @param $fakeQuote + * + * @return PHPUnit_Framework_MockObject_MockObject + */ + protected function getQuoteHelperMock($fakeQuote) + { + $quoteHelperMock = $this->getHelperMock('ops/quote', array('getQuote')); + $quoteHelperMock->expects($this->once()) + ->method('getQuote') + ->will($this->returnValue($fakeQuote)); + return $quoteHelperMock; + } + + public function testCaptureDirectSaleDirectDebit() + { + $directDebitMock = $this->getModelMock('ops/payment_directDebit', array('getConfigPaymentAction', 'confirmPayment')); + $directDebitMock->expects($this->once()) + ->method('getConfigPaymentAction') + ->will($this->returnValue(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE)); + $fakeOrder = $this->getFakeOrder(); + $payment = $this->getFakePayment($fakeOrder); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('isInlinePayment')); + $paymentHelperMock->expects($this->once()) + ->method('isInlinePayment') + ->with($payment) + ->will($this->returnValue(true)); + + + + $fakeQuote = $this->getFakeQuote(); + $quoteHelperMock = $this->getQuoteHelperMock($fakeQuote); + + $directDebitMock->expects($this->once()) + ->method('confirmPayment') + ->with($fakeOrder, $fakeQuote, $payment); + $directDebitMock->setPaymentHelper($paymentHelperMock); + $directDebitMock->setQuoteHelper($quoteHelperMock); + $directDebitMock->capture($payment, 100); + } + + public function testCaptureDirectSaleDirectDebitInvoice() + { + $directDebitMock = $this->getModelMock('ops/payment_directDebit', array('getConfigPaymentAction', 'confirmPayment')); + $directDebitMock->expects($this->once()) + ->method('getConfigPaymentAction') + ->will($this->returnValue(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE)); + $fakeOrder = $this->getFakeOrder(); + $payment = $this->getFakePayment($fakeOrder); + $payment->setAdditionalInformation('paymentId', 4711); + + $directDebitMock->expects($this->never()) + ->method('confirmPayment'); + $directDebitMock->capture($payment, 0); + } + + public function testCaptureDirectSaleCreditCard() + { + $directDebitMock = $this->getModelMock('ops/payment_cc', array('getConfigPaymentAction', 'confirmPayment')); + $directDebitMock->expects($this->once()) + ->method('getConfigPaymentAction') + ->will($this->returnValue(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE)); + $fakeOrder = $this->getFakeOrder(); + $payment = $this->getFakePayment($fakeOrder); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('isInlinePayment')); + $paymentHelperMock->expects($this->once()) + ->method('isInlinePayment') + ->with($payment) + ->will($this->returnValue(true)); + + $fakeQuote = $this->getFakeQuote(); + $quoteHelperMock = $this->getQuoteHelperMock($fakeQuote); + + $directDebitMock->expects($this->once()) + ->method('confirmPayment') + ->with($fakeOrder, $fakeQuote, $payment); + $directDebitMock->setPaymentHelper($paymentHelperMock); + $directDebitMock->setQuoteHelper($quoteHelperMock); + $directDebitMock->capture($payment, 100); + } + + public function testCaptureDirectSaleCreditCardRedirect() + { + $directDebitMock = $this->getModelMock('ops/payment_cc', array('getConfigPaymentAction', 'confirmPayment')); + $directDebitMock->expects($this->once()) + ->method('getConfigPaymentAction') + ->will($this->returnValue(Mage_Payment_Model_Method_Abstract::ACTION_AUTHORIZE_CAPTURE)); + $fakeOrder = $this->getFakeOrder(); + $payment = $this->getFakePayment($fakeOrder); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('isInlinePayment')); + $paymentHelperMock->expects($this->once()) + ->method('isInlinePayment') + ->with($payment) + ->will($this->returnValue(false)); + + + $directDebitMock->expects($this->never()) + ->method('confirmPayment'); + $directDebitMock->setPaymentHelper($paymentHelperMock); + $directDebitMock->capture($payment, 100); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/Features/PaymentEmailTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/Features/PaymentEmailTest.php new file mode 100644 index 0000000..af2cb0b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/Features/PaymentEmailTest.php @@ -0,0 +1,120 @@ +testSubject = Mage::getModel('ops/payment_features_paymentEmail'); + } + + public function testIsAvailableForOrder() + { + // given object is no order model -> returns false + $order = new Varien_Object(); + $this->assertFalse($this->testSubject->isAvailableForOrder($order)); + + // given payment has not fitting status -> returns false + $payment = Mage::getModel('sales/order_payment'); + $payment->setAdditionalInformation(array('status' => 9)); + $order = $this->getModelMock('sales/order', array('getPayment')); + $order->expects($this->once()) + ->method('getPayment') + ->will($this->returnValue($payment)); + $this->assertFalse($this->testSubject->isAvailableForOrder($order)); + + // payment has relevant status -> returns true + $payment = Mage::getModel('sales/order_payment'); + $payment->setAdditionalInformation(array('status' => 1)); + $order = $this->getModelMock('sales/order', array('getPayment')); + $order->expects($this->once()) + ->method('getPayment') + ->will($this->returnValue($payment)); + $this->assertTrue($this->testSubject->isAvailableForOrder($order)); + } + + public function testResendPaymentInfo() + { + $mailMock = $this->getMockBuilder('Zend_Mail') + ->setMethods(array('send')) + ->getMock(); + $mailMock + ->expects($this->once()) + ->method('send') + ->willReturnSelf(); + + $templateMock = $this->getModelMock('core/email_template', array('getMail')); + $templateMock + ->expects($this->any()) + ->method('getMail') + ->willReturn($mailMock); + $this->replaceByMock('model', 'core/email_template', $templateMock); + + $payment = $this->getModelMock('sales/order_payment', array('save')); + $payment->expects($this->once()) + ->method('save') + ->will($this->returnValue(null)); + + $payment->setMethod(Netresearch_OPS_Model_Payment_Flex::CODE); + + $order = Mage::getModel('sales/order'); + $order->setData('customer_email', 'a@bc.de') + ->setData('customer_firstname', 'Hans') + ->setData('customer_lastname', 'Wurst') + ->setStoreId(0) + ->setPayment($payment); + + $this->assertTrue($this->testSubject->resendPaymentInfo($order)); + $this->assertNotEquals($order->getPayment()->getMethod(), 'foobar'); + $this->assertEquals($order->getPayment()->getMethod(), Netresearch_OPS_Model_Payment_Flex::CODE); + } + + /** + * @expectedException Exception + * @expectedExceptionMessage Could not send mail due to internal error! + */ + public function testSendSuspendSubscriptionMailWithException() + { + $this->testSubject->sendSuspendSubscriptionMail(null, null); + } + + public function testSendSuspendSubscriptionMail() + { + $mailMock = $this->getMockBuilder('Zend_Mail') + ->setMethods(array('send')) + ->getMock(); + $mailMock + ->expects($this->once()) + ->method('send') + ->willReturnSelf(); + + $templateMock = $this->getModelMock('core/email_template', array('getMail')); + $templateMock + ->expects($this->any()) + ->method('getMail') + ->willReturn($mailMock); + $this->replaceByMock('model', 'core/email_template', $templateMock); + + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setReferenceId('SUB-123') + ->setStoreId(0); + + $customer = Mage::getModel('customer/customer'); + $customer->setEmail('a@bc.de') + ->setFirstName('Hans') + ->setLastName('Wurst'); + + $this->assertTrue($this->testSubject->sendSuspendSubscriptionMail($profile, $customer)); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/Features/ZeroAmountAuthTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/Features/ZeroAmountAuthTest.php new file mode 100644 index 0000000..4ba47e6 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/Features/ZeroAmountAuthTest.php @@ -0,0 +1,58 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Model_Payment_Features_ZeroAmountAuthTest extends EcomDev_PHPUnit_Test_Case +{ + protected $featureModel = null; + + public function setUp() + { + parent::setUp(); + $this->featureModel = Mage::getModel('ops/payment_features_zeroAmountAuth'); + } + + public function testIsCCAndZeroAmountAuthAllowedNoCC() + { + $payment = Mage::getModel('ops/payment_iDeal'); + $quote = Mage::getModel('sales/quote'); + $this->assertFalse($this->featureModel->isCCAndZeroAmountAuthAllowed($payment, $quote)); + } + + + public function testIsCCAndZeroAmountAuthAllowedFalse() + { + $ccModelMock = $this->getCCMock(); + $quote = Mage::getModel('sales/quote'); + $this->assertFalse($this->featureModel->isCCAndZeroAmountAuthAllowed($ccModelMock, $quote)); + } + + + public function testIsCCAndZeroAmountAuthAllowedTrue() + { + $ccModelMock = $this->getCCMock(true); + $quote = $this->getModelMock('sales/quote', array('getItemsCount', 'isNominal')); + $quote->expects($this->once()) + ->method('getItemsCount') + ->will($this->returnValue(1)); + $quote->expects($this->once()) + ->method('isNominal') + ->will($this->returnValue(false)); + $this->assertTrue($this->featureModel->isCCAndZeroAmountAuthAllowed($ccModelMock, $quote)); + } + + protected function getCCMock($returnValue = false) + { + $ccModelMock = $this->getModelMock('ops/payment_cc', array('isZeroAmountAuthorizationAllowed')); + $ccModelMock->expects($this->once()) + ->method('isZeroAmountAuthorizationAllowed') + ->will($this->returnValue($returnValue)); + + return $ccModelMock; + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/FlexTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/FlexTest.php new file mode 100644 index 0000000..bc42fc6 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/FlexTest.php @@ -0,0 +1,77 @@ + + */ +?> + 'Foobar', + Netresearch_OPS_Model_Payment_Flex::INFO_KEY_PM => 'foo', + Netresearch_OPS_Model_Payment_Flex::INFO_KEY_BRAND => 'bar' + ); + } + + public function testGetOpsCode() + { + $payment = Mage::getModel('sales/order_payment'); + $additionalInfo = $this->getAdditionalInfo(); + $payment->setMethod(Netresearch_OPS_Model_Payment_Flex::CODE) + ->setAdditionalInformation( + $additionalInfo + ); + /** @var Netresearch_OPS_Model_Payment_Flex $subject */ + $subject = $payment->getMethodInstance(); + + $this->assertEquals($additionalInfo[Netresearch_OPS_Model_Payment_Flex::INFO_KEY_PM], $subject->getOpsCode()); + } + + public function testGetOpsBrand() + { + $payment = Mage::getModel('sales/order_payment'); + $additionalInfo = $this->getAdditionalInfo(); + $payment->setMethod(Netresearch_OPS_Model_Payment_Flex::CODE) + ->setAdditionalInformation( + $additionalInfo + ); + /** @var Netresearch_OPS_Model_Payment_Flex $subject */ + $subject = $payment->getMethodInstance(); + + $this->assertEquals( + $additionalInfo[Netresearch_OPS_Model_Payment_Flex::INFO_KEY_BRAND], $subject->getOpsBrand() + ); + + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/IDealTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/IDealTest.php new file mode 100644 index 0000000..66e6874 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/IDealTest.php @@ -0,0 +1,145 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Model_Payment_IDealTest extends EcomDev_PHPUnit_Test_Case +{ + + + public function testGetIdealIssuers() + { + $issuers = Mage::getModel('ops/payment_iDeal')->getIDealIssuers(); + $this->assertTrue(is_array($issuers)); + $this->assertTrue(array_key_exists('ABNANL2A', $issuers)); + $this->assertEquals('ABN AMRO', $issuers['ABNANL2A']); + + $this->assertTrue(array_key_exists('RABONL2U', $issuers)); + $this->assertEquals('Rabobank', $issuers['RABONL2U']); + + $this->assertTrue(array_key_exists('INGBNL2A', $issuers)); + $this->assertEquals('ING', $issuers['INGBNL2A']); + + $this->assertTrue(array_key_exists('SNSBNL2A', $issuers)); + $this->assertEquals('SNS Bank', $issuers['SNSBNL2A']); + + $this->assertTrue(array_key_exists('RBRBNL21', $issuers)); + $this->assertEquals('Regio Bank', $issuers['RBRBNL21']); + + $this->assertTrue(array_key_exists('ASNBNL21', $issuers)); + $this->assertEquals('ASN Bank', $issuers['ASNBNL21']); + + $this->assertTrue(array_key_exists('TRIONL2U', $issuers)); + $this->assertEquals('Triodos Bank', $issuers['TRIONL2U']); + + $this->assertTrue(array_key_exists('FVLBNL22', $issuers)); + $this->assertEquals('Van Lanschot Bankiers', $issuers['FVLBNL22']); + + $this->assertTrue(array_key_exists('KNABNL2H', $issuers)); + $this->assertEquals('Knab Bank', $issuers['KNABNL2H']); + + } + + + public function testAssignData() + { + $payment = Mage::getModel('sales/quote_payment')->setMethod('ops_iDeal'); + $quote = Mage::getModel('sales/quote')->setPayment($payment); + + $data = array('iDeal_issuer_id' => 'RBRBNL21'); + $this->assertEquals('iDEAL', $payment->getMethodInstance()->getOpsCode()); + + $method = $payment->getMethodInstance()->assignData($data); + $this->assertInstanceOf('Netresearch_OPS_Model_Payment_IDeal', $method); + + $this->assertEquals('RBRBNL21', $payment->getAdditionalInformation('iDeal_issuer_id')); + } + + public function testAssignDataWithVarienObject() + { + $payment = Mage::getModel('sales/quote_payment'); + $payment->setMethod('ops_iDeal'); + + $quote = Mage::getModel('sales/quote'); + $quote->setPayment($payment); + + $data = new Varien_object(array('iDeal_issuer_id' => 'ABNAMRO')); + $this->assertEquals('iDEAL', $payment->getMethodInstance()->getOpsCode()); + + $method = $payment->getMethodInstance()->assignData($data); + $this->assertInstanceOf('Netresearch_OPS_Model_Payment_IDeal', $method); + + $this->assertEquals('ABNAMRO', $payment->getAdditionalInformation('iDeal_issuer_id')); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testGetMethodDependentFormFieldsWithIssuerId() + { + $order = Mage::getModel('sales/order')->load(25); + $sessionMock = $this->getModelMock( + 'checkout/session', array('getQuote', 'init', 'save') + ); + + $order->getPayment()->setAdditionalInformation('iDeal_issuer_id', 'ideal_123'); + + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($order)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + + + $sessionMock = $this->getModelMock( + 'customer/session', array('isLoggedIn', 'init', 'save') + ); + $sessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $sessionMock); + + $formFields = Mage::getModel('ops/payment_iDeal') + ->getMethodDependendFormFields($order, array()); + + $this->assertTrue(array_key_exists('ISSUERID', $formFields)); + $this->assertEquals('ideal_123', $formFields['ISSUERID']); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testGetMethodDependentFormFieldsWithoutIssuerId() + { + $order = Mage::getModel('sales/order')->load(25); + $sessionMock = $this->getModelMock( + 'checkout/session', array('getQuote', 'init', 'save') + ); + + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($order)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + + + $sessionMock = $this->getModelMock( + 'customer/session', array('isLoggedIn', 'init', 'save') + ); + $sessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $sessionMock); + + $formFields = Mage::getModel('ops/payment_iDeal') + ->getMethodDependendFormFields($order); + + $this->assertFalse(array_key_exists('ISSUERID', $formFields)); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/IDealTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Model/Payment/IDealTest/fixtures/orders.yaml new file mode 100644 index 0000000..9e96867 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/IDealTest/fixtures/orders.yaml @@ -0,0 +1,58 @@ +tables: + sales/order: + - entity_id: 25 + increment_id: 100000025 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 10 + + + + sales/quote: + - entity_id: 25 + parent_id: 25 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + + + sales/quote_payment: + - payment_id: 25 + quote_id: 25 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + + + sales/order_payment: + - entity_id: 25 + parent_id: 25 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + + + sales/order_address: + - entity_id: 42 + parent_id: 25 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/InterSolveTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/InterSolveTest.php new file mode 100644 index 0000000..07e958b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/InterSolveTest.php @@ -0,0 +1,78 @@ + + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Test_Model_Payment_InterSolveTest + * @author Thomas Birke + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Model_Payment_InterSolveTest extends EcomDev_PHPUnit_Test_Case_Config +{ + public function testClassExists() + { + $this->assertModelAlias('ops/payment_interSolve', 'Netresearch_OPS_Model_Payment_InterSolve'); + $this->assertTrue(Mage::getModel('ops/payment_interSolve') instanceof Netresearch_OPS_Model_Payment_InterSolve); + $this->assertTrue(Mage::getModel('ops/payment_interSolve') instanceof Netresearch_OPS_Model_Payment_Abstract); + } + + public function testMethodConfig() + { + $this->assertConfigNodeValue('default/payment/ops_interSolve/model', 'ops/payment_interSolve'); + } + + public function testPm() + { + $payment = Mage::getModel('payment/info'); + $this->assertEquals('InterSolve', Mage::getModel('ops/payment_interSolve')->getOpsCode($payment)); + } + + public function testBrand() + { + $payment = Mage::getModel('sales/quote_payment'); + $payment->setAdditionalInformation('BRAND', 'InterSolve'); + $this->assertEquals('InterSolve', Mage::getModel('ops/payment_interSolve')->getOpsBrand($payment)); + } + + public function testAssignDataWithBrand() + { + $payment = Mage::getModel('sales/quote_payment'); + $payment->setMethod('ops_interSolve'); + + $quote = Mage::getModel('sales/quote'); + $quote->setPayment($payment); + + $data = array('intersolve_brand' => 'FooBar'); + $this->assertEquals('InterSolve', $payment->getMethodInstance()->getOpsCode()); + + $method = $payment->getMethodInstance()->assignData($data); + $this->assertInstanceOf('Netresearch_OPS_Model_Payment_InterSolve', $method); + + $this->assertEquals('FooBar', $payment->getAdditionalInformation('BRAND')); + } + + public function testAssignDataWithoutBrand() + { + $payment = Mage::getModel('sales/quote_payment'); + $payment->setMethod('ops_interSolve'); + + $quote = Mage::getModel('sales/quote'); + $quote->setPayment($payment); + + $this->assertEquals('InterSolve', $payment->getMethodInstance()->getOpsCode()); + + /** @var Netresearch_OPS_Model_Payment_InterSolve $method */ + $method = $payment->getMethodInstance(); + $this->assertInstanceOf('Netresearch_OPS_Model_Payment_InterSolve', $method); + + $method->assignData(array()); + $this->assertEquals('InterSolve', $payment->getAdditionalInformation('BRAND')); + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest.php new file mode 100644 index 0000000..5c1482d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest.php @@ -0,0 +1,768 @@ +load(11); + $sessionMock = $this->getModelMock( + 'checkout/session', array('getQuote') + ); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($order)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + + + $sessionMock = $this->getModelMock( + 'customer/session', array('isLoggedIn') + ); + $sessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $sessionMock); + + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getMethodDependendFormFields($order); + + + $this->assertTrue(array_key_exists('CN', $formFields)); + $this->assertTrue(array_key_exists('OWNERZIP', $formFields)); + $this->assertTrue(array_key_exists('OWNERCTY', $formFields)); + $this->assertTrue(array_key_exists('OWNERTOWN', $formFields)); + $this->assertTrue(array_key_exists('COM', $formFields)); + $this->assertTrue(array_key_exists('OWNERTELNO', $formFields)); + $this->assertTrue(array_key_exists('OWNERADDRESS', $formFields)); + $this->assertTrue(array_key_exists('BRAND', $formFields)); + $this->assertTrue(array_key_exists('ADDMATCH', $formFields)); + $this->assertTrue( + array_key_exists('ECOM_BILLTO_POSTAL_POSTALCODE', $formFields) + ); + $this->assertTrue(array_key_exists('CUID', $formFields)); + $this->assertTrue( + array_key_exists('ECOM_ESTIMATEDELIVERYDATE', $formFields) + ); + $this->assertTrue(array_key_exists('RNPOFFERT', $formFields)); + $this->assertTrue(array_key_exists('ECOM_SHIPMETHODTYPE', $formFields)); + $this->assertTrue( + array_key_exists('ECOM_SHIPMETHODSPEED', $formFields) + ); + $this->assertTrue(array_key_exists('ORDERID', $formFields)); + $this->assertEquals(Mage::getModel('ops/config')->getConfigData('devprefix') . $order->getQuoteId(), $formFields['ORDERID']); + } + + /** + * + * @loadFixture ../../../../../var/fixtures/orders.yaml + */ + public function testGetMethodDependendFormFieldsWithShipmentDetails() + { + $order = Mage::getModel('sales/order')->load(11); + $sessionMock = $this->getModelMock( + 'checkout/session', array('getQuote') + ); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($order)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + + + $sessionMock = $this->getModelMock( + 'customer/session', array('isLoggedIn') + ); + $sessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $sessionMock); + $modelMock = $this->getModelMock( + 'ops/payment_kwixo_abstract', array('getShippingMethodDetails') + ); + $modelMock->expects($this->any()) + ->method('getShippingMethodDetails') + ->will($this->returnValue('shipping method details')); + $formFields = $modelMock->getMethodDependendFormFields($order); + $this->assertArrayHasKey('ECOM_SHIPMETHODDETAILS', $formFields); + $this->assertEquals( + 'shipping method details', $formFields['ECOM_SHIPMETHODDETAILS'] + ); + } + + /** + * + * @loadFixture ../../../../../var/fixtures/orders.yaml + */ + public function testGetMethodDependendFormFieldsWithShipmentDetailsFromAddress( + ) + { + $order = Mage::getModel('sales/order')->load(11); + $sessionMock = $this->getModelMock( + 'checkout/session', array('getQuote') + ); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($order)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + + + $sessionMock = $this->getModelMock( + 'customer/session', array('isLoggedIn') + ); + $sessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $sessionMock); + $modelMock = $this->getModelMock( + 'ops/payment_kwixo_abstract', + array('getShippingMethodDetails', 'getShippingMethodType') + ); + $modelMock->expects($this->any()) + ->method('getShippingMethodDetails') + ->will($this->returnValue('')); + $modelMock->expects($this->any()) + ->method('getShippingMethodType') + ->will($this->returnValue(4)); + $formFields = $modelMock->getMethodDependendFormFields($order); + $this->assertArrayHasKey('ECOM_SHIPMETHODDETAILS', $formFields); + } + + /** + * @test + * @loadFixture ../../../../../var/fixtures/orders.yaml + */ + public function testGetMethodDependendFormFieldsCheckItemProductCateg() + { + $order = Mage::getModel('sales/order')->load(11); + $sessionMock = $this->getModelMock( + 'checkout/session', array('getQuote') + ); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($order)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + $sessionMock = $this->getModelMock( + 'customer/session', array('isLoggedIn') + ); + $sessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $sessionMock); + $itemProductCategories = array( + array( + 'value' => 1, + 'label' => 'Food & gastronomy' + ), + array( + 'value' => 2, + 'label' => 'Car & Motorbike' + ) + ); + $kwixoAbstractModelMock = $this->getModelMock( + 'ops/payment_kwixo_abstract', array( + 'getItemFmdProductCateg' + ) + ); + $kwixoAbstractModelMock->expects($this->any()) + ->method('getItemFmdProductCateg') + ->will($this->returnValue($itemProductCategories)); + $this->replaceByMock( + 'model', 'ops/payment_kwixo_abstract', $kwixoAbstractModelMock + ); + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getMethodDependendFormFields($order); + $this->assertTrue( + array_key_exists('ITEMFDMPRODUCTCATEG1', $formFields) + ); + $this->assertTrue( + array_key_exists('ITEMFDMPRODUCTCATEG2', $formFields) + ); + } + + /** + * @test + * @loadFixture ../../../../../var/fixtures/orders.yaml + */ + public function testGetKwixoShipToParams() + { + $order = Mage::getModel('sales/order')->load(11); + $sessionMock = $this->getModelMock( + 'checkout/session', array('getQuote') + ); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($order)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + $sessionMock = $this->getModelMock( + 'customer/session', array('isLoggedIn') + ); + $sessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $sessionMock); + $shippingMethodTypeValues = array(1, 2, 3, 4); + $kwixoAbstractModelMock = $this->getModelMock( + 'ops/payment_kwixo_abstract', array( + 'getShippingMethodTypeValues', + 'getShippingMethodType', + ) + ); + $kwixoAbstractModelMock->expects($this->any()) + ->method('getShippingMethodTypeValues') + ->will($this->returnValue($shippingMethodTypeValues)); + + $kwixoAbstractModelMock->expects($this->any()) + ->method('getShippingMethodType') + ->will($this->returnValue(4)); + + + $this->replaceByMock( + 'model', 'ops/payment_kwixo_abstract', $kwixoAbstractModelMock + ); + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getKwixoShipToParams($order); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_NAME_FIRST', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_NAME_LAST', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_NAME_PREFIX', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_TELECOM_PHONE_NUMBER', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_STREET_LINE1', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_STREET_NUMBER', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_POSTALCODE', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_CITY', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_COUNTRYCODE', $formFields) + ); + + + $order->getShippingAddress()->setStreet( + array('An der Tabaksmühle 3a', 'Etage 4') + ); + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getKwixoShipToParams($order); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_STREET_LINE2', $formFields) + ); + $this->assertEquals( + 'Etage 4', $formFields['ECOM_SHIPTO_POSTAL_STREET_LINE2'] + ); + + $order->getShippingAddress()->setCompany('My great company'); + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getKwixoShipToParams($order); + $this->assertTrue(array_key_exists('ECOM_SHIPTO_COMPANY', $formFields)); + $this->assertEquals( + 'My great company', $formFields['ECOM_SHIPTO_COMPANY'] + ); + + $order->getShippingAddress()->setFax('4711'); + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getKwixoShipToParams($order); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_TELECOM_FAX_NUMBER', $formFields) + ); + $this->assertEquals( + '4711', $formFields['ECOM_SHIPTO_TELECOM_FAX_NUMBER'] + ); + + + $order->getShippingAddress()->setAddressType('shipping2'); + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getKwixoShipToParams($order); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_NAME_FIRST', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_NAME_LAST', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_NAME_PREFIX', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_TELECOM_PHONE_NUMBER', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_STREET_LINE1', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_STREET_NUMBER', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_POSTALCODE', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_CITY', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_SHIPTO_POSTAL_COUNTRYCODE', $formFields) + ); + + } + + /** + * @test + * @loadFixture ../../../../../var/fixtures/orders.yaml + */ + public function testGetKwixoBillToParams() + { + $order = Mage::getModel('sales/order')->load(11); + $sessionMock = $this->getModelMock( + 'checkout/session', array('getQuote') + ); + $sessionMock->expects($this->any()) + ->method('getQuote') + ->will($this->returnValue($order)); + $this->replaceByMock('model', 'checkout/session', $sessionMock); + $sessionMock = $this->getModelMock( + 'customer/session', array('isLoggedIn') + ); + $sessionMock->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(1)); + $this->replaceByMock('model', 'customer/session', $sessionMock); + + $addressData = array( + 'housenumber' => 44, + 'street' => 'teststreet' + ); + + + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getKwixoBillToParams($order); + + $this->assertTrue( + array_key_exists('ECOM_BILLTO_POSTAL_NAME_FIRST', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_BILLTO_POSTAL_NAME_LAST', $formFields) + ); + $this->assertTrue( + array_key_exists('ECOM_BILLTO_POSTAL_STREET_NUMBER', $formFields) + ); + $order->getBillingAddress()->setStreet( + array('An der Tabaksmühle 3a', 'Etage 4') + ); + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getKwixoBillToParams($order); + $this->assertTrue(array_key_exists('OWNERADDRESS2', $formFields)); + $this->assertEquals('Etage 4', $formFields['OWNERADDRESS2']); + } + + public function testGetRnpFee() + { + + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig('payment/ops_kwixoCredit/rnp_fee', 1); + $this->assertEquals( + 1, Mage::getModel('ops/payment_kwixo_abstract')->getRnpFee( + 'ops_kwixoCredit', 0 + ) + ); + $store->setConfig('payment/ops_kwixoCredit/rnp_fee', 0); + $this->assertEquals( + 0, Mage::getModel('ops/payment_kwixo_abstract')->getRnpFee( + 'ops_kwixoCredit', 0 + ) + ); + $store->resetConfig(); + $store->setConfig('payment/ops_kwixoApresReception/rnp_fee', 1); + $this->assertEquals( + 1, Mage::getModel('ops/payment_kwixo_abstract')->getRnpFee( + 'ops_kwixoApresReception', 0 + ) + ); + $store->setConfig('payment/ops_kwixoApresReception/rnp_fee', 0); + $this->assertEquals( + 0, Mage::getModel('ops/payment_kwixo_abstract')->getRnpFee( + 'ops_kwixoApresReception', 0 + ) + ); + $store->resetConfig(); + $store->setConfig('payment/ops_kwixoComptant/rnp_fee', 1); + $this->assertEquals( + 1, Mage::getModel('ops/payment_kwixo_abstract')->getRnpFee( + 'ops_kwixoComptant', 0 + ) + ); + $store->setConfig('payment/ops_kwixoComptant/rnp_fee', 0); + $this->assertEquals( + 0, Mage::getModel('ops/payment_kwixo_abstract')->getRnpFee( + 'ops_kwixoComptant', 0 + ) + ); + } + + public function testGetShippingMethodType() + { + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig( + 'payment/ops_kwixoCredit/ecom_shipMethodType', 'Test' + ); + $this->assertEquals( + 'Test', + Mage::getModel('ops/payment_kwixo_abstract')->getShippingMethodType( + 'ops_kwixoCredit', 0 + ) + ); + + $store->resetConfig(); + $store->setConfig( + 'payment/ops_kwixoApresReception/ecom_shipMethodType', 'Test1' + ); + $this->assertEquals( + 'Test1', + Mage::getModel('ops/payment_kwixo_abstract')->getShippingMethodType( + 'ops_kwixoApresReception', 0 + ) + ); + + $store->resetConfig(); + $store->setConfig( + 'payment/ops_kwixoComptant/ecom_shipMethodType', 'Test2' + ); + $this->assertEquals( + 'Test2', + Mage::getModel('ops/payment_kwixo_abstract')->getShippingMethodType( + 'ops_kwixoComptant', 0 + ) + ); + + $this->assertEquals( + Netresearch_OPS_Model_Source_Kwixo_ShipMethodType::DOWNLOAD, + Mage::getModel('ops/payment_kwixo_abstract')->getShippingMethodType( + 'ops_kwixoComptant', 0, true + ) + ); + + $kwixoShippingMock = $this->getModelMock( + 'ops/kwixo_shipping_setting', array('getKwixoShippingType') + ); + $kwixoShippingMock->expects($this->any()) + ->method('getKwixoShippingType') + ->will($this->returnValue(123)); + $model = Mage::getModel('ops/payment_kwixo_abstract'); + $model->setKwixoShippingModel($kwixoShippingMock); + $this->assertEquals( + 123, $model->getShippingMethodType( + 'ops_kwixoComptant' + ) + ); + } + + public function testGetShippingMethodSpeed() + { + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig('payment/ops_kwixoCredit/ecom_shipMethodSpeed', 25); + $this->assertEquals( + 25, Mage::getModel('ops/payment_kwixo_abstract') + ->getShippingMethodSpeed('ops_kwixoCredit', 0) + ); + + $store->resetConfig(); + $store->setConfig( + 'payment/ops_kwixoApresReception/ecom_shipMethodSpeed', 28 + ); + $this->assertEquals( + 28, Mage::getModel('ops/payment_kwixo_abstract') + ->getShippingMethodSpeed('ops_kwixoApresReception', 0) + ); + + $store->resetConfig(); + $store->setConfig('payment/ops_kwixoComptant/ecom_shipMethodSpeed', 32); + $this->assertEquals( + 32, Mage::getModel('ops/payment_kwixo_abstract') + ->getShippingMethodSpeed('ops_kwixoComptant', 0) + ); + + $kwixoShippingMock = $this->getModelMock( + 'ops/kwixo_shipping_setting', array('getKwixoShippingMethodSpeed') + ); + $kwixoShippingMock->expects($this->any()) + ->method('getKwixoShippingMethodSpeed') + ->will($this->returnValue(123)); + $model = Mage::getModel('ops/payment_kwixo_abstract'); + $model->setKwixoShippingModel($kwixoShippingMock); + $this->assertEquals( + 123, $model->getShippingMethodSpeed( + 'ops_kwixoComptant' + ) + ); + } + + public function testGetItemFmdProductCateg() + { + $itemProductCategories = array( + array( + 'value' => 1, + 'label' => 'Food & gastronomy' + ), + array( + 'value' => 2, + 'label' => 'Car & Motorbike' + ) + ); + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig('payment/ops_kwixoCredit/product_categories', 'Cat1'); + $this->assertTrue( + in_array( + 'Cat1', Mage::getModel('ops/payment_kwixo_abstract') + ->getItemFmdProductCateg('ops_kwixoCredit', 0) + ) + ); + + $store->resetConfig(); + $store->setConfig( + 'payment/ops_kwixoApresReception/product_categories', 'Cat2' + ); + $this->assertTrue( + in_array( + 'Cat2', Mage::getModel('ops/payment_kwixo_abstract') + ->getItemFmdProductCateg('ops_kwixoApresReception', 0) + ) + ); + + $store->resetConfig(); + $store->setConfig( + 'payment/ops_kwixoComptant/product_categories', 'Cat3' + ); + $this->assertTrue( + in_array( + 'Cat3', Mage::getModel('ops/payment_kwixo_abstract') + ->getItemFmdProductCateg('ops_kwixoComptant', 0) + ) + ); + } + + public function testGetShippingMethodDetails() + { + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig( + 'payment/ops_kwixoCredit/shiping_method_details', 'Cat1' + ); + $this->assertEquals( + 'Cat1', Mage::getModel('ops/payment_kwixo_abstract') + ->getShippingMethodDetails('ops_kwixoCredit', 0) + ); + + $store->resetConfig(); + $store->setConfig( + 'payment/ops_kwixoApresReception/shiping_method_details', 'Cat2' + ); + $this->assertEquals( + 'Cat2', Mage::getModel('ops/payment_kwixo_abstract') + ->getShippingMethodDetails('ops_kwixoApresReception', 0) + ); + + $store->resetConfig(); + $store->setConfig( + 'payment/ops_kwixoComptant/shiping_method_details', 'Cat3' + ); + $this->assertEquals( + 'Cat3', Mage::getModel('ops/payment_kwixo_abstract') + ->getShippingMethodDetails('ops_kwixoComptant', 0) + ); + + $kwixoShippingMock = $this->getModelMock( + 'ops/kwixo_shipping_setting', array('getKwixoShippingDetails') + ); + $kwixoShippingMock->expects($this->any()) + ->method('getKwixoShippingDetails') + ->will($this->returnValue('shipping details')); + $model = Mage::getModel('ops/payment_kwixo_abstract'); + $model->setKwixoShippingModel($kwixoShippingMock); + $this->assertEquals( + 'shipping details', $model->getShippingMethodDetails( + 'ops_kwixoComptant' + ) + ); + } + + public function testGetQuestion() + { + $order = new Varien_Object(); + $params = array(); + $this->assertEquals( + 'Please make sure that the displayed data is correct.', + Mage::getModel('ops/payment_kwixo_abstract')->getQuestion()); + } + + public function testGetQuestionedFormFields() + { + $order = new Varien_Object(); + $params = array(); + $fields = Mage::getModel('ops/payment_kwixo_abstract') + ->getQuestionedFormFields($order); + $this->assertTrue(in_array('OWNERADDRESS', $fields)); + $this->assertTrue( + in_array('ECOM_BILLTO_POSTAL_STREET_NUMBER', $fields) + ); + } + + public function testGetQuestionedFormFieldsForAddrFields() + { + $order = $this->getModelMock('sales/order'); + $params = array(); + $modelMock = $this->getModelMock( + 'ops/payment_kwixo_abstract', + array('getShippingMethodTypeValues', 'getShippingMethodType') + ); + $modelMock->expects($this->any()) + ->method('getShippingMethodTypeValues') + ->will($this->returnValue(array(4))); + $modelMock->expects($this->any()) + ->method('getShippingMethodType') + ->will($this->returnValue(4)); + + $fields = $modelMock->getQuestionedFormFields($order, $params); + $this->assertTrue(in_array('OWNERADDRESS', $fields)); + $this->assertTrue( + in_array('ECOM_BILLTO_POSTAL_STREET_NUMBER', $fields) + ); + $this->assertTrue( + in_array('ECOM_SHIPTO_POSTAL_STREET_NUMBER', $fields) + ); + $this->assertTrue( + in_array('ECOM_SHIPTO_TELECOM_PHONE_NUMBER', $fields) + ); + } + + + public function testPopulateFromArray() + { + $kwixoMock = $this->getModelMock( + 'ops/payment_kwixo_abstract', array('getQuestionedFormFields') + ); + $kwixoMock->expects($this->any()) + ->method('getQuestionedFormFields') + ->will( + $this->returnValue( + array('OWNERADDRESS', 'ECOM_BILLTO_POSTAL_STREET_NUMBER') + ) + ); + $reflectionClass = new ReflectionClass(get_class($kwixoMock)); + $method = $reflectionClass->getMethod("populateFromArray"); + $method->setAccessible(true); + $formFields = array( + 'OWNERADDRESS' => 'bla', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER' => 'blub' + ); + $order = Mage::getModel('sales/order'); + $this->assertEquals( + $formFields, $method->invoke($kwixoMock, $formFields, null, $order) + ); + $this->assertEquals( + $formFields, + $method->invoke($kwixoMock, $formFields, array(), $order) + ); + // $this->assertEquals($formFields, $method->invoke($kwixoMock, $formFields , array('wusel', 'dusel'))); + + $questionedFields = array( + 'OWNERADDRESS' => 'blub', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER' => 'bla' + ); + $order = Mage::getModel('sales/order'); + $this->assertEquals( + $questionedFields, + $method->invoke($kwixoMock, $formFields, $questionedFields, $order) + ); + } + + /** + * @test + * @loadFixture ../../../../../var/fixtures/orders.yaml + */ + public function testGetItemParams() + { + $fakeProduct = Mage::getModel('catalog/product'); + $fakeProduct->setCategoryIds(array(1, 2)); + $productMock = $this->getModelMock('catalog/product', array('load')); + $productMock->expects($this->any()) + ->method('load') + ->will($this->returnValue($fakeProduct)); + $this->replaceByMock('model', 'catalog/product', $productMock); + $fakeKwixoCategory = new Varien_Object(); + $fakeKwixoCategory->setId(1); + $fakeKwixoCategory->setKwixoCategoryId(123); + $kwixoMapping = $this->getModelMock( + 'ops/kwixo_category_mapping', array('loadByCategoryId') + ); + $kwixoMapping->expects($this->any()) + ->method('loadByCategoryId') + ->will($this->returnValue($fakeKwixoCategory)); + $this->replaceByMock( + 'model', 'ops/kwixo_category_mapping', $kwixoMapping + ); + $order = Mage::getModel('sales/order')->load(11); + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getItemParams($order); + $orderItems = $order->getAllItems(); + $i = 1; + + + foreach ($orderItems as $orderItem) { + $this->assertTrue(array_key_exists('ITEMID' . $i, $formFields)); + $this->assertEquals( + $orderItem->getId(), $formFields['ITEMID' . $i] + ); + + $this->assertTrue(array_key_exists('ITEMNAME' . $i, $formFields)); + $this->assertEquals( + $orderItem->getName(), $formFields['ITEMNAME' . $i] + ); + + $this->assertTrue(array_key_exists('ITEMPRICE' . $i, $formFields)); + $this->assertEquals( + $orderItem->getBasePrice(), $formFields['ITEMPRICE' . $i] + ); + + $this->assertTrue(array_key_exists('ITEMQUANT' . $i, $formFields)); + $this->assertEquals( + $orderItem->getQtyOrdered(), $formFields['ITEMQUANT' . $i] + ); + + $this->assertTrue(array_key_exists('ITEMVAT' . $i, $formFields)); + $this->assertEquals( + $orderItem->getBaseTaxAmount(), $formFields['ITEMVAT' . $i] + ); + + $i++; + } + + + $fakeItem = new Varien_Object(); + $fakeItem->setParentItemId(1); + $fakeOrder = $this->getModelMock('sales/order', array('getAllItems')); + $fakeOrder->expects($this->any()) + ->method('getAllItems') + ->will($this->returnValue(array($fakeItem))); + $formFields = Mage::getModel('ops/payment_kwixo_abstract') + ->getItemParams($fakeOrder); + $this->assertArrayNotHasKey('ITEMID0', $formFields); + + + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/expectations/paymentMethods.yaml b/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/expectations/paymentMethods.yaml new file mode 100644 index 0000000..e35461a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/expectations/paymentMethods.yaml @@ -0,0 +1,279 @@ +ops_3XCB: + pm: 3XCB + brand: 3XCB +ops_CreditCard: + pm: CreditCard +  brand: AIRPLUS +ops_CreditCard: + pm: CreditCard + brand: American Express +ops_CreditCard: + pm: CreditCard +  brand: Aurore +ops_CreditCard: + pm: CreditCard +  brand: CB +ops_CreditCard: + pm: CreditCard +  brand: Cofinoga +ops_CreditCard: + pm: CreditCard +  brand: Dankort +ops_CreditCard: + pm: CreditCard +  brand: Diners +ops_CreditCard: + pm: CreditCard +  brand: JCB +ops_Creditcard: + pm: Creditcard +  brand: Laser +ops_CreditCard: + pm: CreditCard +  brand: MaestroUK +ops_CreditCard: + pm: CreditCard +  brand: MasterCard +ops_CreditCard: + pm: CreditCard +  brand: UATP +ops_CreditCard: + pm: CreditCard +  brand: VISA +ops_BCMC: + pm: CreditCard +  brand: BCMC +ops_CreditCard: + pm: CreditCard + brand: Maestro +ops_PostFinance: + pm: PostFinance Card + brand: PostFinance Card  +ops_CreditCard: + pm: CreditCard + brand: PRIVILEGE +ops_UNEUROCOM: + pm: UNEUROCOM + brand: UNEUROCOM +ops_AmazonCheckout: + pm: Amazon Checkout + brand: Amazon Checkout +ops_Belfius: + pm: Belfius Direct Net + brand: Belfius Direct Net +ops_cashticket: + pm: cashticket + brand: cashticket +ops_cbcOnline: + pm: CBC Online + brand: CBC Online +ops_directEbanking: + pm: DirectEbanking + brand: Sofort Uberweisung +ops_directEbankingAT: + pm: DirectEbankingAT + brand: DirectEbankingAT +ops_directEbankingBE: + pm: DirectEbankingBE + brand: DirectEbankingBE  +ops_directEbankingCH: + pm: DirectEbankingCH + brand: DirectEbankingCH  +ops_directEbankingDE: + pm: DirectEbankingDE + brand: DirectEbankingDE  +ops_directEbankingFR: + pm: DirectEbankingFR + brand: DirectEbankingFR  +ops_directEbankingGB: + pm: DirectEbankingGB + brand: DirectEbankingGB  +ops_directEbankingIT: + pm: DirectEbankingIT + brand: DirectEbankingIT  +ops_directEbankingNL: + pm: DirectEbankingNL + brand: DirectEbankingNL  +ops_EBS_AXIS: + pm: EBS_AXIS + brand: EBS_AXIS +ops_EBS_BC: + pm: EBS_BC + brand: EBS_BC +ops_EBS_CB: + pm: EBS_CB + brand: EBS_CB +ops_EBS_CORP: + pm: EBS_CORP + brand: EBS_CORP +ops_EBS_DC: + pm: EBS_DC + brand: EBS_DC +ops_EBS_FED: + pm: EBS_FED + brand: EBS_FED +ops_EBS_HDFC: + pm: EBS_HDFC + brand: EBS_HDFC +ops_EBS_HYDERABAD: + pm: EBS_HYDERABAD + brand: EBS_HYDERABAD +ops_EBS_IB: + pm: EBS_IB + brand: EBS_IB +ops_EBS_ICASH: + pm: EBS_ICASH + brand: EBS_ICASH +ops_EBS_ICICI: + pm: EBS_ICICI + brand: EBS_ICICI +ops_EBS_INDIA: + pm: EBS_INDIA + brand: EBS_INDIA +ops_EBS_ITZ: + pm: EBS_ITZ + brand: EBS_ITZ +ops_EBS_JK: + pm: EBS_JK + brand: EBS_JK +ops_EBS_KARNATAKA: + pm: EBS_KARNATAKA + brand: EBS_KARNATAKA +ops_EBS_KOTAK: + pm: EBS_KOTAK + brand: EBS_KOTAK +ops_EBS_MYSORE: + pm: EBS_MYSORE + brand: EBS_MYSORE +ops_EBS_TRAVANCORE: + pm: EBS_TRAVANCORE + brand: EBS_TRAVANCORE +ops_eDankort: + pm: eDankort + brand: eDankort +ops_eps: + pm: EPS + brand: EPS +ops_FidorPay: + pm: FidorPay + brand: FidorPay +ops_fortisPayButton: + pm: Fortis Pay Button + brand: Fortis Pay Button +ops_giroPay: + pm: giropay + brand: giropay +ops_Masterpass: + pm: Masterpass + brand: Masterpass +ops_iDeal: + pm: iDEAL + brand: iDEAL +ops_ingHomePay: + pm: ING HomePay + brand: ING HomePay +ops_kbcOnline: + pm: KBC Online + brand: KBC Online +ops_paysafecard: + pm: paysafecard + brand: paysafecard +ops_postFinanceEFinance: + pm: PostFinance e-finance + brand: PostFinance e-finance +ops_directDebitsAt: + pm: Direct Debits AT + brand: Direct Debits AT +ops_directDebitsDe: + pm: Direct Debits DE + brand: Direct Debits DE +ops_directDebitsNl: + pm: Direct Debits NL + brand: Direct Debits NL +ops_bankTransfer: + pm: "Bank transfer" + brand: "Bank transfer" +ops_bankTransferBe: + pm: Bank transfer BE + brand: Bank transfer BE +ops_bankTransferDe: + pm: Bank transfer DE + brand: Bank transfer DE +ops_bankTransferFr: + pm: Bank transfer FR + brand: Bank transfer FR +ops_bankTransferNl: + pm: Bank transfer NL + brand: Bank transfer NL +ops_installmentsDe: + pm: Installments DE + brand: Installments DE +ops_installmentsDk: + pm: Installments DK + brand: Installments DK +ops_installmentsFi: + pm: Installments FI + brand: Installments FI +ops_installmentsNl: + pm: Installments NL + brand: Installments NL +ops_installmentsNo: + pm: Installments NO + brand: Installments NO +ops_installmentsSe: + pm: Installments SE + brand: Installments SE +ops_openInvoiceDe: + pm: Open Invoice DE + brand: Open Invoice DE +ops_openInvoiceDk: + pm: Open Invoice DK + brand: Open Invoice DK +ops_openInvoiceFi: + pm: Open Invoice FI + brand: Open Invoice FI +ops_openInvoiceNl: + pm: Open Invoice NL + brand: Open Invoice NL +ops_openInvoiceAT: + pm: Open Invoice AT + brand: Open Invoice AT +ops_openInvoiceNo: + pm: Open Invoice NO + brand: Open Invoice NO +ops_openInvoiceSe: + pm: Open Invoice SE + brand: Open Invoice SE +ops_cod: + pm: Payment on Delivery + brand: Payment on Delivery +ops_interSolve: + pm: InterSolve + brand: InterSolve +ops_pingPing: + pm: PingPing + brand: PingPing +ops_tunz: + pm: TUNZ + brand: TUNZ +ops_cashEX: + pm: cashEX + brand: cashEX +ops_cashU: + pm: cashU + brand: cashU +ops_cashUDirect: + pm: cashU Direct + brand: cashU Direct +ops_Payble: + pm: Payble + brand: Payble +ops_paypal: + pm: PAYPAL + brand: PAYPAL +ops_wallie: + pm: Wallie + brand: Wallie +ops_belfiusDirectNet: + pm: Belfius Direct Net + brand: Belfius Direct Net diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/fixtures/.gitkeep b/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/fixtures/orders.yaml new file mode 100644 index 0000000..2f9a07f --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/Kwixo/AbstractTest/fixtures/orders.yaml @@ -0,0 +1,633 @@ +config: + default/payment_services/ops/submitExtraParameters: 1 + +tables: + sales/order: + - entity_id: 11 + increment_id: 100000011 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 23 + store_id: 0 + + - entity_id: 12 + increment_id: 100000012 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 24 + + - entity_id: 13 + increment_id: 100000013 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 177.55 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'processing' + status: 'processing' + quote_id: 25 + store_id: 0 + + - entity_id: 14 + increment_id: 100000014 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 177.55 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 26 + + - entity_id: 15 + increment_id: 100000015 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 200.00 + currency: '$' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 27 + + - entity_id: 16 + increment_id: 100000016 + shipping_address_id: 43 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 28 + + - entity_id: 17 + increment_id: 100000017 + shipping_address_id: 43 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 29 + + - entity_id: 18 + increment_id: 100000018 + shipping_address_id: 43 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 30 + + - entity_id: 19 + increment_id: 100000019 + shipping_address_id: 50 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 43 + + - entity_id: 20 + increment_id: 100000020 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 42# + + - entity_id: 21 + increment_id: 100000021 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 22 + increment_id: 100000022 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 23 + increment_id: 100000023 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 24 + increment_id: 100000024 + store_id: 1 + shipping_address_id: 46 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 24 + increment_id: 100000024 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 25 + increment_id: 100000025 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 26 + increment_id: 100000026 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 27 + increment_id: 100000027 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 28 + increment_id: 100000028 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 29 + increment_id: 100000029 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 30 + increment_id: 100000030 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + sales/quote: + - entity_id: 1 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + - entity_id: 2 + base_grand_total: 119.00 + grand_total: 200.00 + currency: '$' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + - entity_id: 10 + store_id: 0 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + store_id: 0 + + - entity_id: 11 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + - entity_id: 23 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + - entity_id: 12 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + sales/quote_payment: + - payment_id: 1 + quote_id: 10 + updated_at: '1970-01-01' + method: 'ops_cc' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 2 + quote_id: 11 + updated_at: '1970-01-01' + method: 'ops_cc' + quote_id: 11 + + - payment_id: 3 + quote_id: 10 + updated_at: '1970-01-01' + method: 'ops_cc' + additional_information: a:7:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:3:"cvc";s:3:"123";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 4 + quote_id: 12 + updated_at: '1970-01-01' + method: 'ops_paypal' + additional_information: a:7:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:3:"cvc";s:3:"123";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + sales/order_payment: + - entity_id: 1 + parent_id: 11 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_bankTransfer' + + - entity_id: 2 + parent_id: 12 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:6:"status";s:1:"5";} + + - entity_id: 3 + parent_id: 13 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:6:"status";s:1:"0";} + method: 'ops_bankTransfer' + + - entity_id: 4 + parent_id: 14 + additional_information: a:2:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";} + + - entity_id: 5 + parent_id: 15 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFD";} + method: 'ops_cc' + + - entity_id: 6 + parent_id: 16 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFS";} + + - entity_id: 7 + parent_id: 17 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFS";s:9:"canRefund";b:0;} + + - entity_id: 8 + parent_id: 18 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFD";s:9:"canRefund";b:1;} + + - entity_id: 9 + parent_id: 19 + method: 'ops_openInvoiceDe' + + - entity_id: 11 + parent_id: 20 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + + - entity_id: 12 + parent_id: 21 + additional_information: a:4:{s:2:"PM";s:16:"Direct Debits DE";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + method: 'ops_directDebit' + + - entity_id: 13 + parent_id: 22 + additional_information: a:4:{s:2:"PM";s:16:"Direct Debits NL";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + + - entity_id: 14 + parent_id: 23 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + + - entity_id: 42 + parent_id: 20 + + - entity_id: 15 + parent_id: 24 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_cc' + + - entity_id: 16 + parent_id: 25 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_cc' + + - entity_id: 17 + parent_id: 26 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_paypal' + + - entity_id: 18 + parent_id: 27 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_kwixoApresReception' + + - entity_id: 19 + parent_id: 28 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_kwixoComptant' + + - entity_id: 20 + parent_id: 29 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_kwixoCredit' + + - entity_id: 21 + parent_id: 30 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'checkmo' + + sales/shipment: + - entity_id: 1 + increment_id: 100000077 + order_id: 11 + shipping_address_id: 42 + + - entity_id: 2 + increment_id: 100000078 + order_id: 12 + shipping_address_id: 43 + + sales/order_address: + - entity_id: 42 + parent_id: 11 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + - entity_id: 43 + parent_id: 11 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + + - entity_id: 44 + parent_id: 12 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 45 + parent_id: 12 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + + - entity_id: 46 + parent_id: 24 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + + - entity_id: 47 + parent_id: 27 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_shipping: 1 + + - entity_id: 147 + parent_id: 27 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + + - entity_id: 48 + parent_id: 28 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 49 + parent_id: 19 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 50 + parent_id: 19 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 51 + parent_id: 15 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + sales/order_item: + - item_id: 11 + order_id: 11 + qty_ordered: 2.0000 + qty_shipped: 2.0000 + sku: 4711 + name: 'some great test item' + price: 19.99 + tax_amount: 1.99 + + - item_id: 12 + order_id: 11 + qty_ordered: 2.0000 + qty_shipped: 2.0000 + sku: 4711 + name: 'some great test item 2' + price: 19.99 + tax_amount: 1.99 + diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoApresReceptionTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoApresReceptionTest.php new file mode 100644 index 0000000..3fc3426 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoApresReceptionTest.php @@ -0,0 +1,55 @@ +kwixoApresReceptionModel = Mage::getModel('ops/payment_kwixoApresReception'); + $this->kwixoApresReceptionModel->setInfoInstance($payment); + $this->store = Mage::app()->getStore(0)->load(0); + } + + public function testGetOpsCode() + { + $this->assertEquals('KWIXO_RNP', $this->kwixoApresReceptionModel->getOpsCode()); + } + + public function testGetCode() + { + $this->assertEquals('ops_kwixoApresReception', $this->kwixoApresReceptionModel->getCode()); + } + + public function testGetDeliveryDate() + { + $this->setUp(); + $dateNow = date("Y-m-d"); + $path = 'payment/ops_kwixoApresReception/delivery_date'; + $this->store->setConfig($path, "0"); + $this->assertEquals($dateNow, $this->kwixoApresReceptionModel->getEstimatedDeliveryDate('ops_kwixoApresReception')); + $dateNowPlusFiveDays = strtotime($dateNow ."+ 5 days"); + $this->store->setConfig($path, "5"); + $this->assertEquals(date("Y-m-d", $dateNowPlusFiveDays), $this->kwixoApresReceptionModel->getEstimatedDeliveryDate('ops_kwixoApresReception')); + } + + public function testGetFormBlockType() + { + $this->assertEquals('ops/form_kwixo_apresReception', $this->kwixoApresReceptionModel->getFormBlockType()); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoComptantTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoComptantTest.php new file mode 100644 index 0000000..5ae0c2d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoComptantTest.php @@ -0,0 +1,56 @@ +kwixoComptantModel = Mage::getModel('ops/payment_kwixoComptant'); + $this->kwixoComptantModel->setInfoInstance($payment); + $this->store = Mage::app()->getStore(0)->load(0); + } + + public function testGetOpsCode() + { + $this->assertEquals('KWIXO_STANDARD', $this->kwixoComptantModel->getOpsCode()); + } + + public function testGetCode() + { + $this->assertEquals('ops_kwixoComptant', $this->kwixoComptantModel->getCode()); + } + + public function testGetDeliveryDate() + { + $this->setUp(); + $dateNow = date("Y-m-d"); + $path = 'payment/ops_kwixoComptant/delivery_date'; + $this->store->setConfig($path, "0"); + $this->assertEquals($dateNow, $this->kwixoComptantModel->getEstimatedDeliveryDate('ops_kwixoComptant')); + $dateNowPlusFiveDays = strtotime($dateNow ."+ 5 days"); + $this->store->setConfig($path, "5"); + $this->assertEquals(date("Y-m-d", $dateNowPlusFiveDays), $this->kwixoComptantModel->getEstimatedDeliveryDate('ops_kwixoComptant')); + } + + public function testGetFormBlockType() + { + $this->assertEquals('ops/form_kwixo_comptant', $this->kwixoComptantModel->getFormBlockType()); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoCreditTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoCreditTest.php new file mode 100644 index 0000000..c422004 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/KwixoCreditTest.php @@ -0,0 +1,59 @@ +kwixoCreditModel = Mage::getModel('ops/payment_KwixoCredit'); + $this->kwixoCreditModel->setInfoInstance(Mage::getModel('payment/info')); + $this->store = Mage::app()->getStore(0)->load(0); + } + + + public function testGetOpsCode() + { + $this->assertEquals('KWIXO_CREDIT', $this->kwixoCreditModel->getOpsCode()); + } + + public function testGetCode() + { + $this->assertEquals('ops_kwixoCredit', $this->kwixoCreditModel->getCode()); + } + + + public function testGetDeliveryDate() + { + $this->setUp(); + $dateNow = date("Y-m-d"); + $path = 'payment/ops_kwixoCredit/delivery_date'; + $this->store->setConfig($path, "0"); + $this->assertEquals($dateNow, $this->kwixoCreditModel->getEstimatedDeliveryDate('ops_kwixoCredit')); + $dateNowPlusFiveDays = strtotime($dateNow . "+ 5 days"); + $this->store->setConfig($path, "5"); + $this->assertEquals( + date("Y-m-d", $dateNowPlusFiveDays), + $this->kwixoCreditModel->getEstimatedDeliveryDate('ops_kwixoCredit') + ); + } + + public function testGetFormBlockType() + { + $this->assertEquals('ops/form_kwixo_credit', $this->kwixoCreditModel->getFormBlockType()); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/ObjectHandler.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/ObjectHandler.php new file mode 100644 index 0000000..24b6b8b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/ObjectHandler.php @@ -0,0 +1,14 @@ +$key) { + throw new Exception(sprintf('No serialized object "%s" available in %s', $key, $objectsFile)); + } + + return unserialize(base64_decode(current($objects->$key))); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceAtTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceAtTest.php new file mode 100644 index 0000000..36d1fa0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceAtTest.php @@ -0,0 +1,87 @@ + + * @category Netresearch + * @package Netresearch/OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Model_Payment_OpenInvoiceAtTest extends EcomDev_PHPUnit_Test_Case +{ + + protected $model = null; + + public function setUp() + { + parent::setUp(); + $this->model = Mage::getModel('ops/payment_openInvoiceAt'); + } + + /** + * assure that openInvoiceAT can not capture partial, because invoice is always created on feedback in this case + */ + public function testCanCapturePartial() + { + $this->assertFalse($this->model->canCapturePartial()); + } + + public function testIsAvailableNoQuoteGiven() + { + $quote = new Varien_Object(); + $this->assertFalse($this->model->isAvailable($quote)); + } + + public function testIsAvailableNoDiscountAllowed() + { + + $quote = Mage::getModel('sales/quote'); + $quote->setSubtotal(5); + $quote->setSubtotalWithDiscount(10); + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig('payment/ops_openInvoiceAt/allow_discounted_carts', 0); + + $this->assertFalse($this->model->isAvailable($quote)); + } + + public function testIsAvailableNoGender() + { + + $quote = Mage::getModel('sales/quote'); + $quote->setSubtotal(10); + $quote->setSubtotalWithDiscount(10); + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig('payment/ops_openInvoiceAt/allow_discounted_carts', 1); + + $this->assertFalse($this->model->isAvailable($quote)); + } + + public function testGetMethodDependendFormFields() + { + $customerHelper = $this->getHelperMock('customer/data', array('isLoggedIn')); + $customerHelper->expects($this->any()) + ->method('isLoggedIn') + ->will($this->returnValue(false)); + $this->replaceByMock('helper', 'customer/data', $customerHelper); + + $order = Mage::getModel('sales/order'); + $order->setCustomerDob('01/10/1970') + ->setCustomerGender(1); + $billingAddress = Mage::getModel('sales/order_address'); + $billingAddress->setAddressType(Mage_Sales_Model_Order_Address::TYPE_BILLING) + ->setStreet('Klarna-Straße 1/2/3'); + $order->setBillingAddress($billingAddress); + $payment = Mage::getModel('sales/order_payment'); + $model = Mage::getModel('ops/payment_openInvoiceAt'); + $payment->setMethod($model->getCode()); + $model->setInfoInstance($payment); + $payment->setMethodInstance($model); + $order->setPayment($payment); + $params = $model->getMethodDependendFormFields($order); + + $this->assertEquals(' ', $params['ECOM_BILLTO_POSTAL_STREET_NUMBER']); + $this->assertEquals('Klarna-Straße 1/2/3', $params['OWNERADDRESS']); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceDeTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceDeTest.php new file mode 100644 index 0000000..1fba35f --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceDeTest.php @@ -0,0 +1,60 @@ + + * @category Netresearch + * @package Netresearch/OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Model_Payment_OpenInvoiceDeTest extends EcomDev_PHPUnit_Test_Case +{ + + protected $model = null; + + public function setUp() + { + parent::setUp(); + $this->model = Mage::getModel('ops/payment_openInvoiceDe'); + } + + /** + * assure that openInvoiceNL can not capture partial, because invoice is always created on feedback in this case + */ + public function testCanCapturePartial() + { + $this->assertFalse($this->model->canCapturePartial()); + } + + public function testIsAvailableNoQuoteGiven() + { + $quote = new Varien_Object(); + $this->assertFalse($this->model->isAvailable($quote)); + } + + public function testIsAvailableNoDiscountAllowed() + { + + $quote = Mage::getModel('sales/quote'); + $quote->setSubtotal(5); + $quote->setSubtotalWithDiscount(10); + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig('payment/ops_openInvoiceDe/allow_discounted_carts', 0); + + $this->assertFalse($this->model->isAvailable($quote)); + } + + public function testIsAvailableNoGender() + { + + $quote = Mage::getModel('sales/quote'); + $quote->setSubtotal(10); + $quote->setSubtotalWithDiscount(10); + $store = Mage::app()->getStore(0)->load(0); + $store->resetConfig(); + $store->setConfig('payment/ops_openInvoiceDe/allow_discounted_carts', 1); + + $this->assertFalse($this->model->isAvailable($quote)); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceNlTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceNlTest.php new file mode 100644 index 0000000..08b352b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/OpenInvoiceNlTest.php @@ -0,0 +1,94 @@ +model = Mage::getModel('ops/payment_openInvoiceNl'); + } + + public function testQuestionRequired() + { + $order = new Varien_Object(); + $requestParams = array( + 'foo' => 'bar', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER' => '14' + ); + $requestParams = array(); + $formFields = array( + 'OWNERADDRESS' => '', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER' => '', + 'ECOM_SHIPTO_POSTAL_STREET_LINE1' => '', + 'ECOM_SHIPTO_POSTAL_STREET_NUMBER' => '' + ); + + $method = $this->getModelMock( + 'ops/payment_openInvoiceNl', array( + 'getFormFields' + ) + ); + $method->expects($this->any()) + ->method('getFormFields') + ->will($this->returnValue($formFields)); + + $this->assertTrue($method->hasFormMissingParams($order, $requestParams, $formFields), 'expected missing params'); + $this->assertTrue(is_string($method->getQuestion())); + $this->assertEquals( + array( + 'OWNERADDRESS', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER', + 'ECOM_SHIPTO_POSTAL_STREET_LINE1', + 'ECOM_SHIPTO_POSTAL_STREET_NUMBER' + ), + $method->getQuestionedFormFields($order, $requestParams) + ); + } + + public function testQuestionNotRequired() + { + $order = new Varien_Object(); + $requestParams = array( + 'foo' => 'bar', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER' => '14' + ); + $formFields = array( + 'OWNERADDRESS' => 'Nowhere', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER' => '14', + 'ECOM_SHIPTO_POSTAL_STREET_LINE1' => 'Somewhere', + 'ECOM_SHIPTO_POSTAL_STREET_NUMBER' => '1' + ); + + $method = $this->getModelMock( + 'ops/payment_openInvoiceNl', array( + 'getFormFields' + ) + ); + $method->expects($this->any()) + ->method('getFormFields') + ->will($this->returnValue($formFields)); + + $this->assertFalse($method->hasFormMissingParams($order, $requestParams, $formFields), 'expected no missing params'); + + /* independent from that we expect to get question and questioned params when calling these methods directly */ + $this->assertTrue(is_string($method->getQuestion())); + $this->assertEquals( + array( + 'OWNERADDRESS', + 'ECOM_BILLTO_POSTAL_STREET_NUMBER', + 'ECOM_SHIPTO_POSTAL_STREET_LINE1', + 'ECOM_SHIPTO_POSTAL_STREET_NUMBER' + ), + $method->getQuestionedFormFields($order, $requestParams) + ); + } + + /** + * assure that openInvoiceNL can capture partial + */ + public function testCanCapturePartial() + { + $this->assertTrue($this->model->canCapturePartial()); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/PayPerMailTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/PayPerMailTest.php new file mode 100644 index 0000000..8b6d320 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/PayPerMailTest.php @@ -0,0 +1,77 @@ + + */ +?> + 'Foobar', + Netresearch_OPS_Model_Payment_PayPerMail::INFO_KEY_PM => 'foo', + Netresearch_OPS_Model_Payment_PayPerMail::INFO_KEY_BRAND => 'bar' + ); + } + + public function testGetOpsCode() + { + $payment = Mage::getModel('sales/order_payment'); + $additionalInfo = $this->getAdditionalInfo(); + $payment->setMethod(Netresearch_OPS_Model_Payment_PayPerMail::CODE) + ->setAdditionalInformation( + $additionalInfo + ); + /** @var Netresearch_OPS_Model_Payment_PayPerMail $subject */ + $subject = $payment->getMethodInstance(); + + $this->assertEquals($additionalInfo[Netresearch_OPS_Model_Payment_PayPerMail::INFO_KEY_PM], $subject->getOpsCode()); + } + + public function testGetOpsBrand() + { + $payment = Mage::getModel('sales/order_payment'); + $additionalInfo = $this->getAdditionalInfo(); + $payment->setMethod(Netresearch_OPS_Model_Payment_PayPerMail::CODE) + ->setAdditionalInformation( + $additionalInfo + ); + /** @var Netresearch_OPS_Model_Payment_Flex $subject */ + $subject = $payment->getMethodInstance(); + + $this->assertEquals( + $additionalInfo[Netresearch_OPS_Model_Payment_PayPerMail::INFO_KEY_BRAND], $subject->getOpsBrand() + ); + + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/Recurring/Cc/ParameterBagTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/Recurring/Cc/ParameterBagTest.php new file mode 100644 index 0000000..319a8fc --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/Recurring/Cc/ParameterBagTest.php @@ -0,0 +1,396 @@ + + */ +?> +calculateEndDate($startDate, $periodUnit, $frequency, $maxCycles); + $startDate = new DateTime('03.01.2015'); + $this->assertEquals($startDate->format('d.M.Y'), $endDate->format('d.M.Y')); + + $startDate = new DateTime('01.01.2015'); + $periodUnit = Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_MONTH; + $frequency = 1; + $maxCycles = 2; + + $endDate = $subject->calculateEndDate($startDate, $periodUnit, $frequency, $maxCycles); + $startDate = new DateTime('01.03.2015'); + $this->assertEquals($startDate->format('d.M.Y'), $endDate->format('d.M.Y')); + + $startDate = new DateTime('31.01.2015'); + $periodUnit = Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_MONTH; + $frequency = 1; + $maxCycles = 2; + + $endDate = $subject->calculateEndDate($startDate, $periodUnit, $frequency, $maxCycles); + $startDate = new DateTime('28.03.2015'); + $this->assertEquals($startDate->format('d.M.Y'), $endDate->format('d.M.Y')); + + $startDate = new DateTime('01.01.2015'); + $periodUnit = Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_WEEK; + $frequency = 1; + $maxCycles = 2; + + $endDate = $subject->calculateEndDate($startDate, $periodUnit, $frequency, $maxCycles); + $startDate = new DateTime('15.01.2015'); + $this->assertEquals($startDate->format('d.M.Y'), $endDate->format('d.M.Y')); + + $endDate = $subject->calculateEndDate($startDate, $periodUnit, $frequency, ''); + $this->assertNull($endDate); + } + + public function testHelperFunctions() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc_parameterBag'); + $testString = 'abc'; + $this->assertTrue($subject->getSubscriptionHelper() instanceof Netresearch_OPS_Helper_Subscription); + $subject->setSubscriptionHelper($testString); + $this->assertEquals($subject->getSubscriptionHelper(), $testString); + + $this->assertTrue($subject->getQuoteHelper() instanceof Netresearch_OPS_Helper_Quote); + $subject->setQuoteHelper($testString); + $this->assertEquals($subject->getQuoteHelper(), $testString); + + $this->assertTrue($subject->getDataHelper() instanceof Netresearch_OPS_Helper_Data); + $subject->setDataHelper($testString); + $this->assertEquals($subject->getDataHelper(), $testString); + + $this->assertTrue($subject->getRequestHelper() instanceof Netresearch_OPS_Helper_Payment_Request); + $subject->setRequestHelper($testString); + $this->assertEquals($subject->getRequestHelper(), $testString); + + $this->assertTrue($subject->getConfig() instanceof Netresearch_OPS_Model_Config); + $subject->setConfig($testString); + $this->assertEquals($subject->getConfig(), $testString); + } + + /** + * @return Mage_Sales_Model_Recurring_Profile + */ + protected function getTestProfile($withTrial = false) + { + $profile = Mage::getModel('sales/recurring_profile'); + $address = Mage::getModel('sales/quote_address'); + $address->setCity('Leipzig'); + $address->setStreet('Test 123'); + $quote = Mage::getModel('sales/quote'); + $quote->setShippingAddress($address); + $quote->setBillingAddress($address); + + $profile->setScheduleDescription('abc') + ->setPeriodFrequency(1) + ->setPeriodMaxCycles(1) + ->setPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_WEEK) + ->setInitAmount(10) + ->setBillingAmount(15) + ->setTaxAmount(5) + ->setShippingAmount(5) + ->setStartDatetime('') + ->setQuote($quote) + ->setId(0); + + if ($withTrial) { + $profile->setTrialPeriodMaxCycles(1) + ->setTrialPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_DAY) + ->setTrialPeriodFrequency(1) + ->setTrialBillingAmount(15); + } + return $profile; + } + + public function testCollectProfileParameters() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc_parameterBag'); + $profile = $this->getTestProfile(); + $profile->setPeriodMaxCycles(0); + + $params = $subject->collectProfileParameters($profile)->toArray(); + + $startDate = new DateTime(); + $this->assertArrayHasKey('SUB_STARTDATE', $params); + $this->assertEquals($params['SUB_STARTDATE'], $startDate->format('d/m/Y')); + + $id = Netresearch_OPS_Helper_Subscription::SUBSCRIPTION_PREFIX . $profile->getId(); + $this->assertArrayHasKey('SUBSCRIPTION_ID', $params); + $this->assertEquals($id, $params['SUBSCRIPTION_ID']); + + $this->assertArrayHasKey('SUB_ORDERID', $params); + $this->assertEquals($id, $params['SUB_ORDERID']); + + $this->assertArrayHasKey('ORDERID', $params); + $this->assertEquals($id, $params['ORDERID']); + + $this->assertArrayHasKey('SUB_COM', $params); + $this->assertEquals($params['SUB_COM'], 'abc'); + + $this->assertEquals(0, $params['AMOUNT']); + $subAmount = $profile->getBillingAmount() + $profile->getTaxAmount() + $profile->getShippingAmount(); + $this->assertEquals($subAmount, $params['SUB_AMOUNT'] / 100); + + $this->assertArrayHasKey('ECOM_SHIPTO_POSTAL_CITY', $params); + $this->assertEquals('Leipzig', $params['ECOM_SHIPTO_POSTAL_CITY']); + + $this->assertArrayHasKey('ECOM_BILLTO_POSTAL_CITY', $params); + $this->assertEquals('Leipzig', $params['ECOM_BILLTO_POSTAL_CITY']); + + $this->assertArrayHasKey('OWNERTOWN', $params); + $this->assertEquals('Leipzig', $params['OWNERTOWN']); + + $this->assertArrayHasKey('SUB_ENDDATE', $params); + $this->assertEquals('', $params['SUB_ENDDATE']); + + + // test map unit and adjust frequency: + + $profile->setPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_YEAR) + ->setPeriodFrequency(1); + $params = $subject->collectProfileParameters($profile); + + $this->assertEquals(12, $params['SUB_PERIOD_NUMBER']); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_MONTH, $params['SUB_PERIOD_UNIT'] + ); + + $profile->setPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_SEMI_MONTH); + + $params = $subject->collectProfileParameters($profile); + $this->assertEquals(2, $params['SUB_PERIOD_NUMBER']); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_WEEK, $params['SUB_PERIOD_UNIT'] + ); + + $profile->setPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_MONTH); + + $params = $subject->collectProfileParameters($profile); + $this->assertEquals(1, $params['SUB_PERIOD_NUMBER']); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_MONTH, $params['SUB_PERIOD_UNIT'] + ); + + $profile->setPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_DAY); + + $params = $subject->collectProfileParameters($profile); + $this->assertEquals(1, $params['SUB_PERIOD_NUMBER']); + $this->assertEquals( + Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_DAY, $params['SUB_PERIOD_UNIT'] + ); + + + //test trial use-cases + + $profile = $this->getTestProfile(true); + + $params = $subject->collectProfileParameters($profile, true); + + $this->assertEquals(Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag::PERIOD_UNIT_DAY, $params['SUB_PERIOD_UNIT']); + + $id = Netresearch_OPS_Helper_Subscription::SUBSCRIPTION_PREFIX . $profile->getId() . Netresearch_OPS_Helper_Subscription::TRIAL_SUFFIX ; + $this->assertArrayHasKey('SUBSCRIPTION_ID', $params); + $this->assertEquals($id, $params['SUBSCRIPTION_ID']); + + $this->assertArrayHasKey('SUB_ORDERID', $params); + $this->assertEquals($id, $params['SUB_ORDERID']); + + $this->assertArrayHasKey('ORDERID', $params); + $this->assertEquals($id, $params['ORDERID']); + + $this->assertEquals($profile->getTrialBillingAmount()+$profile->getShippingAmount(), $params['SUB_AMOUNT']/100); + } + + public function testCollectPaymentParameters() + { + $subject = Mage::getModel('ops/payment_recurring_cc_parameterBag'); + + $payment = $this->getTestPayment(); + + $params = $subject->collectPaymentParameters($payment)->toArray(); + + $this->assertEquals($params['CN'], 'Olaf'); + $this->assertEquals($params['ALIAS'], '123'); + $this->assertEquals($params['OPERATION'], Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION); + $this->assertEquals($params['FLAG3D'], 'Y'); + } + + /** + * @return Mage_Sales_Model_Quote_Payment + */ + protected function getTestPayment() + { + $quote = Mage::getModel('sales/quote'); + $quote->setRemoteIp('127.0.0.1'); + $payment = Mage::getModel('sales/quote_payment'); + $payment->setAdditionalInformation('alias', '123') + ->setAdditionalInformation('CC_CN', 'Olaf') + ->setAdditionalInformation('CC_BRAND', 'VISA') + ->setMethod(Netresearch_OPS_Model_Payment_Recurring_Cc::CODE) + ->setQuote($quote); + Mage::app()->getStore()->setConfig('payment/ops_recurring_cc/enabled_3dsecure', 1); + return $payment; + } + + public function testCollectAllParameters() + { + $subject = Mage::getModel('ops/payment_recurring_cc_parameterBag'); + $payment = $this->getTestPayment(); + $profile = $this->getTestProfile(); + + $params = $subject->collectAllParameters($payment, $profile); + + $startDate = new DateTime(); + $this->assertArrayHasKey('SUB_STARTDATE', $params); + $this->assertEquals($startDate->format('d/m/Y'), $params['SUB_STARTDATE']); + + $id = Netresearch_OPS_Helper_Subscription::SUBSCRIPTION_PREFIX . $profile->getId(); + $this->assertArrayHasKey('SUBSCRIPTION_ID', $params); + $this->assertEquals($id, $params['SUBSCRIPTION_ID']); + + $this->assertArrayHasKey('SUB_ORDERID', $params); + $this->assertEquals($id, $params['SUB_ORDERID']); + + $this->assertArrayHasKey('SUB_COM', $params); + $this->assertEquals($params['SUB_COM'], 'abc'); + + $this->assertEquals(0, $params['AMOUNT']); + $subAmount = $profile->getBillingAmount() + $profile->getTaxAmount() + $profile->getShippingAmount(); + $this->assertEquals($subAmount, $params['SUB_AMOUNT'] / 100); + + $this->assertArrayHasKey('ECOM_SHIPTO_POSTAL_CITY', $params); + $this->assertEquals('Leipzig', $params['ECOM_SHIPTO_POSTAL_CITY']); + + $this->assertArrayHasKey('ECOM_BILLTO_POSTAL_CITY', $params); + $this->assertEquals('Leipzig', $params['ECOM_BILLTO_POSTAL_CITY']); + + $this->assertArrayHasKey('OWNERTOWN', $params); + $this->assertEquals('Leipzig', $params['OWNERTOWN']); + $this->assertEquals($params['CN'], 'Olaf'); + $this->assertEquals($params['ALIAS'], '123'); + $this->assertEquals($params['ORDERID'], $id); + $this->assertEquals($params['OPERATION'], Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION); + $this->assertEquals($params['FLAG3D'], 'Y'); + } + + public function testCollectAllParametersWithTrial() + { + $subject = Mage::getModel('ops/payment_recurring_cc_parameterBag'); + // test dry run with trial data + $payment = $this->getTestPayment(); + $profile = $this->getTestProfile(true); + + $params = $subject->collectAllParameters($payment, $profile); + + $startDate = new DateTime(); + $startDate->add(new DateInterval('P1D')); + $this->assertArrayHasKey('SUB_STARTDATE', $params); + $this->assertEquals($params['SUB_STARTDATE'], $startDate->format('d/m/Y')); + + $id = Netresearch_OPS_Helper_Subscription::SUBSCRIPTION_PREFIX . $profile->getId(); + $this->assertArrayHasKey('SUBSCRIPTION_ID', $params); + $this->assertEquals($id, $params['SUBSCRIPTION_ID']); + + $this->assertArrayHasKey('SUB_ORDERID', $params); + $this->assertEquals($id, $params['SUB_ORDERID']); + + $this->assertArrayHasKey('SUB_COM', $params); + $this->assertEquals($params['SUB_COM'], 'abc'); + + $this->assertEquals(0, $params['AMOUNT']); + $subAmount = $profile->getBillingAmount() + $profile->getTaxAmount() + $profile->getShippingAmount(); + $this->assertEquals($subAmount, $params['SUB_AMOUNT'] / 100); + + $this->assertArrayHasKey('ECOM_SHIPTO_POSTAL_CITY', $params); + $this->assertEquals('Leipzig', $params['ECOM_SHIPTO_POSTAL_CITY']); + + $this->assertArrayHasKey('ECOM_BILLTO_POSTAL_CITY', $params); + $this->assertEquals('Leipzig', $params['ECOM_BILLTO_POSTAL_CITY']); + + $this->assertArrayHasKey('OWNERTOWN', $params); + $this->assertEquals('Leipzig', $params['OWNERTOWN']); + $this->assertEquals($params['CN'], 'Olaf'); + $this->assertEquals($params['ALIAS'], '123'); + $this->assertEquals($params['ORDERID'], $id); + $this->assertEquals($params['OPERATION'], Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION); + $this->assertEquals($params['FLAG3D'], 'Y'); + } + + public function testCollectAllParametersForTrial() + { + $subject = Mage::getModel('ops/payment_recurring_cc_parameterBag'); + $payment = $this->getTestPayment(); + $profile = $this->getTestProfile(true); + + $params = $subject->collectAllParametersForTrial($payment, $profile); + + $startDate = new DateTime(); + $this->assertArrayHasKey('SUB_STARTDATE', $params); + $this->assertEquals($params['SUB_STARTDATE'], $startDate->format('d/m/Y')); + + $id = Netresearch_OPS_Helper_Subscription::SUBSCRIPTION_PREFIX . $profile->getId() . Netresearch_OPS_Helper_Subscription::TRIAL_SUFFIX; + $this->assertArrayHasKey('SUBSCRIPTION_ID', $params); + $this->assertEquals($id, $params['SUBSCRIPTION_ID']); + + $this->assertArrayHasKey('SUB_ORDERID', $params); + $this->assertEquals($id, $params['SUB_ORDERID']); + + $this->assertArrayHasKey('SUB_COM', $params); + $this->assertEquals($params['SUB_COM'], 'abc'); + + $this->assertEquals(0, $params['AMOUNT']); + $subAmount = $profile->getTrialBillingAmount() + $profile->getShippingAmount(); + $this->assertEquals($subAmount, $params['SUB_AMOUNT'] / 100); + + $this->assertArrayHasKey('ECOM_SHIPTO_POSTAL_CITY', $params); + $this->assertEquals('Leipzig', $params['ECOM_SHIPTO_POSTAL_CITY']); + + $this->assertArrayHasKey('ECOM_BILLTO_POSTAL_CITY', $params); + $this->assertEquals('Leipzig', $params['ECOM_BILLTO_POSTAL_CITY']); + + $this->assertArrayHasKey('OWNERTOWN', $params); + $this->assertEquals('Leipzig', $params['OWNERTOWN']); + $this->assertEquals($params['CN'], 'Olaf'); + $this->assertEquals($params['ALIAS'], '123'); + $this->assertEquals($params['ORDERID'], $id); + $this->assertEquals($params['OPERATION'], Netresearch_OPS_Model_Payment_Abstract::OPS_AUTHORIZE_CAPTURE_ACTION); + $this->assertEquals($params['FLAG3D'], 'Y'); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/Recurring/CcTest.php b/app/code/community/Netresearch/OPS/Test/Model/Payment/Recurring/CcTest.php new file mode 100644 index 0000000..1e8eb15 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/Recurring/CcTest.php @@ -0,0 +1,582 @@ + + */ +class Netresearch_OPS_Test_Model_Payment_Recurring_CcTest extends EcomDev_PHPUnit_Test_Case +{ + /** + * @return Mage_Sales_Model_Recurring_Profile + */ + protected function getTestProfile() + { + /** @var Mage_Sales_Model_Recurring_Profile $profile */ + $profile = Mage::getModel('sales/recurring_profile'); + $address = Mage::getModel('sales/quote_address'); + $address->setCity('Leipzig'); + $quote = Mage::getModel('sales/quote'); + $quote->setShippingAddress($address); + $quote->setBillingAddress($address); + $quote->setPayment($this->getTestPayment()); + + $profile->setScheduleDescription('abc') + ->setPeriodFrequency(1) + ->setPeriodMaxCycles(1) + ->setMethodCode('ops_recurring_cc') + ->setPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_WEEK) + ->setOrderItemInfo(array()) + ->setInitAmount(10) + ->setBillingAmount(15) + ->setTaxAmount(5) + ->setShippingAmount(5) + ->setStartDatetime('') + ->setQuote($quote) + ->setCurrencyCode('EUR') + ->setState(Mage_Sales_Model_Recurring_Profile::STATE_UNKNOWN) + ->setId(0); + + return $profile; + } + + protected function getResponseParams($success) + { + $params = array( + 'subscription_id' => 'SUB-0', + 'orderID' => 'SUB-0', + 'PAYID' => 12345, + 'currency' => 'EUR', + 'amount' => 1000 + ); + if ($success) { + $params['STATUS'] = 9; + $params['creation_status'] = Netresearch_OPS_Model_Subscription_Manager::CREATION_SUCCEEDED; + } else { + $params['STATUS'] = 2; + $params['creation_status'] = Netresearch_OPS_Model_Subscription_Manager::CREATION_FAILED; + } + + return $params; + } + + /** + * @return Mage_Sales_Model_Quote_Payment + */ + protected function getTestPayment() + { + $quote = Mage::getModel('sales/quote'); + $quote->setRemoteIp('127.0.0.1'); + $payment = Mage::getModel('sales/quote_payment'); + $payment->setAdditionalInformation('alias', '123') + ->setAdditionalInformation('CC_CN', 'Olaf') + ->setAdditionalInformation('CC_BRAND', 'VISA') + ->setMethod(Netresearch_OPS_Model_Payment_Recurring_Cc::CODE) + ->setQuote($quote); + Mage::app()->getStore()->setConfig('payment/ops_recurring_cc/enabled_3dsecure', 1); + + return $payment; + } + + public function testValidateRecurringProfile() + { + $subject = Mage::getModel('ops/payment_recurring_cc'); + + $profile = $this->getTestProfile(); + + $subject->validateRecurringProfile($profile); + } + + + public function testSubmitRecurringProfileOnlyRegular() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + + $params = $this->getResponseParams(true); + $this->mockDirectLinkRequest($params); + + $profile = $this->getTestProfile(); + + $profile->setInitAmount(0); + $paymentInfo = $this->getTestPayment(); + $subject->submitRecurringProfile($profile, $paymentInfo); + + $this->assertEquals($profile->getReferenceId(), 'SUB-0'); + $this->assertEquals(serialize($params), $profile->getProfileVendorInfo()); + } + + /** + * @test + * @expectedException Exception + */ + public function testSubmitRecurringProfileOnlyRegularWithException() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + + $params = $this->getResponseParams(false); + $this->mockDirectLinkRequest($params); + + $profile = $this->getTestProfile(); + + $profile->setInitAmount(0); + $paymentInfo = $this->getTestPayment(); + $subject->submitRecurringProfile($profile, $paymentInfo); + + $this->assertEquals($profile->getReferenceId(), 'SUB-0'); + $this->assertEquals(serialize($params), $profile->getProfileVendorInfo()); + } + + public function testSubmitRecurringProfileWithTrial() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + + $params = $this->getResponseParams(true); + $this->mockDirectLinkRequest($params); + + $profile = $this->getTestProfile(); + $profile->setInitAmount(0) + ->setTrialBillingAmount(1) + ->setTrialPeriodFrequency(5) + ->setTrialPeriodMaxCycles(10) + ->setTrialPeriodMaxCycles(5) + ->setTrialPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_DAY); + + $paymentInfo = $this->getTestPayment(); + $subject->submitRecurringProfile($profile, $paymentInfo); + + $this->assertEquals($profile->getReferenceId(), 'SUB-0'); + $this->assertEquals(serialize($params), $profile->getProfileVendorInfo()); + } + + /** + * @test + * @expectedException Exception + */ + public function testSubmitRecurringProfileWithTrialWithException() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + + $params = $this->getResponseParams(false); + $this->mockTwoDirectLinkRequests($this->getResponseParams(true), $params); + + $profile = $this->getTestProfile(); + $profile->setInitAmount(0) + ->setTrialBillingAmount(1) + ->setTrialPeriodFrequency(5) + ->setTrialPeriodMaxCycles(10) + ->setTrialPeriodMaxCycles(5) + ->setTrialPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_DAY); + + $paymentInfo = $this->getTestPayment(); + $subject->submitRecurringProfile($profile, $paymentInfo); + + $this->assertEquals($profile->getReferenceId(), 'SUB-0'); + $this->assertEquals(serialize($params), $profile->getProfileVendorInfo()); + } + + public function testSubmitRecurringProfileWithInitialFee() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + + $profileMock = $this->getModelMock('sales/recurring_profile', array('getChildOrderIds', 'addOrderRelation')); + $profileMock->expects($this->any()) + ->method('getChildOrderIds') + ->will($this->returnSelf()); + $profileMock->expects($this->any()) + ->method('addOrderRelation') + ->will($this->returnSelf()); + $this->replaceByMock('model', 'sales/recurring_profile', $profileMock); + + $paymentHelperMock = $this->getHelperMock('ops/payment', array('applyStateForOrder')); + $paymentHelperMock->expects($this->any()) + ->method('applyStateForOrder') + ->will($this->returnSelf()); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + + $params = $this->getResponseParams(true); + $this->mockDirectLinkRequest($params); + + $profile = $this->getTestProfile(); + $profile->setTrialBillingAmount(1) + ->setTrialPeriodFrequency(5) + ->setTrialPeriodMaxCycles(10) + ->setTrialPeriodMaxCycles(5); + + $paymentInfo = $this->getTestPayment(); + $subject->submitRecurringProfile($profile, $paymentInfo); + + $this->assertEquals($profile->getReferenceId(), 'SUB-0'); + $this->assertEquals(serialize($params), $profile->getProfileVendorInfo()); + } + + /** + * @test + * @expectedException Exception + */ + public function testGetRecurringProfileDetails() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + $subject->getRecurringProfileDetails('abc', new Varien_Object()); + } + + public function testCanGetRecurringProfileDetails() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + $this->assertFalse($subject->canGetRecurringProfileDetails()); + } + + /** + * @test + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage Function not supported + */ + public function testUpdateRecurringProfile() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + $subject->updateRecurringProfile($this->getTestProfile()); + } + + /** + * @test + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage Automatic activation not possible. Please contact our support team. + */ + public function testUpdateRecurringProfileStatusWithActivateException() + { + $adminMock = $this->getModelMock('admin/session', array('isLoggedIn', 'init')); + $this->replaceByMock('singleton', 'admin/session', $adminMock); + + $subject = Mage::getModel('ops/payment_recurring_cc'); + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState(Mage_Sales_Model_Recurring_Profile::STATE_ACTIVE); + $subject->updateRecurringProfileStatus($profile); + + } + + public function testUpdateRecurringProfileStatusWithActivate() + { + $adminMock = $this->getModelMock('admin/session', array('isLoggedIn', 'init')); + $adminMock->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->replaceByMock('singleton', 'admin/session', $adminMock); + + $adminHtmlMock = $this->getModelMock('adminhtml/session', array('addNotice', 'init')); + $adminHtmlMock->expects($this->once()) + ->method('addNotice'); + $this->replaceByMock('singleton', 'adminhtml/session', $adminHtmlMock); + + $subject = Mage::getModel('ops/payment_recurring_cc'); + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState(Mage_Sales_Model_Recurring_Profile::STATE_ACTIVE); + $subject->updateRecurringProfileStatus($profile); + } + + /** + * @test + */ + public function testUpdateRecurringProfileStatusWithCancelAsCustomer() + { + $adminMock = $this->getModelMock('admin/session', array('isLoggedIn', 'init')); + $this->replaceByMock('singleton', 'admin/session', $adminMock); + + $sessionMock = $this->getModelMock('customer/session', array('getCustomer', 'init')); + $customer = new Varien_Object(array('id' => 1)); + $sessionMock->expects($this->once()) + ->method('getCustomer') + ->will($this->returnValue($customer)); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + + $mailModelMock = $this->getModelMock('ops/payment_features_paymentEmail', array('sendSuspendSubscriptionMail')); + $mailModelMock->expects($this->once()) + ->method('sendSuspendSubscriptionMail') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/payment_features_paymentEmail', $mailModelMock); + + + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState(Mage_Sales_Model_Recurring_Profile::STATE_CANCELED); + $profile->setCustomerId(1); + + $subject = Mage::getModel('ops/payment_recurring_cc'); + $subject->updateRecurringProfileStatus($profile); + + $this->assertTrue($profile->getOverrideState()); + $this->assertEquals(Mage_Sales_Model_Recurring_Profile::STATE_PENDING, $profile->getNewState()); + $this->assertNotNull($sessionMock->getMessages()->getLastAddedMessage()); + } + + public function testUpdateRecurringProfileStatusWithCancel() + { + $adminMock = $this->getModelMock('admin/session', array('isLoggedIn', 'init')); + $adminMock->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->replaceByMock('singleton', 'admin/session', $adminMock); + + $adminHtmlMock = $this->getModelMock('adminhtml/session', array('addNotice', 'init')); + $adminHtmlMock->expects($this->once()) + ->method('addNotice'); + $this->replaceByMock('singleton', 'adminhtml/session', $adminHtmlMock); + + $subject = Mage::getModel('ops/payment_recurring_cc'); + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState(Mage_Sales_Model_Recurring_Profile::STATE_CANCELED); + $subject->updateRecurringProfileStatus($profile); + } + + public function testUpdateRecurringProfileStatusWithSuspendAsCustomer() + { + $adminMock = $this->getModelMock('admin/session', array('isLoggedIn', 'init')); + $this->replaceByMock('singleton', 'admin/session', $adminMock); + + $sessionMock = $this->getModelMock('customer/session', array('getCustomer', 'init')); + $customer = new Varien_Object(array('id' => 1)); + $sessionMock->expects($this->once()) + ->method('getCustomer') + ->will($this->returnValue($customer)); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + + $mailModelMock = $this->getModelMock('ops/payment_features_paymentEmail', array('sendSuspendSubscriptionMail')); + $mailModelMock->expects($this->once()) + ->method('sendSuspendSubscriptionMail') + ->will($this->returnValue(true)); + $this->replaceByMock('model', 'ops/payment_features_paymentEmail', $mailModelMock); + + + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState(Mage_Sales_Model_Recurring_Profile::STATE_SUSPENDED); + $profile->setCustomerId(1); + + $subject = Mage::getModel('ops/payment_recurring_cc'); + $subject->updateRecurringProfileStatus($profile); + + $this->assertTrue($profile->getOverrideState()); + $this->assertEquals(Mage_Sales_Model_Recurring_Profile::STATE_PENDING, $profile->getNewState()); + $this->assertNotNull($sessionMock->getMessages()->getLastAddedMessage()); + } + + /** + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage You are not allowed to suspend this subscription! + */ + public function testUpdateRecurringProfileStatusWithSuspendAsCustomerWithUnAllowedException() + { + $adminMock = $this->getModelMock('admin/session', array('isLoggedIn', 'init')); + $this->replaceByMock('singleton', 'admin/session', $adminMock); + + $sessionMock = $this->getModelMock('customer/session', array('getCustomer', 'init')); + $customer = new Varien_Object(array('id' => 2)); + $sessionMock->expects($this->once()) + ->method('getCustomer') + ->will($this->returnValue($customer)); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState(Mage_Sales_Model_Recurring_Profile::STATE_SUSPENDED); + $profile->setCustomerId(1); + + $subject = Mage::getModel('ops/payment_recurring_cc'); + $subject->updateRecurringProfileStatus($profile); + } + + /** + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage Could not send suspend mail, please try again or contact our support directly. + */ + public function testUpdateRecurringProfileStatusWithSuspendAsCustomerWithFailedException() + { + $adminMock = $this->getModelMock('admin/session', array('isLoggedIn', 'init')); + $this->replaceByMock('singleton', 'admin/session', $adminMock); + + $sessionMock = $this->getModelMock('customer/session', array('getCustomer', 'init')); + $customer = new Varien_Object(array('id' => 1)); + $sessionMock->expects($this->once()) + ->method('getCustomer') + ->will($this->returnValue($customer)); + $this->replaceByMock('singleton', 'customer/session', $sessionMock); + + $mailModelMock = $this->getModelMock('ops/payment_features_paymentEmail', array('sendSuspendSubscriptionMail')); + $mailModelMock->expects($this->once()) + ->method('sendSuspendSubscriptionMail') + ->will($this->returnValue(false)); + $this->replaceByMock('model', 'ops/payment_features_paymentEmail', $mailModelMock); + + + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState(Mage_Sales_Model_Recurring_Profile::STATE_SUSPENDED); + $profile->setCustomerId(1); + + $subject = Mage::getModel('ops/payment_recurring_cc'); + $subject->updateRecurringProfileStatus($profile); + } + + public function testUpdateRecurringProfileStatusWithSuspendAsAdmin() + { + $adminMock = $this->getModelMock('admin/session', array('isLoggedIn', 'init')); + $adminMock->expects($this->once()) + ->method('isLoggedIn') + ->will($this->returnValue(true)); + $this->replaceByMock('singleton', 'admin/session', $adminMock); + + $adminHtmlMock = $this->getModelMock('adminhtml/session', array('addNotice', 'init')); + $adminHtmlMock->expects($this->once()) + ->method('addNotice'); + $this->replaceByMock('singleton', 'adminhtml/session', $adminHtmlMock); + + $subject = Mage::getModel('ops/payment_recurring_cc'); + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState(Mage_Sales_Model_Recurring_Profile::STATE_SUSPENDED); + $subject->updateRecurringProfileStatus($profile); + } + + /** + * @test + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage Expire function not implemented! + */ + public function testUpdateRecurringProfileStatusWithExpireException() + { + $subject = Mage::getModel('ops/payment_recurring_cc'); + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState(Mage_Sales_Model_Recurring_Profile::STATE_EXPIRED); + $subject->updateRecurringProfileStatus($profile); + } + + /** + * @test + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage Action for state abc not supported + */ + public function testUpdateRecurringProfileStatusWithNotSupportedException() + { + $subject = Mage::getModel('ops/payment_recurring_cc'); + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setNewState('abc'); + $subject->updateRecurringProfileStatus($profile); + } + + public function testIsAvailable() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + $items = array(); + $item1 = Mage::getModel('sales/quote_item'); + $item1->setData('is_nominal', false); + $items[] = $item1; + $quote2 = $this->getModelMock('sales/quote', array('getAllVisibleItems')); + $quote2->expects($this->any()) + ->method('getAllVisibleItems') + ->will($this->returnValue($items)); + + $this->assertFalse($subject->isAvailable($quote2)); + } + + public function testHasBrandAliasInterfaceSupport() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + $this->assertTrue($subject->hasBrandAliasInterfaceSupport()); + } + + public function testGetOrderPlaceRedirectUrl() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + $payment = Mage::getModel('payment/info'); + $payment->setAdditionalInformation('HTML_ANSWER', 'abc'); + $this->assertEquals( + $subject->getOrderPlaceRedirectUrl($payment), + Mage::getModel('ops/config')->get3dSecureRedirectUrl() + ); + + $payment->setAdditionalInformation('HTML_ANSWER', ''); + $this->assertFalse($subject->getOrderPlaceRedirectUrl($payment)); + } + + public function testIsZeroAmountAuthorizationAllowed() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + $this->assertFalse($subject->isZeroAmountAuthorizationAllowed()); + } + + public function testGetBrandsForAliasInterface() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + $this->assertEquals( + $subject->getBrandsForAliasInterface(), + 'American Express,Diners Club,MaestroUK,MasterCard,VISA,JCB' + ); + } + + public function testGetterSetters() + { + /** @var Netresearch_OPS_Model_Payment_Recurring_Cc $subject */ + $subject = Mage::getModel('ops/payment_recurring_cc'); + + $this->assertTrue($subject->getSubscriptionManager() instanceof Netresearch_OPS_Model_Subscription_Manager); + $subject->setSubscriptionManager('abc'); + $this->assertTrue($subject->getSubscriptionManager() === 'abc'); + + $this->assertTrue( + $subject->getParameterModel() instanceof + Netresearch_OPS_Model_Payment_Recurring_Cc_ParameterBag + ); + $subject->setParameterModel('abc'); + $this->assertTrue($subject->getParameterModel() === 'abc'); + } + + /** + * @param $params + */ + protected function mockDirectLinkRequest($params) + { + $helperMock = $this->getHelperMock('ops/directlink', array('performDirectLinkRequest')); + $helperMock->expects($this->any()) + ->method('performDirectLinkRequest') + ->will($this->returnValue($params)); + $this->replaceByMock('helper', 'ops/directlink', $helperMock); + } + + + protected function mockTwoDirectLinkRequests($params1, $params2) + { + $helperMock = $this->getHelperMock('ops/directlink', array('performDirectLinkRequest')); + $helperMock->expects($this->any()) + ->method('performDirectLinkRequest') + ->will($this->onConsecutiveCalls($params1, $params2)); + $this->replaceByMock('helper', 'ops/directlink', $helperMock); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Payment/objects.xml b/app/code/community/Netresearch/OPS/Test/Model/Payment/objects.xml new file mode 100644 index 0000000..f1aa46d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Payment/objects.xml @@ -0,0 +1,4 @@ + + + TzoyMjoiTWFnZV9TYWxlc19Nb2RlbF9RdW90ZSI6MTc6e3M6MTU6IgAqAF9ldmVudFByZWZpeCI7czoxMToic2FsZXNfcXVvdGUiO3M6MTU6IgAqAF9ldmVudE9iamVjdCI7czo1OiJxdW90ZSI7czoxMjoiACoAX2N1c3RvbWVyIjtPOjI4OiJNYWdlX0N1c3RvbWVyX01vZGVsX0N1c3RvbWVyIjoxOTp7czoxNToiACoAX2V2ZW50UHJlZml4IjtzOjg6ImN1c3RvbWVyIjtzOjE1OiIAKgBfZXZlbnRPYmplY3QiO3M6ODoiY3VzdG9tZXIiO3M6MTA6IgAqAF9lcnJvcnMiO2E6MDp7fXM6MTQ6IgAqAF9hdHRyaWJ1dGVzIjtOO3M6MTM6IgAqAF9hZGRyZXNzZXMiO047czoyMzoiACoAX2FkZHJlc3Nlc0NvbGxlY3Rpb24iO047czoxNjoiACoAX2lzRGVsZXRlYWJsZSI7YjoxO3M6MTQ6IgAqAF9pc1JlYWRvbmx5IjtiOjA7czoxNjoiACoAX3Jlc291cmNlTmFtZSI7czoxNzoiY3VzdG9tZXIvY3VzdG9tZXIiO3M6MTI6IgAqAF9yZXNvdXJjZSI7TjtzOjI2OiIAKgBfcmVzb3VyY2VDb2xsZWN0aW9uTmFtZSI7czoyODoiY3VzdG9tZXIvY3VzdG9tZXJfY29sbGVjdGlvbiI7czoxMjoiACoAX2NhY2hlVGFnIjtiOjA7czoxOToiACoAX2RhdGFTYXZlQWxsb3dlZCI7YjoxO3M6MTU6IgAqAF9pc09iamVjdE5ldyI7TjtzOjg6IgAqAF9kYXRhIjthOjE4OntzOjEwOiJ3ZWJzaXRlX2lkIjtzOjE6IjEiO3M6OToiZW50aXR5X2lkIjtzOjE6IjIiO3M6MTQ6ImVudGl0eV90eXBlX2lkIjtzOjE6IjEiO3M6MTY6ImF0dHJpYnV0ZV9zZXRfaWQiO3M6MToiMCI7czo1OiJlbWFpbCI7czoyOToibWFyaW8uYmVocmVuZHRAbmV0cmVzZWFyY2guZGUiO3M6ODoiZ3JvdXBfaWQiO3M6MToiMSI7czoxMjoiaW5jcmVtZW50X2lkIjtzOjA6IiI7czo4OiJzdG9yZV9pZCI7czoxOiIxIjtzOjEwOiJjcmVhdGVkX2F0IjtzOjE5OiIyMDExLTA4LTE2IDEyOjIwOjI4IjtzOjEwOiJ1cGRhdGVkX2F0IjtzOjE5OiIyMDExLTA4LTIyIDA2OjUyOjAxIjtzOjk6ImlzX2FjdGl2ZSI7czoxOiIxIjtzOjk6ImZpcnN0bmFtZSI7czo1OiJNYXJpbyI7czo4OiJsYXN0bmFtZSI7czo4OiJCZWhyZW5kdCI7czoxMzoicGFzc3dvcmRfaGFzaCI7czozNToiZmMwZDZiYzdjZWU1ZmNmMTQwM2VhNGMyYjhmYjAxYjA6WHEiO3M6MTA6ImNyZWF0ZWRfaW4iO3M6NzoiRW5nbGlzaCI7czoxNToiZGVmYXVsdF9iaWxsaW5nIjtzOjE6IjEiO3M6MTY6ImRlZmF1bHRfc2hpcHBpbmciO3M6MToiMSI7czoxMjoidGF4X2NsYXNzX2lkIjtzOjE6IjMiO31zOjE4OiIAKgBfaGFzRGF0YUNoYW5nZXMiO2I6MTtzOjEyOiIAKgBfb3JpZ0RhdGEiO2E6MTc6e3M6MTA6IndlYnNpdGVfaWQiO3M6MToiMSI7czo5OiJlbnRpdHlfaWQiO3M6MToiMiI7czoxNDoiZW50aXR5X3R5cGVfaWQiO3M6MToiMSI7czoxNjoiYXR0cmlidXRlX3NldF9pZCI7czoxOiIwIjtzOjU6ImVtYWlsIjtzOjI5OiJtYXJpby5iZWhyZW5kdEBuZXRyZXNlYXJjaC5kZSI7czo4OiJncm91cF9pZCI7czoxOiIxIjtzOjEyOiJpbmNyZW1lbnRfaWQiO3M6MDoiIjtzOjg6InN0b3JlX2lkIjtzOjE6IjEiO3M6MTA6ImNyZWF0ZWRfYXQiO3M6MTk6IjIwMTEtMDgtMTYgMTI6MjA6MjgiO3M6MTA6InVwZGF0ZWRfYXQiO3M6MTk6IjIwMTEtMDgtMjIgMDY6NTI6MDEiO3M6OToiaXNfYWN0aXZlIjtzOjE6IjEiO3M6OToiZmlyc3RuYW1lIjtzOjU6Ik1hcmlvIjtzOjg6Imxhc3RuYW1lIjtzOjg6IkJlaHJlbmR0IjtzOjEzOiJwYXNzd29yZF9oYXNoIjtzOjM1OiJmYzBkNmJjN2NlZTVmY2YxNDAzZWE0YzJiOGZiMDFiMDpYcSI7czoxMDoiY3JlYXRlZF9pbiI7czo3OiJFbmdsaXNoIjtzOjE1OiJkZWZhdWx0X2JpbGxpbmciO3M6MToiMSI7czoxNjoiZGVmYXVsdF9zaGlwcGluZyI7czoxOiIxIjt9czoxNToiACoAX2lkRmllbGROYW1lIjtzOjk6ImVudGl0eV9pZCI7czoxMzoiACoAX2lzRGVsZXRlZCI7YjowO31zOjEzOiIAKgBfYWRkcmVzc2VzIjtOO3M6OToiACoAX2l0ZW1zIjtOO3M6MTI6IgAqAF9wYXltZW50cyI7TjtzOjE2OiIAKgBfcmVzb3VyY2VOYW1lIjtzOjExOiJzYWxlcy9xdW90ZSI7czoxMjoiACoAX3Jlc291cmNlIjtOO3M6MjY6IgAqAF9yZXNvdXJjZUNvbGxlY3Rpb25OYW1lIjtzOjIyOiJzYWxlcy9xdW90ZV9jb2xsZWN0aW9uIjtzOjEyOiIAKgBfY2FjaGVUYWciO2I6MDtzOjE5OiIAKgBfZGF0YVNhdmVBbGxvd2VkIjtiOjE7czoxNToiACoAX2lzT2JqZWN0TmV3IjtOO3M6ODoiACoAX2RhdGEiO2E6NTE6e3M6OToiZW50aXR5X2lkIjtzOjI6Ijk4IjtzOjg6InN0b3JlX2lkIjtzOjE6IjEiO3M6MTA6ImNyZWF0ZWRfYXQiO3M6MTk6IjIwMTEtMDgtMjIgMTE6MzM6MzMiO3M6MTA6InVwZGF0ZWRfYXQiO3M6MTk6IjIwMTEtMDgtMjIgMTE6MzU6MjYiO3M6MTI6ImNvbnZlcnRlZF9hdCI7czoxOToiMDAwMC0wMC0wMCAwMDowMDowMCI7czo5OiJpc19hY3RpdmUiO3M6MToiMSI7czoxMDoiaXNfdmlydHVhbCI7czoxOiIwIjtzOjE3OiJpc19tdWx0aV9zaGlwcGluZyI7czoxOiIwIjtzOjExOiJpdGVtc19jb3VudCI7czoxOiIxIjtzOjk6Iml0ZW1zX3F0eSI7czo2OiIxLjAwMDAiO3M6MTM6Im9yaWdfb3JkZXJfaWQiO3M6MToiMCI7czoxODoic3RvcmVfdG9fYmFzZV9yYXRlIjtzOjY6IjEuMDAwMCI7czoxOToic3RvcmVfdG9fcXVvdGVfcmF0ZSI7czo2OiIxLjAwMDAiO3M6MTk6ImJhc2VfdG9fZ2xvYmFsX3JhdGUiO3M6NjoiMS4wMDAwIjtzOjE4OiJiYXNlX3RvX3F1b3RlX3JhdGUiO3M6NjoiMS4wMDAwIjtzOjIwOiJnbG9iYWxfY3VycmVuY3lfY29kZSI7czozOiJFVVIiO3M6MTg6ImJhc2VfY3VycmVuY3lfY29kZSI7czozOiJFVVIiO3M6MTk6InN0b3JlX2N1cnJlbmN5X2NvZGUiO3M6MzoiRVVSIjtzOjE5OiJxdW90ZV9jdXJyZW5jeV9jb2RlIjtzOjM6IkVVUiI7czoxMToiZ3JhbmRfdG90YWwiO3M6ODoiMzkwLjcwMDAiO3M6MTY6ImJhc2VfZ3JhbmRfdG90YWwiO3M6ODoiMzkwLjcwMDAiO3M6MTU6ImNoZWNrb3V0X21ldGhvZCI7czowOiIiO3M6MTE6ImN1c3RvbWVyX2lkIjtzOjE6IjIiO3M6MjE6ImN1c3RvbWVyX3RheF9jbGFzc19pZCI7czoxOiIzIjtzOjE3OiJjdXN0b21lcl9ncm91cF9pZCI7czoxOiIxIjtzOjE0OiJjdXN0b21lcl9lbWFpbCI7czoyOToibWFyaW8uYmVocmVuZHRAbmV0cmVzZWFyY2guZGUiO3M6MTU6ImN1c3RvbWVyX3ByZWZpeCI7TjtzOjE4OiJjdXN0b21lcl9maXJzdG5hbWUiO3M6NToiTWFyaW8iO3M6MTk6ImN1c3RvbWVyX21pZGRsZW5hbWUiO047czoxNzoiY3VzdG9tZXJfbGFzdG5hbWUiO3M6ODoiQmVocmVuZHQiO3M6MTU6ImN1c3RvbWVyX3N1ZmZpeCI7TjtzOjEyOiJjdXN0b21lcl9kb2IiO047czoxMzoiY3VzdG9tZXJfbm90ZSI7TjtzOjIwOiJjdXN0b21lcl9ub3RlX25vdGlmeSI7czoxOiIxIjtzOjE3OiJjdXN0b21lcl9pc19ndWVzdCI7czoxOiIwIjtzOjE1OiJjdXN0b21lcl90YXh2YXQiO047czo5OiJyZW1vdGVfaXAiO3M6MTM6IjE5Mi4xNjguMS4xNDgiO3M6MTY6ImFwcGxpZWRfcnVsZV9pZHMiO3M6MDoiIjtzOjE3OiJyZXNlcnZlZF9vcmRlcl9pZCI7czowOiIiO3M6MTM6InBhc3N3b3JkX2hhc2giO047czoxMToiY291cG9uX2NvZGUiO047czo4OiJzdWJ0b3RhbCI7czo4OiIzMjMuMzIwMCI7czoxMzoiYmFzZV9zdWJ0b3RhbCI7czo4OiIzMjMuMzIwMCI7czoyMjoic3VidG90YWxfd2l0aF9kaXNjb3VudCI7czo4OiIzMjMuMzIwMCI7czoyNzoiYmFzZV9zdWJ0b3RhbF93aXRoX2Rpc2NvdW50IjtzOjg6IjMyMy4zMjAwIjtzOjE1OiJnaWZ0X21lc3NhZ2VfaWQiO047czoxMDoiaXNfY2hhbmdlZCI7czoxOiIxIjtzOjE3OiJ0cmlnZ2VyX3JlY29sbGVjdCI7czoxOiIwIjtzOjE3OiJleHRfc2hpcHBpbmdfaW5mbyI7TjtzOjE1OiJjdXN0b21lcl9nZW5kZXIiO047czoxNToieF9mb3J3YXJkZWRfZm9yIjtOO31zOjE4OiIAKgBfaGFzRGF0YUNoYW5nZXMiO2I6MTtzOjEyOiIAKgBfb3JpZ0RhdGEiO047czoxNToiACoAX2lkRmllbGROYW1lIjtzOjk6ImVudGl0eV9pZCI7czoxMzoiACoAX2lzRGVsZXRlZCI7YjowO30= + diff --git a/app/code/community/Netresearch/OPS/Test/Model/Response/TestCase.php b/app/code/community/Netresearch/OPS/Test/Model/Response/TestCase.php new file mode 100644 index 0000000..0b72848 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Response/TestCase.php @@ -0,0 +1,62 @@ + + * @copyright 2016 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ + +/** + * Netresearch_OPS_Test_Model_Response_TestCase + * + * @category Netresearch + * @package Netresearch_OPS + * @author Christoph Aßmann + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ +abstract class Netresearch_OPS_Test_Model_Response_TestCase extends EcomDev_PHPUnit_Test_Case +{ + protected function mockOrderConfig() + { + $configMock = $this->getModelMock('sales/order_config', array('getDefaultStatus')); + $configMock + ->expects($this->any()) + ->method('getDefaultStatus') + ->will($this->returnArgument(0)); + $this->replaceByMock('singleton', 'sales/order_config', $configMock); + } + + /** + * Assert order confirmation email being (not) sent. + * + * @param PHPUnit_Framework_MockObject_Matcher_Invocation $matcher + */ + protected function mockEmailHelper(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher) + { + $helperMock = $this->getHelperMock('ops/data', array('sendTransactionalEmail')); + $helperMock + ->expects($matcher) + ->method('sendTransactionalEmail') + ->with($this->isInstanceOf('Mage_Sales_Model_Order')); + $this->replaceByMock('helper', 'ops/data', $helperMock); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Response/Type/AuthorizeTest.php b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/AuthorizeTest.php new file mode 100644 index 0000000..30dcb62 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/AuthorizeTest.php @@ -0,0 +1,267 @@ +mockEmailHelper($this->never()); + + $order = Mage::getModel('sales/order')->load(25); + $response = array( + 'status' => 2, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Type_Capture $handler */ + $handler = Mage::getModel('ops/response_type_authorize'); + $handler->handleResponse($response, $order->getPayment()->getMethodInstance(), false); + } + + /** + * @test + * @loadFixture orders.yaml + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage 500 is not a authorize status! + */ + public function testExceptionThrownForNoAuthorizeStatus() + { + $this->mockEmailHelper($this->never()); + + $order = Mage::getModel('sales/order')->load(25); + $response = array( + 'status' => 500, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Type_Capture $handler */ + $handler = Mage::getModel('ops/response_type_authorize'); + $handler->handleResponse($response, $order->getPayment()->getMethodInstance(), false); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPendingPaymentAndIntermediate() + { + $this->mockEmailHelper($this->once()); + $this->mockOrderConfig(); + + $order = Mage::getModel('sales/order')->load(28); + + $response = array( + 'status' => 51, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW, $order->getState()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPaymentReviewAndFinal() + { + $this->mockEmailHelper($this->once()); + $this->mockOrderConfig(); + + $order = Mage::getModel('sales/order')->load(26); + + $response = array( + 'status' => 5, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $payment = $order->getPayment()->getMethodInstance(); + $handler->processResponse($response, $payment); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, $order->getState()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPaymentReviewAndIntermediate() + { + $this->mockEmailHelper($this->once()); + $this->mockOrderConfig(); + + $order = Mage::getModel('sales/order')->load(26); + + $response = array( + 'status' => 51, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW, $order->getState()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPaymentReviewAndFinalDeclined() + { + $this->mockEmailHelper($this->never()); + $this->mockOrderConfig(); + + $order = Mage::getModel('sales/order')->load(26); + + $response = array( + 'status' => 2, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + $order->getPayment()->setAdditionalInformation('status', 46); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_CANCELED, $order->getState()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPendingPaymentAndFinal() + { + $this->mockEmailHelper($this->once()); + $this->mockOrderConfig(); + + $order = Mage::getModel('sales/order')->load(29); + $response = array( + 'status' => 5, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, $order->getState()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPendingPaymentAndSuspectedFraudStatus() + { + $this->mockEmailHelper($this->once()); + $order = Mage::getModel('sales/order')->load(30); + + $response = array( + 'status' => Netresearch_OPS_Model_Status::AUTHORIZED_WAITING_EXTERNAL_RESULT, + 'payid' => 12345678, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW, $order->getState()); + $this->assertEquals(Mage_Sales_Model_Order::STATUS_FRAUD, $order->getStatus()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPaymentReviewAndSuspectedFraudStatus() + { + $this->mockEmailHelper($this->once()); + + $order = Mage::getModel('sales/order')->load(31); + $response = array( + 'status' => Netresearch_OPS_Model_Status::AUTHORIZED_WAITING_EXTERNAL_RESULT, + 'payid' => 1234567897, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW, $order->getState()); + $this->assertEquals(Mage_Sales_Model_Order::STATUS_FRAUD, $order->getStatus()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + public function testStatusAuthorizationUnclear() + { + $this->mockEmailHelper($this->never()); + + $order = Mage::getModel('sales/order')->setState(Mage_Sales_Model_Order::STATE_NEW); + $payment = Mage::getModel('sales/order_payment')->setMethod('ops_cc'); + $order->setPayment($payment); + + $response = array( + 'status' => 52, + 'payid' => 12345678, + 'payidsub' => 3, + 'amount' => 33.33 + ); + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance(), false); + + $this->assertEquals(true, $payment->getIsTransactionPending()); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Response/Type/AuthorizeTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/AuthorizeTest/fixtures/orders.yaml new file mode 100644 index 0000000..b25d4fe --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/AuthorizeTest/fixtures/orders.yaml @@ -0,0 +1,239 @@ +tables: + sales/order: + - entity_id: 25 + increment_id: 100000025 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 10 + + - entity_id: 26 + increment_id: 100000026 + shipping_address_id: 43 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'payment_review' + status: 'payment_review' + quote_id: 26 + + - entity_id: 27 + increment_id: 100000027 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 27 + + + - entity_id: 28 + increment_id: 100000028 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 28 + + - entity_id: 29 + increment_id: 100000029 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 29 + + - entity_id: 30 + increment_id: 100000030 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 30 + + - entity_id: 31 + increment_id: 100000031 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'payment_review' + status: 'payment_review' + quote_id: 31 + + + + sales/quote: + - entity_id: 25 + parent_id: 25 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 26 + parent_id: 26 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 27 + parent_id: 27 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 28 + parent_id: 28 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 29 + parent_id: 29 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 30 + parent_id: 30 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 31 + parent_id: 31 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + + + + + + sales/quote_payment: + - payment_id: 25 + quote_id: 25 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 26 + quote_id: 26 + updated_at: '1970-01-01' + method: 'ops_iDeal' + + - payment_id: 27 + quote_id: 27 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:9:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 30 + quote_id: 30 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:9:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 31 + quote_id: 31 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:9:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + + + sales/order_payment: + - entity_id: 25 + parent_id: 25 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 26 + parent_id: 26 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 27 + parent_id: 27 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 28 + parent_id: 28 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 29 + parent_id: 29 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 30 + parent_id: 30 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 31 + parent_id: 31 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + + + sales/order_address: + - entity_id: 42 + parent_id: 25 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + - entity_id: 43 + parent_id: 26 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 diff --git a/app/code/community/Netresearch/OPS/Test/Model/Response/Type/CaptureTest.php b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/CaptureTest.php new file mode 100644 index 0000000..154478e --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/CaptureTest.php @@ -0,0 +1,188 @@ + + */ +class Netresearch_OPS_Test_Model_Response_Type_CaptureTest + extends Netresearch_OPS_Test_Model_Response_TestCase +{ + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPaymentReviewAndIntermediate() + { + $this->mockEmailHelper($this->once()); + + $order = Mage::getModel('sales/order')->load(26); + + $response = array( + 'status' => 91, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW, $order->getState()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPendingPaymentAndIntermediate() + { + $this->mockEmailHelper($this->once()); + + $order = Mage::getModel('sales/order')->load(25); + + $response = array( + 'status' => 91, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PAYMENT_REVIEW, $order->getState()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPaymentReviewAndFinal() + { + $this->mockEmailHelper($this->once()); + $this->mockOrderConfig(); + + $order = Mage::getModel('sales/order')->load(26); + + $response = array( + 'status' => 9, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PROCESSING, $order->getState()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testHandleResponseWithPendingPaymentAndFinal() + { + $this->mockEmailHelper($this->once()); + $this->mockOrderConfig(); + + $order = Mage::getModel('sales/order')->load(25); + $order->setBaseGrandTotal(33.33); + + $response = array( + 'status' => 9, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PROCESSING, $order->getState()); + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertNotEmpty($order->getInvoiceCollection()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + } + + /** + * @test + * @loadFixture orders.yaml + * @expectedException Mage_Core_Exception + */ + public function testExceptionThrown() + { + $this->mockEmailHelper($this->never()); + + $order = Mage::getModel('sales/order')->load(25); + + $response = array( + 'status' => 43, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Type_Capture $handler */ + $handler = Mage::getModel('ops/response_type_capture'); + $handler->handleResponse($response, $order->getPayment()->getMethodInstance()); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testAbortBecauseSameStatus() + { + $this->mockEmailHelper($this->never()); + + $order = Mage::getModel('sales/order')->load(27); + $order->getPayment()->setAdditionalInformation('status', 9); + + $response = array( + 'status' => 9, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + $order->getPayment()->setLastTransId($response['payid'].'/'.$response['payidsub']); + + /** @var Netresearch_OPS_Model_Response_Type_Capture $handler */ + $handler = Mage::getModel('ops/response_type_capture'); + $handler->handleResponse($response, $order->getPayment()->getMethodInstance()); + $this->assertEquals(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, $order->getState()); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Response/Type/CaptureTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/CaptureTest/fixtures/orders.yaml new file mode 100644 index 0000000..b85e560 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/CaptureTest/fixtures/orders.yaml @@ -0,0 +1,135 @@ +tables: + sales/order: + - entity_id: 25 + increment_id: 100000025 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 10 + + - entity_id: 26 + increment_id: 100000026 + shipping_address_id: 43 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'payment_review' + status: 'payment_review' + quote_id: 26 + + - entity_id: 27 + increment_id: 100000027 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 27 + + + + sales/quote: + - entity_id: 25 + parent_id: 25 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 26 + parent_id: 26 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 27 + parent_id: 27 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + + + sales/quote_payment: + - payment_id: 25 + quote_id: 25 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 26 + quote_id: 26 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 27 + quote_id: 27 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:9:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + + + sales/order_payment: + - entity_id: 25 + parent_id: 25 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 26 + parent_id: 26 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 27 + parent_id: 27 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + + + sales/order_address: + - entity_id: 42 + parent_id: 25 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + - entity_id: 43 + parent_id: 26 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + + diff --git a/app/code/community/Netresearch/OPS/Test/Model/Response/Type/RefundTest.php b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/RefundTest.php new file mode 100644 index 0000000..b0861bc --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/RefundTest.php @@ -0,0 +1,274 @@ + + */ +?> +load(25); + $response = array( + 'status' => 43, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Type_Capture $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + } + + /** + * @test + * @loadFixture orders.yaml + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage 2 is not a refund status! + */ + public function testExceptionThrownDueToNoRefundStatus() + { + /** @var Netresearch_OPS_Model_Payment_IDeal $instance */ + + $order = Mage::getModel('sales/order')->load(25); + $response = array( + 'status' => Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Netresearch_OPS_Model_Response_Type_Capture $handler */ + $handler = Mage::getModel('ops/response_type_refund'); + $handler->handleResponse($response, $order->getPayment()->getMethodInstance()); + } + + + /** + * @test + * @loadFixture orders.yaml + */ + public function testAbortBecauseSameStatus() + { + $order = Mage::getModel('sales/order')->load(27); + $order->getPayment()->setAdditionalInformation('status', 8); + $response = array( + 'status' => Netresearch_OPS_Model_Status::REFUNDED, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + $order->getPayment()->setLastTransId($response['payid'].'/'.$response['payidsub']); + /** @var Netresearch_OPS_Model_Response_Type_Capture $handler */ + $handler = Mage::getModel('ops/response_type_refund'); + $handler->handleResponse($response, $order->getPayment()->getMethodInstance()); + $this->assertEquals(Mage_Sales_Model_Order::STATE_PENDING_PAYMENT, $order->getState()); + } + + /** + * @test + * @loadFixture orders.yaml + */ + public function testCreditMemoStateOpenRefundSuccess() + { + $this->mockOrderConfig(); + + $order = Mage::getModel('sales/order')->load(27); + $response = array( + 'status' => Netresearch_OPS_Model_Status::REFUNDED, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + $order->setBillingAddress(Mage::getModel('sales/order_address')); + $invoice = Mage::getModel('sales/order_invoice'); + $invoice->setOrder($order); + $creditMemo = Mage::getModel('sales/order_creditmemo'); + $creditMemo->setInvoice($invoice); + $creditMemo->setId('1234567/3'); + $creditMemo->setOrder($order); + $creditMemo->setState(Mage_Sales_Model_Order_Creditmemo::STATE_OPEN); + $creditMemoMock = $this->getModelMock('sales/order_creditmemo', array('load')); + $creditMemoMock->expects($this->any()) + ->method('load') + ->will($this->returnValue($creditMemo)); + $order->getPayment()->setOrder($order); + + $this->replaceByMock('model', 'sales/order_creditmemo', $creditMemoMock); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order_Creditmemo::STATE_REFUNDED, $creditMemo->getState()); + + } + + + /** + * @test + * @loadFixture orders.yaml + */ + public function testCreditMemoStateOpenRefundRefused() + { + $this->mockOrderConfig(); + + $creditMemo = Mage::getModel('sales/order_creditmemo')->load(122); + $response = array( + 'status' => Netresearch_OPS_Model_Status::REFUND_REFUSED, + 'payid' => 1234567, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + $creditMemoMock = $this->getModelMock('sales/order_creditmemo', array('save', 'cancel', 'load')); + $creditMemoMock->expects($this->any()) + ->method('save') + ->will($this->returnSelf()); + $creditMemoMock->expects($this->any()) + ->method('cancel') + ->will($this->returnSelf()); + $creditMemoMock->expects($this->any()) + ->method('load') + ->will($this->returnValue($creditMemo)); + $this->replaceByMock('model', 'sales/order_creditmemo', $creditMemoMock); + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $creditMemo->getOrder()->getPayment()->getMethodInstance()); + + $this->assertEquals(Mage_Sales_Model_Order::STATE_PROCESSING, $creditMemo->getOrder()->getState()); + $this->assertEquals(Mage_Sales_Model_Order_Creditmemo::STATE_CANCELED, $creditMemo->getState()); + } + + + /** + * @test + * @loadFixture orders.yaml + */ + public function testCreditMemoRefundFinalState() + { + $this->mockOrderConfig(); + + $order = Mage::getModel('sales/order')->load(25); + + $response = array( + 'status' => Netresearch_OPS_Model_Status::REFUNDED, + 'payid' => 12345679, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Mage_Sales_Model_Order_Invoice $invoice */ + $invoice = $order->getInvoiceCollection()->getFirstItem(); + $creditMemoMock = $this->getModelMock('sales/order_creditmemo', array('getInvoice')); + $creditMemoMock->expects($this->any()) + ->method('getInvoice') + ->will($this->returnValue($invoice)); + + $this->replaceByMock('model', 'sales/order_creditmemo', $creditMemoMock); + + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $order->getPayment()->getMethodInstance()); + + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertNotEmpty($order->getInvoiceCollection()); + $this->assertEquals($response['status'], $order->getPayment()->getAdditionalInformation('status')); + + } + + + /** + * @test + * @loadFixture orders.yaml + */ + public function testCreditMemoRefundPendingState() + { + $this->mockOrderConfig(); + + $creditMemo = Mage::getModel('sales/order_creditmemo'); + + $paymentMock = $this->getModelMock('sales/order_payment', array('getCreatedCreditMemo')); + $paymentMock->expects($this->any()) + ->method('getCreatedCreditMemo') + ->will($this->returnValue($creditMemo)); + $this->replaceByMock('model', 'sales/order_payment', $paymentMock); + + + $payment = Mage::getModel('sales/order_payment')->load(25); + $order = Mage::getModel('sales/order')->load(25); + $payment->setOrder($order); + + $response = array( + 'status' => Netresearch_OPS_Model_Status::REFUND_PENDING, + 'payid' => 1234567534, + 'payidsub' => 3, + 'amount' => 33.33 + ); + + /** @var Mage_Sales_Model_Order_Invoice $invoice */ + $invoice = Mage::getModel('sales/order_invoice'); + $creditMemoMock = $this->getModelMock('sales/order_creditmemo', array('getInvoice')); + $creditMemoMock->expects($this->any()) + ->method('getInvoice') + ->will($this->returnValue($invoice)); + + $this->replaceByMock('model', 'sales/order_creditmemo', $creditMemoMock); + + + $transactionMock = $this->getModelMock('core/resource_transaction', array('save')); + $transactionMock->expects($this->any()) + ->method('save') + ->will($this->returnSelf()); + $this->replaceByMock('model', 'core/resource_transaction', $transactionMock); + + + + /** @var Netresearch_OPS_Model_Response_Handler $handler */ + $handler = Mage::getModel('ops/response_handler'); + $handler->processResponse($response, $payment->getMethodInstance()); + + + $this->assertNotEmpty($order->getAllStatusHistory()); + $this->assertNotEmpty($order->getInvoiceCollection()); + $this->assertEquals($response['status'], $payment->getAdditionalInformation('status')); + $this->assertEquals(Mage_Sales_Model_Order_Creditmemo::STATE_OPEN, $creditMemo->getState()); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Response/Type/RefundTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/RefundTest/fixtures/orders.yaml new file mode 100644 index 0000000..724662a --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Response/Type/RefundTest/fixtures/orders.yaml @@ -0,0 +1,157 @@ +tables: + sales/order: + - entity_id: 25 + increment_id: 100000025 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 10 + + - entity_id: 26 + increment_id: 100000026 + shipping_address_id: 43 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'payment_review' + status: 'payment_review' + quote_id: 26 + + - entity_id: 27 + increment_id: 100000027 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 27 + + + + sales/quote: + - entity_id: 25 + parent_id: 25 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 26 + parent_id: 26 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 27 + parent_id: 27 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + + + sales/quote_payment: + - payment_id: 25 + quote_id: 25 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 26 + quote_id: 26 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 27 + quote_id: 27 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:9:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + + + sales/order_payment: + - entity_id: 25 + parent_id: 25 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 26 + parent_id: 26 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + - entity_id: 27 + parent_id: 27 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_iDeal' + + + + sales/order_address: + - entity_id: 42 + parent_id: 25 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + - entity_id: 43 + parent_id: 26 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + sales/order_item: + - item_id: 1 + order_id: 27 + qty_ordered: 1 + + sales/invoice: + - entity_id: 27 + order_id: 27 + + - entity_id: 25 + order_id: 25 + + sales/creditmemo: + - entity_id: 122 + order_id: 27 + state: 'open' + transaction_id: '1234567/3' + invoice_id: 27 + + sales/creditmemo_item: + - entity_id: 1 + parent_id: 122 + order_item_id: 1 + qty: 1 diff --git a/app/code/community/Netresearch/OPS/Test/Model/Source/Cc/AliasInterfaceEnabledTypesTest.php b/app/code/community/Netresearch/OPS/Test/Model/Source/Cc/AliasInterfaceEnabledTypesTest.php new file mode 100644 index 0000000..8c9f20b --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Source/Cc/AliasInterfaceEnabledTypesTest.php @@ -0,0 +1,20 @@ + + */ +?> +assertArrayHasKey('VISA', $subject->toOptionArray()); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Source/DirectEbanking/BrandsTest.php b/app/code/community/Netresearch/OPS/Test/Model/Source/DirectEbanking/BrandsTest.php new file mode 100644 index 0000000..3e160c8 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Source/DirectEbanking/BrandsTest.php @@ -0,0 +1,19 @@ +toOptionArray(); + $this->assertTrue(is_array($options)); + $this->assertEquals($options[0]['value'], 'DirectEbanking'); + $this->assertEquals($options[1]['value'], 'DirectEbankingAT'); + $this->assertEquals($options[2]['value'], 'DirectEbankingBE'); + $this->assertEquals($options[3]['value'], 'DirectEbankingCH'); + $this->assertEquals($options[4]['value'], 'DirectEbankingDE'); + $this->assertEquals($options[5]['value'], 'DirectEbankingFR'); + $this->assertEquals($options[6]['value'], 'DirectEbankingGB'); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Source/Kwixo/ProductCategoriesTest.php b/app/code/community/Netresearch/OPS/Test/Model/Source/Kwixo/ProductCategoriesTest.php new file mode 100644 index 0000000..421e8a7 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Source/Kwixo/ProductCategoriesTest.php @@ -0,0 +1,51 @@ +toOptionArray(); + $this->assertTrue(is_array($options)); + // check for the existence of the keys for order or quote id + $this->assertEquals($options[0]['label'], 'Food & gastronomy'); + $this->assertEquals($options[1]['label'], 'Car & Motorbike'); + $this->assertEquals($options[2]['label'], 'Culture & leisure'); + $this->assertEquals($options[3]['label'], 'Home & garden'); + $this->assertEquals($options[4]['label'], 'Appliances'); + $this->assertEquals($options[5]['label'], 'Auctions and bulk purchases'); + $this->assertEquals($options[6]['label'], 'Flowers & gifts'); + $this->assertEquals($options[7]['label'], 'Computer & software'); + $this->assertEquals($options[8]['label'], 'Health & beauty'); + $this->assertEquals($options[9]['label'], 'Services for individuals'); + $this->assertEquals($options[10]['label'], 'Services for professionals'); + $this->assertEquals($options[11]['label'], 'Sports'); + $this->assertEquals($options[12]['label'], 'Clothing & accessories'); + $this->assertEquals($options[13]['label'], 'Travel & tourism'); + $this->assertEquals($options[14]['label'], 'Hifi, photo & video'); + $this->assertEquals($options[15]['label'], 'Telephony & communication'); + $this->assertEquals($options[16]['label'], 'Jewelry & precious metals'); + $this->assertEquals($options[17]['label'], 'Baby articles and accessories'); + $this->assertEquals($options[18]['label'], 'Sound & light'); + $this->assertEquals($options[0]['value'], 1); + $this->assertEquals($options[1]['value'], 2); + $this->assertEquals($options[2]['value'], 3); + $this->assertEquals($options[3]['value'], 4); + $this->assertEquals($options[4]['value'], 5); + $this->assertEquals($options[5]['value'], 6); + $this->assertEquals($options[6]['value'], 7); + $this->assertEquals($options[7]['value'], 8); + $this->assertEquals($options[8]['value'], 9); + $this->assertEquals($options[9]['value'], 10); + $this->assertEquals($options[10]['value'], 11); + $this->assertEquals($options[11]['value'], 12); + $this->assertEquals($options[12]['value'], 13); + $this->assertEquals($options[13]['value'], 14); + $this->assertEquals($options[14]['value'], 15); + $this->assertEquals($options[15]['value'], 16); + $this->assertEquals($options[16]['value'], 17); + $this->assertEquals($options[17]['value'], 18); + $this->assertEquals($options[18]['value'], 19); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Source/Kwixo/ShipMethodTypeTest.php b/app/code/community/Netresearch/OPS/Test/Model/Source/Kwixo/ShipMethodTypeTest.php new file mode 100644 index 0000000..b989a8d --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Source/Kwixo/ShipMethodTypeTest.php @@ -0,0 +1,25 @@ +toOptionArray(); + $this->assertTrue(is_array($options)); + // check for the existence of the keys for order or quote id + $this->assertEquals($options[0]['label'], '--Please select--'); + $this->assertEquals($options[1]['label'], 'Pick up at merchant'); + $this->assertEquals($options[2]['label'], 'Collection point (Kiala...)'); + $this->assertEquals($options[3]['label'], 'Collect at airport, train station or travel agency'); + $this->assertEquals($options[4]['label'], 'Transporter (La Poste, UPS...)'); + $this->assertEquals($options[5]['label'], 'Download'); + $this->assertEquals($options[0]['value'], ''); + $this->assertEquals($options[1]['value'], 1); + $this->assertEquals($options[2]['value'], 2); + $this->assertEquals($options[3]['value'], 3); + $this->assertEquals($options[4]['value'], 4); + $this->assertEquals($options[5]['value'], 5); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Source/ModeTest.php b/app/code/community/Netresearch/OPS/Test/Model/Source/ModeTest.php new file mode 100644 index 0000000..df36bae --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Source/ModeTest.php @@ -0,0 +1,21 @@ +toOptionArray(); + $this->assertTrue(is_array($options)); + $this->assertEquals(Netresearch_OPS_Model_Source_Mode::TEST, $options[0]['value']); + $this->assertEquals(Netresearch_OPS_Model_Source_Mode::PROD, $options[1]['value']); + $this->assertEquals(Netresearch_OPS_Model_Source_Mode::CUSTOM, $options[2]['value']); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Source/OrderReferenceTest.php b/app/code/community/Netresearch/OPS/Test/Model/Source/OrderReferenceTest.php new file mode 100644 index 0000000..9b43536 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Source/OrderReferenceTest.php @@ -0,0 +1,22 @@ +toOptionArray(); + $this->assertTrue(is_array($options)); + // check for the existence of the keys for order or quote id + $this->assertEquals($options[0]['value'], Netresearch_OPS_Model_Payment_Abstract::REFERENCE_QUOTE_ID); + $this->assertEquals($options[1]['value'], Netresearch_OPS_Model_Payment_Abstract::REFERENCE_ORDER_ID); + } + +} + + + diff --git a/app/code/community/Netresearch/OPS/Test/Model/Status/UpdateTest.php b/app/code/community/Netresearch/OPS/Test/Model/Status/UpdateTest.php new file mode 100644 index 0000000..86d4498 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Status/UpdateTest.php @@ -0,0 +1,311 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Test_Model_Status_UpdateTest extends EcomDev_PHPUnit_Test_Case +{ + + protected function mockSessions() + { + $sessionMock = $this->getModelMock('admin/session', array()); + $sessionMock->disableOriginalConstructor(); + $this->replaceByMock('singleton', 'admin/session', $sessionMock); + } + + public function testNoUpdateForNonOpsPayments() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('checkmo'); + $order->setPayment($payment); + $statusUpdateApi = Mage::getModel('ops/status_update'); + $statusUpdateApi->updateStatusFor($order); + $this->assertNull($statusUpdateApi->getOrder()); + } + + public function testBuildParamsForOpsOrderWithOrderId() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_paypal'); + $order->setPayment($payment); + $orderHelperMock = $this->getHelperMock('ops/order', array('getOpsOrderId')); + $orderHelperMock->expects($this->once()) + ->method('getOpsOrderId') + ->with($order, true) + ->will($this->returnValue('#1000000')); + + $statusUpdateApiMock = $this->getModelMock('ops/status_update', array('performRequest', 'updatePaymentStatus')); + $statusUpdateApiMock->setOrderHelper($orderHelperMock); + $statusUpdateApiMock->updateStatusFor($order); + $requestParams = $statusUpdateApiMock->getRequestParams(); + $this->assertArrayHasKey('ORDERID', $requestParams); + $this->assertEquals('#1000000', $requestParams['ORDERID']); + } + + public function testBuildParamsForOpsOrderWithQuoteId() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_directDebit'); + $order->setPayment($payment); + $orderHelperMock = $this->getHelperMock('ops/order', array('getOpsOrderId')); + $orderHelperMock->expects($this->once()) + ->method('getOpsOrderId') + ->with($order, false) + ->will($this->returnValue('100')); + + $statusUpdateApiMock = $this->getModelMock('ops/status_update', array('performRequest', 'updatePaymentStatus')); + $statusUpdateApiMock->setOrderHelper($orderHelperMock); + $statusUpdateApiMock->updateStatusFor($order); + $requestParams = $statusUpdateApiMock->getRequestParams(); + $this->assertArrayHasKey('ORDERID', $requestParams); + $this->assertEquals('100', $requestParams['ORDERID']); + } + + public function testBuildParamsForOpsOrderWithPayId() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_directDebit'); + $payment->setAdditionalInformation('paymentId', 4711); + $order->setPayment($payment); + + $statusUpdateApiMock = $this->getModelMock('ops/status_update', array('performRequest', 'updatePaymentStatus')); + $statusUpdateApiMock->updateStatusFor($order); + $requestParams = $statusUpdateApiMock->getRequestParams(); + $this->assertArrayHasKey('PAYID', $requestParams); + $this->assertEquals(4711, $requestParams['PAYID']); + } + + public function testPerformRequestWithPayId() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_directDebit'); + $payment->setAdditionalInformation('paymentId', 4711); + $order->setPayment($payment); + + $statusUpdateApiMock = $this->getModelMock('ops/status_update', array('updatePaymentStatus')); + $directLinkApiMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkApiMock->expects($this->once()) + ->method('performRequest') + ->with( + array( + 'PAYID' => 4711 + ), + Mage::getModel('ops/config')->getDirectLinkMaintenanceApiPath($order->getStoreId()), + $order->getStoreId() + ) + ->will($this->returnValue(array('STATUS' => 5))); + $statusUpdateApiMock->setDirectLinkApi($directLinkApiMock); + $statusUpdateApiMock->updateStatusFor($order); + $opsResponse = $statusUpdateApiMock->getOpsResponse(); + $this->assertArrayHasKey('STATUS', $opsResponse); + $this->assertEquals(5, $opsResponse['STATUS']); + } + + + public function testPerformRequestWithPayIdAndPayIdSub() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_directDebit'); + $payment->setAdditionalInformation('paymentId', 4711); + $payment->setAdditionalInformation('status', 91); + $payment->setLastTransId('4711/1'); + $order->setPayment($payment); + + $statusUpdateApiMock = $this->getModelMock('ops/status_update', array('updatePaymentStatus')); + $directLinkApiMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkApiMock->expects($this->once()) + ->method('performRequest') + ->with( + array( + 'PAYID' => 4711, + 'PAYIDSUB' => 1 + ), + Mage::getModel('ops/config')->getDirectLinkMaintenanceApiPath($order->getStoreId()), + $order->getStoreId() + ) + ->will($this->returnValue(array('STATUS' => 5))); + $statusUpdateApiMock->setDirectLinkApi($directLinkApiMock); + $statusUpdateApiMock->updateStatusFor($order); + $opsResponse = $statusUpdateApiMock->getOpsResponse(); + $this->assertArrayHasKey('STATUS', $opsResponse); + $this->assertEquals(5, $opsResponse['STATUS']); + } + + public function testPerformRequestWithPayIdAndPayIdSubForRefund() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_directDebit'); + $payment->setAdditionalInformation('paymentId', 4711); + $payment->setAdditionalInformation('status', 81); + $payment->setLastTransId('4711/1'); + $order->setPayment($payment); + + $statusUpdateApiMock = $this->getModelMock('ops/status_update', array('updatePaymentStatus')); + $directLinkApiMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkApiMock->expects($this->once()) + ->method('performRequest') + ->with( + array( + 'PAYID' => 4711, + 'PAYIDSUB' => 1, + ), + Mage::getModel('ops/config')->getDirectLinkMaintenanceApiPath($order->getStoreId()), + $order->getStoreId() + ) + ->will($this->returnValue(array('STATUS' => 8, 'AMOUNT' => 1,))); + + $statusUpdateApiMock->setDirectLinkApi($directLinkApiMock); + $statusUpdateApiMock->updateStatusFor($order); + $opsResponse = $statusUpdateApiMock->getOpsResponse(); + $this->assertArrayHasKey('STATUS', $opsResponse); + $this->assertEquals(8, $opsResponse['STATUS']); + $this->assertArrayHasKey('AMOUNT', $opsResponse); + $this->assertArrayHasKey('amount', $opsResponse); + $this->assertEquals(1, $opsResponse['AMOUNT']); + $this->assertEquals($opsResponse['amount'], $opsResponse['AMOUNT']); + } + + public function testUpdatePaymentStatusWithoutStatusChange() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_directDebit'); + $payment->setAdditionalInformation('paymentId', 4711); + $payment->setAdditionalInformation('status', Netresearch_OPS_Model_Status::REFUNDED); + $order->setPayment($payment); + + $paymentHelperMock = $this->getHelperMock('ops/payment', array('saveOpsStatusToPayment')); + $paymentHelperMock->expects($this->never()) + ->method('saveOpsStatusToPayment') + ->will($this->returnValue('foo')); + ; + + // no email on refund response type + $dataHelperMock = $this->getHelperMock('ops/data', array('sendTransactionalEmail')); + $dataHelperMock + ->expects($this->never()) + ->method('sendTransactionalEmail') + ->with($this->isInstanceOf('Mage_Sales_Model_Order')); + $this->replaceByMock('helper', 'ops/data', $dataHelperMock); + + + $statusUpdateApi = Mage::getModel('ops/status_update'); + $directLinkApiMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkApiMock->expects($this->once()) + ->method('performRequest') + ->with( + array( + 'PAYID' => 4711, + ), + Mage::getModel('ops/config')->getDirectLinkMaintenanceApiPath($order->getStoreId()), + $order->getStoreId() + ) + ->will($this->returnValue(array('STATUS' => Netresearch_OPS_Model_Status::REFUNDED,))); + + $adminSessionMock = $this->getModelMock('adminhtml/session', array('init', 'save', 'addNotice')); + $statusUpdateApi->setMessageContainer($adminSessionMock); + $statusUpdateApi->setPaymentHelper($paymentHelperMock); + $statusUpdateApi->setDirectLinkApi($directLinkApiMock); + $statusUpdateApi->updateStatusFor($order); + $opsResponse = $statusUpdateApi->getOpsResponse(); + $this->assertArrayHasKey('STATUS', $opsResponse); + $this->assertEquals(Netresearch_OPS_Model_Status::REFUNDED, $opsResponse['STATUS']); + } + + public function testUpdatePaymentStatusWithStatusChange() + { + $order = Mage::getModel('sales/order'); + $payment = $this->getModelMock('sales/order_payment', array('save')); + $payment->setMethod('ops_directDebit'); + $payment->setAdditionalInformation('paymentId', 4711); + $payment->setAdditionalInformation('status', 5); + $order->setPayment($payment); + $response = array('STATUS' => 91,); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('saveOpsStatusToPayment')); + $paymentHelperMock->expects($this->any()) + ->method('saveOpsStatusToPayment') + ->with($payment, $response); + + $dataHelperMock = $this->getHelperMock('ops/data', array('sendTransactionalEmail')); + $dataHelperMock + ->expects($this->once()) + ->method('sendTransactionalEmail') + ->with($this->isInstanceOf('Mage_Sales_Model_Order')); + $this->replaceByMock('helper', 'ops/data', $dataHelperMock); + + $adminSessionMock = $this->getModelMock('adminhtml/session', array('init', 'save', 'addSuccess')); + + $statusUpdateApi = Mage::getModel('ops/status_update'); + $statusUpdateApi->setMessageContainer($adminSessionMock); + $directLinkApiMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkApiMock->expects($this->once()) + ->method('performRequest') + ->with( + array( + 'PAYID' => 4711, + ), + Mage::getModel('ops/config')->getDirectLinkMaintenanceApiPath($order->getStoreId()), + $order->getStoreId() + ) + ->will($this->returnValue($response)); + + $statusUpdateApi->setPaymentHelper($paymentHelperMock); + $statusUpdateApi->setDirectLinkApi($directLinkApiMock); + + $statusUpdateApi->updateStatusFor($order); + $opsResponse = $statusUpdateApi->getOpsResponse(); + $this->assertArrayHasKey('STATUS', $opsResponse); + $this->assertEquals(91, $opsResponse['STATUS']); + + } + + public function testUpdatePaymentStatusWithStatusChangeOnInitialRequest() + { + $order = Mage::getModel('sales/order'); + $payment = Mage::getModel('sales/order_payment'); + $payment->setMethod('ops_directDebit'); + $order->setPayment($payment); + $response = array('STATUS' => 91,); + $paymentHelperMock = $this->getHelperMock('ops/payment', array('saveOpsStatusToPayment', 'applyStateForOrder')); + $paymentHelperMock->expects($this->any()) + ->method('saveOpsStatusToPayment') + ->with($payment, $response); + + $paymentHelperMock->expects($this->any()) + ->method('applyStateForOrder') + ->with($order, $response); + $adminSessionMock = $this->getModelMock('adminhtml/session', array('init', 'save', 'addSuccess')); + $statusUpdateApiMock = Mage::getModel('ops/status_update'); + $statusUpdateApiMock->setMessageContainer($adminSessionMock); + $directLinkApiMock = $this->getModelMock('ops/api_directlink', array('performRequest')); + $directLinkApiMock->expects($this->once()) + ->method('performRequest') + ->with( + array( + 'ORDERID' => Mage::getModel('ops/config')->getConfigData('devprefix') . '', + ), + Mage::getModel('ops/config')->getDirectLinkMaintenanceApiPath($order->getStoreId()), + $order->getStoreId() + ) + ->will($this->returnValue($response)); + + + $statusUpdateApiMock->setPaymentHelper($paymentHelperMock); + $statusUpdateApiMock->setDirectLinkApi($directLinkApiMock); + + $statusUpdateApiMock->updateStatusFor($order); + $opsResponse = $statusUpdateApiMock->getOpsResponse(); + $this->assertArrayHasKey('STATUS', $opsResponse); + $this->assertEquals(91, $opsResponse['STATUS']); + + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Status/UpdateTest/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/Model/Status/UpdateTest/fixtures/orders.yaml new file mode 100644 index 0000000..e99edfd --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Status/UpdateTest/fixtures/orders.yaml @@ -0,0 +1,135 @@ +tables: + sales/order: + - entity_id: 25 + increment_id: 100000025 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'payment_review' + status: 'payment_review' + quote_id: 10 + + - entity_id: 26 + increment_id: 100000026 + shipping_address_id: 43 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'payment_review' + status: 'payment_review' + quote_id: 26 + + - entity_id: 27 + increment_id: 100000027 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 27 + + + + sales/quote: + - entity_id: 25 + parent_id: 25 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 26 + parent_id: 26 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + - entity_id: 27 + parent_id: 27 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + + + + sales/quote_payment: + - payment_id: 25 + quote_id: 25 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + + - payment_id: 26 + quote_id: 26 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + + - payment_id: 27 + quote_id: 27 + updated_at: '1970-01-01' + method: 'ops_iDeal' + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:9:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + + + + sales/order_payment: + - entity_id: 25 + parent_id: 25 + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + method: 'ops_iDeal' + + - entity_id: 26 + parent_id: 26 + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + method: 'ops_iDeal' + + - entity_id: 27 + parent_id: 27 + additional_information: 'a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;}' + method: 'ops_iDeal' + + + + sales/order_address: + - entity_id: 42 + parent_id: 25 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + - entity_id: 43 + parent_id: 26 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + + diff --git a/app/code/community/Netresearch/OPS/Test/Model/StatusTest.php b/app/code/community/Netresearch/OPS/Test/Model/StatusTest.php new file mode 100644 index 0000000..87eb9b9 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/StatusTest.php @@ -0,0 +1,122 @@ +assertTrue(Netresearch_OPS_Model_Status::isFinal($status)); + $status = Netresearch_OPS_Model_Status::AUTHORIZED_WAITING_EXTERNAL_RESULT; + $this->assertFalse(Netresearch_OPS_Model_Status::isFinal($status)); + + } + + public function testIsIntermediate() + { + $status = Netresearch_OPS_Model_Status::AUTHORIZED_WAITING_EXTERNAL_RESULT; + $this->assertTrue(Netresearch_OPS_Model_Status::isIntermediate($status)); + $status = Netresearch_OPS_Model_Status::AUTHORIZED; + $this->assertFalse(Netresearch_OPS_Model_Status::isIntermediate($status)); + } + + + public function testIsCapture() + { + $captureStatus = array( + Netresearch_OPS_Model_Status::PAYMENT_REQUESTED, + Netresearch_OPS_Model_Status::PAYMENT_PROCESSING, + Netresearch_OPS_Model_Status::PAYMENT_UNCERTAIN, + Netresearch_OPS_Model_Status::PAYMENT_REFUSED, + Netresearch_OPS_Model_Status::PAYMENT_DECLINED_BY_ACQUIRER, + Netresearch_OPS_Model_Status::PAYMENT_PROCESSED_BY_MERCHANT, + Netresearch_OPS_Model_Status::REFUND_REVERSED, + Netresearch_OPS_Model_Status::PAYMENT_IN_PROGRESS + ); + foreach ($captureStatus as $status) { + $this->assertTrue(Netresearch_OPS_Model_Status::isCapture($status)); + } + $this->assertFalse(Netresearch_OPS_Model_Status::isCapture(Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED)); + } + + public function testIsRefund() + { + $refundStatus = array( + Netresearch_OPS_Model_Status::REFUNDED, + Netresearch_OPS_Model_Status::REFUND_PENDING, + Netresearch_OPS_Model_Status::REFUND_UNCERTAIN, + Netresearch_OPS_Model_Status::REFUND_REFUSED, + Netresearch_OPS_Model_Status::REFUNDED_OK, + Netresearch_OPS_Model_Status::REFUND_PROCESSED_BY_MERCHANT, + ); + foreach ($refundStatus as $status) { + $this->assertTrue(Netresearch_OPS_Model_Status::isRefund($status)); + } + $this->assertFalse(Netresearch_OPS_Model_Status::isCapture(Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED)); + } + + public function testIsVoid() + { + $voidStatus = array( + Netresearch_OPS_Model_Status::AUTHORIZED_AND_CANCELLED, + Netresearch_OPS_Model_Status::DELETION_WAITING, + Netresearch_OPS_Model_Status::DELETION_UNCERTAIN, + Netresearch_OPS_Model_Status::DELETION_REFUSED, + Netresearch_OPS_Model_Status::AUTHORIZED_AND_CANCELLED_OK, + ); + foreach ($voidStatus as $status) { + $this->assertTrue(Netresearch_OPS_Model_Status::isVoid($status)); + } + $this->assertFalse(Netresearch_OPS_Model_Status::isVoid(Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED)); + } + + + public function testIsAuthorize() + { + $authStatus = array( + Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED, + Netresearch_OPS_Model_Status::AUTHORIZED, + Netresearch_OPS_Model_Status::AUTHORIZED_WAITING_EXTERNAL_RESULT, + Netresearch_OPS_Model_Status::AUTHORIZATION_WAITING, + Netresearch_OPS_Model_Status::AUTHORIZED_UNKNOWN, + Netresearch_OPS_Model_Status::STAND_BY, + Netresearch_OPS_Model_Status::OK_WITH_SHEDULED_PAYMENTS, + Netresearch_OPS_Model_Status::NOT_OK_WITH_SHEDULED_PAYMENTS, + Netresearch_OPS_Model_Status::AUTHORISATION_TO_BE_REQUESTED_MANUALLY + ); + foreach ($authStatus as $status) { + $this->assertTrue(Netresearch_OPS_Model_Status::isAuthorize($status)); + } + $this->assertFalse(Netresearch_OPS_Model_Status::isAuthorize(Netresearch_OPS_Model_Status::REFUND_PENDING)); + } + + public function testIsWaitingStatus() + { + $waitingStatus = array( + Netresearch_OPS_Model_Status::WAITING_CLIENT_PAYMENT, + Netresearch_OPS_Model_Status::WAITING_FOR_IDENTIFICATION, + Netresearch_OPS_Model_Status::STORED_WAITING_EXTERNAL_RESULT + ); + foreach ($waitingStatus as $status) { + $this->assertTrue(Netresearch_OPS_Model_Status::isSpecialStatus($status)); + } + $this->assertFalse(Netresearch_OPS_Model_Status::isSpecialStatus(Netresearch_OPS_Model_Status::REFUND_PENDING)); + } + + + public function testCanResendPaymentInfo() + { + $canResendInfoStatus = array( + Netresearch_OPS_Model_Status::NOT_OK_WITH_SHEDULED_PAYMENTS, + Netresearch_OPS_Model_Status::CANCELED_BY_CUSTOMER, + Netresearch_OPS_Model_Status::AUTHORISATION_DECLINED, + Netresearch_OPS_Model_Status::AUTHORISATION_TO_BE_REQUESTED_MANUALLY, + Netresearch_OPS_Model_Status::PAYMENT_UNCERTAIN, + Netresearch_OPS_Model_Status::PAYMENT_REFUSED, + ); + foreach ($canResendInfoStatus as $status) { + $this->assertTrue(Netresearch_OPS_Model_Status::canResendPaymentInfo($status)); + } + $this->assertFalse(Netresearch_OPS_Model_Status::canResendPaymentInfo(Netresearch_OPS_Model_Status::REFUND_PENDING)); + } +} + diff --git a/app/code/community/Netresearch/OPS/Test/Model/Subscription/ManagerTest.php b/app/code/community/Netresearch/OPS/Test/Model/Subscription/ManagerTest.php new file mode 100644 index 0000000..5621ea5 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Subscription/ManagerTest.php @@ -0,0 +1,176 @@ + + */ +?> +assertTrue($subject->getSubscriptionHelper() instanceof Netresearch_OPS_Helper_Subscription); + $subject->setSubscriptionHelper($testString); + $this->assertEquals($subject->getSubscriptionHelper(), $testString); + + $this->assertTrue($subject->getPaymentHelper() instanceof Netresearch_OPS_Helper_Payment); + $subject->setPaymentHelper($testString); + $this->assertEquals($subject->getPaymentHelper(), $testString); + } + + /** + * @return Mage_Sales_Model_Recurring_Profile + */ + protected function getTestProfile() + { + $profile = Mage::getModel('sales/recurring_profile'); + $profile->setScheduleDescription('abc') + ->setPeriodFrequency(1) + ->setPeriodMaxCycles(1) + ->setPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_WEEK) + ->setInitAmount(10) + ->setBillingAmount(15) + ->setTaxAmount(5) + ->setShippingAmount(5) + ->setStartDatetime('') + ->setBillingAddressInfo(array('quote_id' => 1)) + ->setMethodCode(Netresearch_OPS_Model_Payment_Recurring_Cc::CODE) + ->setId(0); + + return $profile; + } + + /** + * @return string[] + */ + protected function getResponseParams() + { + + $responseParams = array( + 'PAYID' => '123', + 'currency' => 'EUR', + 'STATUS' => '5', + 'orderID' => 'SUB-123', + 'amount' => '0' + ); + + return $responseParams; + + } + + protected function mockDependencies() + { + + $profileMock = $this->getModelMock('sales/recurring_profile', array('addOrderRelation')); + $profileMock->expects($this->once()) + ->method('addOrderRelation') + ->will($this->returnValue('')); + $this->replaceByMock('model', 'sales/recurring_profile', $profileMock); + + $paymentHelperMock = $this->getHelperMock('ops/payment', array('applyStateForOrder')); + $paymentHelperMock->expects($this->once()) + ->method('applyStateForOrder') + ->will($this->returnValue('')); + $this->replaceByMock('helper', 'ops/payment', $paymentHelperMock); + + $orderMock = $this->getModelMock('sales/order', array('save')); + $orderMock->expects($this->any()) + ->method('save') + ->will($this->returnValue('')); + $this->replaceByMock('model', 'sales/order', $orderMock); + + $paymentMock = $this->getModelMock( + 'sales/order_payment', + array('registerAuthorizationNotification', 'registerCaptureNotification') + ); + $paymentMock->expects($this->any()) + ->method('registerCaptureNotification') + ->will($this->returnValue('')); + $paymentMock->expects($this->any()) + ->method('registerAuthorizationNotification') + ->will($this->returnValue('')); + $this->replaceByMock('model', 'sales/order_payment', $paymentMock); + } + + public function testProcessSubscriptionFeedbackWithInitialCreation() + { + $subject = Mage::getModel('ops/subscription_manager'); + $profile = $this->getTestProfile(); + $responseParams = $this->getResponseParams(); + + $responseParams['creation_status'] = Netresearch_OPS_Model_Subscription_Manager::CREATION_SUCCEEDED; + $responseParams['subscription_id'] = 'test'; + $result = $subject->processSubscriptionFeedback($responseParams, $profile); + + $this->assertFalse($result); + $this->assertEquals($profile->getReferenceId(), 'test'); + $this->assertEquals($profile->getState(), Mage_Sales_Model_Recurring_Profile::STATE_ACTIVE); + } + + public function testProcessSubscriptionFeedbackWithRegularSubscriptionFeedback() + { + $this->mockDependencies(); + $subject = Mage::getModel('ops/subscription_manager'); + $profile = $this->getTestProfile(); + $responseParams = $this->getResponseParams(); + + $result = $subject->processSubscriptionFeedback($responseParams, $profile); + + $this->assertTrue($result instanceof Mage_Sales_Model_Order); + $this->assertEquals($result->getShippingAmount(), $profile->getShippingAmount()); + $this->assertEquals($result->getTaxAmount(), $profile->getTaxAmount()); + $this->assertEquals($result->getSubtotal(), $profile->getBillingAmount()); + } + + public function testProcessSubscriptionFeedbackWithTrialSubscriptionFeedback() + { + /** @var $subject Netresearch_OPS_Model_Subscription_Manager */ + $this->mockDependencies(); + $subject = Mage::getModel('ops/subscription_manager'); + $profile = $this->getTestProfile(); + $responseParams = $this->getResponseParams(); + $responseParams['orderID'] = $responseParams['orderID'] . Netresearch_OPS_Helper_Subscription::TRIAL_SUFFIX; + $profile->setTrialPeriodUnit(Mage_Sales_Model_Recurring_Profile::PERIOD_UNIT_MONTH) + ->setTrialBillingAmount(5) + ->setTrialPeriodFrequency(3) + ->setTrialPeriodMaxCycles(4) + ->setInitAmount(0) + ->setOrderItemInfo(array('tax_percent' => 7)); + + + $result = $subject->processSubscriptionFeedback($responseParams, $profile); + + $this->assertTrue($result instanceof Mage_Sales_Model_Order); + $this->assertEquals($result->getShippingAmount(), $profile->getShippingAmount()); + $this->assertEquals($result->getBaseGrandTotal(), $profile->getTrialBillingAmount() + $profile->getShippingAmount()); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/Design/BrandsTest.php b/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/Design/BrandsTest.php new file mode 100644 index 0000000..1de0549 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/Design/BrandsTest.php @@ -0,0 +1,53 @@ + + */ +class Netresearch_OPS_Test_Model_System_Config_Backend_Intersolve_BrandsTest + extends EcomDev_PHPUnit_Test_Case +{ + + public function testSave() + { + $model = Mage::getModel('ops/system_config_backend_intersolve_brands'); + $invalidData = array( + array('brand' => '123', 'value' => '1234'), + array('brand' => '123', 'value' => '1234') + ); + $model->setValue($invalidData); + $this->setExpectedException('Mage_Core_Exception', 'Brands must be unique'); + $model->save(); + $validData = array( + array('brand' => '123', 'value' => '1234'), + array('brand' => '1234', 'value' => '1234') + ); + $model->setValue($validData); + $this->assertTrue(($model->save() instanceof Netresearch_Ops_Model_System_Config_Backend_Intersolve_Brands)); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/EncodingTest.php b/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/EncodingTest.php new file mode 100644 index 0000000..4170b30 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/EncodingTest.php @@ -0,0 +1,60 @@ + + * @copyright 2016 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ + +/** + * Netresearch_OPS_Test_Block_System_Config_EncodingTest + * + * @category OPS + * @package Netresearch_OPS + * @author Sebastian Ertner + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ +class Netresearch_OPS_Test_Model_System_Config_Backend_EncodingTest + extends EcomDev_PHPUnit_Test_Case +{ + /** + * @test + */ + public function testToOptionArray() + { + /** @var Netresearch_Ops_Model_System_Config_Backend_PaymentLogo $optionModel */ + $optionModel = Mage::getModel('ops/system_config_backend_encoding'); + + $result = $optionModel->toOptionArray(); + + $this->assertInternalType('array', $result); + $this->assertArrayHasKey('value', $result[0]); + $this->assertArrayHasKey('label', $result[0]); + + $this->assertEquals('utf-8', $result[0]['value']); + $this->assertEquals('UTF-8', $result[0]['label']); + + $this->assertEquals('other', $result[1]['value']); + $this->assertEquals('Other', $result[1]['label']); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/Flex/MethodsTest.php b/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/Flex/MethodsTest.php new file mode 100644 index 0000000..59385f4 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/Flex/MethodsTest.php @@ -0,0 +1,106 @@ + + */ +?> +setValue($this->getEmpty()); + + $model->save(); + } + + /** + * @expectedException Mage_Core_Exception + * @expectedExceptionMessage PM and Brand combination must be unique + */ + public function testSaveWithDuplicateException() + { + $model = Mage::getModel('ops/system_config_backend_flex_methods'); + $model->setValue($this->getDuplicate()); + + $model->save(); + + } + + public function testSave() + { + $model = Mage::getModel('ops/system_config_backend_flex_methods'); + $model->setValue(array($this->getSimpleData())); + $model->setScope('default') + ->setScopeId(0) + ->setPath('payment/ops_flex/methods'); + + $model->save(); + Mage::getConfig()->cleanCache(); + $methods = unserialize(Mage::getStoreConfig('payment/ops_flex/methods')); + + $this->assertTrue(is_array($methods)); + $this->assertEquals(1, count($methods)); + } + + protected function getDuplicate() + { + return array( + $this->getSimpleData(), + $this->getSimpleData() + ); + } + + protected function getEmpty() + { + return array( + $this->getSimpleData(), + array( + 'title' => '', + 'brand' => '', + 'pm' => '' + ) + ); + } + + protected function getSimpleData() + { + return array( + 'title' => 'foo', + 'brand' => 'bar', + 'pm' => 'zzz' + ); + } +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/PaymentLogoTest.php b/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/PaymentLogoTest.php new file mode 100644 index 0000000..05fc416 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/System/Config/Backend/PaymentLogoTest.php @@ -0,0 +1,53 @@ + + * @copyright 2016 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ + +/** + * Netresearch_OPS_Test_Block_System_Config_PaymentLogoTest + * + * @category OPS + * @package Netresearch_OPS + * @author Benjamin Heuer + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * @link http://www.netresearch.de/ + */ +class Netresearch_OPS_Test_Model_System_Config_Backend_PaymentLogoTest + extends EcomDev_PHPUnit_Test_Case +{ + /** + * @test + */ + public function toOptionArray() + { + /** @var Netresearch_Ops_Model_System_Config_Backend_PaymentLogo $optionModel */ + $optionModel = Mage::getModel('ops/system_config_backend_paymentLogo'); + + $result = $optionModel->toOptionArray(); + + $this->assertInternalType('array', $result); + $this->assertArrayHasKey('value', $result[0]); + } + +} diff --git a/app/code/community/Netresearch/OPS/Test/Model/Validator/Kwixo/Shipping/Setting.php b/app/code/community/Netresearch/OPS/Test/Model/Validator/Kwixo/Shipping/Setting.php new file mode 100644 index 0000000..72addd3 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Validator/Kwixo/Shipping/Setting.php @@ -0,0 +1,124 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Model_Validator_Kwixo_Shipping_Setting + extends EcomDev_PHPUnit_Test_Case_Config +{ + + private function getValidator() + { + return Mage::getModel('ops/validator_kwixo_shipping_setting'); + } + + public function testKwixoShippingTypeIsValid() + { + $validator = $this->getValidator(); + $this->assertFalse($validator->isValid(array())); + $falseData = array('dhl' => array('kwixo_shipping_type' => 'abc')); + $this->assertFalse($validator->isValid($falseData)); + $this->assertTrue(0 < count($validator->getMessages())); + $falseData = array('dhl' => array('foo' => 'abc')); + $this->assertFalse($validator->isValid($falseData)); + $falseData = array('dhl' => array('kwixo_shipping_type' => 0)); + $this->assertFalse($validator->isValid($falseData)); + $falseData = array('dhl' => array('kwixo_shipping_type' => 6)); + $this->assertFalse($validator->isValid($falseData)); + $messages = $validator->getMessages(); + $this->assertEquals( + 'invalid shipping type provided', + $messages['dhl']['kwixo_shipping_type_error'] + ); + $correctData = array( + 'dhl' => array( + 'kwixo_shipping_type' => 4, 'kwixo_shipping_speed' => 1, + 'kwixo_shipping_details' => '' + ) + ); + $this->assertTrue($validator->isValid($correctData)); + } + + public function testKwixoShippingSpeedIsValid() + { + $validator = $this->getValidator(); + $this->assertFalse($validator->isValid(array())); + $falseData = array( + 'dhl' => array( + 'kwixo_shipping_speed' => 'abc', 'kwixo_shipping_type' => 4 + ) + ); + $this->assertFalse($validator->isValid($falseData)); + $this->assertTrue(0 < count($validator->getMessages())); + $falseData = array( + 'dhl' => array( + 'foo' => 'abc', 'kwixo_shipping_type' => 4 + ) + ); + $this->assertFalse($validator->isValid($falseData)); + $falseData = array( + 'dhl' => array( + 'kwixo_shipping_speed' => 0, 'kwixo_shipping_type' => 4 + ) + ); + $this->assertFalse($validator->isValid($falseData)); + $messages = $validator->getMessages(); + $this->assertEquals( + 'invalid shipping speed provided', + $messages['dhl']['kwixo_shipping_speed_error'] + ); + $correctData = array( + 'dhl' => array( + 'kwixo_shipping_speed' => 4, 'kwixo_shipping_type' => 4, + 'kwixo_shipping_details' => '' + ) + ); + $this->assertTrue($validator->isValid($correctData)); + } + + public function testKwixoShippingDetailsIsValid() + { + $validator = $this->getValidator(); + $this->assertFalse($validator->isValid(array())); + $falseData = array( + 'dhl' => array( + 'kwixo_shipping_speed' => 4, 'kwixo_shipping_type' => 4 + ) + ); + $this->assertFalse($validator->isValid($falseData)); + $correctData = array( + 'dhl' => array( + 'kwixo_shipping_speed' => 4, 'kwixo_shipping_type' => 4, + 'kwixo_shipping_details' => '' + ) + ); + $this->assertTrue($validator->isValid($correctData)); + $longString = '012345678901234567890123456789012345678901234567891'; + $falseData = array( + 'dhl' => array( + 'kwixo_shipping_speed' => 4, 'kwixo_shipping_type' => 4, + 'kwixo_shipping_details' => $longString + ) + ); + $this->assertFalse($validator->isValid($falseData)); + $messages = $validator->getMessages(); + $this->assertEquals( + 'invalid shipping details provided', + $messages['dhl']['kwixo_shipping_details_error'] + ); + $longString = '01234567890123456789012345678901234567890123456789'; + $correctData = array( + 'dhl' => array( + 'kwixo_shipping_speed' => 4, 'kwixo_shipping_type' => 4, + 'kwixo_shipping_details' => $longString + ) + ); + $this->assertTrue($validator->isValid($correctData)); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/FactoryTest.php b/app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/FactoryTest.php new file mode 100644 index 0000000..5378bdd --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/FactoryTest.php @@ -0,0 +1,34 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Model_Validator_Parameter_FactoryTest extends EcomDev_PHPUnit_Test_Case +{ + + protected $validatorFactory = null; + + public function setUp() + { + parent::setUp(); + $this->validatorFactory = Mage::getModel('ops/validator_parameter_factory'); + } + + public function testGetValidatorFor() + { + $validator = $this->validatorFactory->getValidatorFor(null); + $this->assertTrue($validator instanceof Netresearch_OPS_Model_Validator_Parameter_Validator); + $this->assertEquals(0, count($validator->getValidators())); + + $validator = $this->validatorFactory->getValidatorFor(Netresearch_OPS_Model_Validator_Parameter_Factory::TYPE_REQUEST_PARAMS_VALIDATION); + $this->assertTrue($validator instanceof Netresearch_OPS_Model_Validator_Parameter_Validator); + $this->assertEquals(1, count($validator->getValidators())); + $this->assertTrue(current($validator->getValidators()) instanceof Netresearch_OPS_Model_Validator_Parameter_Length); + $this->assertTrue(0 < count(current($validator->getValidators())->getFieldLengths())); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/LengthTest.php b/app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/LengthTest.php new file mode 100644 index 0000000..ebf2267 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/LengthTest.php @@ -0,0 +1,47 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Model_Validator_Parameter_LengthTest extends EcomDev_PHPUnit_Test_Case +{ + + protected $validator = null; + + public function setUp() + { + parent::setUp(); + $this->validator = Mage::getModel('ops/validator_parameter_length'); + } + + public function testValidationPassed() + { + $this->assertTrue($this->validator->isValid(null)); + $this->assertTrue($this->validator->isValid(new Varien_Object())); + $this->assertTrue($this->validator->isValid(array())); + $map = array('foo' => 5, 'bar' => 4, 'baz' => 3, 'borg' => 5); + $this->validator->setFieldLengths($map); + $data = array('foo' => '12345', 'bar' => '1234', 'baz' => '123', 'borg' => null); + $this->assertTrue($this->validator->isValid($data)); + } + + public function testValidationFailed() + { + $map = array('foo' => 5, 'bar' => 4, 'baz' => 3); + $this->validator->setFieldLengths($map); + $data = array('foo' => '123456', 'bar' => '1234', 'baz' => '1238'); + $this->assertFalse($this->validator->isValid($data)); + $this->assertTrue(2 == count($this->validator->getMessages())); + + $this->validator = Mage::getModel('ops/validator_parameter_length'); + $map = array('foo' => 5, 'bar' => 4, 'baz' => 3); + $this->validator->setFieldLengths($map); + $data = array('foo' => '123456', 'bar' => '1234', 'baz' => '123'); + $this->assertFalse($this->validator->isValid($data)); + $this->assertTrue(1 == count($this->validator->getMessages())); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/ValidatorTest.php b/app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/ValidatorTest.php new file mode 100644 index 0000000..d868532 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/Model/Validator/Parameter/ValidatorTest.php @@ -0,0 +1,36 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Test_Model_Validator_Parameter_ValidatorTest extends EcomDev_PHPUnit_Test_Case +{ + protected $validator = null; + + public function setUp() + { + parent::setUp(); + $this->validator = Mage::getModel('ops/validator_parameter_validator'); + } + + public function testIsValid() + { + $this->assertTrue($this->validator->isValid(null)); + $this->validator->addValidator(new Zend_Validate_Alnum()); + $this->assertFalse($this->validator->isValid(null)); + $this->assertTrue(0 < count($this->validator->getMessages())); + $this->validator->addValidator(new Zend_Validate_EmailAddress()); + } + + public function testMultipleValidators() + { + $this->validator->addValidator(new Zend_Validate_Alnum()); + $this->validator->addValidator(new Zend_Validate_EmailAddress()); + $this->assertFalse($this->validator->isValid(null)); + $this->assertTrue(1 < count($this->validator->getMessages())); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/var/fixtures/aliases.yaml b/app/code/community/Netresearch/OPS/Test/var/fixtures/aliases.yaml new file mode 100644 index 0000000..829dae0 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/var/fixtures/aliases.yaml @@ -0,0 +1,92 @@ +tables: + ops/alias: + - id: 1 + customer_id: 1 + alias: 4711 + brand: 'VISA' + billing_address_hash: '0815' + shipping_address_hash: '2342' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0116' + payment_method: 'ops_cd' + state: 'active' + store_id: 0 + created_at: '2000-01-01 00:00:01' + + - id: 2 + customer_id: 1 + alias: 4712 + brand: 'VISA' + billing_address_hash: '0816' + shipping_address_hash: '2342' + pseudo_account_or_cc_no: 'xxxx1112' + expiration_date: '0116' + payment_method: 'ops_cc' + state: 'active' + store_id: 0 + created_at: '1970-01-01 00:00:01' + + - id: 3 + customer_id: 1 + alias: 4713 + brand: 'VISA' + billing_address_hash: '0817' + shipping_address_hash: '0817' + pseudo_account_or_cc_no: 'xxxx1113' + expiration_date: '0116' + payment_method: 'ops_cc' + state: 'pending' + store_id: 0 + created_at: '1970-01-01 00:00:02' + + - id: 4 + customer_id: 2 + alias: 4711 + brand: 'VISA' + billing_address_hash: '1508' + shipping_address_hash: '4223' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0117' + payment_method: 'ops_cc' + state: 'active' + store_id: 0 + created_at: '1970-01-01 00:00:02' + + - id: 5 + customer_id: 1 + alias: 4711 + brand: 'VISA' + billing_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + shipping_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0117' + payment_method: 'ops_cc' + state: 'active' + store_id: 0 + created_at: '1970-01-01 00:00:02' + + - id: 6 + customer_id: 1 + alias: 4714 + brand: 'VISA' + billing_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + shipping_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0116' + payment_method: 'ops_cd' + state: 'pending' + store_id: 0 + created_at: '2000-11-01 00:00:01' + + - id: 7 + customer_id: 23 + alias: 4714 + brand: 'VISA' + billing_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + shipping_address_hash: '1b9ecdf409e240717f04b7155712658ab09116bb' + pseudo_account_or_cc_no: 'xxxx1111' + expiration_date: '0116' + payment_method: 'ops_cd' + state: 'active' + store_id: 0 + created_at: '2000-11-01 00:00:01' \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Test/var/fixtures/orders.yaml b/app/code/community/Netresearch/OPS/Test/var/fixtures/orders.yaml new file mode 100644 index 0000000..bbebe1c --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/var/fixtures/orders.yaml @@ -0,0 +1,703 @@ +tables: + sales/order: + - entity_id: 11 + increment_id: 100000011 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 23 + store_id: 0 + + - entity_id: 12 + increment_id: 100000012 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 24 + store_id: 0 + + - entity_id: 13 + increment_id: 100000013 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 177.55 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'processing' + status: 'processing' + quote_id: 25 + store_id: 0 + + - entity_id: 14 + increment_id: 100000014 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 177.55 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 26 + + - entity_id: 15 + increment_id: 100000015 + shipping_address_id: 43 + base_grand_total: 177.55 + grand_total: 200.00 + currency: '$' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 27 + + - entity_id: 16 + increment_id: 100000016 + shipping_address_id: 43 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 28 + + - entity_id: 17 + increment_id: 100000017 + shipping_address_id: 43 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 29 + + - entity_id: 18 + increment_id: 100000018 + shipping_address_id: 43 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 30 + + - entity_id: 19 + increment_id: 100000019 + shipping_address_id: 50 + base_grand_total: 19.99 + grand_total: 19.99 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 43 + + - entity_id: 20 + increment_id: 100000020 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + quote_id: 42# + + - entity_id: 21 + increment_id: 100000021 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 22 + increment_id: 100000022 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 23 + increment_id: 100000023 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 24 + increment_id: 100000024 + store_id: 1 + shipping_address_id: 46 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 24 + increment_id: 100000024 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 25 + increment_id: 100000025 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 26 + increment_id: 100000026 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 27 + increment_id: 100000027 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 28 + increment_id: 100000028 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 29 + increment_id: 100000029 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 30 + increment_id: 100000030 + store_id: 1 + shipping_address_id: 42 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'hubertus.von.fuerstenberg@trash-mail.com' + state: 'pending_payment' + status: 'pending_payment' + + - entity_id: 31 + increment_id: 100000031 + shipping_address_id: 42 + base_grand_total: 177.55 + grand_total: 177.55 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'processing' + status: 'processing' + quote_id: 25 + store_id: 0 + + - entity_id: 32 + increment_id: 100000032 + shipping_address_id: 42 + base_grand_total: 177.55 + grand_total: 177.55 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + customer_email: 'willi.usa.customer@trash-mail.com' + state: 'processing' + status: 'processing' + quote_id: 32 + store_id: 0 + + sales/quote: + - entity_id: 1 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + - entity_id: 2 + base_grand_total: 119.00 + grand_total: 200.00 + currency: '$' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + - entity_id: 10 + store_id: 0 + customer_id: 23 + created_at: '2013-05-14 14:45:00' + store_id: 0 + + - entity_id: 11 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + - entity_id: 23 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + - entity_id: 12 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + - entity_id: 32 + store_id: 0 + created_at: '2013-05-14 14:45:00' + + sales/quote_payment: + - payment_id: 1 + quote_id: 10 + updated_at: '1970-01-01' + method: 'ops_cc' + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 2 + quote_id: 11 + updated_at: '1970-01-01' + method: 'ops_cc' + quote_id: 11 + + - payment_id: 3 + quote_id: 10 + updated_at: '1970-01-01' + method: 'ops_cc' + additional_information: a:7:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:3:"cvc";s:3:"123";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 4 + quote_id: 12 + updated_at: '1970-01-01' + method: 'ops_paypal' + additional_information: a:7:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:3:"cvc";s:3:"123";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:4:"4714";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + + - payment_id: 5 + quote_id: 32 + updated_at: '1970-01-01' + method: 'ops_BCMC' + additional_information: a:37:{s:5:"PSPID";s:10:"NRMAGENTO8";s:6:"AMOUNT";d:34500;s:8:"CURRENCY";s:3:"EUR";s:7:"ORDERID";s:10:"#145000120";s:8:"LANGUAGE";s:5:"de_DE";s:2:"PM";s:10:"CreditCard";s:5:"EMAIL";s:31:"sebastian.ertner@netresearch.de";s:9:"ACCEPTURL";s:56:"http://ser-ogone-de.magento.nrdev.de/ops/payment/accept/";s:10:"DECLINEURL";s:57:"http://ser-ogone-de.magento.nrdev.de/ops/payment/decline/";s:12:"EXCEPTIONURL";s:59:"http://ser-ogone-de.magento.nrdev.de/ops/payment/exception/";s:9:"CANCELURL";s:56:"http://ser-ogone-de.magento.nrdev.de/ops/payment/cancel/";s:7:"BACKURL";s:125:"http://ser-ogone-de.magento.nrdev.de/ops/payment/retry/?SHASIGN=AEF578B3ED129000FC779255DC04CA535ABB155F&orderID=%23145000120";s:8:"FP_ACTIV";s:1:"0";s:9:"OPERATION";s:3:"RES";s:4:"ORIG";s:10:"OGmg160526";s:5:"BRAND";s:4:"BCMC";s:2:"CN";s:16:"Sebastian Ertner";s:3:"COM";s:27:"Lafayette Convertible Dress";s:8:"ADDMATCH";i:1;s:29:"ECOM_BILLTO_POSTAL_POSTALCODE";s:5:"04229";s:12:"OWNERADDRESS";s:18:"Nonnenstraße 11 d";s:9:"OWNERTOWN";s:7:"Leipzig";s:8:"OWNERZIP";s:5:"04229";s:10:"OWNERTELNO";s:11:"23123123123";s:8:"OWNERCTY";s:2:"DE";s:4:"CUID";s:3:"140";s:6:"DEVICE";s:8:"computer";s:2:"TP";s:57:"http://ser-ogone-de.magento.nrdev.de/ops/payment/paypage/";s:23:"ECOM_SHIPTO_POSTAL_CITY";s:7:"Leipzig";s:29:"ECOM_SHIPTO_POSTAL_POSTALCODE";s:5:"04229";s:24:"ECOM_SHIPTO_POSTAL_STATE";s:2:"SN";s:30:"ECOM_SHIPTO_POSTAL_COUNTRYCODE";s:2:"DE";s:29:"ECOM_SHIPTO_POSTAL_NAME_FIRST";s:9:"Sebastian";s:28:"ECOM_SHIPTO_POSTAL_NAME_LAST";s:6:"Ertner";s:31:"ECOM_SHIPTO_POSTAL_STREET_LINE1";s:13:"Nonnenstraße";s:32:"ECOM_BILLTO_POSTAL_STREET_NUMBER";s:4:"11 d";s:31:"ECOM_SHIPTO_POSTAL_STREET_LINE2";s:0:"";} + + sales/order_payment: + - entity_id: 1 + parent_id: 11 + additional_information: a:7:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;s:6:"status";s:1:"0";} + method: 'ops_bankTransfer' + + - entity_id: 2 + parent_id: 12 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:6:"status";s:1:"5";} + + - entity_id: 3 + parent_id: 13 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:6:"status";s:1:"0";} + method: 'ops_bankTransfer' + + - entity_id: 4 + parent_id: 14 + additional_information: a:2:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";} + + - entity_id: 5 + parent_id: 15 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFD";} + method: 'ops_cc' + + - entity_id: 6 + parent_id: 16 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFS";} + + - entity_id: 7 + parent_id: 17 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFS";s:9:"canRefund";b:0;} + + - entity_id: 8 + parent_id: 18 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:23:"lastRefundOperationCode";s:3:"RFD";s:9:"canRefund";b:1;} + + - entity_id: 9 + parent_id: 19 + method: 'ops_openInvoiceDe' + + - entity_id: 11 + parent_id: 20 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + + - entity_id: 12 + parent_id: 21 + additional_information: a:4:{s:2:"PM";s:16:"Direct Debits DE";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + method: 'ops_directDebit' + + - entity_id: 13 + parent_id: 22 + additional_information: a:4:{s:2:"PM";s:16:"Direct Debits NL";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + + - entity_id: 14 + parent_id: 23 + additional_information: a:4:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:11:"HTML_ANSWER";s:4:"HTML";s:9:"canRefund";b:1;} + + - entity_id: 42 + parent_id: 20 + + - entity_id: 15 + parent_id: 24 + additional_information: a:6:{s:8:"CC_BRAND";s:4:"VISA";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_cc' + + - entity_id: 16 + parent_id: 25 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_cc' + + - entity_id: 17 + parent_id: 26 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_paypal' + + - entity_id: 18 + parent_id: 27 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_kwixoApresReception' + + - entity_id: 19 + parent_id: 28 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_kwixoComptant' + + - entity_id: 20 + parent_id: 29 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'ops_kwixoCredit' + + - entity_id: 21 + parent_id: 30 + additional_information: a:6:{s:8:"CC_BRAND";s:16:"PostFinance Card";s:5:"CC_CN";s:10:"Max Muster";s:12:"saveOpsAlias";s:1:"0";s:5:"alias";s:12:"041907169941";s:12:"ops_response";a:22:{s:7:"orderID";s:11:"SER13041841";s:5:"PAYID";s:8:"20908736";s:8:"NCSTATUS";s:1:"0";s:7:"NCERROR";s:1:"0";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"5";s:5:"IPCTY";s:2:"AU";s:5:"CCCTY";s:2:"US";s:3:"ECI";s:2:"12";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:6:"amount";s:2:"35";s:8:"currency";s:3:"EUR";s:2:"PM";s:10:"CreditCard";s:5:"BRAND";s:4:"VISA";s:7:"SCORING";s:1:"3";s:12:"SCO_CATEGORY";s:1:"O";s:5:"ALIAS";s:12:"041907169941";s:11:"NCERRORPLUS";s:1:"!";}s:28:"checkoutFinishedSuccessfully";b:1;} + method: 'checkmo' + + - entity_id: 22 + parent_id: 31 + additional_information: a:3:{s:2:"PM";s:16:"Bank transfer DE";s:5:"BRAND";s:16:"Bank transfer DE";s:6:"status";s:2:"51";} + method: 'ops_bankTransfer' + + - entity_id: 23 + parent_id: 32 + additional_information: a:29:{s:7:"orderID";s:10:"#145000119";s:8:"currency";s:3:"EUR";s:6:"amount";s:2:"80";s:2:"PM";s:10:"CreditCard";s:10:"ACCEPTANCE";s:7:"test123";s:6:"STATUS";s:1:"9";s:6:"CARDNO";s:18:"670300-XXXXXXXX-03";s:3:"BIN";s:6:"670300";s:2:"ED";s:4:"0926";s:2:"CN";s:16:"Sebastian Ertner";s:7:"TRXDATE";s:8:"09/15/16";s:5:"PAYID";s:10:"3009110929";s:7:"NCERROR";s:1:"0";s:5:"BRAND";s:22:"Bancontact/Mister Cash";s:11:"CREDITDEBIT";s:0:"";s:5:"IPCTY";s:2:"DE";s:5:"CCCTY";s:2:"BE";s:3:"ECI";s:1:"5";s:8:"CVCCheck";s:2:"NO";s:8:"AAVCheck";s:2:"NO";s:2:"VC";s:2:"NO";s:6:"AAVZIP";s:2:"NO";s:10:"AAVADDRESS";s:2:"NO";s:7:"AAVMAIL";s:2:"NO";s:7:"SCORING";s:1:"0";s:12:"SCO_CATEGORY";s:1:"G";s:2:"IP";s:13:"178.24.30.185";s:10:"MOBILEMODE";s:2:"QR";s:7:"SHASIGN";s:40:"76A28E77731F97339BB752509BBE46B8E464A8D6";} + method: 'ops_BCMC' + + sales/shipment: + - entity_id: 1 + increment_id: 100000077 + order_id: 11 + shipping_address_id: 42 + + - entity_id: 2 + increment_id: 100000078 + order_id: 12 + shipping_address_id: 43 + + sales/order_address: + - entity_id: 42 + parent_id: 11 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + fax: 12345678 + + - entity_id: 43 + parent_id: 11 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + + - entity_id: 44 + parent_id: 12 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 45 + parent_id: 12 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: 'An der Tabaksmühle 3a' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + + - entity_id: 46 + parent_id: 24 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_billing: 1 + + - entity_id: 47 + parent_id: 27 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + same_as_shipping: 1 + + - entity_id: 147 + parent_id: 27 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04229' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + + - entity_id: 48 + parent_id: 28 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 49 + parent_id: 19 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 50 + parent_id: 19 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 51 + parent_id: 15 + address_type: 'shipping' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + - entity_id: 52 + parent_id: 32 + address_type: 'billing' + firstname: 'Hubertus' + postcode: '04227' + lastname: 'Fürstenberg' + street: '44 rue Parmentier' + city: 'Leipzig' + email: 'hubertus.von.fuerstenberg@trash-mail.com' + country_id: 'DE' + prefix: 'Prof. Dr.' + middlename: 'von' + suffix: 'MdL' + company: '' + + sales/order_item: + - item_id: 11 + order_id: 11 + qty_ordered: 2.0000 + qty_shipped: 2.0000 + sku: 4711 + name: 'some great test item' + price: 19.99 + tax_amount: 1.99 + + - item_id: 12 + order_id: 11 + qty_ordered: 2.0000 + qty_shipped: 2.0000 + sku: 4711 + name: 'some great test item 2' + price: 19.99 + tax_amount: 1.99 + + - item_id: 13 + order_id: 19 + qty_ordered: 1.0000 + qty_shipped: 0.0000 + sku: 4711 + name: 'some great test item' + price: 19.99 + tax_amount: 1.99 + diff --git a/app/code/community/Netresearch/OPS/Test/var/fixtures/quotes.yaml b/app/code/community/Netresearch/OPS/Test/var/fixtures/quotes.yaml new file mode 100644 index 0000000..412ffd3 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Test/var/fixtures/quotes.yaml @@ -0,0 +1,21 @@ +tables: + sales/quote: + - entity_id: 1 + base_grand_total: 119.00 + grand_total: 119.00 + currency: 'EUR' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + - entity_id: 2 + increment_id: 100000002 + base_grand_total: 119.00 + grand_total: 200.00 + currency: '$' + customer_gender: 1 + shipping_method: 'flatrate_flatrate' + + sales/quote_payment: + - entity_id: 1 + quote_id: 1 + method: 'ops_cc' \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Trait/AliasController.php b/app/code/community/Netresearch/OPS/Trait/AliasController.php new file mode 100644 index 0000000..49ef187 --- /dev/null +++ b/app/code/community/Netresearch/OPS/Trait/AliasController.php @@ -0,0 +1,259 @@ + + */ +?> +getRequest()->getParams(); + $requiredParams = array_fill_keys(array('Alias_OrderId', 'Alias_AliasId'), ''); + $missingParams = count(array_diff_key($requiredParams, $params)); + if ($missingParams) { + + return $this->parseFrontendException(); + } else { + $helper = Mage::helper('ops'); + $helper->log( + $helper->__( + "Incoming accepted Ingenico ePayments Alias Feedback\n\nRequest Path: %s\nParams: %s\n", + $this->getRequest()->getPathInfo(), + serialize($params) + ) + ); + Mage::helper('ops/alias')->saveAlias($params); + + + if (array_key_exists('Alias_OrderId', $params)) { + $quote = Mage::getModel('sales/quote')->load($params['Alias_OrderId']); + $this->updateAdditionalInformation($quote, $params); + } else { + $quote = Mage::getSingleton('checkout/session')->getQuote(); + } + + // OGNH-7 special handling for admin orders + Mage::helper('ops/alias')->setAliasToPayment( + $quote->getPayment(), + array_change_key_case($params, CASE_LOWER), + false + ); + + return $this->parseFrontendSuccess($params['Alias_AliasId']); + } + } + + /** + * exception-action for Alias-generating iframe-response + * + */ + public function exceptionAction() + { + $params = $this->getRequest()->getParams(); + $errors = array(); + + foreach ($params as $key => $value) { + if (stristr($key, 'error') && 0 != $value) { + $errors[] = $value; + } + } + + $helper = Mage::helper('ops'); + $helper->log( + $helper->__( + "Incoming exception Ingenico ePayments Alias Feedback\n\nRequest Path: %s\nParams: %s\n", + $this->getRequest()->getPathInfo(), + serialize($params) + ) + ); + + return $this->parseFrontendException(); + } + + /** + * Generates the hash for the hosted tokenization page request + * + * @throws Exception + */ + public function generateHashAction() + { + $storeId = $this->getStoreId(); + + $result = $this->generateHash($storeId); + + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($result)); + } + + /** + * Get store id from quote or request + * + * @return int + */ + protected function getStoreId() + { + $storeId = null; + + if (Mage::app()->getStore()->isAdmin()) { + $quote = Mage::getSingleton('adminhtml/session_quote'); + $storeId = $quote->getStoreId(); + } else { + $quoteId = $this->getRequest()->getParam('orderid'); + + $quote = Mage::getModel('sales/quote')->load($quoteId); + + if ($quote->getId() === null) { + $quote = Mage::getModel('sales/quote')->loadByIdWithoutStore($quoteId); + } + + if ($quote->getId() !== null) { + $storeId = $quote->getStoreId(); + } + if ($this->getRequest()->getParam('storeId') !== null) { + $storeId = $this->getRequest()->getParam('storeId'); + } + } + + return $storeId; + } + + /** + * updates the additional information from payment, thats needed for backend reOrders + * + * @param Mage_Sales_Model_Quote $quote + * @param string[] $params + */ + public function updateAdditionalInformation(Mage_Sales_Model_Quote $quote, $params) + { + if ($quote->getId() !== null && $quote->getPayment() && $quote->getPayment()->getId() !== null) { + $payment = $quote->getPayment(); + if (array_key_exists('Alias_AliasId', $params)) { + $payment->setAdditionalInformation('alias', $params['Alias_AliasId']); + } + if (array_key_exists('Card_Brand', $params)) { + $payment->setAdditionalInformation('CC_BRAND', $params['Card_Brand']); + } + if (array_key_exists('Card_CardHolderName', $params)) { + $payment->setAdditionalInformation('CC_CN', $params['Card_CardHolderName']); + } + if ($this->userIsRegistering()) { + $payment->setAdditionalInformation('userIsRegistering', true); + } + $quote->setPayment($payment)->setDataChanges(true)->save(); + $quote->setDataChanges(true)->save(); + } + } + + /** + * Checks if checkout method is registering + * + * @return bool + */ + protected function userIsRegistering() + { + return Mage::getSingleton('checkout/type_onepage')->getCheckoutMethod() + === Mage_Checkout_Model_Type_Onepage::METHOD_REGISTER; + } + + /** + * Generate hash from request parameters + * + * @param $storeId + * + * @return array + */ + protected function generateHash($storeId) + { + $data = $this->cleanParamKeys(); + + $secret = $this->getConfig()->getShaOutCode($storeId); + $raw = $this->getPaymentHelper()->getSHAInSet($data, $secret); + $result = array('hash' => Mage::helper('ops/payment')->shaCrypt($raw)); + + return $result; + } + + /** + * Cleans param array from magentos admin params, fixes underscored keys + * + * @return array + */ + protected function cleanParamKeys() + { + $data = array(); + foreach ($this->getRequest()->getParams() as $key => $value) { + if ($key == 'form_key' || $key == 'isAjax' || $key == 'key') { + continue; + } + $data[str_replace('_', '.', $key)] = $value; + } + + return $data; + } + + /** + * @return Mage_Core_Controller_Response_Http + */ + protected function parseFrontendException() + { + $result = ""; + + return $this->getResponse()->setBody($result); + } + + /** + * @param string $alias alias to be parsed in frontend javascript event + * + * @return Mage_Core_Controller_Response_Http + */ + protected function parseFrontendSuccess($alias) + { + $result = sprintf( + "", + $alias + ); + + return $this->getResponse()->setBody($result); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/Trait/PaymentHelper.php b/app/code/community/Netresearch/OPS/Trait/PaymentHelper.php new file mode 100644 index 0000000..34135bb --- /dev/null +++ b/app/code/community/Netresearch/OPS/Trait/PaymentHelper.php @@ -0,0 +1,60 @@ + + */ +?> +paymentHelper) { + $this->paymentHelper = Mage::helper('ops/payment'); + } + + return $this->paymentHelper; + } + + /** + * @param Netresearch_OPS_Helper_Payment $paymentHelper + * + * @returns $this + */ + public function setPaymentHelper($paymentHelper) + { + $this->paymentHelper = $paymentHelper; + + return $this; + } + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/controllers/Adminhtml/AdminController.php b/app/code/community/Netresearch/OPS/controllers/Adminhtml/AdminController.php new file mode 100644 index 0000000..ef0b4e3 --- /dev/null +++ b/app/code/community/Netresearch/OPS/controllers/Adminhtml/AdminController.php @@ -0,0 +1,80 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Adminhtml_AdminController extends Mage_Adminhtml_Controller_Action +{ + protected function _isAllowed() + { + return true; + } + + public function saveAliasAction() + { + $alias = $this->_request->getParam('alias'); + $quote = Mage::getSingleton('admin/session_quote'); + if (0 < strlen($alias)) { + $payment = $quote->getPayment(); + $payment->setAdditionalInformation('alias', $alias); + Mage::helper('ops')->log('saved alias ' . $alias . ' for quote #' . $quote->getId()); + $payment->setDataChanges(true); + $payment->save(); + } + } + + public function downloadLogAction() + { + $helper = Mage::helper('ops/data'); + $logPath = $helper->getLogPath(); + $downloader = Mage::getModel('ops/file_download'); + $fileToDownload = ''; + try { + $fileToDownload = $downloader->getFile($logPath); + } catch (Exception $e) { + $this->_getSession()->addError($e->getMessage()); + } + if ($fileToDownload === '') { + $message = Mage::helper('ops')->__('Log file could not be retrieved.'); + $this->_getSession()->addError($message); + } else { + $this->_prepareDownloadResponse( + 'ops.log', array( + 'value' => $fileToDownload, + 'type' => 'filename' + ) + ); + } + $this->_redirectReferer(); + } + + /** + * Resends the payment information for the given order + */ + public function resendInfoAction() + { + $orderId = $this->getRequest()->getParam('order_id'); + $order = Mage::getModel('sales/order')->load($orderId); + /** @var Netresearch_OPS_Model_Payment_Features_PaymentEmail $paymentEmail */ + $paymentEmail = Mage::getModel('ops/payment_features_paymentEmail'); + $success = false; + + + if ($paymentEmail->isAvailableForOrder($order)) { + $success = $paymentEmail->resendPaymentInfo($order); + } + + if ($success) { + $this->_getSession()->addSuccess($this->__('Payment information has been resend to customer.')); + } else { + $this->_getSession()->addError($this->__('Payment information could not be sent.')); + } + + $this->_redirectReferer(); + } +} diff --git a/app/code/community/Netresearch/OPS/controllers/Adminhtml/AliasController.php b/app/code/community/Netresearch/OPS/controllers/Adminhtml/AliasController.php new file mode 100644 index 0000000..85c2196 --- /dev/null +++ b/app/code/community/Netresearch/OPS/controllers/Adminhtml/AliasController.php @@ -0,0 +1,42 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Adminhtml_AliasController extends Mage_Adminhtml_Controller_Action +{ + use Netresearch_OPS_Trait_AliasController; + + protected $_publicActions = array('accept', 'exception'); + + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('customer/manage'); + } + + public function deleteAction() + { + $aliasId = $this->_request->getParam('id'); + $alias = Mage::getModel('ops/alias')->load($aliasId); + if ($alias->getId()) { + $alias->delete(); + $this->_getSession()->addSuccess( + Mage::helper('ops')->__('Removed alias %s.', $alias->getAlias()) + ); + return $this->_redirectReferer(); + } + $this->_getSession()->addError( + Mage::helper('ops')->__('Could not remove alias %s.', $alias->getAlias()) + ); + return $this->_redirectReferer(); + } + + protected function _getSession() + { + return Mage::getSingleton('adminhtml/session'); + } +} diff --git a/app/code/community/Netresearch/OPS/controllers/Adminhtml/KwixocategoryController.php b/app/code/community/Netresearch/OPS/controllers/Adminhtml/KwixocategoryController.php new file mode 100644 index 0000000..cb9d7cf --- /dev/null +++ b/app/code/community/Netresearch/OPS/controllers/Adminhtml/KwixocategoryController.php @@ -0,0 +1,228 @@ + + * @license OSL 3.0 + */ +class Netresearch_OPS_Adminhtml_KwixocategoryController + extends Mage_Adminhtml_Controller_Action +{ + + protected $_block = 'ops/adminhtml_kwixocategory_edit'; + + /** + * @param bool $getRootInstead + * @return bool + */ + protected function _initCategory($getRootInstead = false) + { + $categoryId = (int)$this->getRequest()->getParam('id', false); + $storeId = (int)$this->getRequest()->getParam('store', 0); + $category = Mage::getModel('catalog/category'); + $category->setStoreId($storeId); + + if ($categoryId) { + $category->load($categoryId); + if ($storeId) { + $rootId = Mage::app()->getStore($storeId)->getRootCategoryId(); + if (!in_array($rootId, $category->getPathIds())) { + // load root category instead wrong one + if ($getRootInstead) { + $category->load($rootId); + } else { + $this->_redirect( + '*/*/', array('_current' => true, 'id' => null) + ); + + return false; + } + } + } + } + + Mage::register('category', $category); + Mage::register('current_category', $category); + + return $category; + } + + public function treeAction() + { + $categoryId = (int)$this->getRequest()->getParam('id'); + $storeId = $this->getRequest()->getParam('store', 0); + if ($storeId) { + if (!$categoryId) { + $store = Mage::app()->getStore($storeId); + $rootId = $store->getRootCategoryId(); + $this->getRequest()->setParam('id', $rootId); + } + } + + $category = $this->_initCategory(true); + + $block = $this->getLayout()->createBlock('ops/adminhtml_kwixocategory_categoryTree'); + $root = $block->getRoot(); + $this->getResponse()->setBody( + Zend_Json::encode( + array( + 'data' => $block->getTree(), + 'parameters' => array( + 'text' => $block->buildNodeName($root), + 'draggable' => false, + 'allowDrop' => false, + 'id' => (int)$root->getId(), + 'expanded' => (int)$block->getIsWasExpanded(), + 'store_id' => (int)$block->getStore()->getId(), + 'category_id' => (int)$category->getId(), + 'root_visible' => (int)$root->getIsVisible() + )) + ) + ); + } + + + public function categoriesJsonAction() + { + if ($this->getRequest()->getParam('expand_all')) { + Mage::getSingleton('admin/session')->setIsTreeWasExpanded(true); + } else { + Mage::getSingleton('admin/session')->setIsTreeWasExpanded(false); + } + if (($categoryId = (int)$this->getRequest()->getPost('id'))) { + $this->getRequest()->setParam('id', $categoryId); + + if (!$category = $this->_initCategory()) { + return; + } + $this->getResponse()->setBody( + $this->getLayout()->createBlock( + 'ops/adminhtml_kwixocategory_categoryTree' + )->getTreeJson($category) + ); + } + } + + public function indexAction() + { + $this->loadLayout(); + + $selectedCategory = Mage::getSingleton('admin/session')->getLastEditedCategory(true); + if ($selectedCategory) { + $this->getRequest()->setParam('id', $selectedCategory); + } + $selectedCategory = (int)$this->getRequest()->getParam('id', 0); + $this->_initCategory(true); + + if ($selectedCategory > 0) { + $this->getLayout()->getBlock('tree')->setData( + 'selectedCategory', $selectedCategory + ); + } + + $this->renderLayout(); + } + + public function editAction() + { + + $params = array('_current' => true); + $redirect = false; + + $storeId = (int)$this->getRequest()->getParam('store'); + $parentId = (int)$this->getRequest()->getParam('parent'); + $prevStoreId = Mage::getSingleton('admin/session')->getLastViewedStore( + true + ); + + if ($prevStoreId != null && !$this->getRequest()->getQuery('isAjax')) { + $params['store'] = $prevStoreId; + $redirect = true; + } + + $prevCategoryId = Mage::getSingleton('admin/session') + ->getLastEditedCategory(true); + if ($prevCategoryId && !$this->getRequest()->getQuery('isAjax')) { + $this->getRequest()->setParam('id', $prevCategoryId); + } + if ($redirect) { + $this->_redirect('*/*/edit', $params); + + return; + } + + $categoryId = (int)$this->getRequest()->getParam('id'); + if ($storeId && !$categoryId && !$parentId) { + $store = Mage::app()->getStore($storeId); + $prevCategoryId = (int)$store->getRootCategoryId(); + $this->getRequest()->setParam('id', $prevCategoryId); + } + + if (!($category = $this->_initCategory(true))) { + return; + } + + $data = Mage::getSingleton('adminhtml/session')->getCategoryData(true); + if (isset($data['general'])) { + $category->addData($data['general']); + } + + if ($this->getRequest()->getQuery('isAjax')) { + Mage::getSingleton('admin/session')->setLastViewedStore( + $this->getRequest()->getParam('store') + ); + Mage::getSingleton('admin/session')->setLastEditedCategory( + $category->getId() + ); + $this->_initLayoutMessages('adminhtml/session'); + $this->getResponse()->setBody( + $this->getLayout()->getMessagesBlock()->getGroupedHtml() + . $this->getLayout()->createBlock( + 'ops/adminhtml_kwixocategory_edit' + )->setController('kwixocategory')->toHtml() + ); + + return; + } + $this->_redirect('*/*/index', $params); + } + + public function saveAction() + { + $post = $this->getRequest()->getPost(); + try { + Mage::helper('ops/kwixo')->saveKwixoconfigurationMapping($post); + } catch (Exception $e) { + Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); + } + $this->_redirect( + '*/*/index/', + array('_current' => true, "id" => $post['category_id'], "store" => $post['storeId']) + ); + } + + public function deleteAction() + { + $id = $this->getRequest()->getParam('id', 0); + $storeId = $this->getRequest()->getParam('store', 0); + try { + Mage::getModel('ops/kwixo_category_mapping') + ->loadByCategoryId($id) + ->delete(); + $message = Mage::helper('ops/data')->__('Data succesfully deleted.'); + Mage::getSingleton('adminhtml/session')->addSuccess($message); + } catch (Exception $e) { + Mage::getSingleton('adminhtml/session')->addError($e->getMessage()); + } + $this->_redirect('*/*/index/', array("id" => $id, "store" => $storeId)); + } + + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('catalog/categories'); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/controllers/Adminhtml/KwixoshippingController.php b/app/code/community/Netresearch/OPS/controllers/Adminhtml/KwixoshippingController.php new file mode 100644 index 0000000..c17ad74 --- /dev/null +++ b/app/code/community/Netresearch/OPS/controllers/Adminhtml/KwixoshippingController.php @@ -0,0 +1,93 @@ + + * @category Netresearch + * @package Netresearch_OPS + * @copyright Copyright (c) 2013 Netresearch GmbH & Co. KG + * (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php + * Open Software License (OSL 3.0) + */ +class Netresearch_OPS_Adminhtml_KwixoshippingController + extends Mage_Adminhtml_Controller_Action +{ + + /** + * displays the form + */ + public function indexAction() + { + $this->loadLayout(); + + $storeId = $this->getRequest()->getParam('store', 0); + $this->getLayout()->getBlock('kwixoshipping')->setData( + 'store', $storeId + ); + $this->getLayout()->getBlock('kwixoshipping')->setData( + 'postData', + Mage::getModel('adminhtml/session')->getData('errorneousData') + ); + Mage::getModel('adminhtml/session')->unsetData('errorneousData'); + $this->renderLayout(); + } + + /** + * save submitted form data + */ + public function saveAction() + { + if ($this->getRequest()->isPost()) { + $postData = $this->getCleanPostData(); + $validator = Mage::getModel('ops/validator_kwixo_shipping_setting'); + if (true === $validator->isValid($postData)) { + $collection = Mage::getModel('ops/kwixo_shipping_setting')->getCollection() + ->addFieldToFilter('shipping_code', array('in' => array_keys($postData))); + /** @var Netresearch_OPS_Model_Kwixo_Shipping_Setting $kwixoShippingModel */ + foreach ($collection->getItems() as $kwixoShippingModel) { + if (!array_key_exists($kwixoShippingModel->getShippingCode(), $postData)) { + continue; + } + $kwixoData = $postData[$kwixoShippingModel->getShippingCode()]; + $kwixoShippingModel + ->setKwixoShippingType($kwixoData['kwixo_shipping_type']) + ->setKwixoShippingSpeed($kwixoData['kwixo_shipping_speed']) + ->setKwixoShippingDetails($kwixoData['kwixo_shipping_details']); + } + $collection->save(); + } else { + $postData = array_merge_recursive($postData, $validator->getMessages()); + Mage::getModel('adminhtml/session')->setData('errorneousData', $postData); + } + + } + $this->_redirect('adminhtml/kwixoshipping/index'); + } + + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('sales/shipment'); + } + + /** + * removes any methods not in the actual systems carrier list from the post parameters + * + * @return array + */ + protected function getCleanPostData() + { + $postData = $this->getRequest()->getPost(); + $methodCodes = array_keys( + Mage::getSingleton('shipping/config')->getAllCarriers() + ); + $invalidateShippingCodes = function ($key) use ($methodCodes) { + return !in_array($key, $methodCodes); + }; + $validKeys = array_filter(array_keys($postData), $invalidateShippingCodes); + $cleanPostData = array_diff_key($postData, $validKeys); + + return $cleanPostData; + } + + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/controllers/Adminhtml/OpsstatusController.php b/app/code/community/Netresearch/OPS/controllers/Adminhtml/OpsstatusController.php new file mode 100644 index 0000000..2140c25 --- /dev/null +++ b/app/code/community/Netresearch/OPS/controllers/Adminhtml/OpsstatusController.php @@ -0,0 +1,31 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + + +class Netresearch_OPS_Adminhtml_OpsstatusController extends Mage_Adminhtml_Controller_Action +{ + + protected function _isAllowed() + { + return Mage::getSingleton('admin/session')->isAllowed('sales/order/actions/invoice'); + } + + /** + * performs the status update call to Ingenico ePayments + */ + public function updateAction() + { + $orderId = $this->getRequest()->getParam('order_id'); + if (0 < $orderId) { + $order = Mage::getModel('sales/order')->load($orderId); + Mage::getModel('ops/status_update')->updateStatusFor($order); + } + $this->_redirect('adminhtml/sales_order/view/', array("order_id" => $orderId)); + } + +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/controllers/AliasController.php b/app/code/community/Netresearch/OPS/controllers/AliasController.php new file mode 100644 index 0000000..06290a1 --- /dev/null +++ b/app/code/community/Netresearch/OPS/controllers/AliasController.php @@ -0,0 +1,13 @@ + + * @author André Herrn + * @license OSL 3.0 + */ +class Netresearch_OPS_ApiController extends Netresearch_OPS_Controller_Abstract +{ + /** + * Order instance + */ + protected $_order; + + /* + * Predispatch to check the validation of the request from OPS + */ + public function preDispatch() + { + parent::preDispatch(); + if (!$this->_validateOPSData()) { + $this->getResponse()->setHttpResponseCode(422); + $this->setFlag('', self::FLAG_NO_DISPATCH, 1); + + return; + } + } + + /** + * Action to control postback data from ops + * + */ + public function postBackAction() + { + $params = $this->getRequest()->getParams(); + + try { + $status = $this->getPaymentHelper()->applyStateForOrder( + $this->_getOrder(), + $params + ); + $redirectRoute = Mage::helper('ops/api') + ->getRedirectRouteFromStatus($status); + + $this->_redirect( + $redirectRoute, array( + '_store' => $this->_getOrder()->getStoreId(), + '_query' => $params, + '_nosid' => true + ) + ); + } catch (Exception $e) { + Mage::helper('ops')->log( + "Run into exception '{$e->getMessage()}' in postBackAction" + ); + $this->getResponse()->setHttpResponseCode(500); + } + } + + /** + * Action to control postback data from ops + * + */ + public function directLinkPostBackAction() + { + + $params = $this->getRequest()->getParams(); + + try { + if (Mage::helper('ops/subscription')->isSubscriptionFeedback($params)) { + $this->getSubscriptionManager()->processSubscriptionFeedback($params); + } else { + $this->getDirectlinkHelper()->processFeedback( + $this->_getOrder(), + $params + ); + } + } catch (Exception $e) { + Mage::helper('ops')->log( + "Run into exception '{$e->getMessage()}' in directLinkPostBackAction" + ); + $this->getResponse()->setHttpResponseCode(500); + } + } +} diff --git a/app/code/community/Netresearch/OPS/controllers/CustomerController.php b/app/code/community/Netresearch/OPS/controllers/CustomerController.php new file mode 100644 index 0000000..fdc05ca --- /dev/null +++ b/app/code/community/Netresearch/OPS/controllers/CustomerController.php @@ -0,0 +1,68 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_CustomerController + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +class Netresearch_OPS_CustomerController extends Mage_Core_Controller_Front_Action +{ + /** + * Check customer authentication + */ + public function preDispatch() + { + parent::preDispatch(); + $loginUrl = Mage::helper('customer')->getLoginUrl(); + + if (!Mage::getSingleton('customer/session')->authenticate($this, $loginUrl)) { + $this->setFlag('', self::FLAG_NO_DISPATCH, true); + } + } + + /** + * + */ + public function aliasesAction() + { + $this->loadLayout(); + $this->_initLayoutMessages('customer/session'); + if ($block = $this->getLayout()->getBlock('ops_customer_aliases')) { + $block->setRefererUrl($this->_getRefererUrl()); + } + $headBlock = $this->getLayout()->getBlock('head'); + if ($headBlock) { + $headBlock->setTitle(Mage::helper('ops')->__('My payment information')); + } + $this->renderLayout(); + } + + public function deleteAliasAction() + { + $aliasId = $this->_request->getParam('id'); + $alias = Mage::getModel('ops/alias')->load($aliasId); + $customerId = Mage::helper('customer')->getCustomer()->getId(); + if ($alias->getId() && $alias->getCustomerId() == $customerId) { + $alias->delete(); + Mage::getSingleton('customer/session')->addSuccess( + Mage::helper('ops')->__('Removed payment information %s.', $alias->getPseudoAccountOrCcNo()) + ); + return $this->_redirectReferer(); + } + Mage::getSingleton('customer/session')->addError( + Mage::helper('ops')->__('Could not remove payment information %s.', $alias->getPseudoAccountOrCcNo()) + ); + return $this->_redirectReferer(); + } +} diff --git a/app/code/community/Netresearch/OPS/controllers/DeviceController.php b/app/code/community/Netresearch/OPS/controllers/DeviceController.php new file mode 100644 index 0000000..4c5b736 --- /dev/null +++ b/app/code/community/Netresearch/OPS/controllers/DeviceController.php @@ -0,0 +1,83 @@ + + */ +?> +config) { + $this->config = Mage::getModel('ops/config'); + } + + return $this->config; + } + + /** + * Toggles the customers consent to transmit the generated deviceid to Ingenico ePayments + * to the value specified in request parameter consent + * + */ + public function toggleConsentAction() + { + if ($this->getConfig()->getDeviceFingerPrinting()) { + $consent = (bool)$this->getRequest()->getParam(self::CONSENT_PARAMETER_KEY); + Mage::getSingleton('customer/session') + ->setData(Netresearch_OPS_Model_Payment_Abstract::FINGERPRINT_CONSENT_SESSION_KEY, $consent); + } + + $this->consentAction(); + } + + /** + * Returns the state of consent of the customer + */ + public function consentAction() + { + $resultArray = array(self::CONSENT_PARAMETER_KEY => false); + + if ($this->getConfig()->getDeviceFingerPrinting()) { + $resultArray[self::CONSENT_PARAMETER_KEY] = + (bool)Mage::getSingleton('customer/session') + ->getData(Netresearch_OPS_Model_Payment_Abstract::FINGERPRINT_CONSENT_SESSION_KEY); + } + + $this->getResponse()->setHeader('Content-type', 'application/json'); + $this->getResponse()->setBody(Mage::helper('core')->jsonEncode($resultArray)); + } +} diff --git a/app/code/community/Netresearch/OPS/controllers/PaymentController.php b/app/code/community/Netresearch/OPS/controllers/PaymentController.php new file mode 100644 index 0000000..245f74f --- /dev/null +++ b/app/code/community/Netresearch/OPS/controllers/PaymentController.php @@ -0,0 +1,408 @@ + + * @author André Herrn + * @author Paul Siedler + */ +class Netresearch_OPS_PaymentController extends Netresearch_OPS_Controller_Abstract +{ + /** + * Load place from layout to make POST on ops + */ + public function placeformAction() + { + + $lastIncrementId = $this->_getCheckout()->getLastRealOrderId(); + + if ($lastIncrementId) { + $order = Mage::getModel('sales/order'); + $order->loadByIncrementId($lastIncrementId); + } else { + return $this->_redirect('checkout/cart'); + } + + $quote = $this->_getCheckout()->getQuote(); + if ($quote) { + $quote->setIsActive(false)->save(); + } + $this->_getCheckout()->setOPSQuoteId($this->_getCheckout()->getQuoteId()); + $this->_getCheckout()->setOPSLastSuccessQuoteId($this->_getCheckout()->getLastSuccessQuoteId()); + $this->_getCheckout()->clear(); + + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Render 3DSecure response HTML_ANSWER + */ + public function placeform3dsecureAction() + { + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * Display our pay page, need to ops payment with external pay page mode * + */ + public function paypageAction() + { + $this->loadLayout(); + $this->renderLayout(); + } + + /** + * when payment gateway accept the payment, it will land to here + * need to change order status as processed ops + * update transaction id + * + */ + public function acceptAction() + { + $redirect = ''; + try { + $order = $this->_getOrder(); + if ($this->getQuote()) { + $this->getQuote()->setIsActive(false)->save(); + } + $this->_getCheckout()->setLastSuccessQuoteId($order->getQuoteId()); + $this->_getCheckout()->setLastQuoteId($order->getQuoteId()); + $this->_getCheckout()->setLastOrderId($order->getId()); + } catch (Exception $e) { + /** @var Netresearch_OPS_Helper_Data $helper */ + $helper = Mage::helper('ops'); + $helper->log($helper->__("Exception in acceptAction: " . $e->getMessage())); + $this->getPaymentHelper()->refillCart($this->_getOrder()); + $redirect = 'checkout/cart'; + } + if ($redirect === '') { + $redirect = 'checkout/onepage/success'; + } + + $this->redirectOpsRequest($redirect); + } + + /** + * the payment result is uncertain + * exception status can be 52 or 92 + * need to change order status as processing ops + * update transaction id + * + */ + public function exceptionAction() + { + $order = $this->_getOrder(); + $this->_getCheckout()->setLastSuccessQuoteId($order->getQuoteId()); + $this->_getCheckout()->setLastQuoteId($order->getQuoteId()); + $this->_getCheckout()->setLastOrderId($order->getId()); + + $msg = 'Your order has been registered, but your payment is still marked as pending.'; + $msg .= ' Please have patience until the final status is known.'; + $this->_getCheckout()->addError(Mage::helper('ops/data')->__($msg)); + + $this->redirectOpsRequest('checkout/onepage/success'); + } + + /** + * when payment got decline + * need to change order status to cancelled + * take the user back to shopping cart + * + */ + public function declineAction() + { + try { + $this->_getCheckout()->setQuoteId($this->_getCheckout()->getOPSQuoteId()); + } catch (Exception $e) { + Mage::logException($e); + } + + $this->getPaymentHelper()->refillCart($this->_getOrder()); + + $message = Mage::helper('ops')->__( + 'Your payment information was declined. Please select another payment method.' + ); + Mage::getSingleton('core/session')->addNotice($message); + $redirect = 'checkout/onepage'; + $this->redirectOpsRequest($redirect); + } + + /** + * when user cancel the payment + * change order status to cancelled + * need to redirect user to shopping cart + * + */ + public function cancelAction() + { + try { + $this->_getCheckout()->setQuoteId($this->_getCheckout()->getOPSQuoteId()); + } catch (Exception $e) { + Mage::logException($e); + } + if (false == $this->_getOrder()->getId()) { + $this->_order = null; + $this->_getOrder($this->_getCheckout()->getLastQuoteId()); + } + + $this->getPaymentHelper()->refillCart($this->_getOrder()); + + $redirect = 'checkout/cart'; + $this->redirectOpsRequest($redirect); + + } + + /** + * when user cancel the payment and press on button "Back to Catalog" or "Back to Merchant Shop" in Orops + * + */ + public function continueAction() + { + /** @var Mage_Sales_Model_Order $order */ + $order = Mage::getModel('sales/order')->load( + $this->_getCheckout()->getLastOrderId() + ); + $this->getPaymentHelper()->refillCart($order); + $redirect = $this->getRequest()->getParam('redirect'); + if ($redirect == 'catalog'): //In Case of "Back to Catalog" Button in OPS + $redirect = '/'; + else: //In Case of Cancel Auto-Redirect or "Back to Merchant Shop" Button + $redirect = 'checkout/cart'; + endif; + $this->redirectOpsRequest($redirect); + } + + /* + * Check the validation of the request from OPS + */ + protected function checkRequestValidity() + { + if (!$this->_validateOPSData()) { + Mage::throwException("Hash is not valid"); + } + } + + public function registerDirectDebitPaymentAction() + { + $params = $this->getRequest()->getParams(); + $validator = Mage::getModel('ops/validator_payment_directDebit'); + if (false === $validator->isValid($params)) { + $this->getResponse() + ->setHttpResponseCode(406) + ->setBody($this->__(implode(PHP_EOL, $validator->getMessages()))) + ->sendHeaders(); + + return; + } + $payment = $this->_getCheckout()->getQuote()->getPayment(); + $helper = Mage::helper('ops/directDebit'); + $payment = $helper->setDirectDebitDataToPayment($payment, $params); + + $payment->save(); + + $this->getResponse()->sendHeaders(); + } + + public function saveCcBrandAction() + { + $brand = $this->getRequest()->getParam('brand'); + $alias = $this->getRequest()->getParam('alias'); + $quote = $this->getQuote(); + if ($quote->getId() === null) { + $this->_redirect('checkout/cart'); + } else { + $payment = $quote->getPayment(); + $payment->setAdditionalInformation('CC_BRAND', $brand); + $payment->setAdditionalInformation('alias', $alias); + $payment->setDataChanges(true); + $payment->save(); + Mage::helper('ops')->log('saved cc brand ' . $brand . ' for quote #' . $quote->getId()); + $this->getResponse()->sendHeaders(); + } + } + + /** + * Action to retry paying the order on Ingenico + * + */ + public function retryAction() + { + + $order = $this->_getOrder(); + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $order->getPayment(); + $message = false; + + // only validate the parameters we added to the BACKURL ourselves + $params = array( + 'SHASIGN' => $this->getRequest()->getParam('SHASIGN'), + 'orderID' => $this->getRequest()->getParam('orderID') + ); + + if ($this->_validateOPSData($params) === false) { + $message = Mage::helper('ops')->__('Hash not valid'); + + } else { + + if ($this->canRetryPayment($payment)) { + + // Add Quote to Session, for the payment methods + /** @var Mage_Sales_Model_Quote $quote */ + $quote = Mage::getModel('sales/quote')->load($order->getQuoteId()); + /** @var Mage_Checkout_Model_Session $checkoutSession */ + $checkoutSession = Mage::getSingleton('checkout/session'); + $checkoutSession->setQuoteId($quote->getId()); + // Set Quote to Active, to be able to load quote data + $quote->setIsActive(1); + $quote->save(); + + $this->loadLayout(); + $this->renderLayout(); + + } else { + $message = Mage::helper('ops')->__( + 'Not possible to reenter the payment details for order %s', $order->getIncrementId() + ); + } + } + if ($message) { + Mage::getSingleton('core/session')->addNotice($message); + $this->redirectOpsRequest('/'); + } + } + + public function updatePaymentAndPlaceFormAction() + { + // Save new payment method in order + /** @var Mage_Sales_Model_Order $order */ + $order = $this->_getOrder(); + /** @var Mage_Sales_Model_Order_Payment $payment */ + $payment = $order->getPayment(); + /** @var Mage_Sales_Model_Quote $quote */ + $quote = Mage::getModel('sales/quote')->load($order->getQuoteId()); + $quote->setIsActive(0); + $message = false; + + try { + $newPayment = $this->getRequest()->getParam('payment'); + $quote->getPayment()->importData($newPayment); + $payment->setMethod($newPayment['method'])->getMethodInstance()->assignData(new Varien_Object($newPayment)); + $remoteAddr = Mage::helper('core/http')->getRemoteAddr(); + $quote->setRemoteIp($remoteAddr); + $order->setRemoteIp($remoteAddr); + $quote->save(); + $payment->save(); + $order->save(); + + // Set Session Data for further process + /** @var Mage_Checkout_Model_Session $checkoutSession */ + $checkoutSession = Mage::getSingleton('checkout/session'); + $checkoutSession->replaceQuote($quote); + $checkoutSession->setLastOrderId($order->getId()); + $checkoutSession->setLastRealOrderId($order->getIncrementId()); + $checkoutSession->setLastQuoteId($quote->getId()); + $checkoutSession->setLastSuccessQuoteId($quote->getId()); + + $redirectUrl = $payment->getMethodInstance()->getOrderPlaceRedirectUrl(); + + // Place order or rather in this case, send the inline payment method to Ingenico ePayments + if (empty($redirectUrl)) { + $checkoutSession->setRedirectUrl($redirectUrl); + $order->place(); + $order->save(); + } + } catch (Exception $e) { + $message = $e->getMessage(); + } + + if ($message) { + Mage::getSingleton('core/session')->addNotice($message); + $this->_redirect('checkout/cart'); + } else { + if (empty($redirectUrl)) { + $this->_redirect('checkout/onepage/success'); + } else { + $this->_redirectUrl($redirectUrl); + } + } + } + + protected function wasIframeRequest() + { + return $this->getConfig()->getConfigData('template', $this->_getOrder()->getStoreId()) + === Netresearch_OPS_Model_Payment_Abstract::TEMPLATE_OPS_IFRAME; + } + + /** + * Generates the Javascript snippet that move the redirect to the parent frame in iframe mode. + * + * @param $redirect + * + * @return string javascript snippet + */ + protected function generateJavaScript($redirect) + { + $javascript + = " + "; + + return $javascript; + } + + /** + * Redirects the customer to the given redirect path or inserts the js-snippet needed for iframe template mode into + * the response instead + * + * @param $redirect + */ + protected function redirectOpsRequest($redirect) + { + if ($this->wasIframeRequest()) { + $this->getResponse()->setBody($this->generateJavaScript($redirect)); + } else { + $this->_redirect($redirect); + } + } + + /** + * @param Mage_Sales_Model_Order_Payment $payment + * + * @return bool + */ + protected function canRetryPayment($payment) + { + $additionalInformation = $payment->getAdditionalInformation(); + if (is_array($additionalInformation) && array_key_exists('status', $additionalInformation)) { + $status = $additionalInformation['status']; + return Netresearch_OPS_Model_Status::canResendPaymentInfo($status); + } + + return true; + } +} diff --git a/app/code/community/Netresearch/OPS/data/ops_setup/data-install-14.02.05.php b/app/code/community/Netresearch/OPS/data/ops_setup/data-install-14.02.05.php new file mode 100644 index 0000000..3821c2a --- /dev/null +++ b/app/code/community/Netresearch/OPS/data/ops_setup/data-install-14.02.05.php @@ -0,0 +1,12 @@ +setConfigData(Netresearch_OPS_Model_Config::OPS_PAYMENT_PATH . 'mode', Netresearch_OPS_Model_Source_Mode::TEST); diff --git a/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-14.02.05-14.06.25.php b/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-14.02.05-14.06.25.php new file mode 100644 index 0000000..3deb805 --- /dev/null +++ b/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-14.02.05-14.06.25.php @@ -0,0 +1,12 @@ +getStores(); +foreach ($stores as $store) { + $activatedBrands = Mage::getStoreConfig('payment/ops_directEbanking/brands', $store->getId()); + if (0 < strlen(trim($activatedBrands))) { + $activatedBrands = str_replace('Sofort Uberweisung', 'DirectEbanking', $activatedBrands); + Mage::getConfig()->saveConfig('payment/ops_directEbanking/brands', $activatedBrands, 'stores', $store->getId()); + } +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-14.06.25-14.07.16.php b/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-14.06.25-14.07.16.php new file mode 100644 index 0000000..05ff059 --- /dev/null +++ b/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-14.06.25-14.07.16.php @@ -0,0 +1,150 @@ + + * @category Netresearch + * @copyright Copyright (c) 2014 Netresearch GmbH & Co. KG (http://www.netresearch.de) + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ +/* + * @var $installer Mage_Core_Model_Resource_Setup + */ +$installer = $this; + +$regions = array( + array("CN",11,"Beijing"), + array("CN",50,"Chongqing"), + array("CN",31,"Shanghai"), + array("CN",12,"Tianjin"), + array("CN",34,"Anhui"), + array("CN",35,"Fujian"), + array("CN",62,"Gansu"), + array("CN",44,"Guangdong"), + array("CN",52,"Guizhou"), + array("CN",46,"Hainan"), + array("CN",13,"Hebei"), + array("CN",23,"Heilongjiang"), + array("CN",41,"Henan"), + array("CN",42,"Hubei"), + array("CN",43,"Hunan"), + array("CN",32,"Jiangsu"), + array("CN",36,"Jiangxi"), + array("CN",22,"Jilin"), + array("CN",21,"Liaoning"), + array("CN",63,"Qinghai"), + array("CN",61,"Shaanxi"), + array("CN",37,"Shandong"), + array("CN",14,"Shanxi"), + array("CN",51,"Sichuan"), + array("CN",71,"Taiwan"), + array("CN",53,"Yunnan"), + array("CN",33,"Zhejiang"), + array("CN",45,"Guangxi"), + array("CN",15,"Nei Mongol"), + array("CN",64,"Ningxia"), + array("CN",65,"Xinjiang"), + array("CN",54,"Xizang"), + array("CN",91,"Xianggang"), + array("CN",92,"Aomen"), + array("JP",1,"Hokkaidô"), + array("JP",2,"Aomori"), + array("JP",3,"Iwate"), + array("JP",4,"Miyagi"), + array("JP",5,"Akita"), + array("JP",6,"Yamagata"), + array("JP",7,"Hukusima"), + array("JP",8,"Ibaraki"), + array("JP",9,"Totigi"), + array("JP",10,"Gunma"), + array("JP",11,"Saitama"), + array("JP",12,"Tiba"), + array("JP",13,"Tôkyô"), + array("JP",14,"Kanagawa"), + array("JP",15,"Niigata"), + array("JP",16,"Toyama"), + array("JP",17,"Isikawa"), + array("JP",18,"Hukui"), + array("JP",19,"Yamanasi"), + array("JP",20,"Nagano"), + array("JP",21,"Gihu"), + array("JP",22,"Sizuoka"), + array("JP",23,"Aiti"), + array("JP",24,"Mie"), + array("JP",25,"Siga"), + array("JP",26,"Kyôto"), + array("JP",27,"Ôsaka"), + array("JP",28,"Hyôgo"), + array("JP",29,"Nara"), + array("JP",30,"Wakayama"), + array("JP",31,"Tottori"), + array("JP",32,"Simane"), + array("JP",33,"Okayama"), + array("JP",34,"Hirosima"), + array("JP",35,"Yamaguti"), + array("JP",36,"Tokusima"), + array("JP",37,"Kagawa"), + array("JP",38,"Ehime"), + array("JP",39,"Kôti"), + array("JP",40,"Hukuoka"), + array("JP",41,"Saga"), + array("JP",42,"Nagasaki"), + array("JP",43,"Kumamoto"), + array("JP",44,"Ôita"), + array("JP",45,"Miyazaki"), + array("JP",46,"Kagosima"), + array("JP",47,"Okinawa"), + array("MX","DIF","Distrito Federal"), + array("MX","AGU","Aguascalientes"), + array("MX","BCN","Baja California"), + array("MX","BCS","Baja California Sur"), + array("MX","CAM","Campeche"), + array("MX","COA","Coahuila"), + array("MX","COL","Colima"), + array("MX","CHP","Chiapas"), + array("MX","CHH","Chihuahua"), + array("MX","DUR","Durango"), + array("MX","GUA","Guanajuato"), + array("MX","GRO","Guerrero"), + array("MX","HID","Hidalgo"), + array("MX","JAL","Jalisco"), + array("MX","MEX","México"), + array("MX","MIC","Michoacán"), + array("MX","MOR","Morelos"), + array("MX","NAY","Nayarit"), + array("MX","NLE","Nuevo León"), + array("MX","OAX","Oaxaca"), + array("MX","PUE","Puebla"), + array("MX","QUE","Querétaro"), + array("MX","ROO","Quintana Roo"), + array("MX","SLP","San Luis Potosí"), + array("MX","SIN","Sinaloa"), + array("MX","SON","Sonora"), + array("MX","TAB","Tabasco"), + array("MX","TAM","Tamaulipas"), + array("MX","TLA","Tlaxcala"), + array("MX","VER","Veracruz"), + array("MX","YUC","Yucatán"), + array("MX","ZAC","Zacatecas") +); + +foreach ($regions as $row) { + $bind = array( + 'country_id' => $row[0], + 'code' => $row[1], + 'default_name' => $row[2], + ); + $installer->getConnection()->insert($installer->getTable('directory/country_region'), $bind); + $regionId = $installer->getConnection()->lastInsertId($installer->getTable('directory/country_region')); + + $bind = array( + 'locale' => 'en_US', + 'region_id' => $regionId, + 'name' => $row[2] + ); + $installer->getConnection()->insert($installer->getTable('directory/country_region_name'), $bind); +} + +$countries = explode(',', Mage::getStoreConfig(Mage_Directory_Helper_Data::XML_PATH_STATES_REQUIRED)); +$countries[] = 'CN'; +$countries[] = 'JP'; +$countries[] = 'MX'; +Mage::getConfig()->saveConfig(Mage_Directory_Helper_Data::XML_PATH_STATES_REQUIRED, implode(',', $countries)); \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-15.06.08-15.11.02.php b/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-15.06.08-15.11.02.php new file mode 100644 index 0000000..6e27115 --- /dev/null +++ b/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-15.06.08-15.11.02.php @@ -0,0 +1,44 @@ + + */ +?> +getModuleConfig('Mage_Admin')->version; +if (version_compare($adminVersion, '1.6.1.1', '>')) { + /** @var Mage_Admin_Model_Variable $variable */ + $variable = Mage::getModel('admin/variable')->load($variableName, 'variable_name'); + + $variable->setData('variable_name', $variableName) + ->setData('is_allowed', 1) + ->save(); +} \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-15.11.09-16.03.09.php b/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-15.11.09-16.03.09.php new file mode 100644 index 0000000..40dfa65 --- /dev/null +++ b/app/code/community/Netresearch/OPS/data/ops_setup/data-upgrade-15.11.09-16.03.09.php @@ -0,0 +1,37 @@ +saveConfig($aliasUrlPath, $newAliasUrl); + +$websites = Mage::app()->getWebsites(); + +/** @var Mage_Core_Model_Website $website */ +foreach($websites as $website){ + $oldWebsiteAliasUrl = $website->getConfig($aliasUrlPath); + if(strlen($oldWebsiteAliasUrl) > 0 && $oldWebsiteAliasUrl != $oldAliasUrl){ + + $newWebsiteAliasUrl = str_replace('ncol/prod/alias_gateway_utf8.asp', 'Tokenization/HostedPage', $oldWebsiteAliasUrl); + Mage::getConfig()->saveConfig($aliasUrlPath, $newWebsiteAliasUrl, 'websites', $website->getId()); + } + /** @var Mage_Core_Model_Store $store */ + foreach($website->getStores() as $store){ + $oldStoreAliasUrl = Mage::getStoreConfig($aliasUrlPath, $store->getId()); + if(strlen($oldStoreAliasUrl) > 0 && $oldStoreAliasUrl != $oldAliasUrl) { + $newStoreAliasUrl = str_replace('ncol/prod/alias_gateway_utf8.asp', 'Tokenization/HostedPage', $oldStoreAliasUrl); + Mage::getConfig()->saveConfig($aliasUrlPath, $newStoreAliasUrl, 'stores', $store->getId()); + } + } +} + +Mage::getConfig()->cleanCache(); + + diff --git a/app/code/community/Netresearch/OPS/etc/config.xml b/app/code/community/Netresearch/OPS/etc/config.xml new file mode 100644 index 0000000..16345ef --- /dev/null +++ b/app/code/community/Netresearch/OPS/etc/config.xml @@ -0,0 +1,599 @@ + + + + + + 16.12.14 + + + + + + Netresearch_OPS_Helper + + + + + Netresearch_OPS_Model + ops_mysql4 + + + Netresearch_OPS_Model_Mysql4 + + + ops_alias
+
+ + ops_kwixo_category_mapping
+
+ + ops_kwixo_shipping_setting
+
+
+
+
+ + + Netresearch_OPS_Block + + + + + + + + singleton + ops/observer + checkoutTypeOnepageSaveOrderBefore + + + + + + + singleton + ops/observer + setOrderStateDirectLink + + + + + + + + singleton + ops/observer + updateOrderCancelButton + + + singleton + ops/observer + updateRecurringProfileButtons + + + + + + + + downloadable/observer + setLinkStatus + + + + + + + + singleton + ops/observer + clearPaymentMethodFromQuote + + + + + + + ops/observer + addCcPaymentMethod + + + + + + + + ops/observer + sendTransactionalEmails + singleton + + + + + + + + + Netresearch_OPS + Mage_Sales_Model_Mysql4_Setup + + + + + +
+ + + + + + Netresearch_OPS_Adminhtml + + + + + + + + + + ops.xml + + + + + + + + ops/observer + appendCheckBoxToRefundForm + + + ops/observer + showWarningForClosedTransactions + + + singleton + ops/observer + updateRecurringProfileEditForm + + + + + + + + ops/observer + salesOrderPaymentCapture + + + + + + + ops/observer + addStatusUpdateButtonToOrderView + + + ops/observer + disableCaptureForZeroAmountInvoice + + + ops/observer + addResendPaymentInfoButtonToOrderView + + + + + + + ops/observer + sendPayPerMailInfo + + + + + + + + + Netresearch_OPS.csv + + + + + + + + + + ops.xml + + + + + /ops/api + + + + standard + + Netresearch_OPS + ops + + + + + + + + Netresearch_OPS.csv + + + + + + + + + ops/observer + controllerActionCheckoutOnepagePostdispatch + + + + + + + ops/observer + overrideRecurringProfileState + + + + + + + + + + ops/payment_abstract + Ingenico ePayments + + sha1 + authorize + 0 + + custom + + https://secure.ogone.com/ncol/test + https://secure.ogone.com/ncol/prod + /orderstandard_utf8.asp + /Tokenization/HostedPage + /orderstandard_utf8.asp + /maintenancedirect.asp + /orderdirect.asp + /querydirect.asp + + https://secure.ogone.com/ncol/test/orderstandard_utf8.asp + https://secure.ogone.com/ncol/test/Tokenization/HostedPage + https://secure.ogone.com/ncol/test/orderstandard_utf8.asp + https://secure.ogone.com/ncol/test/maintenancedirect.asp + https://secure.ogone.com/ncol/test/orderdirect.asp + https://secure.ogone.com/ncol/test/querydirect.asp + https://ogone.test.v-psp.com/Tokenization/HostedPage + 1 + 1 + support@ecom.ingenico.com + + http://payment-services.ingenico.com/en/ogone/support/guides/integration%20guides/magento-extension + http://ingenico.support.netresearch.de/ + http://ingenico.support.netresearch.de/ + orderId + orderId + 1 + 0 + + 1 + + 50 + 50 + 35 + 35 + 35 + 2 + 25 + 10 + 25 + 10 + 35 + 35 + 10 + 2 + 40 + 30 + 35 + 10 + 50 + 50 + + + + billing:firstname + billing:lastname + + billing:postcode + billing:region_id + billing:city + OWNERTELNO + billing:street1 + billing:firstname + billing:lastname + billing:postcode + shipping:firstname + shipping:lastname + shipping:street1 + shipping:street2 + shipping:street3 + shipping:country_id + shipping:region_id + shipping:postcode + shipping:city + shipping:street + shipping:region_id + + sales + payment_services_ops_resendPaymentInfo_template + payment_services_ops_payPerMail_template + 15 + 0 + sales + payment_services_ops_suspendSubscription_template + 0 + 45 + 0 + + + + + Ingenico ePayments DirectDebit + ops/payment_directDebit + + + Ingenico ePayments PostFinanceEFinance + ops/payment_postFinanceEFinance + + + Ingenico ePayments EDankort + ops/payment_eDankort + + + Ingenico ePayments KbcOnline + ops/payment_kbcOnline + + + Ingenico ePayments iDEAL + ops/payment_iDeal + + ABN AMRO + Rabobank + ING + SNS Bank + Regio Bank + ASN Bank + Bunq + Triodos Bank + Van Lanschot Bankiers + Knab Bank + + + + Ingenico ePayments Belfius Direct Net + ops/payment_belfiusDirectNet + + + Ingenico ePayments BankTransfer + ops/payment_bankTransfer + + + Rechnung + ops/payment_openInvoiceDe + 0 + 1 + DE + + + Rechnung (Niederlande) + ops/payment_openInvoiceNl + 1 + NL + + + Rechnung (Österreich) + ops/payment_openInvoiceAt + 0 + 1 + AT + + + Ingenico ePayments CbcOnline + ops/payment_cbcOnline + + + Ingenico ePayments GiroPay + ops/payment_giroPay + + + Ingenico ePayments Masterpass + ops/payment_masterpass + + + Ingenico ePayments InterSolve + ops/payment_interSolve + + + Ingenico ePayments CashU + ops/payment_cashU + + + Ingenico ePayments Paypal + ops/payment_paypal + + + Ingenico ePayments Eps + ops/payment_eps + + + Ingenico ePayments PingPing + ops/payment_pingPing + + + Ingenico ePayments FortisPayButton + ops/payment_fortisPayButton + + + Ingenico ePayments SofortÜberweisung + ops/payment_directEbanking + + + Ingenico ePayments Cc + ops/payment_cc + 0 + 0 + American Express,Diners Club,Maestro,MaestroUK,MasterCard,VISA,JCB + AIRPLUS,American Express,Aurore,CB,Cofinoga,Dankort,Diners Club,JCB,Maestro,MaestroUK,MasterCard,PRIVILEGE,PostFinance card,UATP,UNEUROCOM,VISA + 0 + + + Ingenico ePayments DebitCard + ops/payment_debitcard + 0 + 0 + Maestro,MaestroUK,MasterCard,VISA + Maestro,MaestroUK,MasterCard,VISA + 0 + + + Ingenico ePayments IngHomePay + ops/payment_ingHomePay + + + Ingenico ePayments Paysafecard + ops/payment_paysafecard + + + Ingenico ePayments Kwixo Credit + ops/payment_kwixoCredit + + + Ingenico ePayments Kwixo Apres Reception + ops/payment_KwixoApresReception + + + Ingenico ePayments Kwixo Comptant + ops/payment_KwixoComptant + + + Additional Ingenico ePayments payment methods + ops/payment_Flex + + + Existing Ingenico ePayments Transaction + ops/payment_backend_opsId + + + + Netresearch_OPS_Model_Payment_OpenInvoiceNl + Netresearch_OPS_Model_Payment_OpenInvoiceAt + + + Netresearch_OPS_Model_Payment_OpenInvoiceNl + Netresearch_OPS_Model_Payment_OpenInvoiceAt + + + + Ingenico ePayments ChinaUnionPay + ops/payment_chinaUnionPay + + + 0 + Ingenico ePayments Subscription Manager via Cc + ops/payment_recurring_cc + 0 + American Express,Diners Club,MaestroUK,MasterCard,VISA,JCB + American Express,Diners Club,MaestroUK,MasterCard,VISA,JCB + 0 + + + <![CDATA[Ingenico ePayments Bancontact/MisterCash]]> + ops/payment_bancontact + + + <![CDATA[Ingenico ePayments PayPerMail]]> + ops/payment_payPerMail + 1 + + + + + + + + + */5 * * * * + + + ops/observer::cleanUpOldPaymentData + + + + + + + + + + + +
diff --git a/app/code/community/Netresearch/OPS/etc/jstranslator.xml b/app/code/community/Netresearch/OPS/etc/jstranslator.xml new file mode 100644 index 0000000..a4a9472 --- /dev/null +++ b/app/code/community/Netresearch/OPS/etc/jstranslator.xml @@ -0,0 +1,27 @@ + + + + Payment failed. Please select another payment method. + + + Payment failed. Please review your input or select another payment method. + + + LOAD_TOKEN + + + SUCCESS_TOKEN + + + SUCCESS_TOKEN_FRONTEND + + + FAILURE_TOKEN + + + FAILURE_TOKEN_FRONTEND + + + This is a required field. + + \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/etc/system.xml b/app/code/community/Netresearch/OPS/etc/system.xml new file mode 100644 index 0000000..d8bd02f --- /dev/null +++ b/app/code/community/Netresearch/OPS/etc/system.xml @@ -0,0 +1,3542 @@ + + + + + + + sales + text + 450 + 1 + 1 + 1 + + + + text + ops/system_config_support + 0 + 1 + 1 + 1 + + + + text + 1 + 1 + 1 + 1 + + + + This will log requests and responses in /var/log/ops.log + Make sure the global Magento logging is also enabled. + select + adminhtml/system_config_source_yesno + 2000 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>30</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + + + + text + PSPID is a case sensitive field + 40 + 1 + 1 + 1 + + + + select + ops/system_config_backend_hashMethods + 49 + 1 + 1 + 1 + Technical information on tab Global security parameters.]]> + + + + Technical Information on tab Data and origin verification.]]> + Technical information on tab Data and origin verification.]]> + adminhtml/system_config_backend_encrypted + obscure + 50 + 1 + 1 + 1 + + + + Technical Information on tab Transaction feedback.]]> + Technical information on tab Transaction feedback.]]> + adminhtml/system_config_backend_encrypted + obscure + 60 + 1 + 1 + 1 + + + + + 69 + select + ops/source_mode + ops/system_config_mode + Please note that, after changing this value, you might also need to update additional fields like API-User and password, PSPID or SHA-IN/OUT pass phrase.

]]>
+ 1 + 1 + 1 +
+ + + text + Production url example: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp]]> + 70 + 1 + 1 + 1 + custom + + + + text + Production url example: https://secure.ogone.com/Tokenization/HostedPage]]> + 71 + 1 + 1 + 1 + custom + + + + text + Production url example: https://secure.ogone.com/ncol/prod/maintenancedirect.asp]]> + 72 + 1 + 1 + 1 + custom + + + + text + Production url example: https://secure.ogone.com/ncol/prod/orderdirect.asp]]> + 73 + 1 + 1 + 1 + custom + + + + text + Production url example: https://secure.ogone.com/ncol/prod/querydirect.asp]]> + 74 + 1 + 1 + 1 + custom + + + + Users.]]> + text + 85 + 1 + 1 + 1 + + + + + adminhtml/system_config_backend_encrypted + obscure + 88 + 1 + 1 + 1 + + + + Technical information on tab Global transaction parameters.]]> + select + ops/source_paymentAction + 90 + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_yesno + authorize_capture + 95 + 1 + 1 + 0 + + + + + Enter the TemplateRootId you received from Ingenico ePayments or the url to an external template. + text + 102 + 1 + 1 + 1 + + required-entry + + + + text + 110 + 1 + 1 + 1 + + required-entry + + + + text + + 130 + 1 + 1 + 1 + + required-entry + + + + text + + 140 + 1 + 1 + 1 + + required-entry + + + + text + + 150 + 1 + 1 + 1 + + required-entry + + + + text + + 160 + 1 + 1 + 1 + + required-entry + + + + select + adminhtml/system_config_source_yesno + 165 + 1 + 1 + 1 + + required-entry + + + + select + adminhtml/system_config_source_yesno + 165 + 1 + 1 + 1 + + required-entry + + + + text + 170 + 1 + 1 + 1 + + required-entry + + + + text + + 180 + 1 + 1 + 1 + + required-entry + + + + text + + 190 + 1 + 1 + 1 + + required-entry + + + + text + URL/filename of the logo. The URL must be absolute and stored on a secure server. + 200 + 1 + 1 + 1 + + required-entry + + + + + select + ops/source_pmlist + 210 + 1 + 1 + 1 + + required-entry + + + + See chapter 'Configuration', section 'Magento - Backend' in the documentation. Please note that the option 'orderId' is not used for the Kwixo payment methods. + select + ops/source_orderReference + 220 + 1 + 1 + 1 + + + + See chapter 'Configuration', section 'Magento - Backend' in the documentation. + select + ops/source_orderReference + 221 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 230 + 1 + 1 + 1 + + + + + Enable this if you wish to use fraud detection + select + adminhtml/system_config_source_yesno + 240 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_email_identity + 250 + 1 + 1 + 1 + Sender identity. See 'Store Email Adresses'. + + + + select + adminhtml/system_config_source_email_template + 260 + 1 + 1 + 1 + The email template to use. Choose 'Default Template from Locale' to use Ingenico ePayments extensions default template. + + + + When enabling this, you still need to request the explicit consent of the customer in the checkout to transmit this data. + This functionality will transmit specific customer data as hash to Ingenico ePayments to identify the device from which the transaction was sent can be identified with the highest probability. + select + adminhtml/system_config_source_yesno + 280 + 1 + 0 + 0 + + + + This value specifies the request timeout for that transaction. A value between 30 and 60 seconds is possible. + text + validate-digits-range digits-range-30-60 + 290 + 1 + 0 + 0 + + + + + Ingenico ePayments documentation.]]> + select + adminhtml/system_config_source_yesno + 300 + 1 + 1 + 1 + + + + text + validate-digits validate-digits-range digits-range-1-31 + 300 + Determines the day of the month when a monthly subscription gets triggered. + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_locale_weekdays + 310 + Determines the day of the week when a weekly subscription gets triggered. + 1 + 1 + 0 + + + + select + adminhtml/system_config_source_email_identity + 320 + 1 + 1 + 1 + This contact will receive the suspend/cancel requests for the subscriptions. See 'Store Email Adresses'. + + + + select + adminhtml/system_config_source_email_template + 330 + 1 + 1 + 1 + This e-mail template is used for the automatically generated e-mail that is getting sent to the corresponding contact, if a customer wants to cancel a subscription. + +
+
+
+
+ + ops/system_config_paymentForm + + + + 40 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + multiselect + required-entry + 1 + 30 + ops/source_directDebit_countries + 1 + 1 + 1 + + + + 40 + allowspecific + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + 50 + multiselect + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 60 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 70 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 100 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 110 + If this option is enabled, the customer's alias data will be saved in the shop. The data can then be used again later if the buyer has stored them permanently on Ingenico ePayments side. + 1 + 1 + 1 + + + + + + 50 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 60 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 70 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 80 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 90 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 100 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + multiselect + required-entry + 1 + 30 + ops/source_bankTransfer_countries + 1 + 1 + 1 + + + + allowspecific + 40 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 50 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 60 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 70 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 110 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + gender and date of birth are required fields in the checkout. Otherwise this payment method will not be displayed.]]> + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + Allow payment method for discounted carts. Please note that not all aquirers support transmission of discount items. + select + adminhtml/system_config_source_yesno + 10 + 1 + 1 + 1 + + + + text + 20 + 1 + 1 + 1 + + + + 30 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 40 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + 50 + select + ops/system_config_backend_encoding + + + 1 + 1 + 1 + + + + 60 + select + adminhtml/system_config_source_yesno + + 1 + 1 + 1 + + + + 61 + text + 1 + 1 + 1 + + 1 + + required-entry + + + + 62 + text + 1 + 1 + 1 + + 1 + + required-entry + + + + + + 120 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + gender and date of birth are required fields in the checkout. Otherwise this payment method will not be displayed.]]> + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + Allow payment method for discounted carts. Please note that not all aquirers support transmission of discount items. + select + adminhtml/system_config_source_yesno + 10 + 1 + 1 + 1 + + + + text + 20 + 1 + 1 + 1 + + + + 30 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 40 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + 50 + select + ops/system_config_backend_encoding + + + 1 + 1 + 1 + + + + 60 + select + adminhtml/system_config_source_yesno + + 1 + 1 + 1 + + + + 61 + text + 1 + 1 + 1 + + 1 + + required-entry + + + + 62 + text + 1 + 1 + 1 + + 1 + + required-entry + + + + + + 130 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + gender and date of birth are required fields in the checkout. Otherwise this payment method will not be displayed.]]> + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + 30 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 40 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + 50 + select + ops/system_config_backend_encoding + + + 1 + 1 + 1 + + + + 60 + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + + + + + 61 + text + 1 + 1 + 1 + + 1 + + required-entry + + + + 62 + text + 1 + 1 + 1 + + 1 + + required-entry + + + + + + 140 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 150 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 160 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 170 + 1 + 1 + 1fkbc + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + ops/system_config_form_field_brand + ops/system_config_backend_intersolve_brands + 70 + 1 + 1 + 1 + Add one or more brands. In field "BRAND" you will have to enter the value you got from Ingenico ePayments. + + + + + + 180 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 200 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 210 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 220 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 230 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 240 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + multiselect + required-entry + 1 + 20 + ops/source_directEbanking_brands + 1 + 1 + 1 + + + + text + 30 + 1 + 1 + 1 + + + + allowspecific + 40 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 50 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 60 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 70 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 250 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 10 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + multiselect + 30 + ops/source_cc_types + 1 + 1 + 1 + + + + Yes, customers will be redirected to the external Ingenico ePayments payment page.]]> + select + adminhtml/system_config_source_yesno + 40 + 1 + 1 + 1 + + + + + multiselect + ops/source_cc_aliasInterfaceEnabledTypes + 41 + 0 + 1 + 1 + 1 + + + + + select + adminhtml/system_config_source_yesno + 50 + 1 + 1 + 0 + + + + 80 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 90 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + select + adminhtml/system_config_source_yesno + 100 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 110 + If this option is enabled, the customer's alias data will be saved in the shop. The data can then be used again later if the buyer has stored them permanently on Ingenico ePayments side. + 1 + 1 + 1 + + + + text + 110 + Here you can enter the desired text which will be displayed on Ingenico ePayments side. + 1 + 1 + 1 + + + + text + 111 + Here you can enter the desired text which is used for existing alias, the text will be displayed on Ingenico ePayments side. + 1 + 1 + 1 + + + + Only if payment action is authorize. + select + adminhtml/system_config_source_yesno + 130 + 1 + 1 + 1 + + + + + + 260 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 10 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>11</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + multiselect + 30 + ops/source_debitCard_types + 1 + 1 + 1 + + + + Yes, customers will be redirected to the external Ingenico ePayments payment page.]]> + select + adminhtml/system_config_source_yesno + 40 + 1 + 1 + 1 + + + + + multiselect + ops/source_debitCard_aliasInterfaceEnabledTypes + 41 + 0 + 1 + 1 + 1 + + + + + select + adminhtml/system_config_source_yesno + 50 + 1 + 1 + 0 + + + + 80 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 90 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + select + adminhtml/system_config_source_yesno + 100 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 110 + 1 + 1 + 1 + + + + text + 111 + Here you can enter the desired text which will be displayed on Ingenico ePayments side. + 1 + 1 + 1 + + + + text + 112 + Here you can enter the desired text which is used for existing alias, the text will be displayed on Ingenico ePayments side. + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 120 + 1 + 1 + 1 + 1 + + + + Only if payment action is authorize. + select + adminhtml/system_config_source_yesno + 130 + 1 + 1 + 1 + + + + + + 270 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 280 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 290 + 1 + 1 + 1 + + + + text + ops/system_config_kwixoconfiguration + 0 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + text + 30 + required-entry validate-number validate-greater-than-zero + 1 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 40 + 1 + 1 + 1 + 1 + + + + select + ops/source_kwixo_shipMethodType + validate-select + 50 + 1 + 1 + 1 + + 1 + + + + + Text + validate-number validate-greater-than-zero + 60 + 1 + 1 + 1 + 1 + + + + Textarea + validate-length maximum-length-50 + 80 + 1 + 1 + 1 + 1 + + + + allowspecific + 90 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 100 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 110 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 120 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 300 + 1 + 1 + 1 + + + + text + ops/system_config_kwixoconfiguration + 0 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + text + 30 + required-entry validate-greater-than-zero + 1 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 40 + 1 + 1 + 1 + 1 + + + + select + ops/source_kwixo_shipMethodType + 50 + 1 + 1 + 1 + 1 + + + + Text + validate-number validate-greater-than-zero + 60 + 1 + 1 + 1 + 1 + + + + Text + validate-length maximum-length-50 + 70 + 1 + 1 + 1 + 1 + + + + allowspecific + 80 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 90 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 100 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 110 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 310 + 1 + 1 + 1 + + + + text + ops/system_config_kwixoconfiguration + 0 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + text + 30 + required-entry validate-number validate-greater-than-zero + 1 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 40 + 1 + 1 + 1 + 1 + + + + select + ops/source_kwixo_shipMethodType + validate-select + 50 + 1 + 1 + 1 + 1 + + + + Text + validate-number validate-greater-than-zero + 60 + 1 + 1 + 1 + 1 + + + + Textarea + validate-length maximum-length-50 + 80 + 1 + 1 + 1 + 1 + + + + allowspecific + 90 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 100 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 110 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 120 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 320 + 1 + 0 + 0 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 0 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>0</show_in_website> + <show_in_store>0</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 0 + 0 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 330 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + <depends><active>1</active></depends> + + + + text + 20 + 1 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 30 + 1 + 1 + 1 + + 1 + + + + text + 31 + 1 + 1 + 1 + required-entry + Title to display for the default option. + 1 + + + + ops/system_config_form_field_method + ops/system_config_backend_flex_methods + 40 + 1 + 1 + 1 + Add one or more methods. In field 'PM' you will have to enter the PM value you got from Ingenico ePayments, same goes for the field 'BRAND'. + 1 + + + + allowspecific + 50 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 60 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 70 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 80 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 340 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <comment>This payment method always uses direct sale as payment action.</comment> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 350 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + ops/system_config_form_field_recurringActive + To use this functionality you have to have the Subscription manager function enabled in the Ingenico ePayments backend.

]]>
+ + 10 + 1 + 1 + 1 +
+ + + multiselect + 30 + ops/source_cc_recurringTypes + 1 + 1 + 1 + 1 + + + + + select + adminhtml/system_config_source_yesno + 50 + 1 + 1 + 0 + 1 + + + + 80 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 90 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + +
+
+ + + 360 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + + + + text + 20 + 1 + 1 + 1 + + + + allowspecific + 30 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 40 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + 50 + paymentLogo + adminhtml/system_config_backend_image + ops/paymentLogo + ops/paymentLogo + 1 + 1 + 1 + Allowed file types: jpeg, gif, png. + + + + 60 + select + ops/system_config_backend_paymentLogo + 1 + 1 + 1 + + + + + + 370 + 1 + 1 + 1 + + + + select + adminhtml/system_config_source_yesno + 1 + 1 + 1 + 0 + + + <label>Title</label> + <frontend_type>text</frontend_type> + <sort_order>10</sort_order> + <show_in_default>1</show_in_default> + <show_in_website>1</show_in_website> + <show_in_store>1</show_in_store> + <validate>required-entry</validate> + <depends> + <active>1</active> + </depends> + + + + text + 20 + 1 + 1 + 1 + 1 + + 1 + + + + + allowspecific + 50 + adminhtml/system_config_source_payment_allspecificcountries + 1 + 1 + 0 + + + + multiselect + 60 + adminhtml/system_config_source_country + 1 + 1 + 0 + 1 + 1 + + + + select + adminhtml/system_config_source_email_template + 260 + 1 + 1 + 1 + The email template to use. Choose 'Default Template from Locale' to use Ingenico ePayments extensions default template. + + + + + + + 0 + 0 + 0 + + + 0 + 0 + 0 + + + 27 + +
+
+
+
diff --git a/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-install-1.0.1.php b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-install-1.0.1.php new file mode 100644 index 0000000..61f4dae --- /dev/null +++ b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-install-1.0.1.php @@ -0,0 +1,46 @@ + + * @author Thomas Birke + * @copyright 2012 Netresearch App Factory AG (http://www.netresearch.de) + * @license OSL 3.0 (http://opensource.org/licenses/osl-3.0.php) + * @link http://www.netresearch.de/magento/ + */ + +/* @var $this Mage_Sales_Model_Mysql4_Setup */ +$this->startSetup(); +// Add column to grid table +$this->getConnection()->addColumn( + $this->getTable('sales/order_grid'), + 'quote_id', + "int(10) unsigned NOT NULL default '0' comment 'id of related quote'" +); +// Add key to table for this field, +// it will improve the speed of searching & sorting by the field +$this->getConnection()->addKey( + $this->getTable('sales/order_grid'), + 'quote_id', + 'quote_id' +); + +// Now you need to fullfill existing rows with data from address table +$select = $this->getConnection()->select(); +$select->join( + array('flat_order'=>$this->getTable('sales/order')), + $this->getConnection()->quoteInto( + 'flat_order.entity_id = order_grid.entity_id', + 'quote_id' + ), + array('quote_id' => 'quote_id') +); +$this->getConnection()->query( + $select->crossUpdateFromSelect( + array('order_grid' => $this->getTable('sales/order_grid')) + ) +); + +$this->endSetup(); \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-1.0.1-12.12.03.php b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-1.0.1-12.12.03.php new file mode 100644 index 0000000..3a8ee78 --- /dev/null +++ b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-1.0.1-12.12.03.php @@ -0,0 +1,24 @@ +startSetup(); + +$installer->run( + " + DROP TABLE IF EXISTS {$this->getTable('ops_alias')}; + CREATE TABLE {$this->getTable('ops_alias')} ( + `id` int(11) unsigned NOT NULL auto_increment, + `customer_id` int(10), + `alias` varchar(32), + `brand` varchar(50), + `billing_address_hash` varchar(255), + `shipping_address_hash` varchar(255), + `pseudo_account_or_cc_no` varchar(255), + `expiration_date` varchar(10), + `payment_method` varchar(50), + `created_at` timestamp default CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +" +); + +$installer->endSetup(); \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.04.10-13.04.16.php b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.04.10-13.04.16.php new file mode 100644 index 0000000..7313e34 --- /dev/null +++ b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.04.10-13.04.16.php @@ -0,0 +1,126 @@ +startSetup(); + +// delete obsolete credit card brands +$obsoleteCCTypes = array( + 'billy', + 'solo', + 'aurora', + 'netreserve', + +); + +// remove obsolete credit card brands from the configured types array +$ccTypes = Mage::getModel('core/config_data')->getCollection() + ->addFieldToFilter('path', 'payment/ops_cc/types') + ->load(); +foreach ($ccTypes as $ccType) { + $newCcTypes = array(); + $values = explode(',', $ccType->getValue()); + foreach ($values as $value) { + if (in_array(strToLower($value), $obsoleteCCTypes)) { + continue; + } + $newCcTypes[] = $value; + } + $ccType->setValue(implode(',', $newCcTypes)); + $ccType->save(); +} + +// remove obsolete credit card brands from available types array +$ccTypes = Mage::getModel('core/config_data')->getCollection() + ->addFieldToFilter('path', 'payment/ops_cc/availableTypes') + ->load(); +foreach ($ccTypes as $ccType) { + $newCcTypes = array(); + $values = explode(',', $ccType->getValue()); + foreach ($values as $value) { + if (in_array(strToLower($value), $obsoleteCCTypes)) { + continue; + } + $newCcTypes[] = $value; + } + $ccType->setValue(implode(',', $newCcTypes)); + $ccType->save(); +} + +// remove obsolete credit card brands from the configured inline types array +$ccTypes = Mage::getModel('core/config_data')->getCollection() + ->addFieldToFilter('path', 'payment/ops_cc/inline_types') + ->load(); +foreach ($ccTypes as $ccType) { + $newCcTypes = array(); + $values = explode(',', $ccType->getValue()); + foreach ($values as $value) { + if (in_array(strToLower($value), $obsoleteCCTypes)) { + continue; + } + $newCcTypes[] = $value; + } + $ccType->setValue(implode(',', $newCcTypes)); + $ccType->save(); +} + +// delete obsolete payment method config acceptgiro and centea online +$installer->run( + " + DELETE FROM {$this->getTable('core_config_data')} + WHERE 'path' IN + ('payment/ops_acceptgiro/active', + 'payment/ops_acceptgiro/title', + 'payment/ops_acceptgiro/sort_order' + ); + DELETE FROM {$this->getTable('core_config_data')} + WHERE `path` IN + ('payment/ops_centeaonline/active', + 'payment/ops_centeaonline/title', + 'payment/ops_centeaonline/sort_order' + ); +" +); + + +// update dexiaOnline to belfiusOnline +$belfiusActive = Mage::getModel('core/config_data')->getCollection() + ->addFieldToFilter('path', 'payment/ops_belfiusDirectNet/active') + ->load(); +if (0 === $belfiusActive->count()) { + $installer->run( + " + UPDATE {$this->getTable('core_config_data')} + SET path = 'payment/ops_belfiusDirectNet/active' + WHERE path = 'payment/ops_dexiaDirectNet/active'; + " + ); +} + +$belfiusTitle = Mage::getModel('core/config_data')->getCollection() + ->addFieldToFilter('path', 'payment/ops_belfiusDirectNet/title') + ->load(); +if (0 == $belfiusTitle->count()) { + $installer->run( + " + UPDATE {$this->getTable('core_config_data')} + SET path = 'payment/ops_belfiusDirectNet/title' + WHERE path = 'payment/ops_dexiaDirectNet/title'; + " + ); +} + +$belfiusSortOrder = Mage::getModel('core/config_data')->getCollection() + ->addFieldToFilter('path', 'payment/ops_belfiusDirectNet/sort_order') + ->load(); +if (0 === $belfiusSortOrder->count()) { + $installer->run( + " + UPDATE {$this->getTable('core_config_data')} + SET path = 'payment/ops_belfiusDirectNet/sort_order' + WHERE path = 'payment/ops_dexiaDirectNet/sort_order'; + " + ); +} +/* + * + */ +$installer->endSetup(); \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.05.30-13.06.07.php b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.05.30-13.06.07.php new file mode 100644 index 0000000..e4046dc --- /dev/null +++ b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.05.30-13.06.07.php @@ -0,0 +1,70 @@ +startSetup(); +$conn = $installer->getConnection(); +$conn->addColumn( + $this->getTable('ops/alias'), + 'card_holder', + 'VARCHAR(255) NULL DEFAULT NULL AFTER `alias`' +); +$conn->addColumn( + $this->getTable('ops/alias'), + 'state', + "VARCHAR(100) NULL DEFAULT '". Netresearch_OPS_Model_Alias_State::PENDING ."' AFTER `payment_method`" +); + +$conn->addColumn( + $this->getTable('ops/alias'), + 'store_id', + "smallint(5) NULL DEFAULT NULL AFTER `state`" +); + +$installer->run( + " + UPDATE {$this->getTable('ops_alias')} + SET state = '". Netresearch_OPS_Model_Alias_State::ACTIVE ."' + WHERE alias IS NOT NULL; + +" +); + +$installer->run( + " + DELETE FROM {$this->getTable('ops_alias')} + WHERE id NOT in ( + SELECT alias.id FROM ( + SELECT * FROM {$this->getTable('ops_alias')} + ORDER BY `created_at` desc + ) as alias + GROUP BY alias.customer_id, alias.billing_address_hash, alias.shipping_address_hash + ) +" +); + +$aliasActive = Mage::getModel('core/config_data')->getCollection() + ->addFieldToFilter('path', 'payment/ops_cc/active_alias') + ->load(); +if (0 === $aliasActive->count()) { + $installer->run( + " + UPDATE {$this->getTable('core_config_data')} + SET path = 'payment/ops_cc/active_alias' + WHERE path = 'payment/ops_alias/active'; + " + ); +} +$hintForGuestsActive = Mage::getModel('core/config_data')->getCollection() + ->addFieldToFilter('path', 'payment/ops_cc/show_alias_manager_info_for_guests') + ->load(); +if (0 == $hintForGuestsActive->count()) { + $installer->run( + " + UPDATE {$this->getTable('core_config_data')} + SET path = 'payment/ops_cc/show_alias_manager_info_for_guests' + WHERE path = 'payment/ops_alias/show_info_for_guests'; + " + ); +} + +$installer->endSetup(); + diff --git a/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.06.07-13.07.04.php b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.06.07-13.07.04.php new file mode 100644 index 0000000..2ce5b89 --- /dev/null +++ b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.06.07-13.07.04.php @@ -0,0 +1,17 @@ +startSetup(); + + + +$installer->run( + " + UPDATE {$this->getTable('core_config_data')} + SET value = 'Ingenico ePayments Belfius Direct Net' + WHERE path = 'payment/ops_belfiusDirectNet/title' + AND value = 'Ingenico ePayments BelfiusDirectNet'; + " +); + +$installer->endSetup(); + diff --git a/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.07.10-13.07.23.php b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.07.10-13.07.23.php new file mode 100644 index 0000000..f648f07 --- /dev/null +++ b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.07.10-13.07.23.php @@ -0,0 +1,14 @@ +startSetup(); +$installer->run( + " + CREATE TABLE IF NOT EXISTS {$this->getTable('ops/kwixo_category_mapping')} ( + `id` int(11) unsigned NOT NULL auto_increment, + `kwixo_category_id` int(10), + `category_id` int(10), + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + " +); +$installer->endSetup(); \ No newline at end of file diff --git a/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.11.04-13.11.05.php b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.11.04-13.11.05.php new file mode 100644 index 0000000..4d59817 --- /dev/null +++ b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-13.11.04-13.11.05.php @@ -0,0 +1,15 @@ +startSetup(); + +$installer->run( + " + UPDATE {$this->getTable('core_config_data')} + SET value = '-100' + WHERE path = 'payment/ops_cc/sort_order' + AND value is NULL + " +); + +$installer->endSetup(); + diff --git a/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-14.01.27-14.02.05.php b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-14.01.27-14.02.05.php new file mode 100644 index 0000000..a0ec7be --- /dev/null +++ b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-14.01.27-14.02.05.php @@ -0,0 +1,52 @@ +startSetup(); + +$tableName = $installer->getTable('ops/kwixo_shipping_setting'); +$table = $installer->getConnection() + ->newTable($tableName) + ->addColumn( + + 'id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => true, + 'identity' => true, + ), + 'id' + ) + ->addColumn( + 'shipping_code', Varien_Db_Ddl_Table::TYPE_VARCHAR, 255, array( + 'nullable' => false, + ), + 'shipping code' + ) + ->addColumn( + + 'kwixo_shipping_type', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + ), + 'kwixo shipping type' + ) + ->addColumn( + 'kwixo_shipping_speed', Varien_Db_Ddl_Table::TYPE_SMALLINT, null, array( + 'unsigned' => true, + 'nullable' => false, + 'primary' => false, + 'identity' => false, + ), + 'kwixo shipping type' + ) + ->addColumn( + + 'kwixo_shipping_details', Varien_Db_Ddl_Table::TYPE_VARCHAR, 50, array( + 'unsigned' => true, + 'nullable' => true, + ), + 'kwixo shipping details' + ) + ->addIndex('unique', 'shipping_code'); +$installer->getConnection()->createTable($table); +$installer->endSetup(); + diff --git a/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-16.05.26-27.06.16.php b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-16.05.26-27.06.16.php new file mode 100644 index 0000000..a67d87a --- /dev/null +++ b/app/code/community/Netresearch/OPS/sql/ops_setup/mysql4-upgrade-16.05.26-27.06.16.php @@ -0,0 +1,8 @@ +getConnection()->modifyColumn( + $installer->getTable('ops/alias'), 'alias', "varchar(255) NULL DEFAULT NULL" +); diff --git a/app/design/adminhtml/default/default/layout/ops.xml b/app/design/adminhtml/default/default/layout/ops.xml new file mode 100644 index 0000000..86eab1b --- /dev/null +++ b/app/design/adminhtml/default/default/layout/ops.xml @@ -0,0 +1,89 @@ + + + + + + + quote_id + +
Quote #
+ quote_id + text + 80px +
+ real_order_id +
+
+
+ + + + + + + + + + + + + skin_cssops.css + + + + + + + + + + + + + customer_edit_tab_ops_aliascustomer_edit_tab_ops_alias + + + + + extjs/ext-tree.js + extjs/ext-tree-checkbox.js + jsextjs/ext-tree.js + jsextjs/ext-tree-checkbox.js + js_cssextjs/resources/css/ext-all.css + js_cssextjs/resources/css/ytheme-magento.css + + + + + + + + + + + + + + + +
diff --git a/app/design/adminhtml/default/default/template/ops/categoriestree.phtml b/app/design/adminhtml/default/default/template/ops/categoriestree.phtml new file mode 100644 index 0000000..d9dbf8f --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/categoriestree.phtml @@ -0,0 +1,632 @@ + +
+
+

__('Categories') ?>

+
+getStoreSwitcherHtml() ?> +
+ getRoot()): ?> + getCollapseButtonHtml() ?> + getExpandButtonHtml() ?> + __('Collapse All'); ?> | __('Expand All'); ?> + +
+getRoot()): ?> +
+
+
+
+ + + + \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/ops/form/cc.phtml b/app/design/adminhtml/default/default/template/ops/form/cc.phtml new file mode 100644 index 0000000..31cee98 --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/form/cc.phtml @@ -0,0 +1,177 @@ + + */ + +/** @var $this Netresearch_OPS_Block_Form_Cc */ +?> + + +getMethodCode() ?> + +getQuote()->getStoreId(); ?> + + + diff --git a/app/design/adminhtml/default/default/template/ops/form/directDebit.phtml b/app/design/adminhtml/default/default/template/ops/form/directDebit.phtml new file mode 100644 index 0000000..133cc5b --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/form/directDebit.phtml @@ -0,0 +1,189 @@ + + */ +?> + + +getMethodCode(); +$storeId = $this->getQuote()->getStoreId(); +?> + \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/ops/form/kwixo/category.phtml b/app/design/adminhtml/default/default/template/ops/form/kwixo/category.phtml new file mode 100644 index 0000000..2193dab --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/form/kwixo/category.phtml @@ -0,0 +1,21 @@ +
+ getFormInitScripts() ?> +
+ getHeaderHtml() ?> +

getButtonsHtml('header') ?>

+
+ +
+ getChildHtml('form') ?> +
+ + hasFooterButtons()): ?> + + + + getFormScripts() ?> +
\ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/ops/form/kwixo/shipping.phtml b/app/design/adminhtml/default/default/template/ops/form/kwixo/shipping.phtml new file mode 100644 index 0000000..cf5614d --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/form/kwixo/shipping.phtml @@ -0,0 +1,102 @@ +
+ + + + + +

__('Shipping configuration'); ?>

+ +
+
+ +getShippingMethods(); +$kwixoShippingTypes = $this->getKwixoShippingTypes(); +if (0 < count($shippingMethods)): +?> +
+ + + + + + + + + + + + + + + + +
+ __('Shipping method type'); ?> + + __('Kwixo shipping type'); ?> + + __('Shipping speed (in hours)'); ?> + + __('Shipping method details'); ?> +
+ escapeHtml($shippingMethod['label']); ?> + + + +
+ __('shipping type not valid'); ?> +
+ +
+ + +
+ __('shipping speed not valid'); ?> +
+ +
+ + +
+ __('shipping details not valid'); ?> +
+ +
+ + \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/ops/form/opsId.phtml b/app/design/adminhtml/default/default/template/ops/form/opsId.phtml new file mode 100644 index 0000000..a129918 --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/form/opsId.phtml @@ -0,0 +1,37 @@ + + */ +?> + + diff --git a/app/design/adminhtml/default/default/template/ops/form/payPerMail.phtml b/app/design/adminhtml/default/default/template/ops/form/payPerMail.phtml new file mode 100644 index 0000000..0b4030b --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/form/payPerMail.phtml @@ -0,0 +1,31 @@ + + */ +?> + + diff --git a/app/design/adminhtml/default/default/template/ops/info.phtml b/app/design/adminhtml/default/default/template/ops/info.phtml new file mode 100644 index 0000000..e97223b --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/info.phtml @@ -0,0 +1,64 @@ + +htmlEscape($this->getMethod()->getTitle()) ?>
+__('Payment ID: %s', $this->htmlEscape($this->getInfo()->getAdditionalInformation('paymentId'))) ?>
+__('Status: %s', Mage::helper('ops')->getStatusText($this->getInfo()->getAdditionalInformation('status'))) ?>
+getInfo()->hasAdditionalInformation('acceptance')): ?> + __('Acceptance: %s', $this->getInfo()->getAdditionalInformation('acceptance')) ?>
+ +getInfo()->getAdditionalInformation('scoring'); ?> +getInfo()->getAdditionalInformation('scoringCategory'); ?> +getInfo()->getAdditionalInformation('additionalScoringData'); ?> + + + + + + $value): ?> + + + + + + +
+ __('Scoring Value: %s', $this->htmlEscape($scoring)) ?>
+ __(sprintf('Scoring Category: %s', $this->htmlEscape($scoringCategory))) ?> +
__($key); ?> + + __($descriptionTranslationTemplate) ?> + + ? +
+ +
+ +
+ +
+ __('This might be a fraud attempt. Please check details for more information.') ?> +
+ + + Foo + __('No scoring information available'); ?> + diff --git a/app/design/adminhtml/default/default/template/ops/info/bancontact.phtml b/app/design/adminhtml/default/default/template/ops/info/bancontact.phtml new file mode 100644 index 0000000..66372f1 --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/info/bancontact.phtml @@ -0,0 +1,81 @@ + + +htmlEscape($this->getMethod()->getTitle()) ?>
+__('Payment ID: %s', $this->htmlEscape($this->getInfo()->getAdditionalInformation('paymentId'))) ?>
+__('Status: %s', Mage::helper('ops')->getStatusText($this->getInfo()->getAdditionalInformation('status'))) ?>
+getInfo()->hasAdditionalInformation('acceptance')): ?> + __('Acceptance: %s', $this->getInfo()->getAdditionalInformation('acceptance')) ?>
+ + +__('Device: %s', $this->getDeviceParameter()) ?>
+__('Mobilemode: %s', $this->getMobileModeParameter()) ?>
+ +getInfo()->getAdditionalInformation('scoring'); ?> +getInfo()->getAdditionalInformation('scoringCategory'); ?> + + +getInfo()->getAdditionalInformation('additionalScoringData'); ?> + + + + + + + $value): ?> + + + + + + + +
+ + __('Scoring Value: %s', $this->htmlEscape($scoring)) ?>
+ + + __(sprintf('Scoring Category: %s', $this->htmlEscape($scoringCategory))) ?> + +
__($key); ?> + + __($descriptionTranslationTemplate) ?> + + ? +
+ +
+ +
+ + +
+ __('This might be a fraud attempt. Please check details for more information.') ?> +
+ + + __('No scoring information available'); ?> + diff --git a/app/design/adminhtml/default/default/template/ops/info/cc.phtml b/app/design/adminhtml/default/default/template/ops/info/cc.phtml new file mode 100644 index 0000000..7ba48c8 --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/info/cc.phtml @@ -0,0 +1,73 @@ + +htmlEscape($this->getMethod()->getTitle()) ?> - htmlEscape($this->getMethod()->getOpsBrand($this->getInfo())) ?>
+__('Payment ID: %s', $this->htmlEscape($this->getInfo()->getAdditionalInformation('paymentId'))) ?>
+__('Alias: %s', $this->htmlEscape($this->getInfo()->getAdditionalInformation('alias'))) ?>
+__('Status: %s', Mage::helper('ops')->getStatusText($this->getInfo()->getAdditionalInformation('status'))) ?>
+getInfo()->hasAdditionalInformation('acceptance')): ?> + __('Acceptance: %s', $this->getInfo()->getAdditionalInformation('acceptance')) ?>
+ +getInfo()->getAdditionalInformation('scoring'); ?> +getInfo()->getAdditionalInformation('scoringCategory'); +$scoringCssClass = ''; +if (0 < strlen(trim($scoringCategory))): + $scoringCssClass = 'scoring-' . $scoringCategory; +endif; ?> +getInfo()->getAdditionalInformation('additionalScoringData'); ?> + + + + + + $value): ?> + + + + + + +
+ + __('Scoring Value: %s', $this->htmlEscape($scoring)) ?>
+ + + __(sprintf('Scoring Category: %s', $this->htmlEscape($scoringCategory))) ?> + +
__($key); ?> + + __($descriptionTranslationTemplate) ?> + + ? +
+ +
+ +
+ + +
+ __('This might be a fraud attempt. Please check details for more information.') ?> +
+ + + __('No scoring information available'); ?> + diff --git a/app/design/adminhtml/default/default/template/ops/info/flex.phtml b/app/design/adminhtml/default/default/template/ops/info/flex.phtml new file mode 100644 index 0000000..7190799 --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/info/flex.phtml @@ -0,0 +1,84 @@ + + */ + +/** @var Netresearch_OPS_Block_Info_Flex $this */ +?> +

escapeHtml($this->getMethod()->getTitle()) ?> - escapeHtml($this->getFlexTitle()); ?>

+__('Payment ID: %s', $this->escapeHtml($this->getInfo()->getAdditionalInformation('paymentId'))) ?>
+__('Status: %s', Mage::helper('ops')->getStatusText($this->getInfo()->getAdditionalInformation('status'))) ?>
+getInfo()->hasAdditionalInformation('acceptance')): ?> + __('Acceptance: %s', $this->getInfo()->getAdditionalInformation('acceptance')) ?>
+ +getInfo()->getAdditionalInformation('scoring'); ?> +getInfo()->getAdditionalInformation('scoringCategory'); +$scoringCssClass = ''; +if (0 < strlen(trim($scoringCategory))): + $scoringCssClass = 'scoring-' . $scoringCategory; +endif; ?> +getInfo()->getAdditionalInformation('additionalScoringData'); ?> + + + + + + + $value): ?> + + + + + + + +
+ + __('Scoring Value: %s', $this->htmlEscape($scoring)) ?>
+ + + __(sprintf('Scoring Category: %s', $this->htmlEscape($scoringCategory))) ?> + +
__($key); ?> + + __($descriptionTranslationTemplate) ?> + + ? +
+ +
+ +
+ + +
+ __('This might be a fraud attempt. Please check details for more information.') ?> +
+ + + __('No scoring information available'); ?> + diff --git a/app/design/adminhtml/default/default/template/ops/info/opsId.phtml b/app/design/adminhtml/default/default/template/ops/info/opsId.phtml new file mode 100644 index 0000000..14e279d --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/info/opsId.phtml @@ -0,0 +1,23 @@ + +htmlEscape($this->getMethod()->getTitle()) ?> - htmlEscape($this->getMethod()->getOpsBrand($this->getInfo())) ?>
+__('Payment ID: %s', $this->htmlEscape($this->getInfo()->getAdditionalInformation('paymentId'))) ?>
\ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/ops/info/payPerMail.phtml b/app/design/adminhtml/default/default/template/ops/info/payPerMail.phtml new file mode 100644 index 0000000..14e279d --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/info/payPerMail.phtml @@ -0,0 +1,23 @@ + +htmlEscape($this->getMethod()->getTitle()) ?> - htmlEscape($this->getMethod()->getOpsBrand($this->getInfo())) ?>
+__('Payment ID: %s', $this->htmlEscape($this->getInfo()->getAdditionalInformation('paymentId'))) ?>
\ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/ops/info/redirect.phtml b/app/design/adminhtml/default/default/template/ops/info/redirect.phtml new file mode 100644 index 0000000..b2c275c --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/info/redirect.phtml @@ -0,0 +1,75 @@ + + +htmlEscape($this->getMethod()->getTitle()) ?>
+__('Payment ID: %s', $this->htmlEscape($this->getInfo()->getAdditionalInformation('paymentId'))) ?>
+__('Status: %s', Mage::helper('ops')->getStatusText($this->getInfo()->getAdditionalInformation('status'))) ?>
+getInfo()->hasAdditionalInformation('acceptance')): ?> + __('Acceptance: %s', $this->getInfo()->getAdditionalInformation('acceptance')) ?>
+ +getInfo()->getAdditionalInformation('scoring'); ?> +getInfo()->getAdditionalInformation('scoringCategory'); +$scoringCssClass = ''; +if (0 < strlen(trim($scoringCategory))): + $scoringCssClass = 'scoring-' . $scoringCategory; +endif; ?> +getInfo()->getAdditionalInformation('additionalScoringData'); ?> + + + + + + + $value): ?> + + + + + + + +
+ + __('Scoring Value: %s', $this->htmlEscape($scoring)) ?>
+ + + __(sprintf('Scoring Category: %s', $this->htmlEscape($scoringCategory))) ?> + +
__($key); ?> + + __($descriptionTranslationTemplate) ?> + + ? +
+ +
+ +
+ + +
+ __('This might be a fraud attempt. Please check details for more information.') ?> +
+ + + __('No scoring information available'); ?> + diff --git a/app/design/adminhtml/default/default/template/ops/sales/order/creditmemo/closed-transaction/warning.phtml b/app/design/adminhtml/default/default/template/ops/sales/order/creditmemo/closed-transaction/warning.phtml new file mode 100644 index 0000000..ad6b5ae --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/sales/order/creditmemo/closed-transaction/warning.phtml @@ -0,0 +1,28 @@ + +
    +
  • + __('Only offline refund is available, since Ingenico ePayments transaction is already closed.'); + ?> +
  • +
\ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/ops/sales/order/creditmemo/totals/checkbox.phtml b/app/design/adminhtml/default/default/template/ops/sales/order/creditmemo/totals/checkbox.phtml new file mode 100644 index 0000000..01d3acb --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/sales/order/creditmemo/totals/checkbox.phtml @@ -0,0 +1,30 @@ + +
+

+ + +

+ __('If this checkbox is marked, the refund will be final: the transaction will be closed and it will not be possible to perform any further refund for this transaction.'); ?> +
+

+
\ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/ops/system/config/kwixoconfiglinks.phtml b/app/design/adminhtml/default/default/template/ops/system/config/kwixoconfiglinks.phtml new file mode 100644 index 0000000..0bc3a22 --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/system/config/kwixoconfiglinks.phtml @@ -0,0 +1,44 @@ + + +__( + "Don't forget to set your shipping method to use KWIXO in the best way" +); ?> - + + + __('Kwixo shipping setting'); ?> + + +
+ + + __('Kwixo Category Mapping'); ?> + + + __( + 'Please note that a proper category mapping is necessary in order to use this payment method' + ); ?>. + \ No newline at end of file diff --git a/app/design/adminhtml/default/default/template/ops/system/config/support.phtml b/app/design/adminhtml/default/default/template/ops/system/config/support.phtml new file mode 100644 index 0000000..a41d56e --- /dev/null +++ b/app/design/adminhtml/default/default/template/ops/system/config/support.phtml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + hasDocumentation()): ?> + + + + + + hasFaq()): ?> + + + + + + + getSupportMail())): ?> + + + getMageVersion(), + 'Ingenico ePayments app version: ' . $this->getExtensionVersion(), + 'Shop URI: ' . Mage::getBaseUrl() + ); + ?> + + + + hasDevPrefix()): ?> + + + + + + + + + +
+ escapeHtml($this->getFieldsetLabel()) ?> +
__('Extension Version'); ?>getExtensionVersion(); ?>
__('Documentation'); ?> + +
__('First Aid - FAQ'); ?> + +
__('Need support? Contact Ingenico ePayments:'); ?> + ">getSupportMail() ?> +
__('devprefix'); ?>getDevPrefix(); ?>
+ + __('Download Logfile'); ?> + +
+ diff --git a/app/design/frontend/base/default/layout/ops.xml b/app/design/frontend/base/default/layout/ops.xml new file mode 100644 index 0000000..1d74c27 --- /dev/null +++ b/app/design/frontend/base/default/layout/ops.xml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + css/ops.css + + + + + + + + + + + + + + + + + + css/ops.css + + + + + + + + + + + + + + css/ops.css + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + css/ops.css + + + + + + + + + + + + + + + + skin_js + js/opcheckout.js + + + css/ops.css + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + css/ops.css + + + + + ops_payment_information + ops/customer/aliases + + + + + + + + + + + + + + My payment information + + + + + + + + + diff --git a/app/design/frontend/base/default/template/ops/checkout/deviceFingerprinting.phtml b/app/design/frontend/base/default/template/ops/checkout/deviceFingerprinting.phtml new file mode 100644 index 0000000..a70e422 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/checkout/deviceFingerprinting.phtml @@ -0,0 +1,30 @@ + + * @copyright Copyright (c) 2016 Netresearch GmbH & Co. KG (http://www.netresearch.de/) + * @license Open Software License (OSL 3.0) + * @link http://opensource.org/licenses/osl-3.0.php + */ +?> + + diff --git a/app/design/frontend/base/default/template/ops/checkout/subscriptionNotice.phtml b/app/design/frontend/base/default/template/ops/checkout/subscriptionNotice.phtml new file mode 100644 index 0000000..5f1d735 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/checkout/subscriptionNotice.phtml @@ -0,0 +1,45 @@ + + */ +?> +getProfile(); +?> +displayNotice()): ?> +
+ hasInitialFee()): ?> +

getInitialFeeText(); ?>

+ + hasTrial()): ?> +

getTrialSubscriptionText(); ?>

+ +

getRegularSubscriptionText(); ?>

+

getCancelInformationText(); ?>

+
+ \ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/customer/alias/list.phtml b/app/design/frontend/base/default/template/ops/customer/alias/list.phtml new file mode 100644 index 0000000..64a9123 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/customer/alias/list.phtml @@ -0,0 +1,68 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_CustomerController + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * @var $this Netresearch_OPS_Block_Alias_List + */ +?> +getAliases(); ?> +
+

__('My payment information') ?>

+
+getMessagesBlock()->getGroupedHtml() ?> +getChildHtml('pager'); ?> + +
+ + + + + + + + + + + + + + + + getAliasDeleteUrl($alias->getId()); ?> + + + + + + + + + + + +
__('Payment method') ?>__('Credit Card Type') ?>__('Card Number/Account Number') ?>__('Expiration Date') ?>__('Card Holder') ?>__('State') ?>
getMethodName($alias->getPaymentMethod()); ?>escapeHtml($alias->getBrand()); ?>escapeHtml($alias->getPseudoAccountOrCCNo()); ?>escapeHtml($alias->getExpirationDate()); ?>escapeHtml($alias->getCardHolder()); ?>escapeHtml($this->__($alias->getState())); ?>__('Delete') ?>
+
+ +

__('You did not save any payment information, yet.'); ?>

+ +getChildHtml('pager'); ?> + + diff --git a/app/design/frontend/base/default/template/ops/form.phtml b/app/design/frontend/base/default/template/ops/form.phtml new file mode 100644 index 0000000..e341f62 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form.phtml @@ -0,0 +1,38 @@ + + +getMethodCode(); +$isUserRegistering = $this->isUserRegistering(); +?> + + + diff --git a/app/design/frontend/base/default/template/ops/form/alias.phtml b/app/design/frontend/base/default/template/ops/form/alias.phtml new file mode 100644 index 0000000..ea983c5 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/alias.phtml @@ -0,0 +1,42 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Model_Payment_Alias + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/* $this instanceof Netresearch_OPS_Block_Form_Alias */ +?> +getMethodCode() ?> + + + diff --git a/app/design/frontend/base/default/template/ops/form/bankTransfer.phtml b/app/design/frontend/base/default/template/ops/form/bankTransfer.phtml new file mode 100644 index 0000000..848d1c5 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/bankTransfer.phtml @@ -0,0 +1,51 @@ + + +getMethodCode(); +?> + + + + + diff --git a/app/design/frontend/base/default/template/ops/form/cc.phtml b/app/design/frontend/base/default/template/ops/form/cc.phtml new file mode 100644 index 0000000..36db00f --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/cc.phtml @@ -0,0 +1,168 @@ +getMethodCode(); +?> + diff --git a/app/design/frontend/base/default/template/ops/form/directDebit.phtml b/app/design/frontend/base/default/template/ops/form/directDebit.phtml new file mode 100644 index 0000000..49890c8 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/directDebit.phtml @@ -0,0 +1,145 @@ + + * @author Thomas Birke + * @license OSL 3.0 + */ + +/** @var Netresearch_OPS_Block_Form_DirectDebit $this */ +$code = $this->getMethodCode(); +$display = 'none'; +$storedAliases = $this->getStoredAliasForCustomer(); +?> + + + + diff --git a/app/design/frontend/base/default/template/ops/form/directEbanking.phtml b/app/design/frontend/base/default/template/ops/form/directEbanking.phtml new file mode 100644 index 0000000..c1d8ed6 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/directEbanking.phtml @@ -0,0 +1,51 @@ +getMethodCode(); +?> + + + + + diff --git a/app/design/frontend/base/default/template/ops/form/flex.phtml b/app/design/frontend/base/default/template/ops/form/flex.phtml new file mode 100644 index 0000000..bf2d01f --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/flex.phtml @@ -0,0 +1,98 @@ + + */ + +/** @var Netresearch_OPS_Block_Form_Flex $this */ + +$_code = $this->getMethodCode(); +$methods = $this->getFlexMethods(); + +?> + +
    + isDefaultOptionActive()): ?> +
  • + +
  • + + +
  • + +
  • + + + + + +
+

getRedirectMessage(); ?>

diff --git a/app/design/frontend/base/default/template/ops/form/ideal.phtml b/app/design/frontend/base/default/template/ops/form/ideal.phtml new file mode 100644 index 0000000..2fd2f20 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/ideal.phtml @@ -0,0 +1,37 @@ +getMethodCode(); +$issuers = $this->getIssuers(); +?> + diff --git a/app/design/frontend/base/default/template/ops/form/intersolve.phtml b/app/design/frontend/base/default/template/ops/form/intersolve.phtml new file mode 100644 index 0000000..86922e4 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/intersolve.phtml @@ -0,0 +1,56 @@ + + */ + +/** @var Netresearch_OPS_Block_Form_InterSolve $this */ +?> + + diff --git a/app/design/frontend/base/default/template/ops/form/kwixo/apres_reception.phtml b/app/design/frontend/base/default/template/ops/form/kwixo/apres_reception.phtml new file mode 100644 index 0000000..5e7ca50 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/kwixo/apres_reception.phtml @@ -0,0 +1,11 @@ +getMethodCode() ?> + + + \ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/form/kwixo/comptant.phtml b/app/design/frontend/base/default/template/ops/form/kwixo/comptant.phtml new file mode 100644 index 0000000..1b86be3 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/kwixo/comptant.phtml @@ -0,0 +1,11 @@ +getMethodCode() ?> + + + \ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/form/kwixo/credit.phtml b/app/design/frontend/base/default/template/ops/form/kwixo/credit.phtml new file mode 100644 index 0000000..4efcbf5 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/kwixo/credit.phtml @@ -0,0 +1,11 @@ +getMethodCode() ?> + + + \ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/form/openInvoice.phtml b/app/design/frontend/base/default/template/ops/form/openInvoice.phtml new file mode 100644 index 0000000..da1b44b --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/openInvoice.phtml @@ -0,0 +1,42 @@ + + */ + +/** @var $this Netresearch_OPS_Block_Form_OpenInvoice */ +?> + \ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/form/other.phtml b/app/design/frontend/base/default/template/ops/form/other.phtml new file mode 100644 index 0000000..228f45d --- /dev/null +++ b/app/design/frontend/base/default/template/ops/form/other.phtml @@ -0,0 +1,26 @@ + + \ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/frauddetection.phtml b/app/design/frontend/base/default/template/ops/frauddetection.phtml new file mode 100644 index 0000000..0ca9ef0 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/frauddetection.phtml @@ -0,0 +1,48 @@ +getTrackingCodeAid(); +$sid = $this->getTrackingSid(); +?> + + + + + \ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/info.phtml b/app/design/frontend/base/default/template/ops/info.phtml new file mode 100644 index 0000000..3b1c09d --- /dev/null +++ b/app/design/frontend/base/default/template/ops/info.phtml @@ -0,0 +1,27 @@ + +

htmlEscape($this->getMethod()->getTitle()) ?>

diff --git a/app/design/frontend/base/default/template/ops/info/alias.phtml b/app/design/frontend/base/default/template/ops/info/alias.phtml new file mode 100644 index 0000000..1300e17 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/info/alias.phtml @@ -0,0 +1,23 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_Model_Payment_Alias + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/* $this instanceof Netresearch_OPS_Block_Info_Alias */ +?> +

htmlEscape($this->getMethod()->getTitle()) ?>

+ diff --git a/app/design/frontend/base/default/template/ops/info/bancontact.phtml b/app/design/frontend/base/default/template/ops/info/bancontact.phtml new file mode 100644 index 0000000..a465a7b --- /dev/null +++ b/app/design/frontend/base/default/template/ops/info/bancontact.phtml @@ -0,0 +1,23 @@ + +

htmlEscape($this->getMethod()->getTitle()) ?>

+ diff --git a/app/design/frontend/base/default/template/ops/info/cc.phtml b/app/design/frontend/base/default/template/ops/info/cc.phtml new file mode 100644 index 0000000..79cfde8 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/info/cc.phtml @@ -0,0 +1,23 @@ + +

htmlEscape($this->getMethod()->getTitle() . ' - ' . $this->getMethod()->getOpsBrand($this->getInfo())) ?>

+ diff --git a/app/design/frontend/base/default/template/ops/info/flex.phtml b/app/design/frontend/base/default/template/ops/info/flex.phtml new file mode 100644 index 0000000..551d954 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/info/flex.phtml @@ -0,0 +1,33 @@ + + */ + +/** @var Netresearch_OPS_Block_Info_Flex $this */ +?> +

escapeHtml($this->getMethod()->getTitle()) ?>: escapeHtml($this->getFlexTitle()); ?>

+ \ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/info/opsId.phtml b/app/design/frontend/base/default/template/ops/info/opsId.phtml new file mode 100644 index 0000000..14e279d --- /dev/null +++ b/app/design/frontend/base/default/template/ops/info/opsId.phtml @@ -0,0 +1,23 @@ + +htmlEscape($this->getMethod()->getTitle()) ?> - htmlEscape($this->getMethod()->getOpsBrand($this->getInfo())) ?>
+__('Payment ID: %s', $this->htmlEscape($this->getInfo()->getAdditionalInformation('paymentId'))) ?>
\ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/info/payPerMail.phtml b/app/design/frontend/base/default/template/ops/info/payPerMail.phtml new file mode 100644 index 0000000..14e279d --- /dev/null +++ b/app/design/frontend/base/default/template/ops/info/payPerMail.phtml @@ -0,0 +1,23 @@ + +htmlEscape($this->getMethod()->getTitle()) ?> - htmlEscape($this->getMethod()->getOpsBrand($this->getInfo())) ?>
+__('Payment ID: %s', $this->htmlEscape($this->getInfo()->getAdditionalInformation('paymentId'))) ?>
\ No newline at end of file diff --git a/app/design/frontend/base/default/template/ops/info/redirect.phtml b/app/design/frontend/base/default/template/ops/info/redirect.phtml new file mode 100644 index 0000000..a465a7b --- /dev/null +++ b/app/design/frontend/base/default/template/ops/info/redirect.phtml @@ -0,0 +1,23 @@ + +

htmlEscape($this->getMethod()->getTitle()) ?>

+ diff --git a/app/design/frontend/base/default/template/ops/paypage.phtml b/app/design/frontend/base/default/template/ops/paypage.phtml new file mode 100644 index 0000000..e6d7e07 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/paypage.phtml @@ -0,0 +1,23 @@ + + +$$$PAYMENT ZONE$$$ diff --git a/app/design/frontend/base/default/template/ops/placeform.phtml b/app/design/frontend/base/default/template/ops/placeform.phtml new file mode 100644 index 0000000..7fdbf8e --- /dev/null +++ b/app/design/frontend/base/default/template/ops/placeform.phtml @@ -0,0 +1,128 @@ + + + + getFormData(); ?> + hasMissingParams()): ?> +
+
    +
  • +
      +
    • getQuestion(); ?>
    • +
    +
  • +
+
+
+

__('Your details') ?>

+
    + +

    + + isIframeTarget()) { + echo Mage::helper('ops')->__('Please wait a moment while the Ingenico ePayments payment page is loading.'); + } else { + echo Mage::helper('ops')->__('Please wait a moment. This page will transfer your data to Ingenico ePayments payment gateway in a few seconds.'); + } + + ?> + +

    + + + +
    + $value) { ?> + hasMissingParams() && in_array($name, $this->getQuestionedFormFields())): ?> + + +
    +
    + + + +
    + +
    + + +
    + + +
    + + +
    + + hasMissingParams()): ?> +
+
+
+

__('* Required Fields') ?>

+ +
+ + +isIframeTarget()): ?> + + + diff --git a/app/design/frontend/base/default/template/ops/placeform3dsecure.phtml b/app/design/frontend/base/default/template/ops/placeform3dsecure.phtml new file mode 100644 index 0000000..a7911a7 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/placeform3dsecure.phtml @@ -0,0 +1,23 @@ + +

__('Please wait a moment. This page will transfer your data to Ingenico ePayments payment gateway in a few seconds.'); ?>

+getFormData();?> diff --git a/app/design/frontend/base/default/template/ops/retryPayment.phtml b/app/design/frontend/base/default/template/ops/retryPayment.phtml new file mode 100644 index 0000000..362b481 --- /dev/null +++ b/app/design/frontend/base/default/template/ops/retryPayment.phtml @@ -0,0 +1,146 @@ + + * + * @var Netresearch_OPS_Block_RetryPayment $this + */ +?> +
+
+ + __('Your given data') ?> + +
+
+ __('Order Number') . ': ' . $this->getOrderId(); ?> +
+
+
__('Billing Address') ?>
+ getBillingAddress()->format('html'); ?> +
+ getShippingAddress(); + if ($shippingAddress):?> +
+
__('Shipping Address') ?>
+ format('html'); ?> +
+ +
+ +
+
    +
  1. +
    +

    __('Payment Information'); ?>

    +
    +
    +
    + +
    + +
    +
    + getChildHtml('ops_retryPayment_methods'); ?> +
    +
    +
    +

    __('* Required Fields') ?>

    + + + +
    +
    + +
    +
    +
  2. +
+
diff --git a/app/design/frontend/rwd/default/template/ops/customer/alias/list.phtml b/app/design/frontend/rwd/default/template/ops/customer/alias/list.phtml new file mode 100644 index 0000000..0f2c735 --- /dev/null +++ b/app/design/frontend/rwd/default/template/ops/customer/alias/list.phtml @@ -0,0 +1,80 @@ + + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * Netresearch_OPS_CustomerController + * + * @author Thomas Birke + * @author Michael Lühr + * @copyright Copyright (c) 2012 Netresearch GmbH & Co. KG + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + */ + +/** + * @var $this Netresearch_OPS_Block_Alias_List + */ +?> +getAliases(); ?> +
+

__('My payment information') ?>

+
+getMessagesBlock()->getGroupedHtml() ?> +getChildHtml('pager'); ?> + + + + + + + + + + + + + + + + + getAliasDeleteUrl($alias->getId()); ?> + + + + + + + + + + + +
__('Payment method') ?>__('Credit Card Type') ?>__('Card Number/Account Number') ?>__('Expiration Date') ?>__('Card Holder') ?>__('State') ?>
+ getMethodName($alias->getPaymentMethod()); ?> + + escapeHtml($alias->getBrand()); ?> + + escapeHtml($alias->getPseudoAccountOrCCNo()); ?> + + escapeHtml($alias->getExpirationDate()); ?> + + escapeHtml($alias->getCardHolder()); ?> + + escapeHtml($this->__($alias->getState())); ?> + + __('Delete') ?> +
+ +

__('You did not save any payment information, yet.'); ?>

+ +getChildHtml('pager'); ?> + + diff --git a/app/etc/modules/Netresearch_OPS.xml b/app/etc/modules/Netresearch_OPS.xml new file mode 100644 index 0000000..a7d0a16 --- /dev/null +++ b/app/etc/modules/Netresearch_OPS.xml @@ -0,0 +1,44 @@ + + + + + + + false + + + + true + community + + + + + + + diff --git a/app/locale/de_DE/Netresearch_OPS.csv b/app/locale/de_DE/Netresearch_OPS.csv new file mode 100644 index 0000000..4cabbd3 --- /dev/null +++ b/app/locale/de_DE/Netresearch_OPS.csv @@ -0,0 +1,485 @@ +"STATUS_0", "Ungültig / Unvollständig" +"STATUS_1", "Durch den Kunden annuliert" +"STATUS_2", "Genehmigung verweigert" +"STATUS_4", "Bestellung gespeichert" +"STATUS_40", "Gespeichert, warten auf externes Ergebnis" +"STATUS_41", "Erwartete Zahlung durch den Kunden" +"STATUS_46", "Austehend (Warten auf Identifikation)" +"STATUS_5", "Genehmigt" +"STATUS_50", "Genehmigt, warten auf externes Ergebnis" +"STATUS_51", "Genehmigung im Wartezustand" +"STATUS_52", "Genehmigung nicht bekannt" +"STATUS_55", "Stand-by" +"STATUS_56", "OK mit planmäßigen Zahlungen" +"STATUS_57", "Fehler in planmäßigen Zahlungen" +"STATUS_59", "Manuelle Genehmigung" +"STATUS_6", "Genehmigt und storniert" +"STATUS_61", "Annulierung der Genehmigung im Wartezustand" +"STATUS_62", "Annulierung der Genehmigung unsicher" +"STATUS_63", "Annulierung der Genehmigung verweigert" +"STATUS_64", "Genehmigt und storniert" +"STATUS_7", "Zahlung gelöscht" +"STATUS_71", "Löschung der Zahlung im Wartezustand" +"STATUS_72", "Payment deletion uncertain" +"STATUS_73", "Löschung der Zahlung verweigert" +"STATUS_74", "Zahlung gelöscht" +"STATUS_75", "Löschung durch Händler" +"STATUS_8", "Gutschrift" +"STATUS_81", "Gutschrift im Wartezustand" +"STATUS_82", "Gutschrift unsicher" +"STATUS_83", "Gutschrift verweigert" +"STATUS_84", "Bezahlung von Akzeptanzpartner verweigert" +"STATUS_85", "Gutschrift durch Händler" +"STATUS_9", "Zahlung beantragt" +"STATUS_91", "Zahlung im Wartezustand" +"STATUS_92", "Zahlung unsicher" +"STATUS_93", "Zahlung verweigert" +"STATUS_94", "Gutschrift von Akzeptanzpartner verweigert" +"STATUS_95", "Zahlung durch Händler" +"STATUS_99", "In Bearbeitung" +"Hash is not valid", "SHA-Verschlüsselung ist ungültig" +"Order is not valid", "Bestellung ist ungültig" +"Process outgoing transaction", "Verarbeitung ausgehender Transaktionen" +"Start Ingenico ePayments processing", "Starte Ingenico ePayments Verarbeitung" +"Your payment information was declined. Please select another payment method.", "Die Zahlung wurde abgelehnt. Bitte wählen Sie eine andere Zahlungsweise" +"Order canceled on Ingenico ePayments side. Status: %s, Payment ID: %s.", "Bestellung von Ingenico ePayments Seite verweigert. Status: %s, Bezahlungs ID: %s." +"Account number must contain numbers only.", "Die Kontonummer darf nur Ziffern beinhalten" +"Bank code must contain numbers only.", "Die Bankleitzahl darf nur Ziffern beinhalten" +"Account Holder must be filled in.", "Der Kontoinhaber muß angegeben werden." +"Capture process complete", "Datenverarbeitungsprozess komplettiert" +"The capture has already been invoiced.", "Die Transaktion wurde bereits in Rechnung gestellt" +"Cannot create an invoice without products.", "Es kann keine Rechnung ohne Artikel erstellt werden." +"Partial capture process complete", "Teilzahlungsprozess abgeschlossen." +"Invoice '%s' was created automatically. Ingenico ePayments Status: %s.", "Rechnung '%s' wurde automatisch erstellt. Ingenico ePayments Status: %s." +"Start Ingenico ePayments refund request", "Starte Ingenico ePayments Gutschrifts Anfrage" +"Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments Status: %s.", "Eine Gutschrift wird automatisch erstellt sobald Ingenico ePayments dies bestätigt. Ingenico ePayments Status: %s." +"Creditmemo '%s' was created automatically. Ingenico ePayments Status: %s.", "Gutschrift '%s' wurde autmatisch erstellt. Ingenico ePayments Staus: %s." +"Could not perform actions for Ingenico ePayments status: %s.", "Aktionen für Ingenico ePayments Status: %s nicht möglich." +"Refund is waiting or uncertain. Ingenico ePayments status: %s.", "Gutschrift in Wartezustand oder unsicher. Ingenico ePayments Status: %s." +"Refund was refused. Automatic creation failed. Ingenico ePayments status: %s.", "Gutschrift verweigert. Automatische Erstellung fehlgeschlagen. Ingenico ePayments Status: %s." +"Capture is waiting or uncertain. Ingenico ePayments status: %s.", "Zahlung in Wartezustand oder unsicher. Ingenico ePayments status: %s." +"Capture was refused. Automatic creation failed. Ingenico ePayments status: %s.", "Zahlung verweigert. Automatische Erstellung fehlgeschlagen. Ingenico ePayments Status: %s." +"Void is waiting or uncertain. Ingenico ePayments status: %s.", "Void in Wartezustand oder unsicher. Ingenico ePayments status: %s." +"Void was refused. Automatic creation failed. Ingenico ePayments status: %s.", "Void verweigert. Automatische Erstellung fehlgeschlagen. Ingenico ePayments Status: %s." +"Authorization status changed. Current Ingenico ePayments status is: %s.", "Status der Genehmigung geändert. Aktueller Ingenico ePayments Status ist: %s." +"Unknown Ingenico ePayments status: %s.", "Unbekannter Ingenico ePayments Status: %s." +"Error, transaction count is %s instead of 1 for the Payid '%s', order '%s' and Operation '%s'.", "Fehler, Anzahl der Transaktionen ist %s anstatt 1 für Pay ID '%s', Bestellung '%s' und Operation '%s'." +"Order can not be saved.", "Bestellung kann nicht gespeichert werden." +"Order can not be canceled for system reason.", "Bestellung kann aus Systemgründen nicht abgebrochen werden." +"Order declined on ops side. Ingenico ePayments status: %s, Payment ID: %s.", "Bestellung von Ingenico ePayments verweigert. Ingenico ePayments Status: %s, Bezahlungs ID: %s." +"Order can not be saved for system reason.", "Bestellung kann aus Systemgründen nicht gespeichert werden." +"An unknown exception occured.", "Ein unbekannter Fehler ist aufgetreten." +"A technical problem arose during payment process, giving unpredictable result. Ingenico ePayments status: %s.", "Ein technisches Problem ist während der Verarbeitung aufgetreten, Ingenico ePayments Status: %s." +"An unknown exception was thrown in the payment process. Ingenico ePayments status: %s.", "Ein unbekanntes Ereignis wurde im Zahlungsprozess ausgelöst. Ingenico ePayments Status: %s." +"Waiting for the payment of the customer", "Warten auf die Zahlung des Kunden" +"Authorization waiting from Ingenico ePayments", "Warten auf Genehmigung von Ingenico ePayments" +"Processed by Ingenico ePayments", "Verarbeitet von Ingenico ePayments" +"Waiting for payment. Ingenico ePayments status: %s.", "Warten auf Zahlung. Ingenico ePayments Status: %s." +"Authorization waiting. Ingenico ePayments status: %s.", "Genehmigung im Wartezustand. Ingenico ePayments Status: %s." +"Authorization uncertain. Ingenico ePayments status: %s.", "Genehmigung unsicher. Ingenico ePayments Status: %s." +"Processed by Ingenico ePayments. Payment ID: %s. Ingenico ePayments status: %s.", "Verarbeitet von Ingenico ePayments. Bezahlungs ID: %s. Ingenico ePayments Status: %s." +"Ingenico ePayments server is temporarily not available, please try again later.", "Ingenico ePayments Server ist vorübergehend nicht erreichbar. Bitte versuchen Sie es später erneut." +"An error occured during the Ingenico ePayments request. Your action could not be executed. Message: '%s'.", "Während der Ingenico ePayments Anfrage trat ein Fehler auf. Ihr Vorgang konnte nicht ausgeführt werden. Nachricht: '%s'." +"You already sent a capture request. Please wait until the capture request is acknowledged.", "Sie haben bereits eine Buchungsanfrage gesendet. Bitte warten Sie bis die Buchungsanfrage bestätigt wurde." +"There is one void request waiting. Please wait until this request is acknowledged.", "Es ist eine Void Anfrage im Wartezustand. Bitte warten Sie bis die Anfrage bestätigt wurde." +"Start Ingenico ePayments %s capture request", "Starte Ingenico ePayments %s Buchungsanfrage" +"Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments status: %s.", "Die Rechnung wird automatisch erstellt, sobald eine Bestätigung von OSP gesendet wird. Ingenico ePayments Status: %s." +"The Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "Die Rechnung wird automatisch erstellt, sobald eine Bestätigung von Ingenico ePayments gesendet wird." +"The Invoice was not created. Ingenico ePayments status: %s.", "Die Rechnung wurde nicht erstellt. Ingenico ePayments Status: %s." +"The CreditMemo was not created. Ingenico ePayments status: %s.", "Die Gutschrift wurde nicht erstellt. Ingenico ePayments Status: %s." +"The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "Die Gutschrift wird automatisch erstellt, sobald eine Bestätigung von Ingenico ePayments gesendet wird." +"There is already one creditmemo in the queue. The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "Es ist bereits eine Gutschrift in der Warteschlange. Die Gutschrift wird automatisch erstellt, sobald eine Bestätigung von Ingenico ePayments gesendet wurde." +"Please use void to cancel the operation.", "Bitte benutzen Sie Void um den Vorgang abzubrechen." +"You already sent a void request. Please wait until the void request will be acknowledged.", "Sie haben bereits eine Void Anfrage gesendet. Bitte warten Sie bis diese bestätigt ist." +"There is one capture request waiting. Please wait until this request is acknowledged.", "Eine Zahlungserfassung ist im Wartezustand. Bitte warten Sie bis die Anfrage bestätigt wurde." +"The void request is sent. Please wait until the void request will be accepted.", "Die Void Anfrage wurde gesendet. Bitte warten Sie bis die Void Anfrage akzeptiert wird." +"Void order succeed. Ingenico ePayments status: %s.", "Void der Bestellung war erfolgreich. Ingenico ePayments Status: %s." +"Void order failed. Ingenico ePayments status: %s.", "Void der Bestellung ist nicht erfolgt. Ingenico ePayments Status: %s." +"Authorization", "Genehmigung" +"Direct Sale", "Direktbuchung" +"Horizontally grouped logo with group name on left", "horizontal gruppiertes Logo mit Gruppenname auf der linken Seite" +"Horizontally grouped logo with no group name", "horizontal gruppiertes Logo ohne Gruppenname" +"Verical list", "vertikale Liste" +"You will be redirected to the payment page after the order confirmation.", "Nach der Bestellbestätigung werden Sie auf die Bezahlungsseite weitergeleitet." +"Payment failed. Please select another payment method.", "Zahlung nicht erfolgt. Bitte wählen Sie eine andere Zahlungsmethode." +"Payment failed. Please review your input or select another payment method.", "Zahlung nicht erfolgt. Bitte überprüfen Sie ihre Eingaben oder wählen Sie eine andere Zahlungsmethode." +"Incorrect credit card expiration date.", "Falsches Gültigkeitsdatum der Kreditkarte." +"Credit Card Type", "Kreditkartentyp" +"Name on Card", "Karteninhaber" +"Credit Card Number", "Kreditkartennummer" +"Expiration Date", "Gültigkeitsdatum" +"Account holder", "Kontoinhaber" +"Account number", "Kontonummer" +"Bank code", "Bankleitzahl" +"BCMC", "Bancontact/Mister Cash" +"You will be redirected to Ingenico ePayments website when you place an order.", "Sie werden auf die Ingenico ePayments Webseite weitergeleitet wenn Sie eine Bestellung ausführen." +"Please wait a moment. This page will transfer your data to Ingenico ePayments payment gateway in a few seconds.", "Bitte warten Sie. Diese Seite leitet Ihre Daten in wenigen Sekunden an das Ingenico ePayments Zahlungsprotokoll weiter." +"An error occured during 3D-Secure processing of Ingenico ePayments. Error: %s", "Ein Fehler ist während der 3D-Secure Verarbeitung seitens Ingenico ePayments aufgetreten. Fehler: %s" +"PSPID is a case sensitive field", "Bitte achten Sie auf Groß-/Kleinschreibung" +"SHA-IN Pass phrase", "SHA-1-IN Signatur" +"Only alphanumeric characters, not longer than 20 characters. Refer to Ingenico ePayments's backend in section Technical Information on tab Data and origin verification.", "Nur alphanumerische Zeichen, nicht länger als 20 Stellen. Diese finden Sie im Ingenico ePayments Backend im Bereich Technische Informationen im Reiter Daten- und Ursprungsüberprüfung." +"SHA-OUT Pass phrase", "SHA-1-OUT Signatur" +"Only alphanumeric characters, not longer than 20 characters. Refer to Ingenico ePayments's backend in section Technical Information on tab Transaction feedback.", "Nur alphanumerische Zeichen, nicht länger als 20 Stellen. Diese finden Sie im Ingenico ePayments Backend im Bereich Technische Informationen im Reiter Transaktions Feedback." +"Test url example: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
Production url example: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp", "URL-Beispiel Testbetrieb: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
URL-Beispiel Produktivbetrieb: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp" +"Test url example: https://ogone.test.v-psp.com/Tokenization/HostedPage
Production url example: https://secure.ogone.com/Tokenization/HostedPage", "URL Beispiel Testbetrieb: https://ogone.test.v-psp.com/Tokenization/HostedPage
URL Beispiel Produktivbetrieb: https://secure.ogone.com/Tokenization/HostedPage" +"Test url example: https://secure.ogone.com/ncol/test/maintenancedirect.asp
Production url example: https://secure.ogone.com/ncol/prod/maintenancedirect.asp", "URL-Beispiel Testbetrieb: https://secure.ogone.com/ncol/test/maintenancedirect.asp
URL-Beispiel Produktivbetrieb: https://secure.ogone.com/ncol/prod/maintenancedirect.asp" +"Test url example: https://secure.ogone.com/ncol/test/orderdirect.asp
Production url example: https://secure.ogone.com/ncol/prod/orderdirect.asp", "URL-Beispiel Testbetrieb: https://secure.ogone.com/ncol/test/orderdirect.asp
URL-Beispiel Produktivbetrieb: https://secure.ogone.com/ncol/prod/orderdirect.asp" +"API User", "API Benutzer" +"API Password", "API Passwort" +"Payment Action", "Zahlungsaktion" +"If you choose 'Ingenico ePayments', you will need to fill in all the following fields.", "Wenn Sie 'Ingenico ePayments Template' wählen, müssen Sie die folgenden Felder ausfüllen." +"Payment Template", "Bezahlseitenvorlage" +"Title of the Ingenico ePayments Template", "Titel der Ingenico ePayments-Vorlage" +"Background Color of Ingenico ePayments Template", "Hintergrundfarbe der Ingenico ePayments-Vorlage" +"Text Color of the Ingenico ePayments Template", "Textfarbe der Ingenico ePayments-Vorlage" +"Table Background Color of the Ingenico ePayments Template", "Tabellenhintergrundfarbe der Ingenico ePayments Vorlage" +"Table Text Color of the Ingenico ePayments Template", "Tabellentextfarbe der Ingenico ePayments Vorlage" +"Button Background Color of the Ingenico ePayments Template", "Schaltflächenhintergrundfarbe der Ingenico ePayments Vorlage" +"Button Text Color of the Ingenico ePayments Template", "Schaltflächentextfarbe der Ingenico ePayments Vorlage" +"Font Family of the Ingenico ePayments Template", "Schriftfamilie der Ingenico ePayments Vorlage" +"Logo of the Ingenico ePayments Template", "Logo der Ingenico ePayments Vorlage" +"Layout of Payment Methods", "Ausrichtung der Zahlungsarten" +"e.g: white", "in englisch, z. B. white" +"e.g: black", "in englisch, z. B. black" +"e.g: Verdana", "z. B. Verdana" +"Debug", "Fehlerprotokoll" +"Ingenico ePayments Account", "Ingenico ePayments Einstellungen" +"devprefix", "Präfix für Parameter ORDERID" +"URL/filename of the logo. The URL must be absolute and stored on a secure server.", "URL/Dateipfad zum Logo. Die URL muss absolut sein und auf einen sicheren Server verweisen." +"Countries", "Länder" +"Enabled Card Types", "Aktivierte Kartentypen" +"Enable 3D secure", "3D-Secure aktivieren" +"OWNERADDRESS", "Straßenname der Rechnungsadresse" +"ECOM_BILLTO_POSTAL_STREET_NUMBER", "Hausnummer der Rechnungsadresse" +"ECOM_SHIPTO_POSTAL_STREET_LINE1", "Straßenname der Lieferadresse" +"ECOM_SHIPTO_POSTAL_STREET_NUMBER", "Hausnummer der Lieferadresse" +"Please make sure that your street and house number are correct!", "Bitte prüfen Sie, ob Straße und Hausnummer korrekt sind." +"Brand", "Gutscheincode" +"Add Brand", "Gutschein hinzufügen" +"Add one or more brands. In field 'BRAND' you will have to enter the value you got from Ingenico ePayments.", "Fügen Sie einen oder mehrere Gutscheine hinzu. Im Feld 'Gutscheincode' ist der Wert einzugeben, den Sie von Ingenico ePayments erhalten haben." +"Title", "Titel" +"Please select a brand", "Bitte wählen Sie einen Gutschein" +"Attention: This may be an attempt to defraud!", "Achtung! Dies könnte ein Betrugsversuch sein!" +"This might be a fraud attempt. Please check details for more information.", "Dies könnte ein Betrugsversuch sein. Bitte prüfen Sie die Details." +"Scoring Category: G", "Scoring-Kategorie: G (Grün)" +"Scoring Category: O", "Scoring-Kategorie: O (Orange)" +"Scoring Value: %s", "Scoring-Wert: %s" +"SCORING_DESCRIPTION_AAVCHECK", "Ergebnis der automatischen Adressprüfung (derzeit nur für American Express verfügbar).
Mögliche Werte:
KO: Ungültige Adresse
OK: Adresse ist gültig
NO: Prüfung wurde nicht durchgeführt" +"SCORING_DESCRIPTION_CVCCHECK", "Ergebnis der Prüfung des Kreditkarten-Sicherheitscodes.
Mögliche Werte:
KO: Code ungültig
OK: Code ist gültig
NO: Prüfung wurde nicht durchgeführt" +"SCORING_DESCRIPTION_CCCTY", "Ausgabeland der Kreditkarte (2-stelliger Ländercode, '99' bei unbekanntem Ausgabeland)" +"SCORING_DESCRIPTION_IPCTY", "IP-Herkunftsland (2-stelliger Ländercode, '99' bei unbekanntem Land)" +"SCORING_DESCRIPTION_NBREMAILUSAGE", "Anzahl der Verwendungen dieser E-Mail-Adresse über einen bestimmten Zeitraum (wenn die entsprechende Regel konfiguriert wurde)" +"SCORING_DESCRIPTION_NBRIPUSAGE", "Anzahl der Verwendungen dieser IP-Adresse für erfolgreiche Transaktionen über einen bestimmten Zeitraum (wenn die entsprechende Regel konfiguriert wurde)" +"SCORING_DESCRIPTION_NBRIPUSAGE_ALLTX", "Anzahl der Verwendungen dieser IP-Adresse für erfolgreiche und/oder abgelehnte Transaktionen über einen bestimmten Zeitraum (wenn die entsprechende Regel konfiguriert wurde)" +"SCORING_DESCRIPTION_NBRUSAGE", "Anzahl der Verwendungen dieser Kreditkarte (wenn die entsprechende Regel konfiguriert wurde)" +"SCORING_DESCRIPTION_VC", "Virtuelle Karte
Mögliche Werte:
ECB: E Carte Bleue
ICN: Internet City Number
NO: Alle anderen Karten (z.B. unbekannt oder nicht virtuell)" +"SCORING_DESCRIPTION_CARDNO", "Kartennummer (pseudonymisiert)" +"SCORING_DESCRIPTION_ED", "Ablaufdatum der verwendeten Karte" +"SCORING_DESCRIPTION_CN", "Karteninhaber" +"Sofort Uberweisung", "SOFORT Überweisung" +"DirectEbanking", "sofortüberweisung.de" +"DirectEbankingAT", "SOFORT Überweisung (AT)" +"DirectEbankingBE", "SOFORT Überweisung (BE)" +"DirectEbankingCH", "SOFORT Überweisung (CH)" +"DirectEbankingDE", "SOFORT Überweisung (DE)" +"DirectEbankingFR", "SOFORT Überweisung (FR)" +"DirectEbankingGB", "SOFORT Überweisung (GB)" +"Card Verification Number", "Kartenprüfnummer" +"Invalid payment information provided!", "Die angegebenen Zahlungsdaten sind ungültig!" +"Unknown Ingenico ePayments state for this order. Please check Ingenico ePayments backend for this order.", "Unbekannter Ingenico ePayments Status für diese Bestellung. Bitte überprüfen sie den Status im Ingenico ePayments Backend." +"The order was cancelled manually. The Ingenico ePayments-state is 0 or null.", "Die Bestellung wurde manuell storniert. Der Ingenico ePayments-Status ist 0 oder null." +"Last refund (Close payment transaction)", "Letzte Gutschrift (Schließt die Zahlungstransaktion)" +"Only offline refund is available, since Ingenico ePayments transaction is already closed.", "Die Gutschrift kann nur noch offline erfolgen, da die Ingenico ePayments Transaktion bereits geschlossen ist." +"Order reference in case of redirect payments", "Referenz der Bestellungen für Redirect-Zahlungen" +"Order reference in case of inline payments", "Referenz der Bestellungen für Inline-Zahlungen" +"See chapter 'Configuration', section 'Magento - Backend' in the documentation. Please note that the option 'orderId' is not used for the Kwixo payment methods.", "Siehe Kapitel 'Konfiguration' Abschnitt 'Magento-Backend' in der Dokumentation. Bitte beachten Sie, dass die Option 'OrderId' nicht bei den Kwixo Zahlungsmethoden zur Verfügung steht." +"See chapter 'Configuration', section 'Magento - Backend' in the documentation.", "Siehe Kapitel 'Konfiguration' Abschnitt 'Magento-Backend' in der Dokumentation." +"quote id", "Quote ID" +"order id", "Order ID" +"Enable tracking code", "Tracking Code zur Betrugserkennung aktivieren" +"The ID of Tracker Application Account", "Tracking Application Account ID" +"Delete", "löschen" +"active", "aktiviert" +"Action", "Aktion" +"pending", "ausstehend" +"Kwixo Category Mapping", "Kwixo Kategorien Zuordnung" +"Please note that a proper category mapping is necessary in order to use this payment method", "Bitte beachten Sie, dass eine Kategorienzuordnung notwendig ist, um diese Zahlart nutzen zu können." +"ECOM_SHIPTO_TELECOM_PHONE_NUMBER", "Telefonnummer des Empfängers" +"ECOM_SHIPTO_POSTAL_NAME_PREFIX", "Anrede des Empfängers" +"CIVILITY", "Anrede des Rechnungsempfängers" +"Are you sure you want to cancel this order? Warning: Please check the payment status in the back-office of Ingenico ePayments before. By cancelling this order you won\'t be able to update the status in Magento anymore.", "Wollen Sie diese Bestellung wirklich stornieren? Warnung: Bitte prüfen Sie vorher den Zahlungsstatus im Ingenico ePayments-Backend. Wenn Sie diese Bestellung stornieren, können Sie deren Status in Magento nicht mehr verändern." +"Error transaction count is %s instead of 1 for the Payid '%s'", "Fehler: Transaktionsversuche sind %s anstatt von 1 für die Bezahlungs ID '%s', Bestellung '%s' und Vorgang '%s'." +"--Please select--", "--Bitte auswählen--" +"Pick up at merchant", "Abholen beim Verkäufer" +"Collection point (Kiala...)", "Sammelstelle (Kiala...)" +"Collect at airport, train station or travel agency", "Flughafen, Bahnstation oder Reisebüro" +"Transporter (La Poste, UPS...)", "Transport (Post, UPS)" +"Download", "Herunterladen" +"Food & gastronomy", "Essen & Gastronomie" +"Car & Motorbike", "Auto & Motorrad" +"Culture & leisure", "Kultur & Freizeit" +"Home & garden", "Haus & Garten" +"Appliances", "Haushaltsgeräte" +"Auctions and bulk purchases", "Auktionen und Großeinkauf" +"Flowers & gifts", "Blumen & Geschenke" +"Computer & software", "Computer & Software" +"Health & beauty", "Gesundheit & Schönheit" +"Services for individuals", "Dienstleistungen für Privatpersonen" +"Services for professionals", "Serviceleistungen für Profis" +"Sports", "Sport" +"Clothing & accessories", "Kleidung & Accessoires" +"Travel & tourism", "Reisen & Tourismus" +"Hifi, photo & video", "Hifi, Foto & Video" +"Telephony & communication", "Telefonie & Kommunikation" +"Jewelry & precious metals", "Schmuck & Edelmetalle" +"Baby articles and accessories", "Baby-Artikel und Accessoires" +"Sound & light", "Sound & Licht" +"Ingenico ePayments server is temporarily not available", "Der Ingenico ePayments Server ist zur Zeit nicht verfügbar. Bitte versuchen Sie es später." +"Country", "Land" +"Enable redirect payment for all card types", "Für alle Kreditkartentypen zur Zahlung weiterleiten" +"Enable inline payment (Direct Link) for these card types", "Eingebettete Zahlung (Direct Link) für diese Kartentypen aktivieren" +"Miscellaneous Countries", "Sonstige Länder" +"Please make sure that you enabled customer gender and customer date of birth in customer attributes configuration. Otherwise this payment method won't be available. Please be informed that when you use Afterpay or Klarna as acquirer on Ingenico ePayments side, it's not possible to perform partial captures.", "Stellen Sie sicher, dass Geschlecht und Geburtstag im Checkout Pflichtfelder sind. Ansonsten wird diese Zahlart nicht angezeigt. Beachten Sie außerdem, dass bei Nutzung von Afterpay oder Klarna bei Ingenico ePayments als Acquirer keine Teilerfassungen möglich sind." +"Your details", "Ihre Angaben" +"Ingenico ePayments Payment failed", "Ingenico ePayments Zahlung fehlgeschlagen" +"Enable for backend", "Für Zahlungen im Backend erlauben" +"Sort Order for backend", "Reihenfolge im Backend" +"Existing Ingenico ePayments transaction", "Existierende Ingenico ePayments-Transaktion" +"Ingenico ePayments payment id", "Ingenico ePayments-Transaktionsnummer" +"Please use this method for the following brands only:", "Bitte benutzen Sie eine der folgenden Kartentypen:" +"Payment using 3D secure is not supported.", "Zahlungsarten, die 3D Secure benutzen, werden nicht unterstützt." +"My payment information", "Meine Zahlungsdaten" +"Payment Method", "Zahlungsart" +"Card Number/Account Number", "Kartennummer/Kontonummer" +"expires: %s", "gültig bis: %s" +"Save payment information", "Zahlungsdaten speichern" +"You don't want to enter your payment information again? Just %s to save your credit card information securely!", "Sie möchten Ihre Zahlungsdaten nicht immer wieder neu eingeben müssen? %s Sie sich, damit Sie Ihre Zahlungsdaten sicher hinterlegen können!" +"register", "Registrieren" +"Removed alias %s.", "Der Alias %s wurde gelöscht." +"Could not remove alias %s.", "Der Alias %s konnte nicht gelöscht werden." +"Show information for guests", "Gästen Hinweis anzeigen" +"If this checkbox is marked, the refund will be final: the transaction will be closed and it will not be possible to perform any further refund for this transaction.", "Wenn diese Checkbox markiert ist, wird dies die abschließende Gutschrift sein und die Transaktion wird geschlossen, sodass keine weiteren Gutschriften mehr angelegt werden können." +"Show Quote ID in the order grid", "Quote ID im Order Grid anzeigen" +"Enabled Alias Manager", "Alias Manager aktivieren" +"Show Alias Manager information for guests", "Alias Manager Info für Gäste anzeigen" +"State", "Status" +"Card Holder", "Karteninhaber" +"You did not save any payment information, yet.", "Sie haben bisher noch keine Zahlungsdaten gespeichert" +"Please make sure that the displayed data is correct.", "Bitte stellen Sie sicher dass die angezeigten Daten korrekt sind." +"Year of Expiration Date", "Jahr des Ablaufdatums" +"Month of Expiration Date", "Monat des Ablaufdatums" +"Kwixo category", "Kwixo Kategorie" +"Apply to sub-categories", "Auf Unterkategorien anwenden" +"Categories configuration", "Kategorien Zuordnung" +"Categorie's %s configuration", "Zuordnung der Kategorie %s" +"Estimated Delivery Date", "Voraussichtliches Lieferdatum" +"RNP Fee", "Nachnahmegebühr" +"Shipping Method Type", "Versandart" +"Shipping Method Details", "weitere Details zum Versand" +"An error occured during 3D-Secure processing of Ingenico ePayments.", "Ein Fehler ist während der 3D-Secure Verarbeitung seitens Ingenico ePayments aufgetreten." +"Shipping method type", "Versandart Typ" +"Shipping method speed", "Versandgeschwindigkeit (Stunden)" +"Shipping method details", "Versanddetails" +"shipping details not valid", "Versanddetails ungültig" +"shipping speed not valid", "Versandgeschwindigkeit ungültig" +"shipping type not valid", "Versandart ungültig" +"Default Shipping Method Type", "Standardversandart" +"Default Shipping method speed", "Standardversandgeschwindigkeit" +"Default Shipping Method Details", "Standardversanddetails" +"Kwixo shipping setting", "Kwixo Versandarten Konfiguration" +"Don't forget to set your shipping method to use KWIXO in the best way", "Vergessen Sie nicht die Versandarten zu konfigurieren, um die Kwixo Zahlarten bestmöglich zu nutzen" +"Unknown country within the IBAN '%value%'", "Unbekanntes Land für den IBAN-Wert '%value%'" +"%value%' has a false IBAN format", "%value%' hat ein falsches IBAN Format" +"%value%' has failed the IBAN check", "%value% ist keine gültige IBAN" +"Country not supported for IBAN", "IBAN wird für dieses Land nicht unterstützt" +"invalid data provided", "Ungültige Daten" +"Account holder must be provided", "Kontoinhaber darf nicht leer sein" +"invalid BIC provided", "Ungültige BIC angegeben" +"Invalid IBAN provided", "Ungültige IBAN" +"Kwixo shipping method type", "Kwixo Versandart" +"Shipping speed (in hours)", "Versandgeschwindigkeit (in Stunden)" +"Default Shipping speed (in hours)", "Standardversandgeschwindigkeit (in Stunden)" +"kwixo_apres_reception_description", "Kwixo Apres Reception Beschreibung" +"kwixo_payement_en_1_fois_description", "Kwixo einmalige Zahlung Beschreibung" +"paiement_en_plusiers_fois_description", "Ratenzahlung Beschreibung" +"DirectLink Maintenance API URL", "DirectLink Maintenance API URL" +"Allow zero amount authorization", "Erlaube Authorisierung auch wenn der Betrag gleich 0 ist" +"Only if payment action is authorize.", "Nur verfügbar, wenn unter Zahlungsservice die Zahlungsaktion auf Genehmigung konfiguriert ist." +"Submit extra parameters", "Zusätzliche Parameter übertragen" +"Enable this if you wish to use fraud detection", "Aktivieren Sie diese Option, wenn Sie die Betrugserkennung nutzen möchten." +"The data you have provided can not be processed by Ingenico ePayments", "Die eingebenen Daten können von Ingenico ePayments nicht verarbeitet werden" +"Test url example: https://secure.ogone.com/ncol/prod/querydirect.asp
Production url example: https://secure.ogone.com/ncol/prod/querydirect.asp", "URL-Beispiel Testbetrieb: https://secure.ogone.com/ncol/prod/querydirect.asp
URL-Beispiel Produktivbetrieb: https://secure.ogone.com/ncol/prod/querydirect.asp" +"Refresh payment status", "Zahlungsstatus aktualisieren" +"Ingenico ePayments status successfully updated", "Ingenico ePayments Status erfolgreich aktualisiert" +"revert state update to it\'s original one because of Ingenico ePayments\'s state restriction", "Der Status der Bestellung konnte nicht geändert werden, da der Ingenico ePayments Status dies nicht erlaubt" +"This must be identical to the value in the Ingenico ePayments backend in section Technical information on tab Data and origin verification.", "Muss identisch mit dem Wert im Ingenico ePayments-Backend unter Technische Informationen auf dem Tab Daten und Ursprungsprüfung sein." +"This must be identical to the value in the Ingenico ePayments backend in section Technical information on tab Transaction feedback.", "Muss identisch mit dem Wert im Ingenico ePayments-Backend unter Technische Informationen auf dem Tab Transaktions-Feedback sein." +"Special user for API must be created in the Ingenico ePayments backend in section Users.", "Spezieller Benutzer für API muss im Ingenico ePayments-Backend unter Benutzerverwaltung erstellt werden." +"Password which has been set in the Ingenico ePayments backend for above API user.", "Passwort, dass im Ingenico ePayments-Backend für den o.g. API-Benutzer vergeben wurde." +"This setting will override the configuration in the Ingenico ePayments backend in section Technical information on tab Global transaction parameters.", "Diese Option überschreibt die Einstellung im Ingenico ePayments-Backend unter Technische Informationen auf dem Tab Globale Transaktionsparameter." +"Only enable this if you wish to use Fraud Detection.", "Nur aktivieren, wenn Betrugserkennung genutzt wird." +"This will log requests and responses in /var/log/ops.log", "Anfragen und Antworten werden in /var/log/ops.log protokolliert." +"Make sure the global Magento logging is also enabled.", "Stellen Sie sicher, dass das globale Logging in Magento ebenfalls aktiviert ist." +If set to Yes, customers will be redirected to the external Ingenico ePayments payment page.", "Wenn Ja gewählt ist, werden Kunden zur externen Ingenico ePayments-Bezahlseite weitergeleitet." +"For the selected card types, the customer can enter the credit card details directly in the checkout.", "Für die hier gewählten Kartentypen kann der Kunde seine Kreditkarten-Daten direkt im Checkout eingeben." +"If the customer's credit card supports 3D Secure, he will be redirected to an external page to enter his password.", "Wenn die Kreditkarte des Kunden 3D Secure unterstützt, wird er zu einer externen Seite weitergeleitet, um sein Passwort einzugeben." +"This sets if this payment method should also be available for orders from the Magento backend.", "Legt fest, ob diese Zahlart auch für Bestellungen im Magento-Backend verfügbar sein soll." +"Make sure that gender and date of birth are required fields in the checkout. Otherwise this payment method will not be displayed.", "Stellen Sie sicher, dass Geschlecht und Geburtstag im Checkout Pflichtfelder sind. Ansonsten wird diese Zahlart nicht angezeigt." +"By choosing this payment method the choice of the concrete payment method will be on Ingenico ePayments side.", "Die konkrete Auswahl der Zahlart erfolgt nach der Bestellung auf Ingenico ePayments Seite." +"Additional Ingenico ePayments payment methods", "Zusätzliche Ingenico ePayments Zahlarten" +"A generic payment method, that lets the customer choose the exact method on Ingenico ePayments side.", "Generische Zahlart, die den Kunden die konkrete Zahlart auf Ingenico ePayments Seite auswählen lässt." +"Depending on the selection the extension will communicate with the Ingenico ePayments test or production service. Choose custom, if you have to use special urls for the gateway.", "Abhängig von Ihrer Auswahl wird das Modul die Test- oder Produktiv-Schnittstelle von Ingenico ePayments ansteuern. Wählen Sie 'Angepasst', wenn Sie spezielle Gateway-Urls nutzen." +"prod", "Produktiv" +"test", "Test" +"custom", "Angepasst" +"Mode", "Umgebung" +"", "" +"Resend payment information", "Zahlungsinformationen erneut senden" +"Ingenico ePayments resend payment information", "Ingenico ePayments Zahlungsinformationen erneut senden" +"Payment information has been resend to customer.", "Zahlungsinformationen wurden an den Kunden gesendet." +"Payment information could not be sent.", "Die Zahlungsinformationen konnten nicht versendet werden." +"Resend payment information identity", "Absender für den Zahlungsinformationen-Neuversand" +"Sender identity. See 'Store Email Adresses'.", "Identität des Senders. Siehe auch 'Store E-Mail-Adressen'" +"Resend payment information template", "Mail-Template für den Zahlungsinformationen-Neuversand" +"The email template to use. Choose 'Default Template from Locale' to use Ingenico ePayments extensions default template.", "Das E-Mail-Template, das verwendet wird. Wählen Sie 'Standardvorlage von  Gebietsschema', um die Ingenico ePayments Modul Standardvorlage zu verwenden." +"This payment method always uses direct sale as payment action.", "Für diese Zahlart wird immer Direktbelastung als Zahlungsaktion verwendet." +"Allow payment method for discounted carts. Please note that not all aquirers support transmission of discount items.", "Erlaube Zahlart auch bei reduziertem Warenkorb. Bitte beachten Sie, dass nicht alle Zahlungsdienstleister reduzierte Artikel unterstützen." +"Maximum length for this field is 35 characters.", "Die Maximallänge für dieses Feld ist 35 Zeichen." +"Enable state restriction", "Aktiviere Status-Einschränkungen" +"If enabled the order status changes that differ from the expected status of the order for the Ingenico ePayments status will get reverted. This can cause conflicts if you use custom order statuses.", "Wenn aktiviert, werden Änderungen des Bestell-Status zurückgesetzt, wenn der Status nicht mit dem für den Ingenico ePayments Status erwarteten Wert übereinstimmt. Dies kann zu Konflikten führen, wenn Sie angepasste Bestellstati verwenden." +"FAILURE_TOKEN_FRONTEND", "Die Zahlungsdaten konnten durch Ingenico ePayments nicht gespeichert werden. Bitte versuchen Sie es erneut oder verwenden Sie eine andere Zahlart." +"SUCCESS_TOKEN_FRONTEND", "Ihre Zahlungsdaten sind bereit zur Verarbeitung durch Ingenico ePayments. Sie können die Daten noch zurücksetzen oder andere Zahlarten auswählen." +"SUCCESS_TOKEN", "Ihre Zahlungsdaten sind bereit zur Verarbeitung durch Ingenico ePayments. Sie können die Daten noch zurücksetzen oder andere Zahlarten auswählen." +"FAILURE_TOKEN", "Die Zahlungsdaten konnten durch Ingenico ePayments nicht gespeichert werden. Bitte versuchen Sie es erneut oder verwenden Sie eine andere Zahlart." +"LOAD_TOKEN", "Bitte warten Sie, während das Ingenico ePayments Zahlungsformular geladen wird." +"If this option is enabled, the customer's alias data will be saved in the shop. The data can then be used again later if the buyer has stored them permanently on Ingenico ePayments side.", "Ist diese Option aktiviert, werden die Aliasdaten des Käufers im Shop gespeichert. Die Daten können dann später wiederverwendet werden, sofern der Käufer die Daten auf Ingenico ePayments Seite speichern lies." +"Ingenico ePayments - Redirect mode", "Ingenico ePayments - Weiterleitungs-Modus" +"Magento - internal shop template", "Magento - Internes Template" +"Ingenico ePayments - iFrame mode", "Ingenico ePayments - iFrame-Modus" +"Ingenico ePayments - Dynamic template", "Ingenico ePayments - Dynamisches Template" +"Enter the TemplateRootId you received from Ingenico ePayments or the url to an external template.", "Geben Sie hier die von Ingenico ePayments erhaltene TemplateRootId oder die Url zu einem Template ein." +"For an easier PCI compliance we recommend redirect mode, iFrame mode or dynamic template with the template hosted on Ingenico ePayments servers. This does NOT affect the Hosted Tokenization iFrame (HTP).", "Für einfachere PCI Compliance empfehlen wir den Weiterleitungs-Modus, den iFrame-Modus oder das dynamische Template, wenn es auf den Servern von Ingenico ePayments gehosted wird. Diese Einstellung bezieht sich NICHT auf das Hosted Tokenization iFrame (HTP)." +"With this setting the customer will enter the payment details on a page in your shop that hosts the Ingenico ePayments paypage in an iFrame. You can style the paypage through the parameters below.", "Mit dieser Einstellung verbleibt der Kunde in Ihrem Shop und die Zahlungsseite von Ingenico ePayments wird über ein iFrame geladen. Sie können die Zahlungsseite über die folgenden Parameter gestalten." +"With this setting the customer will be redirected to the Ingenico ePayments paypage with the look and feel of your shop.
The template used can be seen here:
", "Mit dieser Einstellung wird der Kunde zur Zahlung zu Ingenico ePayments weitergeleitet. Das Aussehen der Zahlungsseite entspricht dabei Ihrem Shop.
Das entsprechende Template kann hier angesehen werden:
" +"With this setting the customer will be redirected to the Ingenico ePayments paypage. The look and feel of that page will be defined by a dynamically loaded template file that you can define below.", "Mit dieser Einstellung wird der Käufer zur Ingenico ePayments Bezahlseite weitergeleitet. Das Aussehen der Seite kann durch eine dynamisch geladene Vorlagendatei bestimmt werden, welche unten angegeben werden kann." +"With this setting the customer will get redirected to Ingenico ePayments to enter his payment details. You can style the page through the parameters below.", "Mit dieser Einstellung wird der Kunde zur Eingabe seiner Zahlungsdaten an Ingenico ePayments weitergeleitet. Die Zahlungsseite können Sie durch die folgenden Parameter stylen." +"Ingenico ePayments Subscription Manager via Cc", "Ingenico ePayments Abonnement-Verwaltung über Cc" +"", "" +"This payment method is used to handle subscriptions via Ingenico ePayments through Magentos recurring profiles. It is only available for orders containing only nominal items.", "Diese Zahlart wird zum Verwalten von Abonnements über Magentos Wiederkehrende Profile verwendet. Sie ist nur für Bestellungen verfügbar, die entsprechende Produkte enthalten." +"A subscription will be created. This will charge you %s every %s %s.", "Ein Abonnement wird erstellt. Dadurch wird Ihnen ein Betrag von %s jede(n) %s. %s berechnet." +" The subscription will end on %s.", " Das Abonnement wird am %s enden." +"A trial subscription will be created. This will charge you %s every %s %s until %s.", "Ein Probe-Abonnement wird erstellt. Dadurch wird Ihnen ein Betrag von %s jede(n) %s. %s berechnet. Das Test-Abonnement läuft bis zum %s." +"To cancel the subscription, please send an email to the shop owner or request this by clicking the suspend button on the subscriptions detail view in your customer account. A link to that page will be displayed on the checkout success page.", "Um das Abonnement zu beenden, können Sie dem Shop-Inhaber eine E-Mail senden oder dies beantragen, indem Sie in der Detail-Ansicht des Abonnements in Ihrem Kunden-Account auf den Button 'Aufheben' klicken. Die Bestellbestätigungs-Seite enthält zudem einen Link auf die besagte Seite." +"You will be charged an initial amount of %s.", "Ihnen wird ein initialer Betrag von %s berechnet." +"Are you sure you want to perform this action? Canceling the subscription here will not actually cancel the subscription on Ingenico ePayments side. To stop charging the customer you will have to deactivate the subscription there.", "Sind Sie sicher, dass Sie diese Aktion ausführen wollen? Hierdurch wird nur der Status in Magento gesetzt. Um dem Kunden keine weiteren Beträge zu berechnen, muss das Abonnement auf Ingenico ePayments-Seite beendet werden." +"Are you sure you want to perform this action? Suspending the subscription here will not actually cancel the subscription on Ingenico ePayments side. To stop charging the customer you will have to deactivate the subscription there.", "Sind Sie sicher, dass Sie diese Aktion ausführen wollen? Hierdurch wird nur der Status in Magento gesetzt. Um dem Kunden keine weiteren Beträge zu berechnen, muss das Abonnement auf Ingenico ePayments-Seite ausgesetzt werden." +"Billing day for weekly subscriptions", "Abrechnungstag für wöchentliche Abonnements" +"Determines the day of the week when a weekly subscription gets triggered.", "Legt den Wochentag fest, an dem die Abrechnung von wöchentlichen Abonnements ausgeführt wird." +"Billing day for monthly subscriptions", "Abrechnungstag für monatliche Abonnements" +"Determines the day of the month when a monthly subscription gets triggered.", "Legt den Tag des Monats fest, an dem die Abrechnung von monatlichen Abonnements ausgeführt wird." +"Store contact responsible for subscription handling", "Für Abonnement-Verwaltung verantwortlicher Kontakt" +"This contact will receive the suspend/cancel requests for the subscriptions. See 'Store Email Adresses'.", "Dieser Kontakt wird die Aussetzungs-/Stornierungs-E-Mails für Abonnements von Kunden erhalten. Siehe auch 'Store E-Mail Adressen'." +"E-Mail template for suspending/canceling a subscription", "E-Mail-Template für Aussetzung/Stornierung eines Abonnements" +"This e-mail template is used for the automatically generated e-mail that is sent to the corresponding contact, if a customer wants to cancel a subscription.", "Diese E-Mail-Vorlage wird für die automatisch generierte E-Mail verwendet, die an die Kontaktadresse gesendet wird, wenn der Käufer ein Abo abbestellen will." +"Ingenico ePayments suspend subscription request", "Ingenico ePayments Abonnement Stornierungsanfrage" +"You are not allowed to suspend this subscription!", "Es ist Ihnen nicht erlaubt dieses Abonnement auszusetzen!" +"Your suspend request was successfully sent. A copy of the email will be sent to your address.", "Ihre Anfrage zur Stilllegung des Abonnements wurde versendet. Sie erhalten eine Kopie der E-Mail an Ihre E-Mail-Adresse" +"Could not send suspend mail, please try again or contact our support directly.", "Stilllegungsanfrage konnte nicht versendet werden, bitte kontaktieren Sie unseren Support direkt." +"Automatic activation not possible. Please contact our support team.", "Automatische Aktivierung nicht möglich. Bitte kontaktieren Sie unser Support-Team" +"To actually suspend the subscription an update in the Ingenico ePayments backend is needed.", "Um das Abonnement tatsächlich zu deaktivieren, ist ein Update im Ingenico ePayments Administrationsbereich notwendig." +"To actually cancel the subscription an update in the Ingenico ePayments backend is needed.", "Um das Abonnement tatsächlich zu stornieren, ist ein Update im Ingenico ePayments Administrationsbereich notwendig." +"To actually activate the subscription an update in the Ingenico ePayments backend is needed.", "Um das Abonnement tatsächlich zu aktivieren, ist ein Update im Ingenico ePayments Administrationsbereich notwendig." +"When using %s as payment method the settings for '%s' and '%s' will not be processed.", "Wenn %s als Zahlungsart verwendet wird, finden die Optionen '%s' und '%s' keine Anwendung." +"Enable device fingerprinting", "Aktiviere Gerät-Fingerabdruck" +"When enabling this, you still need to request the explicit consent of the customer in the checkout to transmit this data.", "Wenn aktiviert, muss weiterhin die Zustimmung des Kunden im Checkout erteilt werden, damit diese Daten übertragen werden dürfen." +"This functionality will transmit specific customer data as hash to Ingenico ePayments to identify the device from which the transaction was sent can be identified with the highest probability.", "Diese Funktion sammelt spezifische Kunden-/Gerätedaten, hasht diese und überträgt sie an Ingenico ePayments, um das Gerät von dem die Transaktion gesendet wurde möglichst eindeutig zu identifizieren. " +"Enter new credit card data...", "Neue Kreditkartendaten eingeben..." +"Please enter a valid credit card number.", "Bitte geben Sie eine gültige Kreditkartennummer ein." +"This is a required field.", "Dies ist ein Pflichtfeld." +"Timeout for transactions", "Zeitüberschreitung für Transaktionen" +"This must be identical to the value in the Ingenico ePayments backend in section Technical information on tab Global security parameters.", "Muss identisch mit dem Wert im Ingenico ePayments-Backend unter Technische Informationen auf dem Tab Globale Sicherheitsparameter sein." +"Hash algorithm", "Hash-Algorithmus" +"Order has been canceled permanently in Magento. Changes in Ingenico ePayments platform will no longer be considered.", "Bestellung wurde in Magento dauerhaft storniert. Änderungen auf der Ingenico ePayments Plattform werden nicht mehr berücksichtigt." +"The order has been accepted in Magento only. For the actual status of the payment check the Ingenico ePayments backend.", "Bestellung wurde nur in Magento akzeptiert. Für den tatsächlichen Zahlungsstatus prüfen Sie bitte das Ingenico ePayments Backend." +"Received Ingenico ePayments feedback status %s.", "Ingenico ePayments Feedback Status %s empfangen." +"Customer received your payment instructions, waiting for actual payment.", "Käufer hat Ihren Zahlungshinweis erhalten, warte auf tatsächliche Zahlung." +"Customer redirected for 3DS authorization.", "Käufer zur 3DS-Autorisierung umgeleitet." +"Ingenico ePayments status 0, the action failed.", "Ingenico ePayments Status 0, Vorgang fehlgeschlagen." +"Payment failed because the authorization was declined! Please choose another payment method.", "Zahlung fehlgeschlagen weil Autorisierung abgelehnt wurde! Bitte wählen Sie eine andere Zahlart." +"Received Ingenico ePayments feedback status update with final status %s.", "Ingenico ePayments Feedback mit endgültigem Status %s empfangen ." +"Received Ingenico ePayments feedback status update with suspected fraud status %s. Please have a look in Ingenico ePayments backend for more information.", "Ingenico ePayments Feedback mit Betrugsverdacht-Status %s empfangen. Bitte prüfen Sie das Ingenico ePayments Backend für weitere Informationen." +"Received Ingenico ePayments feedback status update with intermediate status %s.", "Ingenico ePayments Feedback mit Zwischen-Status %s empfangen." +"Received Ingenico ePayments feedback status update with refused status %s.", "Ingenico ePayments Feedback mit abgelehntem Status %s empfangen." +"Payment has been authorized by Ingenico ePayments, but not yet captured.", "Zahlung wurde durch Ingenico ePayments autorisiert, aber noch nicht erfasst." +"%s is not a authorize status!", "%s ist kein Autorisierungs-Status" +"%s is not a capture status!", "%s ist kein Erfasst-Status" +"%s is not a void status!", "%s ist keine Void-Status" +"%s is not a refund status!", "%s ist kein Erstattungs-Status" +"%s is not a special status!", "%s ist kein spezieller Status" +"Status %s can not be voided.", "Status %s kann nicht auf Void gesetzt werden." +"No update available from Ingenico ePayments.", "Kein Update von Ingenico ePayments verfügbar." +"Received Ingenico ePayments status %s. Order cancelled.", "Ingenico ePayments Feedback Status %s empfangen. Bestellung storniert." +"If activated an extra parameter is transmitted to Ingenico ePayments for the Creditcard/Debitcard payment methods.", "Bei Aktivierung wird ein zusätzlicher Parameter für Kreditkarten- / Debitkarten-Zahlarten an Ingenico ePayments übertragen." +"See also Ingenico ePayments documentation.", "Siehe auch Ingenico ePayments Dokumentation." +"Refund refused by Ingenico ePayments.", "Erstattung seitens Ingenico ePayments abgelehnt." +"Add Method", "Zahlart hinzufügen" +"PM and Brand combination must be unique", "Kombination aus PM und BRAND muss einzigartig sein." +"Can not save empty title or PM fields", "Kann leere Titel- oder PM-Felder nicht speichern." +"A generic option, that lets the customer choose the exact method on Ingenico ePayments side.", "Universelle Option, durch die der Käufer die konkrete Zahlart auf der Ingenico ePayments Seite wählen kann." +"Add one or more methods. In field 'PM' you will have to enter the PM value you got from Ingenico ePayments, same goes for the field 'BRAND'.", "Eine oder mehrere Zahlarten hinzufügen. In das Feld 'PM' muss der PM-Wert eingetragen werden, den Sie von Ingenico ePayments erhalten haben. Gleiches gilt für das Feld 'BRAND'." +"Methods", "Zahlarten" +"Show empty default option", "Leere Standard-Option anzeigen" +"Ingenico ePayments Flex payment method", "Ingenico ePayments Flex-Zahlart" +"Title to display for the default option.", "Titel zur Anzeige der Standard-Option" +"Default option title", "Standard-Option Titel" +"Extension Version", "Extension Version" +"Documentation", "Dokumentation" +"English", "Englisch" +"German", "Deutsch" +"First Aid - FAQ", "Erste Hilfe - FAQ" +"Need support? Contact Ingenico ePayments:", "Support benötigt? Kontaktieren Sie Ingenico ePayments:" +"Download Logfile", "Log-Datei herunterladen" +"Show catalog button", "Zeige Katalog-Button" +"Show home button", "Zeige Home-Button" +"Credit/Debit card Split", "Trennung Kredit-/Debitkarte" +"Payment from Applicable Countries","Zahlung aus erlaubten Ländern" +"Payment from Specific Countries","Zahlung von bestimmten Ländern" +"All Allowed Countries","Alle erlaubten Länder" +"Specific Countries","Bestimmte Länder" +"Delete Image","Lösche Bild" +"Allowed file types: jpeg, gif, png.","Erlaubte Datei-Typen: jpeg, gif, png" +"Payment Logo","Logo der Zahlart" +"Payment Logo Visibility","Sichtbarkeit des Logos" +"Netresearch_OPS::Left","Links" +"Netresearch_OPS::Right","Rechts" +"Netresearch_OPS::Non Visible","Nicht Sichtbar" +"Disallowed file type.","Nicht erlaubter Datei-Typ." +"Payment Information","Zahlungsinformationen" +"Submit","Abschicken" +"Cancel","Abbrechen" +"Transmit new payment method...","Übertrage neue Zahlungsmethode..." +"Your given data","Ihre angegebenen Daten" +"Order Number","Bestellnummer" +"Billing Address","Rechnungsadresse" +"Shipping Address","Lieferadresse" +"PayPerMail Email Template", "Zahlung per Email Template" +"Aliasusage New","Alias Text Neu" +"Here you can enter the desired text which will be displayed on Ingenico ePayments side.","Hier können Sie den gewünschten Text eingeben, welcher auf der Bezahlseite von Ingenico ePayments angezeigt werden soll." +"Aliasusage existing Alias","Alias Text für existierenden Alias" +"Here you can enter the desired text which is used for existing alias, the text will be displayed on Ingenico ePayments side.","Hier können sie den gewünschten Text für einen bereits existierenden Alias eingeben, welcher auf der Bezahlseite von Ingenico ePayments angezeigt werden soll." +"This value specifies the request timeout for that transaction. A value between 30 and 60 seconds is possible.", "Dieser Wert bestimmt den Timeout für die Transaktion. Der Wert darf zwischen 30 und 60 Sekunden liegen." +"Layout on the external payment page (for redirect payment methods).", "Ausrichtung auf der externen Bezahlseite (für Weiterleitungs-Zahlarten)." +"Send Invoice to Customer", "Rechnung an Kunden schicken" +"Value exceeds %d characters","Wert ist länger als %d Zeichen." +"Your order has been registered, but your payment is still marked as pending. Please have patience until the final status is known.","Ihre Bestellung wurde registriert, aber Ihre Zahlung ist noch nicht vollständig abgeschlossen. Bitte haben Sie Geduld, bis der endgültige Zahlungsstatus bekannt ist." +"You will be redirected to finalize your payment.","Sie werden weitergeleitet um Ihre Zahlung abzuschließen." +"When using Klarna as your aquirer you will need to switch to the option 'Other'.","Wenn Sie Klarna als Akzeptanzpartner verwenden, müssen Sie die Option 'Sonstige' auswählen." +"When the option 'Other' is selected the request data will get converted to ASCII chararcters by using the appropriate transliterations for the given locale of the store.","Wenn die Option 'Sonstige' gewählt ist, werden die Requestdaten in ASCII-Zeichen umgewandelt. Dabei werden die Transliterationen der Lokalisierung des aktuellen Stores verwendet." +"Allows you to display a configurable link to your aquirers invoicing terms and conditions.","Erlaubt die Darstellung eines konfigurierbaren Links zu den Rechnungsbedingungen Ihres Akzeptanzpartners." +"Show invoice terms link","Zeige Link zu Rechnungsbedingungen" +"Invoice terms title","Titel Rechnungsbedingungen" +"Invoice terms URL","URL zu Rechnungsbedingungen" +"Request Data Encoding", "Requestdaten Zeichenkodierung" +"Pay now","Jetzt bezahlen" +"Enter new bank data...","Neue Bankdaten eingeben..." +"Other","Sonstige" \ No newline at end of file diff --git a/app/locale/de_DE/template/email/ops/ops_email_resend_payment_info.html b/app/locale/de_DE/template/email/ops/ops_email_resend_payment_info.html new file mode 100644 index 0000000..b3b7f78 --- /dev/null +++ b/app/locale/de_DE/template/email/ops/ops_email_resend_payment_info.html @@ -0,0 +1,49 @@ + + + + + + + +
+ + + + +
+ + + + + + + + + + + + +
{{var logo_alt}}
+

Sehr geehrte(r) {{htmlescape var=$order.getCustomerName()}},

+ +

wir konnten bisher keinen Zahlungseingang zu Ihrer Bestellung bei uns verbuchen. Sie können hier die Zahlungsinformationen zu Ihrer Bestellung nochmals einsehen.

+

Sollten Sie den fälligen Betrag bereits beglichen haben, betrachten Sie diese E-Mail als gegenstandslos.

+

+ Bei Fragen dürfen Sie sich jederzeit gerne an uns wenden unter + {{config path='trans_email/ident_support/email'}} + oder telefonisch unter der Rufnummer {{config path='general/store_information/phone'}}. +

+

Haben Sie nochmals recht herzlichen Dank, {{var store.getFrontendName()}}

+
+
+ \ No newline at end of file diff --git a/app/locale/de_DE/template/email/ops/ops_pay_per_mail_info.html b/app/locale/de_DE/template/email/ops/ops_pay_per_mail_info.html new file mode 100644 index 0000000..6e1560f --- /dev/null +++ b/app/locale/de_DE/template/email/ops/ops_pay_per_mail_info.html @@ -0,0 +1,49 @@ + + + + + + + +
+ + + + +
+ + + + + + + + + + + + +
{{var logo_alt}}
+

Sehr geehrte(r) {{htmlescape var=$order.getCustomerName()}},

+ +

unter Folgenden Link können Sie ihre Bestellung bezahlen.

+ +

+ Bei Fragen dürfen Sie sich jederzeit gerne an uns wenden unter + {{config path='trans_email/ident_support/email'}} + oder telefonisch unter der Rufnummer {{config path='general/store_information/phone'}}. +

+

Haben Sie nochmals recht herzlichen Dank, {{var store.getFrontendName()}}

+
+
+ \ No newline at end of file diff --git a/app/locale/de_DE/template/email/ops/suspend_subscription.html b/app/locale/de_DE/template/email/ops/suspend_subscription.html new file mode 100644 index 0000000..fef17f7 --- /dev/null +++ b/app/locale/de_DE/template/email/ops/suspend_subscription.html @@ -0,0 +1,43 @@ + + + + + + + +
+ + + + +
+ + + + + + + + + + + + +
{{var logo_alt}}
+

Sehr geehrtes {{var store.getFrontendName()}}-Team,

+ +

als Ihr Kunde beantrage ich, {{htmlescape var=$customer.getName()}}, die Stornierung des Abonnements {{var profile.getReferenceId()}}{{if profile.getTrialPeriodUnit()}} und aller zugehörigen Test-Abonnements{{/if}}.

+

Sollten Sie diese Anfrage bereits bearbeitet haben, bitte ich Sie, den Status für mich sichtbar entsprechend in Ihrem Shop zu aktualisieren.

+

Vielen Dank und beste Grüße, {{var customer.getName()}}

+
+
+ \ No newline at end of file diff --git a/app/locale/en_US/Netresearch_OPS.csv b/app/locale/en_US/Netresearch_OPS.csv new file mode 100644 index 0000000..f73d8be --- /dev/null +++ b/app/locale/en_US/Netresearch_OPS.csv @@ -0,0 +1,485 @@ +"STATUS_0","Incomplete or invalid" +"STATUS_1","Cancelled by client" +"STATUS_2","Authorization refused" +"STATUS_4","Order stored" +"STATUS_40","Stored waiting external result" +"STATUS_41","Waiting client payment" +"STATUS_46","Waiting (for identification)" +"STATUS_5","Authorized" +"STATUS_50","Authorized waiting external result" +"STATUS_51","Authorization waiting" +"STATUS_52","Authorization not known" +"STATUS_55","Stand-by" +"STATUS_56","OK with scheduled payments" +"STATUS_57","Error in scheduled payments" +"STATUS_59","Authoriz. to get manually" +"STATUS_6","Authorized and cancelled" +"STATUS_61","Author. deletion waiting" +"STATUS_62","Author. deletion uncertain" +"STATUS_63","Author. deletion refused" +"STATUS_64","Authorized and cancelled" +"STATUS_7","Payment deleted" +"STATUS_71","Payment deletion pending" +"STATUS_72","Payment deletion uncertain" +"STATUS_73","Payment deletion refused" +"STATUS_74","Payment deleted" +"STATUS_75","Deletion processed by merchant" +"STATUS_8","Refund" +"STATUS_81","Refund pending" +"STATUS_82","Refund uncertain" +"STATUS_83","Refund refused" +"STATUS_84","Payment declined by the acquirer" +"STATUS_85","Refund processed by merchant" +"STATUS_9","Payment requested" +"STATUS_91","Payment processing" +"STATUS_92","Payment uncertain" +"STATUS_93","Payment refused" +"STATUS_94","Refund declined by the acquirer" +"STATUS_95","Payment processed by merchant" +"STATUS_99","Being processed" +"Hash is not valid","Hash is not valid" +"Order is not valid","Order is not valid" +"Process outgoing transaction","Process outgoing transaction" +"Start Ingenico ePayments processing","Start Ingenico ePayments processing" +"Your payment information was declined. Please select another payment method.","Your payment information was declined. Please select another payment method." +"Order canceled on Ingenico ePayments side. Status: %s, Payment ID: %s.","Order canceled on Ingenico ePayments side. Status: %s, Payment ID: %s." +"Account number must contain numbers only.","Account number must contain numbers only." +"Bank code must contain numbers only.","Bank code must contain numbers only." +"Account Holder must be filled in.","Account Holder must be filled in." +"Capture process complete","Capture process complete" +"The capture has already been invoiced.","The capture has already been invoiced." +"Cannot create an invoice without products.","Cannot create an invoice without products." +"Partial capture process complete","Partial capture process complete" +"Invoice '%s' was created automatically. Ingenico ePayments Status: %s.","Invoice '%s' was created automatically. Ingenico ePayments Status: %s." +"Start Ingenico ePayments refund request","Start Ingenico ePayments refund request" +"Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments Status: %s.","Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments Status: %s." +"Creditmemo '%s' was created automatically. Ingenico ePayments Status: %s.","Creditmemo '%s' was created automatically. Ingenico ePayments Status: %s." +"Could not perform actions for Ingenico ePayments status: %s.","Could not perform actions for Ingenico ePayments status: %s." +"Refund is waiting or uncertain. Ingenico ePayments status: %s.","Refund is waiting or uncertain. Ingenico ePayments status: %s." +"Refund was refused. Automatic creation failed. Ingenico ePayments status: %s.","Refund was refused. Automatic creation failed. Ingenico ePayments status: %s." +"Capture is waiting or uncertain. Ingenico ePayments status: %s.","Capture is waiting or uncertain. Ingenico ePayments status: %s." +"Capture was refused. Automatic creation failed. Ingenico ePayments status: %s.","Capture was refused. Automatic creation failed. Ingenico ePayments status: %s." +"Void is waiting or uncertain. Ingenico ePayments status: %s.","Void is waiting or uncertain. Ingenico ePayments status: %s." +"Void was refused. Automatic creation failed. Ingenico ePayments status: %s.","Void was refused. Automatic creation failed. Ingenico ePayments status: %s." +"Authorization status changed. Current Ingenico ePayments status is: %s.","Authorization status changed. Current Ingenico ePayments status is: %s." +"Unknown Ingenico ePayments status: %s.","Unknown Ingenico ePayments status: %s." +"Error, transaction count is %s instead of 1 for the Payid '%s', order '%s' and Operation '%s'.","Error, transaction count is %s instead of 1 for the Payid '%s', order '%s' and Operation '%s'." +"Order can not be saved.","Order can not be saved." +"Order can not be canceled for system reason.","Order can not be canceled for system reason." +"Order declined on ops side. Ingenico ePayments status: %s, Payment ID: %s.","Order declined on ops side. Ingenico ePayments status: %s, Payment ID: %s." +"Order can not be saved for system reason.","Order can not be saved for system reason." +"An unknown exception occured.","An unknown exception occured." +"A technical problem arose during payment process, giving unpredictable result. Ingenico ePayments status: %s.","A technical problem arose during payment process, giving unpredictable result. Ingenico ePayments status: %s." +"An unknown exception was thrown in the payment process. Ingenico ePayments status: %s.","An unknown exception was thrown in the payment process. Ingenico ePayments status: %s." +"Waiting for the payment of the customer","Waiting for the payment of the customer" +"Authorization waiting from Ingenico ePayments","Authorization waiting from Ingenico ePayments" +"Processed by Ingenico ePayments","Processed by Ingenico ePayments" +"Waiting for payment. Ingenico ePayments status: %s.","Waiting for payment. Ingenico ePayments status: %s." +"Authorization waiting. Ingenico ePayments status: %s.","Authorization waiting. Ingenico ePayments status: %s." +"Authorization uncertain. Ingenico ePayments status: %s.","Authorization uncertain. Ingenico ePayments status: %s." +"Processed by Ingenico ePayments. Payment ID: %s. Ingenico ePayments status: %s.","Processed by Ingenico ePayments. Payment ID: %s. Ingenico ePayments status: %s." +"Ingenico ePayments server is temporarily not available, please try again later.","Ingenico ePayments server is temporarily not available, please try again later." +"An error occured during the Ingenico ePayments request. Your action could not be executed. Message: '%s'.","An error occured during the Ingenico ePayments request. Your action could not be executed. Message: '%s'." +"You already sent a capture request. Please wait until the capture request is acknowledged.","You already sent a capture request. Please wait until the capture request is acknowledged." +"There is one void request waiting. Please wait until this request is acknowledged.","There is one void request waiting. Please wait until this request is acknowledged." +"Start Ingenico ePayments %s capture request","Start Ingenico ePayments %s capture request" +"Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments status: %s.","Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments status: %s." +"The Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement.","The Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement." +"The Invoice was not created. Ingenico ePayments status: %s.","The Invoice was not created. Ingenico ePayments status: %s." +"The CreditMemo was not created. Ingenico ePayments status: %s.","The CreditMemo was not created. Ingenico ePayments status: %s." +"The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.","The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement." +"There is already one creditmemo in the queue. The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.","There is already one creditmemo in the queue. The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement." +"Please use void to cancel the operation.","Please use void to cancel the operation." +"You already sent a void request. Please wait until the void request will be acknowledged.","You already sent a void request. Please wait until the void request will be acknowledged." +"There is one capture request waiting. Please wait until this request is acknowledged.","There is one capture request waiting. Please wait until this request is acknowledged." +"The void request is sent. Please wait until the void request will be accepted.","The void request is sent. Please wait until the void request will be accepted." +"Void order succeed. Ingenico ePayments status: %s.","Void order succeed. Ingenico ePayments status: %s." +"Void order failed. Ingenico ePayments status: %s.","Void order failed. Ingenico ePayments status: %s." +"Authorization","Authorization" +"Direct Sale","Direct Sale" +"Horizontally grouped logo with group name on left","Horizontally grouped logo with group name on left" +"Horizontally grouped logo with no group name","Horizontally grouped logo with no group name" +"Verical list","Verical list" +"You will be redirected to the payment page after the order confirmation.","You will be redirected to the payment page after the order confirmation." +"Payment failed. Please select another payment method.","Payment failed. Please select another payment method." +"Payment failed. Please review your input or select another payment method.","Payment failed. Please review your input or select another payment method." +"Incorrect credit card expiration date.","Incorrect credit card expiration date." +"Credit Card Type","Credit Card Type" +"Name on Card","Name on Card" +"Credit Card Number","Credit Card Number" +"Expiration Date","Expiration Date" +"Account holder","Account holder" +"Account number","Account number" +"Bank code","Bank code" +"BCMC","Bancontact/Mister Cash" +"You will be redirected to Ingenico ePayments website when you place an order.","You will be redirected to Ingenico ePayments website when you place an order." +"Please wait a moment. This page will transfer your data to Ingenico ePayments payment gateway in a few seconds.","Please wait a moment. This page will transfer your data to Ingenico ePayments payment gateway in a few seconds." +"An error occured during 3D-Secure processing of Ingenico ePayments. Error: %s","Error during 3D-Secure processing of Ingenico ePayments. Error: %s" +"PSPID is a case sensitive field","PSPID is a case sensitive field" +"SHA-IN Pass phrase","SHA-IN Pass phrase" +"Only alphanumeric characters, not longer than 20 characters. Refer to Ingenico ePayments's backend in section Technical Information on tab Data and origin verification.","Only alphanumeric characters, not longer than 20 characters. Refer to Ingenico ePayments's backend in section Technical Information on tab Data and origin verification." +"SHA-OUT Pass phrase","SHA-OUT Pass phrase" +"Only alphanumeric characters, not longer than 20 characters. Refer to Ingenico ePayments's backend in section Technical Information on tab Transaction feedback.","Only alphanumeric characters, not longer than 20 characters. Refer to Ingenico ePayments's backend in section Technical Information on tab Transaction feedback." +"Test url example: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
Production url example: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp","Test url example: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
Production url example: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp" +"Test url example: https://ogone.test.v-psp.com/Tokenization/HostedPage
Production url example: https://secure.ogone.com/Tokenization/HostedPage","Test url example: https://ogone.test.v-psp.com/Tokenization/HostedPage
Production url example: https://secure.ogone.com/Tokenization/HostedPage" +"Test url example: https://secure.ogone.com/ncol/test/maintenancedirect.asp
Production url example: https://secure.ogone.com/ncol/prod/maintenancedirect.asp","Test url example: https://secure.ogone.com/ncol/test/maintenancedirect.asp
Production url example: https://secure.ogone.com/ncol/prod/maintenancedirect.asp" +"Test url example: https://secure.ogone.com/ncol/test/orderdirect.asp
Production url example: https://secure.ogone.com/ncol/prod/orderdirect.asp","Test url example: https://secure.ogone.com/ncol/test/orderdirect.asp
Production url example: https://secure.ogone.com/ncol/prod/orderdirect.asp" +"API User","API User" +"API Password","API Password" +"Payment Action","Payment Action" +"If you choose 'Ingenico ePayments', you will need to fill in all the following fields.","If you choose 'Ingenico ePayments', you will need to fill in all the following fields." +"Payment Template","Payment Template" +"Title of the Ingenico ePayments Template","Title of the Ingenico ePayments Template" +"Background Color of Ingenico ePayments Template","Background Color of Ingenico ePayments Template" +"Text Color of the Ingenico ePayments Template","Text Color of the Ingenico ePayments Template" +"Table Background Color of the Ingenico ePayments Template","Table Background Color of the Ingenico ePayments Template" +"Table Text Color of the Ingenico ePayments Template","Table Text Color of the Ingenico ePayments Template" +"Button Background Color of the Ingenico ePayments Template","Button Background Color of the Ingenico ePayments Template" +"Button Text Color of the Ingenico ePayments Template","Button Text Color of the Ingenico ePayments Template" +"Font Family of the Ingenico ePayments Template","Font Family of the Ingenico ePayments Template" +"Logo of the Ingenico ePayments Template","Logo of the Ingenico ePayments Template" +"Layout of Payment Methods","Layout of Payment Methods" +"e.g: white","e.g: white" +"e.g: black","e.g: black" +"e.g: Verdana","e.g: Verdana" +"Debug","Debug" +"Ingenico ePayments Account","Ingenico ePayments Account" +"devprefix", "Prefix for parameter ORDERID" +"URL/filename of the logo. The URL must be absolute and stored on a secure server.","URL/filename of the logo. The URL must be absolute and stored on a secure server." +"Countries","Countries" +"Enabled Card Types","Enabled Card Types" +"Enable 3D secure","Enable 3D secure" +"OWNERADDRESS","Invoiced customer's address" +"ECOM_BILLTO_POSTAL_STREET_NUMBER","House number of invoicing address" +"ECOM_SHIPTO_POSTAL_STREET_LINE1","Delivery address" +"ECOM_SHIPTO_POSTAL_STREET_NUMBER","Delivery house number" +"Please make sure that your street and house number are correct!","Please make sure that your street and house number are correct!" +"Brand", "Brand" +"Add Brand", "Add Brand" +"Add one or more brands. In field 'BRAND' you will have to enter the value you got from Ingenico ePayments.", "Add one or more brands. In field 'BRAND' you will have to enter the value you got from Ingenico ePayments." +"Title", "Title" +"Please select a brand", "Please select a brand" +"Attention: This may be an attempt to defraud!","Attention: This may be an attempt to defraud!" +"This might be a fraud attempt. Please check details for more information.", "This might be a fraud attempt. Please check details for more information." +"Scoring Category: G","Scoring Categorie: G (Green)" +"Scoring Category: O","Scoring Categorie: O (Orange)" +"Scoring Value: %s","Scoring Value: %s" +"SCORING_DESCRIPTION_AAVCHECK","Result of the automatic address verification. This verification is currently supported by American Express only.

Possible values:

KO: The address has been sent but the acquirer has given a negative response for the address check, i.e. the address is wrong.
OK: The address has been sent and the acquirer has returned a positive response for the address check, i.e. the address is correct.
NO: All other cases. For instance, no address transmitted." +"SCORING_DESCRIPTION_CVCCHECK","Result of the card verification code check.

Possible values:

KO: The CVC has been sent but the acquirer has given a negative response to the CVC check, i.e. the CVC is wrong.
OK: The CVC has been sent and the acquirer has given a positive response to the CVC check, i.e. the CVC is correct.
NO: All other cases. For instance, no CVC transmitted." +"SCORING_DESCRIPTION_CCCTY","Country where the card was issued" +"SCORING_DESCRIPTION_IPCTY","Originating country of the IP address" +"SCORING_DESCRIPTION_NBREMAILUSAGE","Number of times an email address has been used over a certain period" +"SCORING_DESCRIPTION_NBRIPUSAGE","Number of times an IP address has been used for successful transactions over a certain period" +"SCORING_DESCRIPTION_NBRIPUSAGE_ALLTX","Number of times an IP address has been used for successful and/or declined transactions over a certain period" +"SCORING_DESCRIPTION_NBRUSAGE","Number of times a credit card has been used over a certain period" +"SCORING_DESCRIPTION_VC","Virtual Card" +"SCORING_DESCRIPTION_CARDNO","Card number or account number" +"SCORING_DESCRIPTION_ED","Expiry date" +"SCORING_DESCRIPTION_CN","Card holder name" +"Sofort Uberweisung","SOFORT Überweisung" +"DirectEbanking", "sofortüberweisung.de" +"DirectEbankingAT", "SOFORT Überweisung (AT)" +"DirectEbankingBE", "SOFORT Überweisung (BE)" +"DirectEbankingCH", "SOFORT Überweisung (CH)" +"DirectEbankingDE", "SOFORT Überweisung (DE)" +"DirectEbankingFR", "SOFORT Überweisung (FR)" +"DirectEbankingGB", "SOFORT Überweisung (GB)" +"Card Verification Number","CVC / Issue number" +"Invalid payment information provided!", "Invalid payment information provided!" +"Unknown Ingenico ePayments state for this order. Please check Ingenico ePayments backend for this order.", "Unknown Ingenico ePayments state for this order. Please check Ingenico ePayments backend for this order." +"The order was cancelled manually. The Ingenico ePayments-state is 0 or null.","The order was cancelled manually. The Ingenico ePayments-state is 0 or null." +"Last refund (Close payment transaction)", "Last refund (Close payment transaction)" +"Only offline refund is available, since Ingenico ePayments transaction is already closed.", "Only offline refund is available, since Ingenico ePayments transaction is already closed." +"Order reference in case of redirect payments", "Order reference in case of redirect payments" +"Order reference in case of inline payments", "Order reference in case of inline payments" +"See chapter 'Configuration', section 'Magento - Backend' in the documentation. Please note that the option 'orderId' is not used for the Kwixo payment methods.", "See chapter 'configuration', section 'Magento - Backend in the documentation'. Please note that the option 'orderId' is not used for the Kwixo payment methods." +"See chapter 'Configuration', section 'Magento - Backend' in the documentation.","See chapter 'configuration', section 'Magento - Backend' in the documentation." +"quote id", "Quote ID" +"order id", "Order ID" +"Enable tracking code", "Enable tracking code for fraud detection" +"Delete","delete" +"active","active" +"Action","Action" +"pending", "pending" +"Kwixo Category Mapping", "Kwixo Category Mapping" +"Please note that a proper category mapping is necessary in order to use this payment method", "Please note that a proper category mapping is necessary in order to use this payment method" +"ECOM_SHIPTO_TELECOM_PHONE_NUMBER", "Delivery customer's phone number" +"ECOM_SHIPTO_POSTAL_NAME_PREFIX", "Delivery customer's salutation" +"CIVILITY", "Invoiced customer's salutation" +"Are you sure you want to cancel this order? Warning: Please check the payment status in the back-office of Ingenico ePayments before. By cancelling this order you won\'t be able to update the status in Magento anymore.", "Are you sure you want to cancel this order? Warning: Please check the payment status in the back-office of Ingenico ePayments before. By cancelling this order you won\'t be able to update the status in Magento anymore." +"Error transaction count is %s instead of 1 for the Payid '%s'", "Error transaction count is %s instead of 1 for the Payid '%s'" +"--Please select--","--Please select--" +"Pick up at merchant","Pick up at merchant" +"Collection point (Kiala...)","Collection point (Kiala...)" +"Collect at airport, train station or travel agency","Collect at airport, train station or travel agency" +"Transporter (La Poste, UPS...)","Transporter (La Poste, UPS...)" +"Download","Download" +"Food & gastronomy","Food & gastronomy" +"Car & Motorbike", "Car & Motorbike" +"Culture & leisure","Culture & leisure" +"Home & garden","Home & garden" +"Appliances","Appliances" +"Auctions and bulk purchases","Auctions and bulk purchases" +"Flowers & gifts","Flowers & gifts" +"Computer & software","Computer & software" +"Health & beauty","Health & beauty" +"Services for individuals","Services for individuals" +"Services for professionals","Services for professionals" +"Sports", "Sports" +"Clothing & accessories", "Clothing & accessories" +"Travel & tourism","Travel & tourism" +"Hifi, photo & video","Hifi, photo & video" +"Telephony & communication","Telephony & communication" +"Jewelry & precious metals","Jewelry & precious metals" +"Baby articles and accessories","Baby articles and accessories" +"Sound & light","Sound & light" +"Ingenico ePayments server is temporarily not available", "Ingenico ePayments server is temporarily not available" +"Country", "Country" +"Enable redirect payment for all card types", "Enable redirect payment for all card types" +"Enable inline payment (Direct Link) for these card types", "Enable inline payment (Direct Link) for these card types" +"Miscellaneous Countries", "Miscellaneous Countries" +"Please make sure that you enabled customer gender and customer date of birth in customer attributes configuration. Otherwise this payment method won't be available. Please be informed that when you use Afterpay or Klarna as acquirer on Ingenico ePayments side, it's not possible to perform partial captures.", "Please make sure that you enabled customer gender and customer date of birth in customer attributes configuration. Otherwise this payment method won't be available. Please be informed that when you use Afterpay or Klarna as acquirer on Ingenico ePayments side, it's not possible to perform partial captures." +"Your details", "Your details" +"Ingenico ePayments Payment failed", "Ingenico ePayments Payment failed" +"Enable for backend", "Enable for backend" +"Sort Order for backend", "Sort Order for backend" +"Existing Ingenico ePayments transaction", "Existing Ingenico ePayments transaction" +"Ingenico ePayments payment id", "Ingenico ePayments payment id" +"Please use this method for the following brands only:", "Please use this method for the following brands only:" +"Payment using 3D secure is not supported.", "Payment using 3D secure is not supported." +"My payment information", "My payment information" +"Payment Method", "Payment Method" +"Card Number/Account Number", "Card Number/Account Number" +"expires: %s", "expires: %s" +"Save payment information", "Save payment information" +"You don't want to enter your payment information again? Just %s to save your credit card information securely!", "You don't want to enter your payment information again? Just %s to save your credit card information securely!" +"register", "register" +"Removed alias %s.", "Removed alias %s." +"Could not remove alias %s.", "Could not remove alias %s." +"Show information for guests", "Show information for guests" +"If this checkbox is marked, the refund will be final: the transaction will be closed and it will not be possible to perform any further refund for this transaction.", "If this checkbox is marked, the refund will be final: the transaction will be closed and it will not be possible to perform any further refund for this transaction." +"Show Quote ID in the order grid", "Show Quote ID in the order grid" +"The ID of Tracker Application Account.", "The ID of Tracker Application Account." +"Enabled Alias Manager", "Enabled Alias Manager" +"Show Alias Manager information for guests", "Show Alias Manager information for guests" +"State", "State" +"Card Holder", "Card Holder" +"You did not save any payment information, yet.", "You did not save any payment information, yet." +"Please make sure that the displayed data is correct.", "Please make sure that the displayed data is correct." +"Year of Expiration Date", "Year of Expiration Date" +"Month of Expiration Date", "Month of Expiration Date" +"Kwixo category", "Kwixo category" +"Apply to sub-categories", "Apply to sub-categories" +"Categories configuration", "Categories configuration" +"Categorie's %s configuration", "Categorie's %s configuration" +"Estimated Delivery Date", "Estimated delivery delay (in days)" +"RNP Fee", "RNP Fee" +"Shipping Method Type", "Shipping Method Type" +"Shipping Method Details", "Shipping Method Details" +"An error occured during 3D-Secure processing of Ingenico ePayments.", "An error occured during 3D-Secure processing of Ingenico ePayments." +"Shipping method type", "Shipping method type" +"Shipping method speed", "Shipping Speed (in hours)" +"Shipping method details", "Shipping method details" +"shipping details not valid", "Shipping details not valid" +"shipping speed not valid", "Shipping speed not valid" +"shipping type not valid", "Shipping type not valid" +"Default Shipping Method Type", "Default Shipping Method Type" +"Default Shipping method speed", "Default Shipping Speed (in hours)" +"Default Shipping Method Details", "Default Shipping Method Details" +"Kwixo shipping setting", "Kwixo shipping setting" +"Don't forget to set your shipping method to use KWIXO in the best way", "Don't forget to set your shipping method to use KWIXO in the best way" +"Unknown country within the IBAN '%value%'", "Unknown country within the IBAN '%value%'" +"%value%' has a false IBAN format", "%value%' has a false IBAN format" +"'%value%' has failed the IBAN check", "'%value%' has failed the IBAN check" +"Country not supported for IBAN", "Country not supported for IBAN" +"invalid data provided", "Invalid data provided" +"Account holder must be provided", "Account holder must be provided" +"invalid BIC provided", "Invalid BIC provided" +"Invalid IBAN provided", "Invalid IBAN provided" +"Kwixo shipping method type", "Kwixo shipping method type" +"Shipping speed (in hours)", "Shipping speed (in hours)" +"Default Shipping speed (in hours)", "Default Shipping speed (in hours)" +"kwixo_apres_reception_description", "" +"kwixo_payement_en_1_fois_description", "" +"paiement_en_plusiers_fois_description", "" +"DirectLink Maintenance API URL", "DirectLink Maintenance API URL" +"Allow zero amount authorization", "Allow zero amount authorization" +"Only if payment action is authorize.", "Only available if payment action is set to authorize." +"Submit extra parameters", "Submit extra parameters" +"Enable this if you wish to use fraud detection", "Enable this if you wish to use fraud detection" +"The data you have provided can not be processed by Ingenico ePayments", "The data you have provided can not be processed by Ingenico ePayments" +"Test url example: https://secure.ogone.com/ncol/prod/querydirect.asp
Production url example: https://secure.ogone.com/ncol/prod/querydirect.asp", "Test url example: https://secure.ogone.com/ncol/test/orderdirect.asp
Production url example: https://secure.ogone.com/ncol/prod/querydirect.asp" +"Refresh payment status", "Refresh payment status" +"Ingenico ePayments status successfully updated", "Ingenico ePayments status successfully updated" +"revert state update to it\'s original one because of Ingenico ePayments\'s state restriction", "revert state update to it\'s original one because of Ingenico ePayments\'s state restriction" +"This must be identical to the value in the Ingenico ePayments backend in section Technical information on tab Data and origin verification.","This must be identical to the value in the Ingenico ePayments backend in section Technical information on tab Data and origin verification." +"This must be identical to the value in the Ingenico ePayments backend in section Technical information on tab Transaction feedback.","This must be identical to the value in the Ingenico ePayments backend in section Technical information on tab Transaction feedback." +"Special user for API must be created in the Ingenico ePayments backend in section Users.","Special user for API must be created in the Ingenico ePayments backend in section Users." +"Password which has been set in the Ingenico ePayments backend for above API user.","Special user for API must be created in the Ingenico ePayments backend in section Users." +"This setting will override the configuration in the Ingenico ePayments backend in section Technical information on tab Global transaction parameters.","This setting will override the configuration in the Ingenico ePayments backend in section Technical information on tab Global transaction parameters." +"Only enable this if you wish to use Fraud Detection.","Only enable this if you wish to use Fraud Detection." +"This will log requests and responses in /var/log/ops.log","This will log requests and responses in /var/log/ops.log" +"Make sure the global Magento logging is also enabled.","Make sure the global Magento logging is also enabled." +"If set to Yes, customers will be redirected to the external Ingenico ePayments payment page.","If set to Yes, customers will be redirected to the external Ingenico ePayments payment page." +"For the selected card types, the customer can enter the credit card details directly in the checkout.","For the selected card types, the customer can enter the credit card details directly in the checkout." +"If the customer's credit card supports 3D Secure, he will be redirected to an external page to enter his password.","If the customer's credit card supports 3D Secure, he will be redirected to an external page to enter his password." +"This sets if this payment method should also be available for orders from the Magento backend.","This sets if this payment method should also be available for orders from the Magento backend." +"Make sure that gender and date of birth are required fields in the checkout. Otherwise this payment method will not be displayed.","Make sure that gender and date of birth are required fields in the checkout. Otherwise this payment method will not be displayed." +"By choosing this payment method the choice of the concrete payment method will be on Ingenico ePayments side.", "By choosing this payment method the choice of the concrete payment method will be on Ingenico ePayments side." +"Additional Ingenico ePayments payment methods", "Additional Ingenico ePayments payment methods" +"A generic payment method, that lets the customer choose the exact method on Ingenico ePayments side.", "A generic payment method, that lets the customer choose the exact method on Ingenico ePayments side." +"Depending on the selection the extension will communicate with the Ingenico ePayments test or production service. Choose custom, if you have to use special urls for the gateway.", "Depending on the selection the extension will communicate with the Ingenico ePayments test or production service. Choose custom, if you have to use special urls for the gateway." +"prod", "Production" +"test", "Test" +"custom", "Custom" +"Mode", "Environment" +"","" +"Resend payment information", "Resend payment information" +"Ingenico ePayments resend payment information","Ingenico ePayments resend payment information" +"Payment information has been resend to customer.", "Payment information has been resend to customer." +"Payment information could not be sent.", "Payment information could not be sent." +"Resend payment information identity", "Resend payment information identity" +"Sender identity. See 'Store Email Adresses'.", "Sender identity. See 'Store Email Adresses'." +"Resend payment information template", "Resend payment information template" +"The email template to use. Choose 'Default Template from Locale' to use Ingenico ePayments extensions default template.", "The email template to use. Choose 'Default Template from Locale' to use Ingenico ePayments extensions default template." +"This payment method always uses direct sale as payment action.","This payment method always uses direct sale as payment action." +"Allow payment method for discounted carts. Please note that not all aquirers support transmission of discount items.","Allow payment method for discounted carts. Please note that not all aquirers support transmission of discount items." +"Maximum length for this field is 35 characters.", "Maximum length for this field is 35 characters." +"Enable state restriction","Enable state restriction" +"If enabled the order status changes that differ from the expected status of the order for the Ingenico ePayments status will get reverted. This can cause conflicts if you use custom order statuses.","If enabled the order status changes that differ from the expected status of the order for the Ingenico ePayments status will get reverted. This can cause conflicts if you use custom order statuses." +"FAILURE_TOKEN_FRONTEND","Your payment data could not be saved by Ingenico ePayments. Please retry or select another payment method." +"SUCCESS_TOKEN_FRONTEND","Your payment data is ready to be processed by Ingenico ePayments. You can reset it or still select another payment method." +"SUCCESS_TOKEN","Your payment data is ready to be processed by Ingenico ePayments. You can reset it or still select another payment method." +"FAILURE_TOKEN","Your payment data could not be saved by Ingenico ePayments. Please retry or select another payment method." +"LOAD_TOKEN", "Please wait, while we load the Ingenico ePayments payment form." +"If this option is enabled, the customer's alias data will be saved in the shop. The data can then be used again later if the buyer has stored them permanently on Ingenico ePayments side.", "If this option is enabled, the customer's alias data will be saved in the shop. The data can then be used again later if the buyer has stored them permanently on Ingenico ePayments side." +"Ingenico ePayments - Redirect mode", "Ingenico ePayments - Redirect mode" +"Magento - internal shop template", "Magento - internal shop template" +"Ingenico ePayments - iFrame mode", "Ingenico ePayments - iFrame mode" +"Ingenico ePayments - Dynamic template","Ingenico ePayments - Dynamic template" +"Enter the TemplateRootId you received from Ingenico ePayments or the url to an external template.", "Enter the TemplateRootId you received from Ingenico ePayments or the url to an external template." +"For an easier PCI compliance we recommend redirect mode, iFrame mode or dynamic template with the template hosted on Ingenico ePayments servers. This does NOT affect the Hosted Tokenization iFrame (HTP).", "For an easier PCI compliance we recommend redirect mode, iFrame mode or dynamic template with the template hosted on Ingenico ePayments servers. This does NOT affect the Hosted Tokenization iFrame (HTP)." +"With this setting the customer will enter the payment details on a page in your shop that hosts the Ingenico ePayments paypage in an iFrame. You can style the paypage through the parameters below.","With this setting the customer will enter the payment details on a page in your shop that hosts the Ingenico ePayments paypage in an iFrame. You can style the paypage through the parameters below." +"With this setting the customer will be redirected to the Ingenico ePayments paypage with the look and feel of your shop.
The template used can be seen here:
", "With this setting the customer will be redirected to the Ingenico ePayments paypage with the look and feel of your shop.
The template used can be seen here:
" +"With this setting the customer will be redirected to the Ingenico ePayments paypage. The look and feel of that page will be defined by a dynamically loaded template file that you can define below.","With this setting the customer will be redirected to the Ingenico ePayments paypage. The look and feel of that page will be defined by a dynamically loaded template file that you can define below." +"With this setting the customer will get redirected to Ingenico ePayments to enter his payment details. You can style the page through the parameters below.","With this setting the customer will get redirected to Ingenico ePayments to enter his payment details. You can style the page through the parameters below." +"Ingenico ePayments Subscription Manager via Cc", "Ingenico ePayments Subscription Manager via Cc" +"","" +"This payment method is used to handle subscriptions via Ingenico ePayments through Magentos recurring profiles. It is only available for orders containing only nominal items.", "This payment method is used to handle subscriptions via Ingenico ePayments through Magento's Recurring Profiles. It is only available for orders containing suitable (nominal) items." +"A subscription will be created. This will charge you %s every %s %s.", " A subscription will be created. This will charge you %s every %s %s." +" The subscription will end on %s.", " The subscription will end on %s." +"A trial subscription will be created. This will charge you %s every %s %s until %s.", "A trial subscription will be created. This will charge you %s every %s %s until %s." +"To cancel the subscription, please send an email to the shop owner or request this by clicking the suspend button on the subscriptions detail view in your customer account. A link to that page will be displayed on the checkout success page.", "To cancel the subscription, please send an email to the shop owner or request this by clicking the suspend button on the subscriptions detail view in your customer account. A link to that page will be displayed on the checkout success page." +"You will be charged an initial amount of %s.", "You will be charged an initial amount of %s." +"Are you sure you want to perform this action? Canceling the subscription here will not actually cancel the subscription on Ingenico ePayments side. To stop charging the customer you will have to deactivate the subscription there.", "Are you sure you want to perform this action? This will only set the status in Magento. To stop charging the customer you will have to deactivate the subscription on the Ingenico ePayments side." +"Are you sure you want to perform this action? Suspending the subscription here will not actually cancel the subscription on Ingenico ePayments side. To stop charging the customer you will have to deactivate the subscription there.", "Are you sure you want to perform this action? This will only set the status in Magento. To suspend charging the customer, you will have to suspend the subscription on the Ingenico ePayments side." +"Billing day for weekly subscriptions", "Billing day for weekly subscriptions" +"Determines the day of the week when a weekly subscription gets triggered.","Determines the day of the week when a weekly subscription gets triggered." +"Billing day for monthly subscriptions", "Billing day for monthly subscriptions" +"Determines the day of the month when a monthly subscription gets triggered.", "Determines the day of the month when a monthly subscription gets triggered." +"Store contact responsible for subscription handling", "Store contact responsible for subscription handling" +"This contact will receive the suspend/cancel requests for the subscriptions. See 'Store Email Adresses'.", "This contact will receive the suspend/cancel requests for the subscriptions. See 'Store Email Addresses'." +"E-Mail template for suspending/canceling a subscription", "E-Mail template for suspending/canceling a subscription" +"This e-mail template is used for the automatically generated e-mail that is sent to the corresponding contact, if a customer wants to cancel a subscription.", "This e-mail template is used for the automatically generated e-mail which is sent to the corresponding contact, if a customer wants to cancel a subscription." +"Ingenico ePayments suspend subscription request", "Ingenico ePayments suspend subscription request" +"You are not allowed to suspend this subscription!", "You are not allowed to suspend this subscription!" +"Your suspend request was successfully sent. A copy of the email will be sent to your address.", "Your suspend request was successfully sent. A copy of the email will be sent to your address." +"Could not send suspend mail, please try again or contact our support directly.","Could not send suspend mail, please try again or contact our support directly." +"Automatic activation not possible. Please contact our support team.", "Automatic activation not possible. Please contact our support team." +"To actually suspend the subscription an update in the Ingenico ePayments backend is needed.","To actually suspend the subscription an update in the Ingenico ePayments backend is needed." +"To actually cancel the subscription an update in the Ingenico ePayments backend is needed.","To actually cancel the subscription an update in the Ingenico ePayments backend is needed." +"To actually activate the subscription an update in the Ingenico ePayments backend is needed.", "To actually activate the subscription an update in the Ingenico ePayments backend is needed." +"When using %s as payment method the settings for '%s' and '%s' will not be processed.", "When using %s as payment method the settings for '%s' and '%s' will not be processed." +"Enable device fingerprinting","Enable device fingerprinting" +"When enabling this, you still need to request the explicit consent of the customer in the checkout to transmit this data.","When enabling this, you still need to request the explicit consent of the customer in the checkout to transmit this data." +"This functionality will transmit specific customer data as hash to Ingenico ePayments to identify the device from which the transaction was sent can be identified with the highest probability.","This functionality will transmit specific customer data as hash to Ingenico ePayments to identify the device from which the transaction was sent can be identified with the highest probability." +"Enter new credit card data...", "Enter new credit card data..." +"Please enter a valid credit card number.","Please enter a valid credit card number." +"This is a required field.","This is a required field." +"Timeout for transactions","Timeout for transactions" +"This must be identical to the value in the Ingenico ePayments backend in section Technical information on tab Global security parameters.","This must be identical to the value in the Ingenico ePayments backend in section Technical information on tab Global security parameters." +"Hash algorithm", "Hash algorithm" +"Order has been canceled permanently in Magento. Changes in Ingenico ePayments platform will no longer be considered.","Order has been canceled permanently in Magento. Changes in Ingenico ePayments platform will no longer be considered." +"The order has been accepted in Magento only. For the actual status of the payment check the Ingenico ePayments backend.", "The order has been accepted in Magento only. For the actual status of the payment check the Ingenico ePayments backend." +"Received Ingenico ePayments feedback status %s.", "Received Ingenico ePayments feedback status %s." +"Customer received your payment instructions, waiting for actual payment.","Customer received your payment instructions, waiting for actual payment." +"Customer redirected for 3DS authorization.", "Customer redirected for 3DS authorization." +"Ingenico ePayments status 0, the action failed.", "Ingenico ePayments status 0, the action failed." +"Payment failed because the authorization was declined! Please choose another payment method.", "Payment failed because the authorization was declined! Please choose another payment method." +"Received Ingenico ePayments feedback status update with final status %s.", "Received Ingenico ePayments feedback status update with final status %s." +"Received Ingenico ePayments feedback status update with suspected fraud status %s. Please have a look in Ingenico ePayments backend for more information.", "Received Ingenico ePayments feedback status update with suspected fraud status %s. Please have a look in Ingenico ePayments backend for more information." +"Received Ingenico ePayments feedback status update with intermediate status %s.", "Received Ingenico ePayments feedback status update with intermediate status %s." +"Received Ingenico ePayments feedback status update with refused status %s.", "Received Ingenico ePayments feedback status update with refused status %s." +"Payment has been authorized by Ingenico ePayments, but not yet captured.", "Payment has been authorized by Ingenico ePayments, but not yet captured." +"%s is not a authorize status!","%s is not a authorize status!" +"%s is not a capture status!","%s is not a capture status!" +"%s is not a void status!","%s is not a void status!" +"%s is not a refund status!","%s is not a refund status!" +"%s is not a special status!","%s is not a special status!" +"Status %s can not be voided.", "Status %s can not be voided." +"No update available from Ingenico ePayments.","No update available from Ingenico ePayments." +"Received Ingenico ePayments status %s. Order cancelled.", "Received Ingenico ePayments status %s. Order cancelled." +"If activated an extra parameter is transmitted to Ingenico ePayments for the Creditcard/Debitcard payment methods.","If activated an extra parameter is transmitted to Ingenico ePayments for the Creditcard/Debitcard payment methods." +"See also Ingenico ePayments documentation.", "See also Ingenico ePayments documentation." +"Refund refused by Ingenico ePayments.", "Refund refused by Ingenico ePayments." +"Add Method", "Add Method" +"PM and Brand combination must be unique","PM and Brand combination must be unique" +"Can not save empty title or PM fields", "Can not save empty title or PM fields" +"A generic option, that lets the customer choose the exact method on Ingenico ePayments side.", "A generic option, that lets the customer choose the exact method on Ingenico ePayments side." +"Add one or more methods. In field 'PM' you will have to enter the PM value you got from Ingenico ePayments, same goes for the field 'BRAND'.", "Add one or more methods. In field 'PM' you will have to enter the PM value you got from Ingenico ePayments, same goes for the field 'BRAND'." +"Methods", "Methods" +"Show empty default option", "Show empty default option" +"Ingenico ePayments Flex payment method", "Ingenico ePayments Flex payment method" +"Title to display for the default option.", "Title to display for the default option." +"Default option title", "Default option title" +"Extension Version", "Extension Version" +"Documentation", "Documentation" +"English", "English" +"German", "German" +"First Aid - FAQ", "First Aid - FAQ" +"Need support? Contact Ingenico ePayments:", "Need support? Contact Ingenico ePayments:" +"Download Logfile", "Download Logfile" +"Show catalog button", "Show catalog button" +"Show home button", "Show home button" +"Credit/Debit card Split", "Credit/Debit card Split" +"Payment method", "Payment method" +"Payment from Applicable Countries","Payment from Applicable Countries" +"Payment from Specific Countries","Payment from Specific Countries" +"All Allowed Countries","All Allowed Countries" +"Specific Countries","Specific Countries" +"Delete Image","Delete Image" +"Allowed file types: jpeg, gif, png.","Allowed file types: jpeg, gif, png." +"Payment Logo","Payment Logo" +"Payment Logo Visibility","Payment Logo Visibility" +"Netresearch_OPS::Left","Left" +"Netresearch_OPS::Right","Right" +"Netresearch_OPS::Non Visible","Non Visible" +"Disallowed file type.","Disallowed file type." +"Payment Information","Payment Information" +"Submit","Submit" +"Cancel","Cancel" +"Transmit new payment method...","Transmit new payment method..." +"Your given data","Your given data" +"Order Number","Order Number" +"Billing Address","Billing Address" +"Shipping Address","Shipping Address" +"Aliasusage New","Alias Label New" +"Here you can enter the desired text which will be displayed on Ingenico ePayments side.","Here you can enter the desired text which will be displayed on Ingenico ePayments side." +"Aliasusage existing Alias","Aliasusage existing Alias" +"Here you can enter the desired text which is used for existing alias, the text will be displayed on Ingenico ePayments side.","Here you can enter the desired text which is used for existing alias, the text will be displayed on Ingenico ePayments side." +"This value specifies the request timeout for that transaction. A value between 30 and 60 seconds is possible.", "This value specifies the request timeout for that transaction. A value between 30 and 60 seconds is possible." +"Layout on the external payment page (for redirect payment methods).", "Layout on the external payment page (for redirect payment methods)." +"Send Invoice to Customer", "Send Invoice to Customer" +"Value exceeds %d characters","Value exceeds %d characters" +"Your order has been registered, but your payment is still marked as pending. Please have patience until the final status is known.","Your order has been registered, but your payment is still marked as pending. Please have patience until the final status is known." +"You will be redirected to finalize your payment.","You will be redirected to finalize your payment." +"When using Klarna as your aquirer you will need to switch to the option 'Other'.","When using Klarna as your aquirer you will need to switch to the option 'Other'." +"When the option 'Other' is selected the request data will get converted to ASCII chararcters by using the appropriate transliterations for the given locale of the store.","When the option 'Other' is selected the request data will get converted to ASCII chararcters by using the appropriate transliterations for the given locale of the store." +"Allows you to display a configurable link to your aquirers invoicing terms and conditions.","Allows you to display a configurable link to your aquirers invoicing terms and conditions." +"Show invoice terms link","Show invoice terms link" +"Invoice terms title","Invoice terms title" +"Invoice terms URL","Invoice terms URL" +"Request Data Encoding", "Request Data Encoding" +"Pay now","Pay now" +"Enter new bank data...","Enter new bank data..." +"Other","Other" \ No newline at end of file diff --git a/app/locale/en_US/template/email/ops/ops_email_resend_payment_info.html b/app/locale/en_US/template/email/ops/ops_email_resend_payment_info.html new file mode 100644 index 0000000..7f6e720 --- /dev/null +++ b/app/locale/en_US/template/email/ops/ops_email_resend_payment_info.html @@ -0,0 +1,49 @@ + + + + + + + +
+ + + + +
+ + + + + + + + + + + + +
{{var logo_alt}}
+

Dear {{htmlescape var=$order.getCustomerName()}},

+ +

we could not yet register a payment transaction to your order. You can review the payment informations to your order here.

+

Should you already have ordered the transaction, please consider this e-mail as irrelevant.

+

+ If you have any questions, please feel free to contact us at + {{config path='trans_email/ident_support/email'}} + or by phone at {{config path='general/store_information/phone'}}. +

+

Haben Sie nochmals recht herzlichen Dank, {{var store.getFrontendName()}}

+
+
+ \ No newline at end of file diff --git a/app/locale/en_US/template/email/ops/ops_pay_per_mail_info.html b/app/locale/en_US/template/email/ops/ops_pay_per_mail_info.html new file mode 100644 index 0000000..68f1179 --- /dev/null +++ b/app/locale/en_US/template/email/ops/ops_pay_per_mail_info.html @@ -0,0 +1,49 @@ + + + + + + + +
+ + + + +
+ + + + + + + + + + + + +
{{var logo_alt}}
+

Dear {{htmlescape var=$order.getCustomerName()}},

+ +

under following link you can pay for your order.

+

Should you already have ordered the transaction, please consider this e-mail as irrelevant.

+

+ If you have any questions, please feel free to contact us at + {{config path='trans_email/ident_support/email'}} + or by phone at {{config path='general/store_information/phone'}}. +

+

Haben Sie nochmals recht herzlichen Dank, {{var store.getFrontendName()}}

+
+
+ \ No newline at end of file diff --git a/app/locale/en_US/template/email/ops/suspend_subscription.html b/app/locale/en_US/template/email/ops/suspend_subscription.html new file mode 100644 index 0000000..46b054b --- /dev/null +++ b/app/locale/en_US/template/email/ops/suspend_subscription.html @@ -0,0 +1,43 @@ + + + + + + + +
+ + + + +
+ + + + + + + + + + + + +
{{var logo_alt}}
+

Dear {{var store.getFrontendName()}}-Team,

+ +

as your customer I, {{htmlescape var=$customer.getName()}}, request you to cancel the subscription {{var profile.getReferenceId()}} {{if profile.getTrialPeriodUnit()}}and any corresponding trial subscription{{/if}}.

+

Should you already have processed my request, please update the status of the subscription in your shop, so it is visible to me.

+

Thank you very much and best regards, {{var customer.getName()}}

+
+
+ \ No newline at end of file diff --git a/app/locale/fr_FR/Netresearch_OPS.csv b/app/locale/fr_FR/Netresearch_OPS.csv new file mode 100644 index 0000000..14db09a --- /dev/null +++ b/app/locale/fr_FR/Netresearch_OPS.csv @@ -0,0 +1,189 @@ +"STATUS_0", "Incomplet ou invalide" +"STATUS_1", "Annulé par client" +"STATUS_2", "Autorisation refusée" +"STATUS_4", "Order stored" +"STATUS_40", "Stored waiting external result" +"STATUS_41", "Attente paiement par client" +"STATUS_46", "Waiting for identification)" +"STATUS_5", "Autorisé" +"STATUS_50", "Autorisé en attente de résultat externe" +"STATUS_51", "Autorisation en attente" +"STATUS_52", "Autorisation incertaine" +"STATUS_55", "En suspens" +"STATUS_56", "OK avec paiements planifiés" +"STATUS_57", "Erreur dans les paiements planifiés" +"STATUS_59", "Autorisation à obtenir manuellement" +"STATUS_6", "Autorisé et annulé" +"STATUS_61", "Annulation d'autorisation en attente" +"STATUS_62", "Annulation d'autorisation incertaine" +"STATUS_63", "Annulation d'autorisation refusée" +"STATUS_64", "Autorisé et annulé" +"STATUS_7", "Paiement annulé" +"STATUS_71", "Annulation paiement en attente" +"STATUS_72", "Annulation paiement incertaine" +"STATUS_73", "Annulation paiement refusée" +"STATUS_74", "Paiement annulé" +"STATUS_75", "Annulation traitée par le marchand" +"STATUS_8", "Remboursement" +"STATUS_81", "Refund pending" +"STATUS_82", "Refund uncertain" +"STATUS_83", "Refund refused" +"STATUS_84", "Payment declined by the acquirer" +"STATUS_85", "Refund processed by merchant" +"STATUS_9", "Paiement demandé" +"STATUS_91", "Paiement en cours" +"STATUS_92", "Paiement incertain" +"STATUS_93", "Paiement refusé" +"STATUS_94", "Remboursement refusé par l'acquéreur" +"STATUS_95", "Paiement traité par le marchand" +"STATUS_99", "En cours de traitement" +"Hash is not valid", "Hash n'est pas valable" +"Order is not valid", "La commande n'est pas valable" +"Process outgoing transaction", "Traitement de la transaction en cours." +"Start Ingenico ePayments processing", "Démarrage /Démarrer le traitement Ingenico ePayments" +"Your payment information was declined. Please select another payment method.", "Vos données de paiement ont été refusées. Veuillez choisir une autre méthode de paiement." +"Order canceled on Ingenico ePayments side. Status: %s, Payment ID: %s.", "Commande annulée par Ingenico ePayments. Statut: %s, Payment ID: %s." +"Account number must contain numbers only.", "Le numéro de compte ne doit contenir que des nombres." +"Bank code must contain numbers only.", "Le code bancaire ne doit contenir que des nombres." +"Account Holder must be filled in.", "Le titulaire du compte doit être renseigné." +"Capture process complete", "Demande de capture complétée." +"The capture has already been invoiced.", "Le paiement a déjà été facturé." +"Cannot create an invoice without products.", "Une facture ne peut être créée sans produits." +"Partial capture process complete", "Demande de capture partielle complétée." +"Invoice '%s' was created automatically. Ingenico ePayments Status: %s.", "La facture '%s' a été créée automatiquement. Statut Ingenico ePayments: %s." +"Start Ingenico ePayments refund request", "Démarrer la demande de remboursement Ingenico ePayments" +"Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments Status: %s.", "Une note de crédit sera créée automatiquement dès réception de la confirmation de l'Ingenico ePayments. Statu de l'Ingenico ePayments: %s." +"Creditmemo '%s' was created automatically. Ingenico ePayments Status: %s.", "La note de crédit '%s'a été créée automatiquement. Statut Ingenico ePayments: %s." +"Could not perform actions for Ingenico ePayments status: %s.", "La transaction n'a pu être effectuée pour Ingenico ePayments statut: %s." +"Refund is waiting or uncertain. Ingenico ePayments status: %s.", "Remboursement en attente ou incertain. Statut Ingenico ePayments: %s." +"Refund was refused. Automatic creation failed. Ingenico ePayments status: %s.", "Le remboursement a échoué.La création automatique a échoué. Statut Ingenico ePayments : %s." +"Capture is waiting or uncertain. Ingenico ePayments status: %s.", "La capture de paiement est en attente ou incertaine. Statut Ingenico ePayments: %s." +"Capture was refused. Automatic creation failed. Ingenico ePayments status: %s.", "La capture de paiement a échoué.La création automatique a échoué. Statut Ingenico ePayments : %s." +"Void is waiting or uncertain. Ingenico ePayments status: %s.", "L'annulation est en attente ou incertain. Statut Ingenico ePayments: %s." +"Void was refused. Automatic creation failed. Ingenico ePayments status: %s.", "L'annulation est refusée. Statut Ingenico ePayments: %s." +"Authorization status changed. Current Ingenico ePayments status is: %s.", "Le statut de l'autorisation est modifié. Le statut actuel de l'Ingenico ePayments status est: %s." +"Unknown Ingenico ePayments status: %s.", "Statut Ingenico ePayments inconnu: %s." +"Error, transaction count is %s instead of 1 for the Payid '%s', order '%s' and Operation '%s'.", "Erreur, la transaction est %s au lieu de 1 pour le Payid '%s', Commande '%s' et Opération '%s'." +"Order can not be saved.", "La commande n'a pas pu être enregistrée." +"Order can not be canceled for system reason.", "La commande n'a pas pu être annulée pour cause d'une erreur système." +"Order declined on ops side. Ingenico ePayments status: %s, Payment ID: %s.", "Commande refusée par l'Ingenico ePayments. Statut Ingenico ePayments: %s, Payment ID: %s." +"Order can not be saved for system reason.", "La commande n'a pas pu être enregistrée pour cause d'une erreur système." +"An unknown exception occured.", "An unknown exception occured." +"An unknown exception was thrown in the payment process. Ingenico ePayments status: %s.", "An unknown exception was thrown in the payment process. Ingenico ePayments status: %s." +"Waiting for the payment of the customer", "En attente du paiement du client" +"Authorization waiting from Ingenico ePayments", "Autorisation en attente de Ingenico ePayments" +"Processed by Ingenico ePayments", "Traité par Ingenico ePayments" +"Waiting for payment. Ingenico ePayments status: %s.", "En attente de paiement. Statut Ingenico ePayments: %s." +"Authorization waiting. Ingenico ePayments status: %s.", "Autorisation en attente. Statut Ingenico ePayments: %s." +"Authorization uncertain. Ingenico ePayments status: %s.", "Authorization uncertain. Ingenico ePayments status: %s." +"Processed by Ingenico ePayments. Payment ID: %s. Ingenico ePayments status: %s.", "Traité par Ingenico ePayments. Payment ID: %s. Statut Ingenico ePayments : %s." +"Ingenico ePayments server is temporarily not available, please try again later.", "Le serveur Ingenico ePayments est temporairement inaccessible,veuillez réessayer plus tard." +"An error occured during the Ingenico ePayments request. Your action could not be executed. Message: '%s'.", "Une erreur est survenue pendant la demande vers Ingenico ePayments. Votre action n'a pu être exectuée. Message: '%s'." +"You already sent a capture request. Please wait until the capture request is acknowledged.", "Une demande de paiement a déjà été envoyée.Veuillez attendre l'accusé de réception de la demande." +"There is one void request waiting. Please wait until this request is acknowledged.", "Il y a une demande d'annulation en attente. Veuillez attendre l'accusé de réception de la demande." +"Start Ingenico ePayments %s capture request", "Démarrer la demande de capture Ingenico ePayments %s" +"Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments status: %s.", "Une facture va être créée automatiquement dès réception de l'accusé de réception. Statut Ingenico ePayments: %s." +"The Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "La facture va être créée automatiquement dès réception de l'accusé de réception. Statut Ingenico ePayments: %s." +"The Invoice was not created. Ingenico ePayments status: %s.", "La facture n'a pas pu être créée. Statut Ingenico ePayments : %s." +"The CreditMemo was not created. Ingenico ePayments status: %s.", "La note de crédit n'a pas pu être créée. Statut Ingenico ePayments : %s." +"The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "La facture va être créée automatiquement dès réception de l'accusé de réception de Ingenico ePayments." +"There is already one creditmemo in the queue. The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "Il y a une demande de note de crédit en attente. Veuillez attendre l'accusé de réception de l'annulation" +"Please use void to cancel the operation.", "Veuillez utiliser annulation pour annuler cette opération." +"You already sent a void request. Please wait until the void request will be acknowledged.", "Il y a une demande de note de crédit en attente. Veuillez attendre l'accusé de réception de l'annulation." +"There is one capture request waiting. Please wait until this request is acknowledged.", "Il y a une demande de capture de paiement en attente. Veuillez attendre l'accusé de réception de l'annulation." +"The void request is sent. Please wait until the void request will be accepted.", "Une demande d'annulation est envoyée. Merci de patienter jusqu'à validation de la demande d'annulation." +"Void order succeed. Ingenico ePayments status: %s.", "Annulation de commande réussie. Statut Ingenico ePayments : %s." +"Void order failed. Ingenico ePayments status: %s.", "L'annulation de la commande a échoué.Statut Ingenico ePayments : %s." +"Authorization", "Autorisation" +"Direct Sale", "Vente directe" +"Horizontally grouped logo with group name on left", "Logos regroupés horizontalement avec nom du groupe sur la gauche." +"Horizontally grouped logo with no group name", "Logos regroupés horizontalement sans nom de groupe." +"Verical list", "Liste Verticale" +"You will be redirected to the payment page after the order confirmation.", "Vous allez être redirigé vers la page de paiement après confirmation de la commande." +"Payment failed. Please select another payment method.", "Le paiement a échoué. Veuillez choisir une autre méthode de paiement." +"Payment failed. Please review your input or select another payment method.", "Le paiement a échoué. Veuillez revoir les données saisies ou choisir une autre méthode de paiement." +"Incorrect credit card expiration date.", "La date d\'expiration de la carte de crédit est incorrecte." +"Credit Card Type", "Type de carte de Crédit" +"Name on Card", "Nom figurant sur la carte" +"Credit Card Number", "Numéro de Carte de Crédit" +"Expiration Date", "Date d'expiration" +"Account holder", "Titulaire du compte" +"Account number", "Numéro de compte" +"Bank code", "Code bancaire" +"BCMC", "Bancontact/Mister Cash" +"You will be redirected to Ingenico ePayments website when you place an order.", "Vous allez être redirigé( e) vers le site Ingenico ePayments lors de la commande." +"Please wait a moment. This page will transfer your data to Ingenico ePayments payment gateway in a few seconds.", "Veuillez patienter, vous serez dirigé(e) au portail de paiement sécurisé Ingenico ePayments dans quelques instants" +"PSPID is a case sensitive field", "Le PSPID est sensible à la case" +"SHA-IN Pass phrase", "clé SHA-IN" +"Refer to Ingenico ePayments's backend in section Technical Information on tab Data and origin verification.", "Référez-vous au backend Ingenico ePayments dans la section Information technique onglet Contrôle de données et d'origine" +"SHA-OUT Pass phrase", "clé SHA-OUT" +"Refer to Ingenico ePayments's backend in section Technical Information on tab Transaction feedback.", "XXXXX" +"Test url example: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
Production url example: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp", "Expemple url pour test: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
Expemple url pour production: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp" +"Test url example: https://secure.ogone.com/ncol/test/maintenancedirect.asp
Production url example: https://secure.ogone.com/ncol/prod/maintenancedirect.asp", "Expemple url pour test: https://secure.ogone.com/ncol/test/maintenancedirect.asp
Expemple url pour production: https://secure.ogone.com/ncol/prod/maintenancedirect.asp" +"Test url example: https://secure.ogone.com/ncol/test/orderdirect.asp
Production url example: https://secure.ogone.com/ncol/prod/orderdirect.asp", "Exemple url pour test: https://secure.ogone.com/ncol/test/orderdirect.asp
Exemple url pour production: https://secure.ogone.com/ncol/prod/orderdirect.asp" +"API User", "Utilisateur API" +"API Password", "Mot de passe de l'API" +"Payment Action", "Action du paiement" +"If you choose 'Ingenico ePayments', you will need to fill in all the following fields.", "Si vous choisissez 'Ingenico ePayments', vous devrez compléter les champs suivants" +"Payment Template", "Template de Paiement" +"Title of the Ingenico ePayments Template", "Titre du template Ingenico ePayments" +"Background Color of Ingenico ePayments Template", "Couloeur de fond du template Ingenico ePayments" +"Text Color of the Ingenico ePayments Template", "Couleur du texte du template Ingenico ePayments" +"Table Background Color of the Ingenico ePayments Template", "Couleur du tableau du template Ingenico ePayments" +"Table Text Color of the Ingenico ePayments Template", "Couleur du texte du tableau du template Ingenico ePayments" +"Button Background Color of the Ingenico ePayments Template", "Couleur du bouton de fond du template Ingenico ePayments" +"Button Text Color of the Ingenico ePayments Template", "Couleur du texte du bouton du template Ingenico ePayments" +"Font Family of the Ingenico ePayments Template", "________" +"Logo of the Ingenico ePayments Template", "Logo du Template Ingenico ePayments" +"Layout of Payment Methods", "Mise en page des Méthodes de Paiement" +"e.g: white", "ex : blanc" +"e.g: black", "ex : noir" +"e.g: Verdana", "ex : Verdana" +"Debug", "Débug" +"Ingenico ePayments Account", "Compte Ingenico ePayments" +"URL/filename of the logo. The URL must be absolute and stored on a secure server.", "URL/nom du lien du logo. L'URL doit être absolue et sauvegardée hébergée sur un serveur sécurisé." +"Countries", "Pays" +"Enabled Card Types", "Types de cartes" +"Enable 3D secure", "Activer le 3D secure" +"SCORING_DESCRIPTION_AAVCHECK", "Résultat de la vérification automatique d’une adresse. Actuellement, cette vérification est seulement disponible pour la carte American Express. Valeurs possibles :
KO: L’adresse a été envoyée mais l’acquéreur a fourni une réponse négative à la vérification de l’adresse, c’est-à-dire que l’adresse est incorrecte.
OK:
L’adresse a été envoyée et l’acquéreur a fourni une réponse positive à la vérification de l’adresse, c’est-à-dire que l’adresse est correcte.
NO: Tous les autres cas. Par exemple, si aucune adresse n’est transmise" +"SCORING_DESCRIPTION_CVCCHECK", "Résultat de la vérification de la carte. Valeurs possibles :
KO: Le CVC a été envoyé mais l’acquéreur a fourni une réponse négative à la vérification CVC, c’est-à-dire que le CVC est incorrect.
OK:
1. Le CVC a été envoyé et l’acquéreur a fourni une réponse positive à la vérification CVC, c’est-à-dire que le CVC est correct. OU
2. L’acquéreur a fourni un code d’autorisation, mais il n’a pas renvoyé le résultat spécifique pour la vérification CVC.

NO: Tous les autres cas. Par exemple, si aucun CVC n’est transmis" +"SCORING_DESCRIPTION_CCCTY", "Pays d’origine de la carte de crédit.
Format: 2 caractères alphabétiques de code ISO" +"SCORING_DESCRIPTION_IPCTY", "Pays d’origine de l’adresse IP.
Format: 2 caractères alphabétiques de code ISO. Si ce paramètre n’est pas disponible, «99» sera reporté dans la réponse." +"SCORING_DESCRIPTION_NBREMAILUSAGE", "Nombre de fois qu’une adresse e-mail a été utilisée sur une période définie" +"SCORING_DESCRIPTION_NBRIPUSAGE", "Nombre de fois qu’une adresse IP a été utilisée pour des transactions réussies sur une période définie" +"SCORING_DESCRIPTION_NBRIPUSAGE_ALLTX", "Nombre de fois qu’une adresse IP a été utilisée pour des transactions réussies et/ou refusées sur une période définie" +"SCORING_DESCRIPTION_NBRUSAGE", "Nombre de fois qu’une carte de crédit a été utilisée sur une période définie" +"SCORING_DESCRIPTION_VC", "Carte virtuelle. Valeurs possibles :
ECB: Pour E Carte Bleue
ICN: Pour Internet City Number
NO: Tous les autres cas. Par exemple, la carte n’est pas une carte virtuelle" +"SCORING_DESCRIPTION_CARDNO", "Numéro de la carte" +"SCORING_DESCRIPTION_ED", "Date d'expiration" +"SCORING_DESCRIPTION_CN", "Titulaire de la carte" +"Card Verification Number", "Code de vérification de la Carte" +"kwixo_apres_reception_description", "

Grâce au service Paiement après réception, vous receves et vérifiez votre commande AVANT de la payer. Pas besoin d'être inscrit au préalable. Kwixo, une solution de paieent sécurisée, choisie par le Crédit Agricole et LCL. En savoir plus

" +"kwixo_payement_en_1_fois_description", "

C'est simple, pratique & sécurisé. Pas besoin d'être inscrit au préalable
Kwixo, une solution de paieent sécurisée, choisie par le Crédit Agricole et LCL. En savoir plus

" +"paiement_en_plusiers_fois_description", "

Etalez vos paiements jusqu'à 4000 euros.

  • - en 3 fois sans frais à patrir de 300 euros d'achat ;
  • - en 5, 10 mois à partir de 150 euros d'achat ;
  • - en 20 mois à partir de 300 euros d'achat.

Le paiement en plusieurs fois nécessite l'overture d'un crédit renouvelable auprès de notre partenaire Sofinco, avec une résponse de principe en tems réel. En savoir plus
+Un crédit vous engage et doit être remboursé. Vérifiez vos capacités de remboursement avant de vous engager.

" +"Payment from Applicable Countries","Paiement des Pays applicables" +"Payment from Specific Countries","Paiement en provenance des pays spécifiques" +"All Allowed Countries","Tous les pays autorisés" +"Specific Countries","pays spécifiques" +"Delete Image","Supprimer l'image" +"Allowed file types: jpeg, gif, png.","Types de fichiers autorisés: jpeg, gif, png" +"Payment Logo","Logo de paiement" +"Payment Logo Visibility","Logo de paiement Visibilité" +"Netresearch_OPS::Left","Gauche" +"Netresearch_OPS::Right","Droite" +"Netresearch_OPS::Non Visible","Non visibles" +"Disallowed file type.","Rejeté filetype." +"Payment Information","Conditions de vente" +"Submit","Soumettre" +"Cancel","Annuler" +"Transmit new payment method...","ransfert Nouvelle méthode de paiement ..." +"Your given data","Vos données donné" +"Order Number","Numéro de commande" +"Billing Address","Adresse de facturation" +"Shipping Address","Adresse de livraison" +"Aliasusage New","Aliasusage Nouveau" +"Here you can enter the desired text which will be displayed on Ingenico ePayments side.","Ici, vous pouvez saisir le texte souhaité qui sera affiché sur le côté Ingenico ePayments." +"Aliasusage existing Alias","Aliasusage Alias existant" +"Here you can enter the desired text which is used for existing alias, the text will be displayed on Ingenico ePayments side.","Ici, vous pouvez saisir le texte souhaité qui est utilisé pour alias existant, le texte sera affiché sur le côté de Ingenico ePayments." +"Pay now", "Payer" diff --git a/app/locale/it_IT/Netresearch_OPS.csv b/app/locale/it_IT/Netresearch_OPS.csv new file mode 100644 index 0000000..c550ec8 --- /dev/null +++ b/app/locale/it_IT/Netresearch_OPS.csv @@ -0,0 +1,185 @@ +"STATUS_0", "Incompleto o invalido" +"STATUS_1", "Cancellato dal cliente" +"STATUS_2", "Autorizzazione rifiutata" +"STATUS_4", "Ordine salvato" +"STATUS_40", "Salvato, in attesa del risultato estreno" +"STATUS_41", "In attesa del pagamento del cliente" +"STATUS_46", "Waiting (for identification)" +"STATUS_5", "Autorizzato" +"STATUS_50", "Autorizzato, in attesa del risultato estreno" +"STATUS_51", "Autorizzazione in attesa" +"STATUS_52", "Autorizzazione incerta" +"STATUS_55", "Stand-by" +"STATUS_56", "OK con in pagamenti pianificati" +"STATUS_57", "Errore nei pagamenti pianificati" +"STATUS_59", "Autorizzazione da ricevere manualmente" +"STATUS_6", "Autorizzato e cancellato" +"STATUS_61", "Storno dell'autorizzazione in attesa" +"STATUS_62", "Storno dell'autorizzazione incerto" +"STATUS_63", "Storno dell'autorizzazione rifiutato" +"STATUS_64", "Autorizzato e cancellato" +"STATUS_7", "Pagamento cancellato" +"STATUS_71", "Storno del Pagamento in attesa" +"STATUS_72", "Storno del Pagamento incerto" +"STATUS_73", "Storno del Pagamento rifiutato" +"STATUS_74", "Pagamento cancellato" +"STATUS_75", "Storno del Pagamento da parte del mercante" +"STATUS_8", "Rimborso" +"STATUS_81", "Rimborso in attesa" +"STATUS_82", "Rimborso incerto" +"STATUS_83", "Rimborso rifiutato" +"STATUS_84", "Pagamento rifiutato dall'acquirente" +"STATUS_85", "Rimborso processato dal mercante" +"STATUS_9", "Pagamento richiesto" +"STATUS_91", "Pagamento in processo" +"STATUS_92", "Pagamento incerto" +"STATUS_93", "Pagamento rifiutato" +"STATUS_94", "Pagamento rifiutato dall'acquirente" +"STATUS_95", "Pagamento effettuato dal mercante" +"STATUS_99", "In processo" +"Hash is not valid", "Hash non valido" +"Order is not valid", "Ordine non valido" +"Process outgoing transaction", "Processazione della transazioni in uscita" +"Start Ingenico ePayments processing", "Inizio processazione Ingenico ePayments" +"Your payment information was declined. Please select another payment method.", "I Vostri dati di pagammento sono stati rifiutati. Si prega di selezionare un altro metodo di pagamento." +"Order canceled on Ingenico ePayments side. Status: %s, Payment ID: %s.", "Ordine cancellato sul Ingenico ePayments. Status: %s, Payment ID: %s." +"Account number must contain numbers only.", "Il numero di conto deve contenere unicamente numeri." +"Bank code must contain numbers only.", "Il numero di conto deve contenere unicamente numeri." +"Account Holder must be filled in.", "Il campo intestatario deve essere compilato." +"Capture process complete", "Processo di accredito eseguito" +"The capture has already been invoiced.", "L'accredito é stato già eseguito." +"Cannot create an invoice without products.", "No é possibile creare un fattura senza prodotti." +"Partial capture process complete", "Processo dell'accredito parziale eseguito" +"Invoice '%s' was created automatically. Ingenico ePayments Status: %s.", "La fattura '%s' é stata creata automaticamente. Ingenico ePayments Status: %s." +"Start Ingenico ePayments refund request", "Inizio della richiesta di rimborso sul Ingenico ePayments" +"Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments Status: %s.", "Il rimborso sarà creato automaticamente quando il Ingenico ePayments manderà la conferma. Sato del Ingenico ePayments: %s." +"Creditmemo '%s' was created automatically. Ingenico ePayments Status: %s.", "Il rimborso '%s' é stato creato automaticamente. Sato del Ingenico ePayments: %s." +"Could not perform actions for Ingenico ePayments status: %s.", "Impossibile eseguire azioni per lo stato del Ingenico ePayments status: %s." +"Refund is waiting or uncertain. Ingenico ePayments status: %s.", "Rimborso in attesa o incerto. Sato del Ingenico ePayments: %s." +"Refund was refused. Automatic creation failed. Ingenico ePayments status: %s.", "Rimborso é stato rifiutato. Creazione automatica fallita. Sato del Ingenico ePayments: %s." +"Capture is waiting or uncertain. Ingenico ePayments status: %s.", "Accredito in attesa o incerto. Sato del Ingenico ePayments: %s." +"Capture was refused. Automatic creation failed. Ingenico ePayments status: %s.", "L' accredito é stato rifiutato. Creazione automatica fallita. Sato del Ingenico ePayments: %s." +"Void is waiting or uncertain. Ingenico ePayments status: %s.", "Annullamento in attesa o incerto. Sato del Ingenico ePayments: %s." +"Void was refused. Automatic creation failed. Ingenico ePayments status: %s.", "Annullamento é stato rifiutato. Creazione automatica fallita. Sato del Ingenico ePayments: %s." +"Authorization status changed. Current Ingenico ePayments status is: %s.", "Lo sato dell'autorizzazione é cambiato. Sato corrente del Ingenico ePayments: %s." +"Unknown Ingenico ePayments status: %s.", "Sato del Ingenico ePayments ingnoto: %s." +"Order can not be saved.", "L' ordine non puo' essere salvato." +"Order can not be canceled for system reason.", "L' ordine non puo' essere cancellato par motivi di sistema." +"Order declined on ops side. Ingenico ePayments status: %s, Payment ID: %s.", "Ordine rifiutato da parte del Ingenico ePayments. Sato del Ingenico ePayments: %s, Payment ID: %s." +"Order can not be saved for system reason.", "L' ordine non puo' essere savato par motivi di sistema." +"An unknown exception occured.", "Si é verificata un'eccezione ingota." +"A technical problem arose during payment process, giving unpredictable result. Ingenico ePayments status: %s.", "Si é verificato un problema tecnico durante il processo di pagamento, dando un risultato ignoto. Sato del Ingenico ePayments: %s." +"An unknown exception was thrown in the payment process. Ingenico ePayments status: %s.", "Si é verificata un'eccezione inaspettata durante il processo di pagamento. Sato del Ingenico ePayments: %s." +"Waiting for the payment of the customer", "In attesa del pagamento da parte del cliente" +"Authorization waiting from Ingenico ePayments", "In attesa del pagamento da parte del Ingenico ePayments" +"Processed by Ingenico ePayments", "Processato dal Ingenico ePayments" +"Waiting for payment. Ingenico ePayments status: %s.", "In attesa del pagamento. Sato del Ingenico ePayments: %s." +"Authorization waiting. Ingenico ePayments status: %s.", "In attesa dell'autorizzazione. Sato del Ingenico ePayments: %s." +"Authorization uncertain. Ingenico ePayments status: %s.", "Autorizzazione incerta. Sato del Ingenico ePayments: %s." +"Processed by Ingenico ePayments. Payment ID: %s. Ingenico ePayments status: %s.", "Processato da Ingenico ePayments. Payment ID: %s. Sato del Ingenico ePayments: %s." +"Ingenico ePayments server is temporarily not available, please try again later.", "Il server del Ingenico ePayments non é momentaneamente reperibile, si prega di riprovare più tardi." +"An error occured during the Ingenico ePayments request. Your action could not be executed. Message: '%s'.", "Si é verificato un errore durenta la rchiesta del Ingenico ePayments. L'operazione richiesta no é potuta essere eseguita. Messaggio: '%s'." +"You already sent a capture request. Please wait until the capture request is acknowledged.", "La richesta di accredito é stata già inviata. Si prega di attendere la conferma d'accredito." +"There is one void request waiting. Please wait until this request is acknowledged.", "Una richiesta di annullamento in attesa. Si prega di attendere la conferma di tale processo." +"Start Ingenico ePayments %s capture request", "Inizio di richiest di accredito da parte del %s" +"Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments status: %s.", "La fattura verrà stata creata automaticamente quando l'Ingenico ePayments invierà la conferma. Sato del Ingenico ePayments: %s." +"The Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "La fattura verrà stata creata automaticamente quando l'Ingenico ePayments invierà la conferma." +"The Invoice was not created. Ingenico ePayments status: %s.", "La fattura no é stata creata. Sato del Ingenico ePayments: %s." +"The CreditMemo was not created. Ingenico ePayments status: %s.", "La nota di credito non é stata creata. Sato del Ingenico ePayments: %s." +"The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "La nota di credito verrà creata automaticamente quando l'Ingenico ePayments avrà inviato la conferma." +"There is already one creditmemo in the queue. The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "Una nota di credito gia in coda. La nota di credito verrà creata automaticamente quando l'Ingenico ePayments avrà inviato la conferma." +"Please use void to cancel the operation.", "Si prega di utilizare Annulla per cancellare l'operazione." +"You already sent a void request. Please wait until the void request will be acknowledged.", "Si ha gia inviato una richiest di annullamento. Si prega di attendere la conferma di annullamento." +"There is one capture request waiting. Please wait until this request is acknowledged.", "Non vi sono richieste d'accredito in attesa. Si prega di attendere la conferma di questa richiesta." +"The void request is sent. Please wait until the void request will be accepted.", "La richiesta di annullamento é stata inviata. Si prega di attendere finche la richiesta di annullamento si stata accettata." +"Void order succeed. Ingenico ePayments status: %s.", "Annullamento d'ordine completato. Sato del Ingenico ePayments: %s." +"Void order failed. Ingenico ePayments status: %s.", "Annullamento d'ordine fallito. Sato del Ingenico ePayments: %s." +"Authorization", "Autorizzazione" +"Direct Sale", "Vendita" +"Horizontally grouped logo with group name on left", "Ragruppamento dei logi orizontale con nome del gruppo a sinistra" +"Horizontally grouped logo with no group name", "Ragruppamento dei logi orizontale senza nome del gruppo" +"Verical list", "Lista verticale" +"You will be redirected to the payment page after the order confirmation.", "Si verrà trasferiti sulla pagina di pagamento dopo la conferma dell'ordine." +"Payment failed. Please select another payment method.", "Pagamento fallito. Si prega di selezionare un altro metodo di pagamento." +"Payment failed. Please review your input or select another payment method.", "Pagamento fallito. Si prega di di verificare i dati inseritio di selezionare un altro metodo di pagamento." +"Incorrect credit card expiration date.", "Data di scadenza della carta di credito incorretta." +"Credit Card Type", "Tipo di carta di credito" +"Name on Card", "Intestatario" +"Credit Card Number", "Numero di carta di credito" +"Expiration Date", "Data di scadenza" +"Account holder", "Intestatario" +"Account number", "Numero di conto" +"Bank code", "codice bankario" +"BCMC", "Bancontact/Mister Cash" +"You will be redirected to Ingenico ePayments website when you place an order.", "Verrà rindirizzato alla pagina web Ingenico ePayments quando l'ordine sarà piazzato." +"Please wait a moment. This page will transfer your data to Ingenico ePayments payment gateway in a few seconds.", "Si prega di attendere. Questa pagina transferirà i Vostri dati al gateway di pagamento Ingenico ePayments in pochi secondi." +"PSPID is a case sensitive field", "IL PSPID é sensibile alle maiuscole" +"SHA-IN Pass phrase", "Firma SHA-1-IN" +"Refer to Ingenico ePayments's backend in section Technical Information on tab Data and origin verification.", "Riferirsi al Ingenico ePayments backend sezione Informazione tecniche tab Controllo dei dati e d'origine ." +"SHA-OUT Pass phrase", "Firma SHA-1-OUT" +"Refer to Ingenico ePayments's backend in section Technical Information on tab Transaction feedback.", "_________" +"Test url example: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
Production url example: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp", "esempio test: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
esempio production: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp" +"Test url example: https://secure.ogone.com/ncol/test/maintenancedirect.asp
Production url example: https://secure.ogone.com/ncol/prod/maintenancedirect.asp", "esempio test: https://secure.ogone.com/ncol/test/maintenancedirect.asp
esempio production: https://secure.ogone.com/ncol/prod/maintenancedirect.asp" +"Test url example: https://secure.ogone.com/ncol/test/orderdirect.asp
Production url example: https://secure.ogone.com/ncol/prod/orderdirect.asp", "esempio test: https://secure.ogone.com/ncol/test/orderdirect.asp
esempio production: https://secure.ogone.com/ncol/prod/orderdirect.asp" +"API User", "API User" +"API Password", "Password del API" +"Payment Action", "Azione di pagametno" +"If you choose 'Ingenico ePayments', you will need to fill in all the following fields.", "Se si seleziona 'Ingenico ePayments', si drovà compilare i seguenti campi." +"Payment Template", "Modello di pagamento" +"Title of the Ingenico ePayments Template", "Titolo del modello Ingenico ePayments " +"Background Color of Ingenico ePayments Template", "Colore di sfondo del modello Ingenico ePayments" +"Text Color of the Ingenico ePayments Template", "Colore del testo del modello Ingenico ePayments" +"Table Background Color of the Ingenico ePayments Template", "Colore di sfondo della tabella del modello Ingenico ePayments" +"Table Text Color of the Ingenico ePayments Template", "Colore del testo della tabella del modello Ingenico ePayments" +"Button Background Color of the Ingenico ePayments Template", "colore di sfondo del pulsante del modello Ingenico ePayments" +"Button Text Color of the Ingenico ePayments Template", "Colore delle testo del bottone del modello Ingenico ePayments" +"Font Family of the Ingenico ePayments Template", "Tipo di carattere del modello Ingenico ePayments" +"Logo of the Ingenico ePayments Template", "Logo del modello Ingenico ePayments" +"Layout of Payment Methods", "Layout della pagina di pagamento" +"e.g: white", "bianco" +"e.g: black", "nero" +"e.g: Verdana", "Verdana" +"Debug", "Debug" +"Ingenico ePayments Account", "Conto Ingenico ePayments" +"URL/filename of the logo. The URL must be absolute and stored on a secure server.", "URL/nome del file del logo. L' URL deve essere assoluto e salvato su un server sicuro" +"Countries", "Nazioni" +"Enabled Card Types", "Tipi di carte" +"Enable 3D secure", "Attivare 3D secure" +"SCORING_DESCRIPTION_AAVCHECK", "Risultato della verifica automatica dell'indirizzo. Al momento la verifica è disponibile solo per American Express. Valori possibili:
KO: l'indirizzo è stato inviato ma l'acquirente ha dato risposta negativa alla verifica dell'indirizzo, ossia l'indirizzo è errato.
OK:
1. l'indirizzo è stato inviato e l'acquirente ha dato risposta positiva alla verifica dell'indirizzo, ossia l'indirizzo è corretto OPPURE
2. l'acquirente ha inviato un codice di autorizzazione senza però restituire una risposta specifica per la verifica dell'indirizzo.

NO: tutti gli altri casi. Per esempio, non è stato trasmesso alcun indirizzo." +"SCORING_DESCRIPTION_CVCCHECK", "Risultato della verifica del codice di verifica della carta. Valori possibili:
KO: il codice CVC è stato inviato ma l'acquirente ha dato risposta negativa alla verifica del codice CVC, ossia il codice CVC è errato.
OK:
1. il codice CVC è stato inviato e l'acquirente ha dato risposta positiva alla verifica del codice CVC, ossia il codice CVC è corretto OPPURE
2. l'acquirente ha inviato un codice di autorizzazione senza però restituire un risultato specifico per la verifica del codice CVC.

NO: tutti gli altri casi. Per esempio, non è stato trasmesso alcun codice CVC." +"SCORING_DESCRIPTION_CCCTY", "Il paese di origine della carta di credito.
Formato: codice alfabetico ISO a 2 caratteri. Se questo parametro non è disponibile, nella risposta viene restituito il codice '99'." +"SCORING_DESCRIPTION_IPCTY", "Il paese di origine dell'indirizzo IP.
Formato: codice alfabetico ISO a 2 caratteri. Se questo parametro non è disponibile, nella risposta viene restituito il codice '99'." +"SCORING_DESCRIPTION_NBREMAILUSAGE", "Number of times an email address has been used over a certain period" +"SCORING_DESCRIPTION_NBRIPUSAGE", "Numero di transazioni concluse con successo per indirizzo IP, per periodo" +"SCORING_DESCRIPTION_NBRIPUSAGE_ALLTX", "Number of times an IP address has been used for successful and/or declined transactions over a certain period" +"SCORING_DESCRIPTION_NBRUSAGE", "Number of times a credit card has been used over a certain period" +"SCORING_DESCRIPTION_VC", "Carta virtuale. Valori possibili:
ECB: E-Carte Bleue
ICN: Internet City Number
NO: tutti gli altri casi. Per esempio, la carta non è una carta virtuale valida" +"SCORING_DESCRIPTION_CARDNO", "Numero della carta" +"SCORING_DESCRIPTION_ED", "Data di scadenza" +"SCORING_DESCRIPTION_CN", "Titolare della carta" +"Card Verification Number", "CVC" +"Payment from Applicable Countries","Il pagamento da Paesi Applicabili" +"Payment from Specific Countries","Il pagamento da paesi specifici" +"All Allowed Countries","Tutti i paesi ammessi" +"Specific Countries","paesi specifici" +"Delete Image","Elimina Immagine" +"Allowed file types: jpeg, gif, png.","Tipi di file consentiti: jpeg, gif, png" +"Payment Logo","Logo di pagamento" +"Payment Logo Visibility","Logo di pagamento Visibilità" +"Netresearch_OPS::Left","Sinistra" +"Netresearch_OPS::Right","Destra" +"Netresearch_OPS::Non Visible","Non Visibile" +"Disallowed file type.","Non consentito filetype." +"Payment Information","Informazioni di pagamento" +"Submit","Invia" +"Cancel","Annulla" +"Transmit new payment method...","Trasferimento Nuovo metodo di pagamento ..." +"Your given data","La vostra data di dati" +"Order Number","Numero Ordine" +"Billing Address","Indirizzo di fatturazione" +"Shipping Address","Dell'indirizzo di trasporto" +"Aliasusage New","Aliasusage Nuovo" +"Here you can enter the desired text which will be displayed on Ingenico ePayments side.","Qui è possibile inserire il testo desiderato che verrà visualizzato sul lato Ingenico ePayments." +"Aliasusage existing Alias","Aliasusage esistente Alias" +"Here you can enter the desired text which is used for existing alias, the text will be displayed on Ingenico ePayments side.","Qui è possibile inserire il testo desiderato che viene utilizzato per alias esistente, il testo verrà visualizzato sul lato Ingenico ePayments." +"Pay now", "Pagare" diff --git a/app/locale/nl_NL/Netresearch_OPS.csv b/app/locale/nl_NL/Netresearch_OPS.csv new file mode 100644 index 0000000..c7b83a1 --- /dev/null +++ b/app/locale/nl_NL/Netresearch_OPS.csv @@ -0,0 +1,186 @@ +"STATUS_0", "Ongeldig of onvolledig" +"STATUS_1", "Geannuleerd door de klant" +"STATUS_2", "Autorisatie geweigerd" +"STATUS_4", "Bestelling bewaard" +"STATUS_40", "Opgeslagen, wachten op ext. result." +"STATUS_41", "Wachten op klantbetaling" +"STATUS_46", "Waiting (for identification)" +"STATUS_5", "Geautoriseerd" +"STATUS_50", "Geautoriseerd en wachtend op ext. result." +"STATUS_51", "Autorisatie pending" +"STATUS_52", "Autorisatie onzeker" +"STATUS_55", "Stand-by" +"STATUS_56", "OK met geplande betalingen" +"STATUS_57", "Fout in geplande betalingen" +"STATUS_59", "Manueel te bekomen autorisatie" +"STATUS_6", "Geautoriseerd en geannuleerd" +"STATUS_61", "Annul. autor. pending" +"STATUS_62", "Annul. autor. onzeker" +"STATUS_63", "Annul. autor. geweigerd" +"STATUS_64", "Geautoriseerd en geannuleerd" +"STATUS_7", "Betaling geannuleerd" +"STATUS_71", "Annul. betaling pending" +"STATUS_72", "Annul. betaling onzeker" +"STATUS_73", "Annul. betaling geweigerd" +"STATUS_74", "Betaling geannuleerd" +"STATUS_75", "Annul. verwerkt door merchant" +"STATUS_8", "Terugbetaald" +"STATUS_81", "Terugbetaling pending" +"STATUS_82", "Terugbetaling onzeker" +"STATUS_83", "Terugbetaling geweigerd" +"STATUS_84", "Betaling geweigerd door de bank" +"STATUS_85", "Terugbet. verwerkt door merchant" +"STATUS_9", "Betaling aangevraagd" +"STATUS_91", "Betaling pending" +"STATUS_92", "Betaling onzeker" +"STATUS_93", "Betaling geweigerd" +"STATUS_94", "Terugbetaling geweigerd door de bank" +"STATUS_95", "Betaling verwerkt door merchant" +"STATUS_99", "Wordt verwerkt" +"Hash is not valid", "Hash is niet geldig" +"Order is not valid", "Bestelling is niet geldig" +"Process outgoing transaction", "Verwerking uitgaande transactie" +"Start Ingenico ePayments processing", "Start Ingenico ePayments verwerking" +"Your payment information was declined. Please select another payment method.", "Uw betaalinformatie werd geweigerd. Gelieve een andere betaalmethode te selecteren." +"Order canceled on Ingenico ePayments side. Status: %s, Payment ID: %s.", "Bestelling geannuleerd bij Ingenico ePayments. Status: %s, BetaalID: %s." +"Account number must contain numbers only.", "Rekeningnummer mag enkel nummers bevatten." +"Bank code must contain numbers only.", "Bankcode mag enkel nummers bevatten." +"Account Holder must be filled in.", "Rekeninghouder invullen is verplicht." +"Capture process complete", "Betalingsaanvraag afgerond" +"The capture has already been invoiced.", "De betalingsaanvraag werd al gefactureerd." +"Cannot create an invoice without products.", "Een factuur aanmaken is niet mogelijk zonder producten." +"Partial capture process complete", "Gedeeltelijke betalingsaanvraag afgerond" +"Invoice '%s' was created automatically. Ingenico ePayments Status: %s.", "Factuur '%s' werd automatisch aangemaakt. Ingenico ePayments Status: %s." +"Start Ingenico ePayments refund request", "Start Ingenico ePayments aanvraag terugbetaling" +"Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments Status: %s.", "Creditmemo zal automatisch worden aangemaakt zodra Ingenico ePayments een bevestiging stuurt. Ingenico ePayments Status: %s." +"Creditmemo '%s' was created automatically. Ingenico ePayments Status: %s.", "Creditmemo '%s' werd automatisch aangemaakt. Ingenico ePayments Status: %s." +"Could not perform actions for Ingenico ePayments status: %s.", "Kon geen acties uitvoeren voor Ingenico ePayments status: %s." +"Refund is waiting or uncertain. Ingenico ePayments status: %s.", "Terugbetaling is in verwerking of onzeker. Ingenico ePayments status: %s." +"Refund was refused. Automatic creation failed. Ingenico ePayments status: %s.", "Terugbetaling werd geweigerd. Automatische aanmaak mislukt. Ingenico ePayments status: %s." +"Capture is waiting or uncertain. Ingenico ePayments status: %s.", "Betalingsaanvraag in verwerking of onzeker. Ingenico ePayments status: %s." +"Capture was refused. Automatic creation failed. Ingenico ePayments status: %s.", "Betalingsaanvraag werd geweigerd. Automatische aanmaak mislukt. Ingenico ePayments status: %s." +"Void is waiting or uncertain. Ingenico ePayments status: %s.", "Annulering in verwerking of onzeker. Ingenico ePayments status: %s." +"Void was refused. Automatic creation failed. Ingenico ePayments status: %s.", "Annulering werd geweigerd. Automatische aanmaak mislukt. Ingenico ePayments status: %s." +"Authorization status changed. Current Ingenico ePayments status is: %s.", "Status autorisatie gewijzigd. Huidige Ingenico ePayments status is: %s." +"Unknown Ingenico ePayments status: %s.", "Ongekende Ingenico ePayments status: %s." +"Order can not be saved.", "Bestelling kan niet worden bewaard." +"Order can not be canceled for system reason.", "Bestelling kan niet worden geannuleerd omwille van systeemredenen." +"Order can not be saved for system reason.", "Bestelling kan niet worden bewaard omwille van systeemredenen." +"An unknown exception occured.", "Een ongekende uitzondering deed zich voor." +"An unknown exception was thrown in the payment process. Ingenico ePayments status: %s.", "Een ongekende uitzondering deed zich voor in het betaalproces. Ingenico ePayments status: %s." +"Waiting for the payment of the customer", "Wachten op de betaling van de klant" +"Authorization waiting from Ingenico ePayments", "Wachten op autorisatie van Ingenico ePayments" +"Processed by Ingenico ePayments", "Verwerkt door Ingenico ePayments" +"Waiting for payment. Ingenico ePayments status: %s.", "Wachtend op betaling. Ingenico ePayments status: %s." +"Authorization waiting. Ingenico ePayments status: %s.", "Autorisatie wachtend. Ingenico ePayments status: %s." +"Authorization uncertain. Ingenico ePayments status: %s.", "Autorisatie onzeker. Ingenico ePayments status: %s." +"Processed by Ingenico ePayments. Payment ID: %s. Ingenico ePayments status: %s.", "Verwerkt door Ingenico ePayments. BetaalID: %s. Ingenico ePayments status: %s." +"An error occured during the Ingenico ePayments request. Your action could not be executed. Message: '%s'.", "Een fout deed zich voor tijdens het Ingenico ePayments verzoek. Uw actie kon niet worden uitgevoerd. Melding: '%s'." +"You already sent a capture request. Please wait until the capture request is acknowledged.", "U stuurde reeds een betalingsaavraag. Gelieve te wachten tot de aanvraag is bevestigd." +"There is one void request waiting. Please wait until this request is acknowledged.", "Er wacht nog een annuleringsaanvraag. Gelieve te wachten tot de aanvraag is bevestigd." +"Start Ingenico ePayments %s capture request", "Start Ingenico ePayments %s betalingsaanvraag" +"Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement. Ingenico ePayments status: %s.", "Factuur zal automatisch worden aangemaakt zodra dat Ingenico ePayments een bevestiging stuurt. Ingenico ePayments status: %s." +"The Invoice will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "De factuur zal worden aangemaakt zodra dat Ingenico ePayments een bevestiging stuurt." +"The Invoice was not created. Ingenico ePayments status: %s.", "De factuur werd niet aangemaakt. Ingenico ePayments status: %s." +"The CreditMemo was not created. Ingenico ePayments status: %s.", "De creditmemo werd niet aangemaakt. Ingenico ePayments status: %s." +"The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "De creditmemo zal automatisch worden aangemaakt zodra dat Ingenico ePayments een bevestiging stuurt." +"There is already one creditmemo in the queue. The Creditmemo will be created automatically as soon as Ingenico ePayments sends an acknowledgement.", "Er staat reeds een creditmemo in de rij. De creditmemo zal worden aangemaakt zodra dat Ingenico ePayments een bevestiging stuurt." +"Please use void to cancel the operation.", "Gelieve annuleringsknop te gebruiken om de operatie te annuleren." +"You already sent a void request. Please wait until the void request will be acknowledged.", "U stuurde reeds een annuleringsaanvraag. Gelieve te wachten tot de annuleringsaanvraag is bevestigd." +"There is one capture request waiting. Please wait until this request is acknowledged.", "Er wacht nog een betalingsaanvraag. Gelieve te wachten tot deze aanvraag is bevestigd." +"The void request is sent. Please wait until the void request will be accepted.", "De annuleringsaanvraag is verstuurd. Gelieve te wachten tot de annuleringsaanvraag wordt aanvaard." +"Void order succeed. Ingenico ePayments status: %s.", "Annulering bestelling geslaagd. Ingenico ePayments status: %s." +"Void order failed. Ingenico ePayments status: %s.", "Annulering bestelling mislukt. Ingenico ePayments status: %s." +"Authorization", "Autorisatie" +"Direct Sale", "Verkoop" +"Horizontally grouped logo with group name on left", "Horizontaal gegroepeerde logo's met groepsnaam links" +"Horizontally grouped logo with no group name", "Horizontaal gegroepeerde logo's zonder groepsnaam" +"Verical list", "Verticale lijst" +"You will be redirected to the payment page after the order confirmation.", "U zult worden doorverwezen naar de betaalpagina na de bevestiging van de bestelling." +"Payment failed. Please select another payment method.", "Betaling mislukt. Gelieve een andere betaalmethode te selecteren." +"Payment failed. Please review your input or select another payment method.", "Betaling mislukt. Gelieve uw ingevoerde gegevens na te kijken of selecteer een andere betaalmethode." +"Incorrect credit card expiration date.", "Onjuiste vervaldatum creditcard." +"Credit Card Type", "Type creditcard" +"Name on Card", "Naam op kaart" +"Credit Card Number", "Creditcardnummer" +"Expiration Date", "Vervaldatum" +"Account holder", "Rekeninghouder" +"Account number", "Rekeningnummer" +"Bank code", "Bankcode" +"BCMC", "Bancontact/Mister Cash" +"You will be redirected to Ingenico ePayments website when you place an order.", "U zult worden doorverwezen naar de Ingenico ePayments website als u een bestelling plaatst." +"Please wait a moment. This page will transfer your data to Ingenico ePayments payment gateway in a few seconds.", "Gelieve even te wachten. Deze pagina zal uw gegevens sturen naar Ingenico ePayments payment gateway binnen enkele seconden." +"PSPID is a case sensitive field", "Het veld PSPID is hoofdlettergevoelig." +"SHA-IN Pass phrase", "SHA-IN wachtwoord" +"Refer to Ingenico ePayments's backend in section Technical Information on tab Data and origin verification.", "Refereer naar Ingenico ePayments's Backoffice in sectie Technische informatie tabblad Verificatie data en herkomst." +"SHA-OUT Pass phrase", "SHA-OUT wachtwoord" +"Test url example: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
Production url example: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp", "voorbeeld test: https://secure.ogone.com/ncol/test/orderstandard_utf8.asp
voorbeeld production: https://secure.ogone.com/ncol/prod/orderstandard_utf8.asp" +"Test url example: https://secure.ogone.com/ncol/test/maintenancedirect.asp
Production url example: https://secure.ogone.com/ncol/prod/maintenancedirect.asp", "voorbeeld test: https://secure.ogone.com/ncol/test/maintenancedirect.asp
voorbeeld production: https://secure.ogone.com/ncol/prod/maintenancedirect.asp" +"Test url example: https://secure.ogone.com/ncol/test/orderdirect.asp
Production url example: https://secure.ogone.com/ncol/prod/orderdirect.asp", "voorbeeld test: https://secure.ogone.com/ncol/test/orderdirect.asp
voorbeeld production: https://secure.ogone.com/ncol/prod/orderdirect.asp" +"API User", "API Gebruikersnaam" +"API Password", "API Wachtwoord" +"Payment Action", "Transactie actie" +"If you choose 'Ingenico ePayments', you will need to fill in all the following fields.", "Wanneer u 'Ingenico ePayments ' selecteert, dient u de volgende velden verplicht in te vullen:" +"Payment Template", "Template betaalpagina" +"Title of the Ingenico ePayments Template", "Titel van de Ingenico ePayments template" +"Background Color of Ingenico ePayments Template", "Achtergrondkleur van de Ingenico ePayments template" +"Text Color of the Ingenico ePayments Template", "Kleur tekst van de Ingenico ePayments template" +"Table Background Color of the Ingenico ePayments Template", "Achtergrondkleur tabel van de Ingenico ePayments template" +"Table Text Color of the Ingenico ePayments Template", "Tekstkleur tabel van de Ingenico ePayments template" +"Button Background Color of the Ingenico ePayments Template", "Achtergrondkleur knoppen van de Ingenico ePayments template" +"Button Text Color of the Ingenico ePayments Template", "Tekstkleur knoppen van de Ingenico ePayments template" +"Font Family of the Ingenico ePayments Template", "Lettertype van het Ingenico ePayments template" +"Logo of the Ingenico ePayments Template", "Logo van het Ingenico ePayments template" +"Layout of Payment Methods", "Layout van de betaalmethodes" +"e.g: white", "e.g.: wit" +"e.g: black", "e.g.: zwart" +"e.g: Verdana", "e.g.: Verdana" +"Debug", "Debug" +"Ingenico ePayments Account", "Ingenico ePayments Account" +"URL/filename of the logo. The URL must be absolute and stored on a secure server.", "URL/bestandsnaam van het logo. De URL moet absoluut zijn en het logo moet opgeslagen zijn op een beveiligde server (https)." +"Countries", "Landen" +"Enabled Card Types", "Kaart soorten" +"Enable 3D secure", "Activeer 3D-Secure" +"OWNERADDRESS", "straat" +"ECOM_BILLTO_POSTAL_STREET_NUMBER", "huisnummer" +"ECOM_SHIPTO_POSTAL_STREET_LINE1", "verzendadres: straat" +"ECOM_SHIPTO_POSTAL_STREET_NUMBER", "verzendadres: huisnummer" +"SCORING_DESCRIPTION_AAVCHECK", "Resultaat van de automatische adresverificatie. Deze verificatie is op dit moment alleen verkrijgbaar voor American Express. Mogelijke waardes:
KO: Het adres is verzonden maar de acquirer heeft negatief geantwoord op de adres controle, d.w.z. het adres is onjuist.
OK:
1. Het adres is verzonden en de acquirer heeft positief geantwoord op de adres-controle, d.w.z. het adres is correct OF
2. De acquirer heeft een autorisatiecode verzonden, maar heeft geen bepaald resultaat voor de adres-controle.

NO: Alle andere gevallen. Bijvoorbeeld, geen adres verzonden" +"SCORING_DESCRIPTION_CVCCHECK", "Resultaat van de kaart verificatie code check. Mogelijke waarden:
KO: De CVC is verzonden, maar de acquirer heeft negatief geantwoord op de CVC-controle, d.w.z. de CVC is onjuist.
OK:
1. De CVC is verzonden en de acquirer heeft positief geantwoord op de CVC-controle, d.w.z. de CVC is juist, OF
2. De acquirer heeft een autorisatiecode verzonden, maar heeft geen duidelijk resultaat voor de CVC-controle.

NO: Alle andere gevallen. Bijvoorbeeld, geen CVC verzonden" +"SCORING_DESCRIPTION_CCCTY", "Land van herkomst van de credit card.
Formaat: 2 tekens alfabetische ISO-code." +"SCORING_DESCRIPTION_IPCTY", "Land van herkomst van het IP-adres
Formaat: 2 tekens alfabetische ISO-code. Als deze parameter onbeschikbaar is, dan wordt '99' als antwoord weergegeven." +"SCORING_DESCRIPTION_NBREMAILUSAGE", "Aantal keer waarin het e-mailadres gebruikt is binnen een bepaalde periode." +"SCORING_DESCRIPTION_NBRIPUSAGE", "Aantal keer waarin het IP adres gebruikt is in een bepaalde periode." +"SCORING_DESCRIPTION_NBRIPUSAGE_ALLTX", "Aantal keer waarin het IP adres gebruikt is in een succesvolle en/of geweigerde transactie in een bepaalde periode." +"SCORING_DESCRIPTION_NBRUSAGE", "Aantal keer waarin een credit card gebruikt is in een bepaalde periode." +"SCORING_DESCRIPTION_VC", "Virtuele kaart. Mogelijke waardes:
ECB: voor E Carte Bleue
ICN: voor Internet City Number
NO: voor alle andere gevallen. Bijvoorbeeld, de kaart is niet virtueel." +"SCORING_DESCRIPTION_CARDNO", "Naam kaarthouder" +"SCORING_DESCRIPTION_ED", "Vervaldatum" +"SCORING_DESCRIPTION_CN", "Naam op kaart" +"Card Verification Number", "Kaartverificatienummer" +"Your details", "Uw gegevens" +"Payment from Applicable Countries","Betaling van toepasbare landen" +"Payment from Specific Countries","Betaling uit bepaalde landen" +"All Allowed Countries","Alle toegestaan Landen" +"Specific Countries","Specifieke Landen" +"Delete Image","Afbeelding verwijderen" +"Allowed file types: jpeg, gif, png.","Toegestaan bestandstypen: jpeg, gif, png" +"Payment Logo","Payment Logo" +"Payment Logo Visibility","Payment Logo zichtbaarheid" +"Netresearch_OPS::Left","Links" +"Netresearch_OPS::Right","Right" +"Netresearch_OPS::Non Visible","Niet Zichtbaar" +"Disallowed file type.","Verworpen bestandstype." +"Payment Information","Payment Information" +"Submit","Verzenden" +"Cancel","Annuleren" +"Transmit new payment method...","Transfer nieuwe betaalmethode ..." +"Your given data","Uw gegeven data" +"Order Number","Order Number" +"Billing Address","Factuuradres" +"Shipping Address","Shipping Address" +"Aliasusage New","Aliasusage Nieuw" +"Here you can enter the desired text which will be displayed on Ingenico ePayments side.","Hier kunt u de gewenste tekst die op Ingenico ePayments kant worden weergegeven in te voeren." +"Aliasusage existing Alias","Aliasusage bestaande Alias" +"Here you can enter the desired text which is used for existing alias, the text will be displayed on Ingenico ePayments side.","Hier kunt u de gewenste tekst die wordt gebruikt voor bestaande alias invoert, wordt de tekst weergegeven op Ingenico ePayments kant." +"Pay now", "Betalen" diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..826c507 --- /dev/null +++ b/composer.json @@ -0,0 +1,8 @@ +{ + "name": "kirchbergerknorr/magento1_netresearch_ops", + "type": "magento-module", + "description": "Official Ingenico ePayments extension", + "authors": [ + {"name": "Ingenico"} + ] +} \ No newline at end of file diff --git a/js/netresearch/ops/deviceFingerprinting.js b/js/netresearch/ops/deviceFingerprinting.js new file mode 100644 index 0000000..de2619b --- /dev/null +++ b/js/netresearch/ops/deviceFingerprinting.js @@ -0,0 +1,48 @@ +var ConsentHandler = Class.create(); +ConsentHandler.prototype = { + + consentUrl: "/ops/device/", + initialize: function (url) { + this.consentUrl = url; + }, + /** + * Sends request to DeviceController to toggle the customers consent to the given state, will call the callback + * with the current consent state + * + * @param targetState - state of customer consent given + * @param callback - callback to call with consent result + */ + toggleConsent: function (targetState, callback) { + new Ajax.Request(this.consentUrl + 'toggleConsent', { + method: 'post', + parameters: {consent: targetState}, + onSuccess: function (transport) { + var data = transport.responseText.evalJSON(); + callback(data.consent); + }, + onFailure: function () { + callback(null); + } + }) + }, + + /** + * Requests current consent state and forwards it to the given callback + * + * @param callback - callback to call with consent result + */ + getConsent: function (callback) { + new Ajax.Request(this.consentUrl + 'consent', { + method: 'post', + onSuccess: function (transport) { + var data = transport.responseText.evalJSON(); + callback(data.consent); + }, + onFailure: function () { + callback(null); + } + }) + } + +}; + diff --git a/js/netresearch/ops/osc_payment.js b/js/netresearch/ops/osc_payment.js new file mode 100644 index 0000000..df65d6d --- /dev/null +++ b/js/netresearch/ops/osc_payment.js @@ -0,0 +1,101 @@ +Event.observe(window, 'load', function() { + + // check if we are dealing with OneStepCheckout + payment.isOneStepCheckout = $$('.onestepcheckout-place-order'); + payment.formOneStepCheckout = $('onestepcheckout-form'); + payment.holdOneStepCheckout = false; + + if(payment.isOneStepCheckout){ + + window.get_separate_save_methods_function = window.get_separate_save_methods_function.wrap(function (originalCall, url, update_payments) { + + var aliasMethods = ['ops_cc', 'ops_dc']; + + aliasMethods.each(function (method) { + if (typeof $('p_method_' + method) != 'undefined') { + $$('input[type="radio"][name="payment[' + method + '_data][alias]"]').each(function (element) { + element.observe('click', function () { + payment.toggleCCInputfields(this); + }) + }); + } + var newAliasElement = $('new_alias_' + method); + if (newAliasElement + && $$('input[type="radio"][name="payment[' + method + '_data][alias]"]').size() == 1 + ) { + payment.toggleCCInputfields(newAliasElement); + } + }); + + return originalCall(url, update_payments); + + }); + //set the form element + payment.form = payment.formOneStepCheckout; + + //bind event handlers to buttons + payment.isOneStepCheckout.each(function(elem){ + elem.observe('click', function(e){ + + Event.stop(e); + if(!payment.holdOneStepCheckout){ + return; + } + + if ('ops_directDebit' == payment.currentMethod && payment.holdOneStepCheckout) { + window.already_placing_order = true; + } + + if ('ops_cc' == payment.currentMethod && payment.holdOneStepCheckout) { + window.already_placing_order = true; + } + //normally this is not called + payment.save(); + }); + }); + + + //add new method to restore the place order state when failure + payment.toggleOneStepCheckout = function(action){ + var submitelement = $('onestepcheckout-place-order'); + var loaderelement = $$('.onestepcheckout-place-order-loading'); + + if(action === 'payment'){ + + window.already_placing_order = true; + /* Disable button to avoid multiple clicks */ + submitelement.removeClassName('orange').addClassName('grey'); + submitelement.disabled = true; + payment.holdOneStepCheckout = true; + } + + if(action === 'remove'){ + + submitelement.removeClassName('grey').addClassName('orange'); + submitelement.disabled = false; + + if(loaderelement){ + loaderelement = loaderelement[0]; + if(loaderelement){ + loaderelement.remove(); + } + } + + window.already_placing_order = false; + payment.holdOneStepCheckout = false; + } + }; + + //wrap this to toggle the buttons in OneStepCheckout. + payment.opcToggleContinue = function (active) { + + if (!active) { + payment.toggleOneStepCheckout('payment'); + } else { + payment.toggleOneStepCheckout('remove'); + } + }; + } + // check if we are dealing with OneStepCheckout end + +}); diff --git a/js/netresearch/ops/payment.js b/js/netresearch/ops/payment.js new file mode 100644 index 0000000..8301fa4 --- /dev/null +++ b/js/netresearch/ops/payment.js @@ -0,0 +1,198 @@ +Event.observe(window, 'load', function () { + + + if (typeof checkout != 'undefined') { + payment.switchMethod = payment.switchMethod.wrap(function (originalMethod, method) { + if (method && typeof window[method] != 'undefined') { + payment.currentMethodObject = window[method]; + if (payment.isInline() && !payment.opsAliasSuccess) { + payment.toggleContinue(false); + } else { + payment.toggleContinue(true); + } + } else { + if (typeof checkout != 'undefined') { + payment.toggleContinue(true); + } else { + toggleOrderSubmit(true); + } + } + originalMethod(method); + }); + } + + + if (payment.onSave) { + payment.onSave = payment.onSave.wrap(function (original, transport) { + var response = null; + if (transport && transport.responseText) { + try { + response = eval('(' + transport.responseText + ')'); + } + catch (e) { + response = {}; + } + } + + var addValidationErrors = function (pair) { + var element; + if (element = $(pair.key)) { + Validation.ajaxError(element, pair.value); + } + + }; + /* + * if there is an error in payment, need to show error message + */ + if (response.opsError) { + $H(response.fields).each(function (pair) { + addValidationErrors(pair); + }); + checkout.gotoSection(response.goto_section); + + } + original(transport); + }); + } + + if (payment.save) { + payment.save = payment.save.wrap(function (originalSaveMethod) { + payment.originalSaveMethod = originalSaveMethod; + if ($('ops-retry-form')) { + checkout.setLoadWaiting('payment'); + if (paymentForm.validator && paymentForm.validator.validate()) { + $('ops-retry-form').submit(); + } else { + checkout.setLoadWaiting(false); + return false; + } + } else { + originalSaveMethod(); + } + }); + } + + + payment.getSelectedAliasElement = function () { + return $$('input[name="payment[' + payment.currentMethod + '_data][alias]"]:checked')[0]; + }; + + payment.isStoredAliasSelected = function () { + return payment.getSelectedAliasId() != 'new_alias_' + payment.currentMethod; + }; + + payment.getSelectedAlias = function () { + return payment.getSelectedAliasElement().value; + }; + + payment.getSelectedAliasId = function () { + return payment.getSelectedAliasElement().id; + }; + + payment.toggleCCInputfields = function (element) { + if (element.id.indexOf('new_alias') != -1) { + + var currentMethod = element.id.replace('new_alias_', ''); + var currentMethodUC = currentMethod.toUpperCase(); + + var paymenDetailsId = $('insert_payment_details_' + currentMethod).id; + if ($(currentMethod + '_stored_alias_brand') != null) { + $(currentMethod + '_stored_alias_brand').disable(); + } + + if ($(currentMethod + '_stored_country_id') != null) { + $(currentMethod + '_stored_country_id').disable(); + } + + var selector = $(currentMethodUC + '_BRAND') || $(currentMethod + '_country_id'); + selector.enable(); + + $(paymenDetailsId).show(); + + + $$('input[type="text"][name="payment[' + currentMethod + '_data][cvc]"]').each(function (cvcEle) { + cvcEle.up('li').hide(); + cvcEle.disable(); + }); + $$('#' + paymenDetailsId + ' input,#' + paymenDetailsId + ' select').each(function (element) { + element.enable(); + }); + if(payment.currentMethodObject && payment.currentMethodObject.tokenizationFrame.src != 'about:blank'){ + payment.toggleContinue(false); + } + } + else { + var currentMethod = element.up('ul').id.replace('payment_form_', ''); + var currentMethodUC = currentMethod.toUpperCase(); + var paymenDetailsId = $('insert_payment_details_' + currentMethod).id; + if ($(currentMethod + '_stored_alias_brand') != null) { + $(currentMethod + '_stored_alias_brand').enable(); + $(currentMethod + '_stored_alias_brand').value = element.dataset.brand; + } + + if ($(currentMethod + '_stored_country_id') != null) { + $(currentMethod + '_stored_country_id').enable(); + $(currentMethod + '_stored_country_id').value = element.dataset.countryid; + } + + var selector = $(currentMethodUC + '_BRAND') || $(currentMethod + '_country_id'); + selector.disable(); + + + $$('input[type="text"][name="payment[' + currentMethod + '_data][cvc]"]').each(function (cvcEle) { + if ($(currentMethodUC + '_CVC_' + element.id) != null + && $(currentMethodUC + '_CVC_' + element.id).id == cvcEle.id + ) { + cvcEle.up('li').show(); + cvcEle.enable(); + } else { + cvcEle.up('li').hide(); + cvcEle.disable(); + } + }); + + $$('#' + paymenDetailsId + ' input,#' + paymenDetailsId + ' select').each(function (element) { + element.disable(); + }); + $(paymenDetailsId).hide(); + payment.toggleContinue(true); + } + }; + + if (typeof accordion != 'undefined') { + accordion.openSection = accordion.openSection.wrap(function (originalOpenSectionMethod, section) { + if (section.id == 'opc-payment' || section == 'opc-payment') { + + payment.registerAliasEventListeners(); + } + + originalOpenSectionMethod(section); + }); + } + + payment.registerAliasEventListeners = function () { + var aliasMethods = ['ops_cc', 'ops_dc', 'ops_directDebit']; + + aliasMethods.each(function (method) { + if (typeof $('p_method_' + method) != 'undefined') { + $$('input[type="radio"][name="payment[' + method + '_data][alias]"]').each(function (element) { + element.observe('click', function (event) { + payment.toggleCCInputfields(this); + }) + }); + } + if ($('new_alias_' + method) + && $$('input[type="radio"][name="payment[' + method + '_data][alias]"]').size() == 1 + ) { + payment.toggleCCInputfields($('new_alias_' + method)); + } + }); + }; + + payment.jumpToLoginStep = function () { + if (typeof accordion != 'undefined') { + accordion.openSection('opc-login'); + $('login:register').checked = true; + } + }; +}); diff --git a/js/netresearch/ops/tokenization.js b/js/netresearch/ops/tokenization.js new file mode 100644 index 0000000..28a32dc --- /dev/null +++ b/js/netresearch/ops/tokenization.js @@ -0,0 +1,230 @@ +Event.observe(window, 'load', function () { + + payment.opsAliasSuccess = false; + + + payment.isInline = function () { + return payment.isCCInline() || payment.isDbInline(); + }; + + payment.isDbInline = function () { + return payment.currentMethodObject && payment.currentMethodObject.code == 'ops_directDebit' + && (!$('new_alias_ops_directDebit') || $('new_alias_ops_directDebit').checked === true) + && $('ops_directDebit_country_id').value != ''; + }; + + payment.isCCInline = function () { + return payment.currentMethodObject && payment.currentMethodObject.brandsForAliasInterface + && $(payment.currentMethodObject.code.toUpperCase() + '_BRAND') + && -1 < payment.currentMethodObject.brandsForAliasInterface.indexOf( + $(payment.currentMethodObject.code.toUpperCase() + '_BRAND').value + ); + }; + + payment.generateIframeUrl = function (hash) { + var form = payment.prepareOpsForm(hash); + payment.currentMethodObject.tokenizationFrame.src = opsUrl + '?' + form.serialize(); + }; + + + payment.generateHash = function () { + return new Ajax.Request(opsHashUrl + '?' + payment.prepareOpsForm(false).serialize(), { + method: 'post', + onSuccess: function (transport) { + var data = transport.responseText.evalJSON(); + payment.generateIframeUrl(data.hash); + } + }); + }; + + payment.handleBrandChange = function () { + payment.currentMethodObject.tokenizationFrame.src = 'about:blank'; + payment.opsAliasSuccess = false; + if ( payment.isInline()) { + if (payment.currentMethodObject.redirectNote) { + payment.currentMethodObject.redirectNote.style.display = 'none'; + } + payment.fillOpsLoader('LOAD_TOKEN'); + if (typeof checkout != 'undefined') { + payment.toggleContinue(false); + } else { + toggleOrderSubmit(false); + } + + payment.generateHash(); + } else { + payment.fillOpsLoader(); + if (payment.currentMethodObject.redirectNote) { + payment.currentMethodObject.redirectNote.style.display = 'block'; + } + if (typeof checkout != 'undefined') { + payment.toggleContinue(true); + } else { + toggleOrderSubmit(true); + } + } + }; + + payment.prepareOpsForm = function (hash) { + doc = document; + form = doc.createElement('form'); + + + if (payment.currentMethodObject.brand()) { + var brandElement = doc.createElement('input'); + brandElement.id = 'CARD.BRAND'; + brandElement.name = 'CARD.BRAND'; + brandElement.value = payment.currentMethodObject.brand(); + form.appendChild(brandElement); + } + + var pspidElement = doc.createElement('input'); + pspidElement.id = 'ACCOUNT.PSPID'; + pspidElement.name = 'ACCOUNT.PSPID'; + pspidElement.value = opsPspid; + + var orderIdElement = doc.createElement('input'); + orderIdElement.name = 'ALIAS.ORDERID'; + orderIdElement.id = 'ALIAS.ORDERID'; + orderIdElement.value = opsOrderId; + + var acceptUrlElement = doc.createElement('input'); + acceptUrlElement.name = 'PARAMETERS.ACCEPTURL'; + acceptUrlElement.id = 'PARAMETERS.ACCEPTURL'; + acceptUrlElement.value = opsAcceptUrl; + + var exceptionUrlElement = doc.createElement('input'); + exceptionUrlElement.name = 'PARAMETERS.EXCEPTIONURL'; + exceptionUrlElement.id = 'PARAMETERS.EXCEPTIONURL'; + exceptionUrlElement.value = opsExceptionUrl; + + var paramplusElement = doc.createElement('input'); + paramplusElement.name = 'PARAMETERS.PARAMPLUS'; + paramplusElement.id = 'PARAMETERS.PARAMPLUS'; + paramplusElement.value = paramplus; + + var aliasElement = doc.createElement('input'); + aliasElement.name = 'ALIAS.ALIASID'; + aliasElement.id = 'ALIAS.ALIASID'; + aliasElement.value = opsAlias; + + if(payment.currentMethodObject.aliasManager) { + var storePermanentlyElement = doc.createElement('input'); + storePermanentlyElement.name = 'ALIAS.STOREPERMANENTLY'; + storePermanentlyElement.id = 'ALIAS.STOREPERMANENTLY'; + storePermanentlyElement.value = 'N'; + form.appendChild(storePermanentlyElement); + } + + var paymentMethodElement = doc.createElement('input'); + paymentMethodElement.name = 'Card.PaymentMethod'; + paymentMethodElement.id = 'Card.PaymentMethod'; + paymentMethodElement.value = payment.currentMethodObject.paymentMethod + || payment.currentMethodObject.brand(); + + var localeElement = doc.createElement('input'); + localeElement.name = 'Layout.Language'; + localeElement.id = 'Layout.Language'; + localeElement.value = locale; + form.appendChild(localeElement); + + if (hash) { + var hashElement = doc.createElement('input'); + hashElement.id = 'SHASIGNATURE.SHASIGN'; + hashElement.name = 'SHASIGNATURE.SHASIGN'; + hashElement.value = hash.toUpperCase(); + form.appendChild(hashElement); + } + + form.id = 'ops_request_form'; + form.method = 'post'; + form.action = opsUrl; + submit = document.createElement('submit'); + form.appendChild(submit); + + form.appendChild(pspidElement); + form.appendChild(acceptUrlElement); + form.appendChild(exceptionUrlElement); + form.appendChild(orderIdElement); + form.appendChild(paramplusElement); + form.appendChild(aliasElement); + + if (transmitPaymentMethod === true) { + form.appendChild(paymentMethodElement); + } + + // INGD-40 fix for mobile devices from bulkpowders.com crew + if (typeof form.serialize !== "function") { + Element.extend(form); + } + + return form; + }; + + payment.fillOpsLoader = function (token) { + if (token) { + payment.currentMethodObject.loader.innerHTML = Translator.translate(token); + payment.currentMethodObject.loader.style.display = 'block'; + payment.currentMethodObject.tokenizationFrame.style.display = 'none'; + } else { + payment.currentMethodObject.loader.style.display = 'none'; + payment.currentMethodObject.tokenizationFrame.style.display = 'none'; + } + }; + + payment.toggleContinue = function (active) { + if (active) { + checkout.setLoadWaiting('payment', false); + checkout.setLoadWaiting(false); + } else { + checkout.setLoadWaiting('payment', true); + checkout.setLoadWaiting(false, true); + } + if(typeof payment.opcToggleContinue == 'function'){ + payment.opcToggleContinue(active); + } + }; + + payment.onOpsIframeLoad = function () { + if (payment.isInline() && payment.currentMethodObject.tokenizationFrame.src != 'about:blank' && !payment.opsAliasSuccess) { + payment.currentMethodObject.loader.style.display = 'none'; + payment.currentMethodObject.tokenizationFrame.style.display = 'block'; + } + }; + + if(payment.currentMethod && window[payment.currentMethod]){ + payment.currentMethodObject = window[payment.currentMethod]; + } + + payment.opsAdminSwitchMethod = function (method) { + if (typeof window[method] != 'undefined') { + if (typeof payment.currentMethodObject != 'undefined' + && payment.currentMethodObject.code != method + ) { + payment.opsAliasSuccess = false; + } + + payment.currentMethodObject = window[method]; + if (!payment.opsAliasSuccess + && payment.currentMethodObject.tokenizationFrame.src == 'about:blank' + && ( + !$(payment.currentMethodObject.code + '_country_id') + || $(payment.currentMethodObject.code + '_country_id').value != '' + ) + ) { + payment.reloadIframe(); + } else { + toggleOrderSubmit(true); + } + } else { + delete payment.currentMethodObject; + } + } +}); +function toggleOrderSubmit(active) { + if (active) { + enableElements('save') + } else { + disableElements('save') + } +}; \ No newline at end of file diff --git a/lib/MobileDetect/Mobile_Detect.php b/lib/MobileDetect/Mobile_Detect.php new file mode 100644 index 0000000..d01b488 --- /dev/null +++ b/lib/MobileDetect/Mobile_Detect.php @@ -0,0 +1,1387 @@ + + * Nick Ilyin + * + * Original author: Victor Stanciu + * + * @license Code and contributions have 'MIT License' + * More details: https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE.txt + * + * @link Homepage: http://mobiledetect.net + * GitHub Repo: https://github.com/serbanghita/Mobile-Detect + * Google Code: http://code.google.com/p/php-mobile-detect/ + * README: https://github.com/serbanghita/Mobile-Detect/blob/master/README.md + * HOWTO: https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples + * + * @version 2.8.15 + */ +class Mobile_Detect +{ + + /** + * Mobile detection type. + * + * @deprecated since version 2.6.9 + */ + const DETECTION_TYPE_MOBILE = 'mobile'; + + /** + * Extended detection type. + * + * @deprecated since version 2.6.9 + */ + const DETECTION_TYPE_EXTENDED = 'extended'; + + /** + * A frequently used regular expression to extract version #s. + * + * @deprecated since version 2.6.9 + */ + const VER = '([\w._\+]+)'; + + /** + * Top-level device. + */ + const MOBILE_GRADE_A = 'A'; + + /** + * Mid-level device. + */ + const MOBILE_GRADE_B = 'B'; + + /** + * Low-level device. + */ + const MOBILE_GRADE_C = 'C'; + + /** + * Stores the version number of the current release. + */ + const VERSION = '2.8.15'; + + /** + * A type for the version() method indicating a string return value. + */ + const VERSION_TYPE_STRING = 'text'; + + /** + * A type for the version() method indicating a float return value. + */ + const VERSION_TYPE_FLOAT = 'float'; + + /** + * A cache for resolved matches + * @var array + */ + protected $cache = array(); + + /** + * The User-Agent HTTP header is stored in here. + * @var string + */ + protected $userAgent = null; + + /** + * HTTP headers in the PHP-flavor. So HTTP_USER_AGENT and SERVER_SOFTWARE. + * @var array + */ + protected $httpHeaders = array(); + + /** + * CloudFront headers. E.g. CloudFront-Is-Desktop-Viewer, CloudFront-Is-Mobile-Viewer & CloudFront-Is-Tablet-Viewer. + * @var array + */ + protected $cloudfrontHeaders = array(); + + /** + * The matching Regex. + * This is good for debug. + * @var string + */ + protected $matchingRegex = null; + + /** + * The matches extracted from the regex expression. + * This is good for debug. + * @var string + */ + protected $matchesArray = null; + + /** + * The detection type, using self::DETECTION_TYPE_MOBILE or self::DETECTION_TYPE_EXTENDED. + * + * @deprecated since version 2.6.9 + * + * @var string + */ + protected $detectionType = self::DETECTION_TYPE_MOBILE; + + /** + * HTTP headers that trigger the 'isMobile' detection + * to be true. + * + * @var array + */ + protected static $mobileHeaders = array( + 'HTTP_ACCEPT' => array('matches' => array( + // Opera Mini; @reference: http://dev.opera.com/articles/view/opera-binary-markup-language/ + 'application/x-obml2d', + // BlackBerry devices. + 'application/vnd.rim.html', + 'text/vnd.wap.wml', + 'application/vnd.wap.xhtml+xml' + )), + 'HTTP_X_WAP_PROFILE' => null, + 'HTTP_X_WAP_CLIENTID' => null, + 'HTTP_WAP_CONNECTION' => null, + 'HTTP_PROFILE' => null, + // Reported by Opera on Nokia devices (eg. C3). + 'HTTP_X_OPERAMINI_PHONE_UA' => null, + 'HTTP_X_NOKIA_GATEWAY_ID' => null, + 'HTTP_X_ORANGE_ID' => null, + 'HTTP_X_VODAFONE_3GPDPCONTEXT' => null, + 'HTTP_X_HUAWEI_USERID' => null, + // Reported by Windows Smartphones. + 'HTTP_UA_OS' => null, + // Reported by Verizon, Vodafone proxy system. + 'HTTP_X_MOBILE_GATEWAY' => null, + // Seen this on HTC Sensation. SensationXE_Beats_Z715e. + 'HTTP_X_ATT_DEVICEID' => null, + // Seen this on a HTC. + 'HTTP_UA_CPU' => array('matches' => array('ARM')), + ); + + /** + * List of mobile devices (phones). + * + * @var array + */ + protected static $phoneDevices = array( + 'iPhone' => '\biPhone\b|\biPod\b', // |\biTunes + 'BlackBerry' => 'BlackBerry|\bBB10\b|rim[0-9]+', + 'HTC' => 'HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)|APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200|ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\bEVO\b|T-Mobile G1|Z520m', + 'Nexus' => 'Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 6', + // @todo: Is 'Dell Streak' a tablet or a phone? ;) + 'Dell' => 'Dell.*Streak|Dell.*Aero|Dell.*Venue|DELL.*Venue Pro|Dell Flash|Dell Smoke|Dell Mini 3iX|XCD28|XCD35|\b001DL\b|\b101DL\b|\bGS01\b', + 'Motorola' => 'Motorola|DROIDX|DROID BIONIC|\bDroid\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955|A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611|MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863|ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317|XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800|XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925|XT1021|\bMoto E\b', + 'Samsung' => 'Samsung|SM-G9250|GT-19300|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310|GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510|GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K|GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010|GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100|GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210|GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250|GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420|GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700|GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103|GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800|GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210|GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350|GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660|GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230|GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600|SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930|SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730|SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351|SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430|SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740|SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157|SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657|SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817|SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220|SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225|SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105|SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200|SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717|SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917|SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500|SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777|SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229|SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409|SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609|SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749|SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200|SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497|SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10|SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700|SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700|SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220|SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550|SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920|SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535|SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510|GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582|GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K|SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K|SCH-P709|SCH-P729|SM-T2558|GT-I9205', + 'LG' => '\bLG\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200|MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK|E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690|MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740|VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802)', + 'Sony' => 'SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i|C5303|C6902|C6903|C6906|C6943|D2533', + 'Asus' => 'Asus.*Galaxy|PadFone.*Mobile', + // http://www.micromaxinfo.com/mobiles/smartphones + // Added because the codes might conflict with Acer Tablets. + 'Micromax' => 'Micromax.*\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\b', + // @todo Complete the regex. + 'Palm' => 'PalmSource|Palm', // avantgo|blazer|elaine|hiptop|plucker|xiino ; + 'Vertu' => 'Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature', // Just for fun ;) + // http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA (PANTECH) + // Most of the VEGA devices are legacy. PANTECH seem to be newer devices based on Android. + 'Pantech' => 'PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L|IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S|IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995|IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790', + // http://www.fly-phone.com/devices/smartphones/ ; Included only smartphones. + 'Fly' => 'IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250', + // http://fr.wikomobile.com + 'Wiko' => 'KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA|LENNY|BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM', + 'iMobile' => 'i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)', + // Added simvalley mobile just for fun. They have some interesting devices. + // http://www.simvalley.fr/telephonie---gps-_22_telephonie-mobile_telephones_.html + 'SimValley' => '\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\b', + // Wolfgang - a brand that is sold by Aldi supermarkets. + // http://www.wolfgangmobile.com/ + 'Wolfgang' => 'AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q', + 'Alcatel' => 'Alcatel', + 'Nintendo' => 'Nintendo 3DS', + // http://en.wikipedia.org/wiki/Amoi + 'Amoi' => 'Amoi', + // http://en.wikipedia.org/wiki/INQ + 'INQ' => 'INQ', + // @Tapatalk is a mobile app; http://support.tapatalk.com/threads/smf-2-0-2-os-and-browser-detection-plugin-and-tapatalk.15565/#post-79039 + 'GenericPhone' => 'Tapatalk|PDA;|SAGEM|\bmmp\b|pocket|\bpsp\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\bwap\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser', + ); + + /** + * List of tablet devices. + * + * @var array + */ + protected static $tabletDevices = array( + 'iPad' => 'iPad|iPad.*Mobile', // @todo: check for mobile friendly emails topic. + 'NexusTablet' => 'Android.*Nexus[\s]+(7|9|10)|^.*Android.*Nexus(?:(?!Mobile).)*$', + 'SamsungTablet' => 'SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113|GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L|SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-P5200|GT-P5210|GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X|GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X|SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237|SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T|SM-T116NQ|SM-P550|SM-T350|SM-T550|SM-T9000|SM-P9000|SM-T705Y|SM-T805', // SCH-P709|SCH-P729|SM-T2558|GT-I9205 - Samsung Mega - treat them like a regular phone. + // http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html + 'Kindle' => 'Kindle|Silk.*Accelerated|Android.*\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI)\b', + // Only the Surface tablets with Windows RT are considered mobile. + // http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx + 'SurfaceTablet' => 'Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)', + // http://shopping1.hp.com/is-bin/INTERSHOP.enfinity/WFS/WW-USSMBPublicStore-Site/en_US/-/USD/ViewStandardCatalog-Browse?CatalogCategoryID=JfIQ7EN5lqMAAAEyDcJUDwMT + 'HPTablet' => 'HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10', + // Watch out for PadFone, see #132. + // http://www.asus.com/de/Tablets_Mobile/Memo_Pad_Products/ + 'AsusTablet' => '^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\bK00F\b|\bK00C\b|\bK00E\b|\bK00L\b|TX201LA|ME176C|ME102A|\bM80TA\b|ME372CL|ME560CG|ME372CG|ME302KL| K010 | K017 |ME572C|ME103K|ME170C|ME171C|\bME70C\b|ME581C|ME581CL|ME8510C|ME181C', + 'BlackBerryTablet' => 'PlayBook|RIM Tablet', + 'HTCtablet' => 'HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410', + 'MotorolaTablet' => 'xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617', + 'NookTablet' => 'Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2', + // http://www.acer.ro/ac/ro/RO/content/drivers + // http://www.packardbell.co.uk/pb/en/GB/content/download (Packard Bell is part of Acer) + // http://us.acer.com/ac/en/US/content/group/tablets + // http://www.acer.de/ac/de/DE/content/models/tablets/ + // Can conflict with Micromax and Motorola phones codes. + 'AcerTablet' => 'Android.*; \b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\b|W3-810|\bA3-A10\b|\bA3-A11\b', + // http://eu.computers.toshiba-europe.com/innovation/family/Tablets/1098744/banner_id/tablet_footerlink/ + // http://us.toshiba.com/tablets/tablet-finder + // http://www.toshiba.co.jp/regza/tablet/ + 'ToshibaTablet' => 'Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO', + // http://www.nttdocomo.co.jp/english/service/developer/smart_phone/technical_info/spec/index.html + // http://www.lg.com/us/tablets + 'LGTablet' => '\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\b', + 'FujitsuTablet' => 'Android.*\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\b', + // Prestigio Tablets http://www.prestigio.com/support + 'PrestigioTablet' => 'PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C|PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD', + // http://support.lenovo.com/en_GB/downloads/default.page?# + 'LenovoTablet' => 'Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)', + // http://www.dell.com/support/home/us/en/04/Products/tab_mob/tablets + 'DellTablet' => 'Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7', + // http://www.yarvik.com/en/matrix/tablets/ + 'YarvikTablet' => 'Android.*\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152|TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211|TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\b', + 'MedionTablet' => 'Android.*\bOYO\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB', + 'ArnovaTablet' => 'AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2', + // http://www.intenso.de/kategorie_en.php?kategorie=33 + // @todo: http://www.nbhkdz.com/read/b8e64202f92a2df129126bff.html - investigate + 'IntensoTablet' => 'INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004', + // IRU.ru Tablets http://www.iru.ru/catalog/soho/planetable/ + 'IRUTablet' => 'M702pro', + 'MegafonTablet' => 'MegaFon V9|\bZTE V9\b|Android.*\bMT7A\b', + // http://www.e-boda.ro/tablete-pc.html + 'EbodaTablet' => 'E-Boda (Supreme|Impresspeed|Izzycomm|Essential)', + // http://www.allview.ro/produse/droseries/lista-tablete-pc/ + 'AllViewTablet' => 'Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)', + // http://wiki.archosfans.com/index.php?title=Main_Page + 'ArchosTablet' => '\b(101G9|80G9|A101IT)\b|Qilive 97R|Archos5|\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\b', + // http://www.ainol.com/plugin.php?identifier=ainol&module=product + 'AinolTablet' => 'NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark', + // @todo: inspect http://esupport.sony.com/US/p/select-system.pl?DIRECTOR=DRIVER + // Readers http://www.atsuhiro-me.net/ebook/sony-reader/sony-reader-web-browser + // http://www.sony.jp/support/tablet/ + 'SonyTablet' => 'Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321|EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP612', + // http://www.support.philips.com/support/catalog/worldproducts.jsp?userLanguage=en&userCountry=cn&categoryid=3G_LTE_TABLET_SU_CN_CARE&title=3G%20tablets%20/%20LTE%20range&_dyncharset=UTF-8 + 'PhilipsTablet' => '\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\b', + // db + http://www.cube-tablet.com/buy-products.html + 'CubeTablet' => 'Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT', + // http://www.cobyusa.com/?p=pcat&pcat_id=3001 + 'CobyTablet' => 'MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010', + // http://www.match.net.cn/products.asp + 'MIDTablet' => 'M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800|MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733', + // http://www.msi.com/support + // @todo Research the Windows Tablets. + 'MSITablet' => 'MSI \b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\b', + // @todo http://www.kyoceramobile.com/support/drivers/ + // 'KyoceraTablet' => null, + // @todo http://intexuae.com/index.php/category/mobile-devices/tablets-products/ + // 'IntextTablet' => null, + // http://pdadb.net/index.php?m=pdalist&list=SMiT (NoName Chinese Tablets) + // http://www.imp3.net/14/show.php?itemid=20454 + 'SMiTTablet' => 'Android.*(\bMID\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)', + // http://www.rock-chips.com/index.php?do=prod&pid=2 + 'RockChipTablet' => 'Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A', + // http://www.fly-phone.com/devices/tablets/ ; http://www.fly-phone.com/service/ + 'FlyTablet' => 'IQ310|Fly Vision', + // http://www.bqreaders.com/gb/tablets-prices-sale.html + 'bqTablet' => '(bq)?.*(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris E10)|Maxwell.*Lite|Maxwell.*Plus', + // http://www.huaweidevice.com/worldwide/productFamily.do?method=index&directoryId=5011&treeId=3290 + // http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index&directoryId=3372&treeId=0&tb=1&type=software (including legacy tablets) + 'HuaweiTablet' => 'MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim', + // Nec or Medias Tab + 'NecTablet' => '\bN-06D|\bN-08D', + // Pantech Tablets: http://www.pantechusa.com/phones/ + 'PantechTablet' => 'Pantech.*P4100', + // Broncho Tablets: http://www.broncho.cn/ (hard to find) + 'BronchoTablet' => 'Broncho.*(N701|N708|N802|a710)', + // http://versusuk.com/support.html + 'VersusTablet' => 'TOUCHPAD.*[78910]|\bTOUCHTAB\b', + // http://www.zync.in/index.php/our-products/tablet-phablets + 'ZyncTablet' => 'z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900', + // http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/ + 'PositivoTablet' => 'TB07STA|TB10STA|TB07FTA|TB10FTA', + // https://www.nabitablet.com/ + 'NabiTablet' => 'Android.*\bNabi', + 'KoboTablet' => 'Kobo Touch|\bK080\b|\bVox\b Build|\bArc\b Build', + // French Danew Tablets http://www.danew.com/produits-tablette.php + 'DanewTablet' => 'DSlide.*\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\b', + // Texet Tablets and Readers http://www.texet.ru/tablet/ + 'TexetTablet' => 'NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020|TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A|TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD|TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE', + // Avoid detecting 'PLAYSTATION 3' as mobile. + 'PlaystationTablet' => 'Playstation.*(Portable|Vita)', + // http://www.trekstor.de/surftabs.html + 'TrekstorTablet' => 'ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab', + // http://www.pyleaudio.com/Products.aspx?%2fproducts%2fPersonal-Electronics%2fTablets + 'PyleAudioTablet' => '\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\b', + // http://www.advandigital.com/index.php?link=content-product&jns=JP001 + // because of the short codenames we have to include whitespaces to reduce the possible conflicts. + 'AdvanTablet' => 'Android.* \b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\b ', + // http://www.danytech.com/category/tablet-pc + 'DanyTechTablet' => 'Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1', + // http://www.galapad.net/product.html + 'GalapadTablet' => 'Android.*\bG1\b', + // http://www.micromaxinfo.com/tablet/funbook + 'MicromaxTablet' => 'Funbook|Micromax.*\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\b', + // http://www.karbonnmobiles.com/products_tablet.php + 'KarbonnTablet' => 'Android.*\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\b', + // http://www.myallfine.com/Products.asp + 'AllFineTablet' => 'Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide', + // http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr= + 'PROSCANTablet' => '\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K|PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082|PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\b', + // http://www.yonesnav.com/products/products.php + 'YONESTablet' => 'BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026', + // http://www.cjshowroom.com/eproducts.aspx?classcode=004001001 + // China manufacturer makes tablets for different small brands (eg. http://www.zeepad.net/index.html) + 'ChangJiaTablet' => 'TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106|TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205|TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503', + // http://www.gloryunion.cn/products.asp + // http://www.allwinnertech.com/en/apply/mobile.html + // http://www.ptcl.com.pk/pd_content.php?pd_id=284 (EVOTAB) + // @todo: Softwiner tablets? + // aka. Cute or Cool tablets. Not sure yet, must research to avoid collisions. + 'GUTablet' => 'TX-A1301|TX-M9002|Q702|kf026', // A12R|D75A|D77|D79|R83|A95|A106C|R15|A75|A76|D71|D72|R71|R73|R77|D82|R85|D92|A97|D92|R91|A10F|A77F|W71F|A78F|W78F|W81F|A97F|W91F|W97F|R16G|C72|C73E|K72|K73|R96G + // http://www.pointofview-online.com/showroom.php?shop_mode=product_listing&category_id=118 + 'PointOfViewTablet' => 'TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945|TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10', + // http://www.overmax.pl/pl/katalog-produktow,p8/tablety,c14/ + // @todo: add more tests. + 'OvermaxTablet' => 'OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)', + // http://hclmetablet.com/India/index.php + 'HCLTablet' => 'HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync', + // http://www.edigital.hu/Tablet_es_e-book_olvaso/Tablet-c18385.html + 'DPSTablet' => 'DPS Dream 9|DPS Dual 7', + // http://www.visture.com/index.asp + 'VistureTablet' => 'V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10', + // http://www.mijncresta.nl/tablet + 'CrestaTablet' => 'CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989', + // MediaTek - http://www.mediatek.com/_en/01_products/02_proSys.php?cata_sn=1&cata1_sn=1&cata2_sn=309 + 'MediatekTablet' => '\bMT8125|MT8389|MT8135|MT8377\b', + // Concorde tab + 'ConcordeTablet' => 'Concorde([ ]+)?Tab|ConCorde ReadMan', + // GoClever Tablets - http://www.goclever.com/uk/products,c1/tablet,c5/ + 'GoCleverTablet' => 'GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76|TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2|TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042', + // Modecom Tablets - http://www.modecom.eu/tablets/portal/ + 'ModecomTablet' => 'FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702|FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003', + // Vonino Tablets - http://www.vonino.eu/tablets + 'VoninoTablet' => '\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS|Android.*\bQ8\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\b', + // ECS Tablets - http://www.ecs.com.tw/ECSWebSite/Product/Product_Tablet_List.aspx?CategoryID=14&MenuID=107&childid=M_107&LanID=0 + 'ECSTablet' => 'V07OT2|TM105A|S10OT1|TR10CS1', + // Storex Tablets - http://storex.fr/espace_client/support.html + // @note: no need to add all the tablet codes since they are guided by the first regex. + 'StorexTablet' => 'eZee[_\']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab', + // Generic Vodafone tablets. + 'VodafoneTablet' => 'SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7', + // French tablets - Essentiel B http://www.boulanger.fr/tablette_tactile_e-book/tablette_tactile_essentiel_b/cl_68908.htm?multiChoiceToDelete=brand&mc_brand=essentielb + // Aka: http://www.essentielb.fr/ + 'EssentielBTablet' => 'Smart[ \']?TAB[ ]+?[0-9]+|Family[ \']?TAB2', + // Ross & Moor - http://ross-moor.ru/ + 'RossMoorTablet' => 'RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711', + // i-mobile http://product.i-mobilephone.com/Mobile_Device + 'iMobileTablet' => 'i-mobile i-note', + // http://www.tolino.de/de/vergleichen/ + 'TolinoTablet' => 'tolino tab [0-9.]+|tolino shine', + // AudioSonic - a Kmart brand + // http://www.kmart.com.au/webapp/wcs/stores/servlet/Search?langId=-1&storeId=10701&catalogId=10001&categoryId=193001&pageSize=72¤tPage=1&searchCategory=193001%2b4294965664&sortBy=p_MaxPrice%7c1 + 'AudioSonicTablet' => '\bC-22Q|T7-QC|T-17B|T-17P\b', + // AMPE Tablets - http://www.ampe.com.my/product-category/tablets/ + // @todo: add them gradually to avoid conflicts. + 'AMPETablet' => 'Android.* A78 ', + // Skk Mobile - http://skkmobile.com.ph/product_tablets.php + 'SkkTablet' => 'Android.* (SKYPAD|PHOENIX|CYCLOPS)', + // Tecno Mobile (only tablet) - http://www.tecno-mobile.com/index.php/product?filterby=smart&list_order=all&page=1 + 'TecnoTablet' => 'TECNO P9', + // JXD (consoles & tablets) - http://jxd.hk/products.asp?selectclassid=009008&clsid=3 + 'JXDTablet' => 'Android.*\b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603|S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\b', + // i-Joy tablets - http://www.i-joy.es/en/cat/products/tablets/ + 'iJoyTablet' => 'Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7|Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst|Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam|Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)', + // http://www.intracon.eu/tablet + 'FX2Tablet' => 'FX2 PAD7|FX2 PAD10', + // http://www.xoro.de/produkte/ + // @note: Might be the same brand with 'Simply tablets' + 'XoroTablet' => 'KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790|PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032|TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151', + // http://www1.viewsonic.com/products/computing/tablets/ + 'ViewsonicTablet' => 'ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a', + // http://www.odys.de/web/internet-tablet_en.html + 'OdysTablet' => 'LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\bXELIO\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10', + // http://www.captiva-power.de/products.html#tablets-en + 'CaptivaTablet' => 'CAPTIVA PAD', + // IconBIT - http://www.iconbit.com/products/tablets/ + 'IconbitTablet' => 'NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S', + // http://www.teclast.com/topic.php?channelID=70&topicID=140&pid=63 + 'TeclastTablet' => 'T98 4G|\bP80\b|\bX90HD\b|X98 Air|X98 Air 3G|\bX89\b|P80 3G|\bX80h\b|P98 Air|\bX89HD\b|P98 3G|\bP90HD\b|P89 3G|X98 3G|\bP70h\b|P79HD 3G|G18d 3G|\bP79HD\b|\bP89s\b|\bA88\b|\bP10HD\b|\bP19HD\b|G18 3G|\bP78HD\b|\bA78\b|\bP75\b|G17s 3G|G17h 3G|\bP85t\b|\bP90\b|\bP11\b|\bP98t\b|\bP98HD\b|\bG18d\b|\bP85s\b|\bP11HD\b|\bP88s\b|\bA80HD\b|\bA80se\b|\bA10h\b|\bP89\b|\bP78s\b|\bG18\b|\bP85\b|\bA70h\b|\bA70\b|\bG17\b|\bP18\b|\bA80s\b|\bA11s\b|\bP88HD\b|\bA80h\b|\bP76s\b|\bP76h\b|\bP98\b|\bA10HD\b|\bP78\b|\bP88\b|\bA11\b|\bA10t\b|\bP76a\b|\bP76t\b|\bP76e\b|\bP85HD\b|\bP85a\b|\bP86\b|\bP75HD\b|\bP76v\b|\bA12\b|\bP75a\b|\bA15\b|\bP76Ti\b|\bP81HD\b|\bA10\b|\bT760VE\b|\bT720HD\b|\bP76\b|\bP73\b|\bP71\b|\bP72\b|\bT720SE\b|\bC520Ti\b|\bT760\b|\bT720VE\b|T720-3GE|T720-WiFi', + // Onda - http://www.onda-tablet.com/buy-android-onda.html?dir=desc&limit=all&order=price + 'OndaTablet' => '\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811|V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819|V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\b[\s]+', + 'JaytechTablet' => 'TPC-PA762', + 'BlaupunktTablet' => 'Endeavour 800NG|Endeavour 1010', + // http://www.digma.ru/support/download/ + // @todo: Ebooks also (if requested) + 'DigmaTablet' => '\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\b', + // http://www.evolioshop.com/ro/tablete-pc.html + // http://www.evolio.ro/support/downloads_static.html?cat=2 + // @todo: Research some more + 'EvolioTablet' => 'ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\bEvotab\b|\bNeura\b', + // @todo http://www.lavamobiles.com/tablets-data-cards + 'LavaTablet' => 'QPAD E704|\bIvoryS\b|E-TAB IVORY|\bE-TAB\b', + // https://www.celkonmobiles.com/?_a=categoryphones&sid=2 + 'CelkonTablet' => 'CT695|CT888|CT[\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\bCT-1\b', + // http://www.wolderelectronics.com/productos/manuales-y-guias-rapidas/categoria-2-miTab + 'WolderTablet' => 'miTab \b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT|EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND|BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\b', + // http://www.mi.com/en + 'MiTablet' => '\bMI PAD\b|\bHM NOTE 1W\b', + // http://www.nbru.cn/index.html + 'NibiruTablet' => 'Nibiru M1|Nibiru Jupiter One', + // http://navroad.com/products/produkty/tablety/ + 'NexoTablet' => 'NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI', + // http://www.datawind.com/ubislate/ + 'UbislateTablet' => 'UbiSlate[\s]?7C', + // http://www.pocketbook-int.com/ru/support + 'PocketBookTablet' => 'Pocketbook', + // http://www.tesco.com/direct/hudl/ + 'Hudl' => 'Hudl HT7S3', + // http://www.telstra.com.au/home-phone/thub-2/ + 'TelstraTablet' => 'T-Hub2', + 'GenericTablet' => 'Android.*\b97D\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\bA7EB\b|CatNova8|A1_07|CT704|CT1002|\bM721\b|rk30sdk|\bEVOTAB\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab|\bM6pro\b|CT1020W|arc 10HD|\bJolla\b' + ); + + /** + * List of mobile Operating Systems. + * + * @var array + */ + protected static $operatingSystems = array( + 'AndroidOS' => 'Android', + 'BlackBerryOS' => 'blackberry|\bBB10\b|rim tablet os', + 'PalmOS' => 'PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino', + 'SymbianOS' => 'Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\bS60\b', + // @reference: http://en.wikipedia.org/wiki/Windows_Mobile + 'WindowsMobileOS' => 'Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Window Mobile|Windows Phone [0-9.]+|WCE;', + // @reference: http://en.wikipedia.org/wiki/Windows_Phone + // http://wifeng.cn/?r=blog&a=view&id=106 + // http://nicksnettravels.builttoroam.com/post/2011/01/10/Bogus-Windows-Phone-7-User-Agent-String.aspx + // http://msdn.microsoft.com/library/ms537503.aspx + 'WindowsPhoneOS' => 'Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;', + 'iOS' => '\biPhone.*Mobile|\biPod|\biPad', + // http://en.wikipedia.org/wiki/MeeGo + // @todo: research MeeGo in UAs + 'MeeGoOS' => 'MeeGo', + // http://en.wikipedia.org/wiki/Maemo + // @todo: research Maemo in UAs + 'MaemoOS' => 'Maemo', + 'JavaOS' => 'J2ME/|\bMIDP\b|\bCLDC\b', // '|Java/' produces bug #135 + 'webOS' => 'webOS|hpwOS', + 'badaOS' => '\bBada\b', + 'BREWOS' => 'BREW', + ); + + /** + * List of mobile User Agents. + * + * @var array + */ + protected static $browsers = array( + // @reference: https://developers.google.com/chrome/mobile/docs/user-agent + 'Chrome' => '\bCrMo\b|CriOS|Android.*Chrome/[.0-9]* (Mobile)?', + 'Dolfin' => '\bDolfin\b', + 'Opera' => 'Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR/[0-9.]+|Coast/[0-9.]+', + 'Skyfire' => 'Skyfire', + 'IE' => 'IEMobile|MSIEMobile', // |Trident/[.0-9]+ + 'Firefox' => 'fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile', + 'Bolt' => 'bolt', + 'TeaShark' => 'teashark', + 'Blazer' => 'Blazer', + // @reference: http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/OptimizingforSafarioniPhone/OptimizingforSafarioniPhone.html#//apple_ref/doc/uid/TP40006517-SW3 + 'Safari' => 'Version.*Mobile.*Safari|Safari.*Mobile|MobileSafari', + // http://en.wikipedia.org/wiki/Midori_(web_browser) + //'Midori' => 'midori', + 'Tizen' => 'Tizen', + 'UCBrowser' => 'UC.*Browser|UCWEB', + 'baiduboxapp' => 'baiduboxapp', + 'baidubrowser' => 'baidubrowser', + // https://github.com/serbanghita/Mobile-Detect/issues/7 + 'DiigoBrowser' => 'DiigoBrowser', + // http://www.puffinbrowser.com/index.php + 'Puffin' => 'Puffin', + // http://mercury-browser.com/index.html + 'Mercury' => '\bMercury\b', + // http://en.wikipedia.org/wiki/Obigo_Browser + 'ObigoBrowser' => 'Obigo', + // http://en.wikipedia.org/wiki/NetFront + 'NetFront' => 'NF-Browser', + // @reference: http://en.wikipedia.org/wiki/Minimo + // http://en.wikipedia.org/wiki/Vision_Mobile_Browser + 'GenericBrowser' => 'NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger', + ); + + /** + * Utilities. + * + * @var array + */ + protected static $utilities = array( + // Experimental. When a mobile device wants to switch to 'Desktop Mode'. + // http://scottcate.com/technology/windows-phone-8-ie10-desktop-or-mobile/ + // https://github.com/serbanghita/Mobile-Detect/issues/57#issuecomment-15024011 + // https://developers.facebook.com/docs/sharing/best-practices + 'Bot' => 'Googlebot|facebookexternalhit|AdsBot-Google|Google Keyword Suggestion|Facebot|YandexBot|bingbot|ia_archiver|AhrefsBot|Ezooms|GSLFbot|WBSearchBot|Twitterbot|TweetmemeBot|Twikle|PaperLiBot|Wotbox|UnwindFetchor', + 'MobileBot' => 'Googlebot-Mobile|AdsBot-Google-Mobile|YahooSeeker/M1A1-R2D2', + 'DesktopMode' => 'WPDesktop', + 'TV' => 'SonyDTV|HbbTV', // experimental + 'WebKit' => '(webkit)[ /]([\w.]+)', + // @todo: Include JXD consoles. + 'Console' => '\b(Nintendo|Nintendo WiiU|Nintendo 3DS|PLAYSTATION|Xbox)\b', + 'Watch' => 'SM-V700', + ); + + /** + * All possible HTTP headers that represent the + * User-Agent string. + * + * @var array + */ + protected static $uaHttpHeaders = array( + // The default User-Agent string. + 'HTTP_USER_AGENT', + // Header can occur on devices using Opera Mini. + 'HTTP_X_OPERAMINI_PHONE_UA', + // Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/ + 'HTTP_X_DEVICE_USER_AGENT', + 'HTTP_X_ORIGINAL_USER_AGENT', + 'HTTP_X_SKYFIRE_PHONE', + 'HTTP_X_BOLT_PHONE_UA', + 'HTTP_DEVICE_STOCK_UA', + 'HTTP_X_UCBROWSER_DEVICE_UA' + ); + + /** + * The individual segments that could exist in a User-Agent string. VER refers to the regular + * expression defined in the constant self::VER. + * + * @var array + */ + protected static $properties = array( + // Build + 'Mobile' => 'Mobile/[VER]', + 'Build' => 'Build/[VER]', + 'Version' => 'Version/[VER]', + 'VendorID' => 'VendorID/[VER]', + // Devices + 'iPad' => 'iPad.*CPU[a-z ]+[VER]', + 'iPhone' => 'iPhone.*CPU[a-z ]+[VER]', + 'iPod' => 'iPod.*CPU[a-z ]+[VER]', + //'BlackBerry' => array('BlackBerry[VER]', 'BlackBerry [VER];'), + 'Kindle' => 'Kindle/[VER]', + // Browser + 'Chrome' => array('Chrome/[VER]', 'CriOS/[VER]', 'CrMo/[VER]'), + 'Coast' => array('Coast/[VER]'), + 'Dolfin' => 'Dolfin/[VER]', + // @reference: https://developer.mozilla.org/en-US/docs/User_Agent_Strings_Reference + 'Firefox' => 'Firefox/[VER]', + 'Fennec' => 'Fennec/[VER]', + // http://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx + // https://msdn.microsoft.com/en-us/library/ie/hh869301(v=vs.85).aspx + 'IE' => array('IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'Trident/[0-9.]+;.*rv:[VER]'), + // http://en.wikipedia.org/wiki/NetFront + 'NetFront' => 'NetFront/[VER]', + 'NokiaBrowser' => 'NokiaBrowser/[VER]', + 'Opera' => array(' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]'), + 'Opera Mini' => 'Opera Mini/[VER]', + 'Opera Mobi' => 'Version/[VER]', + 'UC Browser' => 'UC Browser[VER]', + 'MQQBrowser' => 'MQQBrowser/[VER]', + 'MicroMessenger' => 'MicroMessenger/[VER]', + 'baiduboxapp' => 'baiduboxapp/[VER]', + 'baidubrowser' => 'baidubrowser/[VER]', + 'Iron' => 'Iron/[VER]', + // @note: Safari 7534.48.3 is actually Version 5.1. + // @note: On BlackBerry the Version is overwriten by the OS. + 'Safari' => array('Version/[VER]', 'Safari/[VER]'), + 'Skyfire' => 'Skyfire/[VER]', + 'Tizen' => 'Tizen/[VER]', + 'Webkit' => 'webkit[ /][VER]', + // Engine + 'Gecko' => 'Gecko/[VER]', + 'Trident' => 'Trident/[VER]', + 'Presto' => 'Presto/[VER]', + // OS + 'iOS' => ' \bi?OS\b [VER][ ;]{1}', + 'Android' => 'Android [VER]', + 'BlackBerry' => array('BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'), + 'BREW' => 'BREW [VER]', + 'Java' => 'Java/[VER]', + // @reference: http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/08/29/introducing-the-ie9-on-windows-phone-mango-user-agent-string.aspx + // @reference: http://en.wikipedia.org/wiki/Windows_NT#Releases + 'Windows Phone OS' => array('Windows Phone OS [VER]', 'Windows Phone [VER]'), + 'Windows Phone' => 'Windows Phone [VER]', + 'Windows CE' => 'Windows CE/[VER]', + // http://social.msdn.microsoft.com/Forums/en-US/windowsdeveloperpreviewgeneral/thread/6be392da-4d2f-41b4-8354-8dcee20c85cd + 'Windows NT' => 'Windows NT [VER]', + 'Symbian' => array('SymbianOS/[VER]', 'Symbian/[VER]'), + 'webOS' => array('webOS/[VER]', 'hpwOS/[VER];'), + ); + + /** + * Construct an instance of this class. + * + * @param array $headers Specify the headers as injection. Should be PHP _SERVER flavored. + * If left empty, will use the global _SERVER['HTTP_*'] vars instead. + * @param string $userAgent Inject the User-Agent header. If null, will use HTTP_USER_AGENT + * from the $headers array instead. + */ + public function __construct( + array $headers = null, $userAgent = null + ) + { + $this->setHttpHeaders($headers); + $this->setUserAgent($userAgent); + } + + /** + * Get the current script version. + * This is useful for the demo.php file, + * so people can check on what version they are testing + * for mobile devices. + * + * @return string The version number in semantic version format. + */ + public static function getScriptVersion() + { + return self::VERSION; + } + + /** + * Set the HTTP Headers. Must be PHP-flavored. This method will reset existing headers. + * + * @param array $httpHeaders The headers to set. If null, then using PHP's _SERVER to extract + * the headers. The default null is left for backwards compatibilty. + */ + public function setHttpHeaders($httpHeaders = null) + { + // use global _SERVER if $httpHeaders aren't defined + if (!is_array($httpHeaders) || !count($httpHeaders)) { + $httpHeaders = $_SERVER; + } + + // clear existing headers + $this->httpHeaders = array(); + + // Only save HTTP headers. In PHP land, that means only _SERVER vars that + // start with HTTP_. + foreach ($httpHeaders as $key => $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $this->httpHeaders[$key] = $value; + } + } + + // In case we're dealing with CloudFront, we need to know. + $this->setCfHeaders($httpHeaders); + } + + /** + * Retrieves the HTTP headers. + * + * @return array + */ + public function getHttpHeaders() + { + return $this->httpHeaders; + } + + /** + * Retrieves a particular header. If it doesn't exist, no exception/error is caused. + * Simply null is returned. + * + * @param string $header The name of the header to retrieve. Can be HTTP compliant such as + * "User-Agent" or "X-Device-User-Agent" or can be php-esque with the + * all-caps, HTTP_ prefixed, underscore seperated awesomeness. + * + * @return string|null The value of the header. + */ + public function getHttpHeader($header) + { + // are we using PHP-flavored headers? + if (strpos($header, '_') === false) { + $header = str_replace('-', '_', $header); + $header = strtoupper($header); + } + + // test the alternate, too + $altHeader = 'HTTP_' . $header; + + //Test both the regular and the HTTP_ prefix + if (isset($this->httpHeaders[$header])) { + return $this->httpHeaders[$header]; + } elseif (isset($this->httpHeaders[$altHeader])) { + return $this->httpHeaders[$altHeader]; + } + + return null; + } + + public function getMobileHeaders() + { + return self::$mobileHeaders; + } + + /** + * Get all possible HTTP headers that + * can contain the User-Agent string. + * + * @return array List of HTTP headers. + */ + public function getUaHttpHeaders() + { + return self::$uaHttpHeaders; + } + + /** + * Set CloudFront headers + * http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-device + * + * @param array $cfHeaders List of HTTP headers + * + * @return boolean If there were CloudFront headers to be set + */ + public function setCfHeaders($cfHeaders = null) + { + // use global _SERVER if $cfHeaders aren't defined + if (!is_array($cfHeaders) || !count($cfHeaders)) { + $cfHeaders = $_SERVER; + } + + // clear existing headers + $this->cloudfrontHeaders = array(); + + // Only save CLOUDFRONT headers. In PHP land, that means only _SERVER vars that + // start with cloudfront-. + $response = false; + foreach ($cfHeaders as $key => $value) { + if (substr(strtolower($key), 0, 16) === 'http_cloudfront_') { + $this->cloudfrontHeaders[strtoupper($key)] = $value; + $response = true; + } + } + + return $response; + } + + /** + * Retrieves the cloudfront headers. + * + * @return array + */ + public function getCfHeaders() + { + return $this->cloudfrontHeaders; + } + + /** + * Set the User-Agent to be used. + * + * @param string $userAgent The user agent string to set. + * + * @return string|null + */ + public function setUserAgent($userAgent = null) + { + // Invalidate cache due to #375 + $this->cache = array(); + + if (false === empty($userAgent)) { + return $this->userAgent = $userAgent; + } else { + $this->userAgent = null; + foreach ($this->getUaHttpHeaders() as $altHeader) { + if (false === empty($this->httpHeaders[$altHeader])) { // @todo: should use getHttpHeader(), but it would be slow. (Serban) + $this->userAgent .= $this->httpHeaders[$altHeader] . " "; + } + } + + if (!empty($this->userAgent)) { + return $this->userAgent = trim($this->userAgent); + } + } + + if (count($this->getCfHeaders()) > 0) { + return $this->userAgent = 'Amazon CloudFront'; + } + return $this->userAgent = null; + } + + /** + * Retrieve the User-Agent. + * + * @return string|null The user agent if it's set. + */ + public function getUserAgent() + { + return $this->userAgent; + } + + /** + * Set the detection type. Must be one of self::DETECTION_TYPE_MOBILE or + * self::DETECTION_TYPE_EXTENDED. Otherwise, nothing is set. + * + * @deprecated since version 2.6.9 + * + * @param string $type The type. Must be a self::DETECTION_TYPE_* constant. The default + * parameter is null which will default to self::DETECTION_TYPE_MOBILE. + */ + public function setDetectionType($type = null) + { + if ($type === null) { + $type = self::DETECTION_TYPE_MOBILE; + } + + if ($type !== self::DETECTION_TYPE_MOBILE && $type !== self::DETECTION_TYPE_EXTENDED) { + return; + } + + $this->detectionType = $type; + } + + public function getMatchingRegex() + { + return $this->matchingRegex; + } + + public function getMatchesArray() + { + return $this->matchesArray; + } + + /** + * Retrieve the list of known phone devices. + * + * @return array List of phone devices. + */ + public static function getPhoneDevices() + { + return self::$phoneDevices; + } + + /** + * Retrieve the list of known tablet devices. + * + * @return array List of tablet devices. + */ + public static function getTabletDevices() + { + return self::$tabletDevices; + } + + /** + * Alias for getBrowsers() method. + * + * @return array List of user agents. + */ + public static function getUserAgents() + { + return self::getBrowsers(); + } + + /** + * Retrieve the list of known browsers. Specifically, the user agents. + * + * @return array List of browsers / user agents. + */ + public static function getBrowsers() + { + return self::$browsers; + } + + /** + * Retrieve the list of known utilities. + * + * @return array List of utilities. + */ + public static function getUtilities() + { + return self::$utilities; + } + + /** + * Method gets the mobile detection rules. This method is used for the magic methods $detect->is*(). + * + * @deprecated since version 2.6.9 + * + * @return array All the rules (but not extended). + */ + public static function getMobileDetectionRules() + { + static $rules; + + if (!$rules) { + $rules = array_merge( + self::$phoneDevices, self::$tabletDevices, self::$operatingSystems, self::$browsers + ); + } + + return $rules; + } + + /** + * Method gets the mobile detection rules + utilities. + * The reason this is separate is because utilities rules + * don't necessary imply mobile. This method is used inside + * the new $detect->is('stuff') method. + * + * @deprecated since version 2.6.9 + * + * @return array All the rules + extended. + */ + public function getMobileDetectionRulesExtended() + { + static $rules; + + if (!$rules) { + // Merge all rules together. + $rules = array_merge( + self::$phoneDevices, self::$tabletDevices, self::$operatingSystems, self::$browsers, self::$utilities + ); + } + + return $rules; + } + + /** + * Retrieve the current set of rules. + * + * @deprecated since version 2.6.9 + * + * @return array + */ + public function getRules() + { + if ($this->detectionType == self::DETECTION_TYPE_EXTENDED) { + return self::getMobileDetectionRulesExtended(); + } else { + return self::getMobileDetectionRules(); + } + } + + /** + * Retrieve the list of mobile operating systems. + * + * @return array The list of mobile operating systems. + */ + public static function getOperatingSystems() + { + return self::$operatingSystems; + } + + /** + * Check the HTTP headers for signs of mobile. + * This is the fastest mobile check possible; it's used + * inside isMobile() method. + * + * @return bool + */ + public function checkHttpHeadersForMobile() + { + + foreach ($this->getMobileHeaders() as $mobileHeader => $matchType) { + if (isset($this->httpHeaders[$mobileHeader])) { + if (is_array($matchType['matches'])) { + foreach ($matchType['matches'] as $_match) { + if (strpos($this->httpHeaders[$mobileHeader], $_match) !== false) { + return true; + } + } + + return false; + } else { + return true; + } + } + } + + return false; + } + + /** + * Magic overloading method. + * + * @method boolean is[...]() + * @param string $name + * @param array $arguments + * @return mixed + * @throws BadMethodCallException when the method doesn't exist and doesn't start with 'is' + */ + public function __call($name, $arguments) + { + // make sure the name starts with 'is', otherwise + if (substr($name, 0, 2) !== 'is') { + throw new BadMethodCallException("No such method exists: $name"); + } + + $this->setDetectionType(self::DETECTION_TYPE_MOBILE); + + $key = substr($name, 2); + + return $this->matchUAAgainstKey($key); + } + + /** + * Find a detection rule that matches the current User-agent. + * + * @param null $userAgent deprecated + * @return boolean + */ + protected function matchDetectionRulesAgainstUA($userAgent = null) + { + // Begin general search. + foreach ($this->getRules() as $_regex) { + if (empty($_regex)) { + continue; + } + + if ($this->match($_regex, $userAgent)) { + return true; + } + } + + return false; + } + + /** + * Search for a certain key in the rules array. + * If the key is found the try to match the corresponding + * regex against the User-Agent. + * + * @param string $key + * + * @return boolean + */ + protected function matchUAAgainstKey($key) + { + // Make the keys lowercase so we can match: isIphone(), isiPhone(), isiphone(), etc. + $key = strtolower($key); + if (false === isset($this->cache[$key])) { + + // change the keys to lower case + $_rules = array_change_key_case($this->getRules()); + + if (false === empty($_rules[$key])) { + $this->cache[$key] = $this->match($_rules[$key]); + } + + if (false === isset($this->cache[$key])) { + $this->cache[$key] = false; + } + } + + return $this->cache[$key]; + } + + /** + * Check if the device is mobile. + * Returns true if any type of mobile device detected, including special ones + * @param null $userAgent deprecated + * @param null $httpHeaders deprecated + * @return bool + */ + public function isMobile($userAgent = null, $httpHeaders = null) + { + + if ($httpHeaders) { + $this->setHttpHeaders($httpHeaders); + } + + if ($userAgent) { + $this->setUserAgent($userAgent); + } + + // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront' + if ($this->getUserAgent() === 'Amazon CloudFront') { + $cfHeaders = $this->getCfHeaders(); + if (array_key_exists('HTTP_CLOUDFRONT_IS_MOBILE_VIEWER', $cfHeaders) && $cfHeaders['HTTP_CLOUDFRONT_IS_MOBILE_VIEWER'] === 'true') { + return true; + } + } + + $this->setDetectionType(self::DETECTION_TYPE_MOBILE); + + if ($this->checkHttpHeadersForMobile()) { + return true; + } else { + return $this->matchDetectionRulesAgainstUA(); + } + } + + /** + * Check if the device is a tablet. + * Return true if any type of tablet device is detected. + * + * @param string $userAgent deprecated + * @param array $httpHeaders deprecated + * @return bool + */ + public function isTablet($userAgent = null, $httpHeaders = null) + { + // Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront' + if ($this->getUserAgent() === 'Amazon CloudFront') { + $cfHeaders = $this->getCfHeaders(); + if (array_key_exists('HTTP_CLOUDFRONT_IS_TABLET_VIEWER', $cfHeaders) && $cfHeaders['HTTP_CLOUDFRONT_IS_TABLET_VIEWER'] === 'true') { + return true; + } + } + + $this->setDetectionType(self::DETECTION_TYPE_MOBILE); + + foreach (self::$tabletDevices as $_regex) { + if ($this->match($_regex, $userAgent)) { + return true; + } + } + + return false; + } + + /** + * This method checks for a certain property in the + * userAgent. + * @todo: The httpHeaders part is not yet used. + * + * @param string $key + * @param string $userAgent deprecated + * @param string $httpHeaders deprecated + * @return bool|int|null + */ + public function is($key, $userAgent = null, $httpHeaders = null) + { + // Set the UA and HTTP headers only if needed (eg. batch mode). + if ($httpHeaders) { + $this->setHttpHeaders($httpHeaders); + } + + if ($userAgent) { + $this->setUserAgent($userAgent); + } + + $this->setDetectionType(self::DETECTION_TYPE_EXTENDED); + + return $this->matchUAAgainstKey($key); + } + + /** + * Some detection rules are relative (not standard), + * because of the diversity of devices, vendors and + * their conventions in representing the User-Agent or + * the HTTP headers. + * + * This method will be used to check custom regexes against + * the User-Agent string. + * + * @param $regex + * @param string $userAgent + * @return bool + * + * @todo: search in the HTTP headers too. + */ + public function match($regex, $userAgent = null) + { + $match = (bool) preg_match(sprintf('#%s#is', $regex), (false === empty($userAgent) ? $userAgent : $this->userAgent), $matches); + // If positive match is found, store the results for debug. + if ($match) { + $this->matchingRegex = $regex; + $this->matchesArray = $matches; + } + + return $match; + } + + /** + * Get the properties array. + * + * @return array + */ + public static function getProperties() + { + return self::$properties; + } + + /** + * Prepare the version number. + * + * @todo Remove the error supression from str_replace() call. + * + * @param string $ver The string version, like "2.6.21.2152"; + * + * @return float + */ + public function prepareVersionNo($ver) + { + $ver = str_replace(array('_', ' ', '/'), '.', $ver); + $arrVer = explode('.', $ver, 2); + + if (isset($arrVer[1])) { + $arrVer[1] = @str_replace('.', '', $arrVer[1]); // @todo: treat strings versions. + } + + return (float) implode('.', $arrVer); + } + + /** + * Check the version of the given property in the User-Agent. + * Will return a float number. (eg. 2_0 will return 2.0, 4.3.1 will return 4.31) + * + * @param string $propertyName The name of the property. See self::getProperties() array + * keys for all possible properties. + * @param string $type Either self::VERSION_TYPE_STRING to get a string value or + * self::VERSION_TYPE_FLOAT indicating a float value. This parameter + * is optional and defaults to self::VERSION_TYPE_STRING. Passing an + * invalid parameter will default to the this type as well. + * + * @return string|float The version of the property we are trying to extract. + */ + public function version($propertyName, $type = self::VERSION_TYPE_STRING) + { + if (empty($propertyName)) { + return false; + } + + // set the $type to the default if we don't recognize the type + if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT) { + $type = self::VERSION_TYPE_STRING; + } + + $properties = self::getProperties(); + + // Check if the property exists in the properties array. + if (true === isset($properties[$propertyName])) { + + // Prepare the pattern to be matched. + // Make sure we always deal with an array (string is converted). + $properties[$propertyName] = (array) $properties[$propertyName]; + + foreach ($properties[$propertyName] as $propertyMatchString) { + + $propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString); + + // Identify and extract the version. + preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match); + + if (false === empty($match[1])) { + $version = ($type == self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]); + + return $version; + } + } + } + + return false; + } + + /** + * Retrieve the mobile grading, using self::MOBILE_GRADE_* constants. + * + * @return string One of the self::MOBILE_GRADE_* constants. + */ + public function mobileGrade() + { + $isMobile = $this->isMobile(); + + if ( + // Apple iOS 4-7.0 – Tested on the original iPad (4.3 / 5.0), iPad 2 (4.3 / 5.1 / 6.1), iPad 3 (5.1 / 6.0), iPad Mini (6.1), iPad Retina (7.0), iPhone 3GS (4.3), iPhone 4 (4.3 / 5.1), iPhone 4S (5.1 / 6.0), iPhone 5 (6.0), and iPhone 5S (7.0) + $this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) >= 4.3 || + $this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) >= 4.3 || + $this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) >= 4.3 || + // Android 2.1-2.3 - Tested on the HTC Incredible (2.2), original Droid (2.2), HTC Aria (2.1), Google Nexus S (2.3). Functional on 1.5 & 1.6 but performance may be sluggish, tested on Google G1 (1.5) + // Android 3.1 (Honeycomb) - Tested on the Samsung Galaxy Tab 10.1 and Motorola XOOM + // Android 4.0 (ICS) - Tested on a Galaxy Nexus. Note: transition performance can be poor on upgraded devices + // Android 4.1 (Jelly Bean) - Tested on a Galaxy Nexus and Galaxy 7 + ( $this->version('Android', self::VERSION_TYPE_FLOAT) > 2.1 && $this->is('Webkit') ) || + // Windows Phone 7.5-8 - Tested on the HTC Surround (7.5), HTC Trophy (7.5), LG-E900 (7.5), Nokia 800 (7.8), HTC Mazaa (7.8), Nokia Lumia 520 (8), Nokia Lumia 920 (8), HTC 8x (8) + $this->version('Windows Phone OS', self::VERSION_TYPE_FLOAT) >= 7.5 || + // Tested on the Torch 9800 (6) and Style 9670 (6), BlackBerry® Torch 9810 (7), BlackBerry Z10 (10) + $this->is('BlackBerry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 6.0 || + // Blackberry Playbook (1.0-2.0) - Tested on PlayBook + $this->match('Playbook.*Tablet') || + // Palm WebOS (1.4-3.0) - Tested on the Palm Pixi (1.4), Pre (1.4), Pre 2 (2.0), HP TouchPad (3.0) + ( $this->version('webOS', self::VERSION_TYPE_FLOAT) >= 1.4 && $this->match('Palm|Pre|Pixi') ) || + // Palm WebOS 3.0 - Tested on HP TouchPad + $this->match('hp.*TouchPad') || + // Firefox Mobile 18 - Tested on Android 2.3 and 4.1 devices + ( $this->is('Firefox') && $this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 18 ) || + // Chrome for Android - Tested on Android 4.0, 4.1 device + ( $this->is('Chrome') && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 4.0 ) || + // Skyfire 4.1 - Tested on Android 2.3 device + ( $this->is('Skyfire') && $this->version('Skyfire', self::VERSION_TYPE_FLOAT) >= 4.1 && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 ) || + // Opera Mobile 11.5-12: Tested on Android 2.3 + ( $this->is('Opera') && $this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11.5 && $this->is('AndroidOS') ) || + // Meego 1.2 - Tested on Nokia 950 and N9 + $this->is('MeeGoOS') || + // Tizen (pre-release) - Tested on early hardware + $this->is('Tizen') || + // Samsung Bada 2.0 - Tested on a Samsung Wave 3, Dolphin browser + // @todo: more tests here! + $this->is('Dolfin') && $this->version('Bada', self::VERSION_TYPE_FLOAT) >= 2.0 || + // UC Browser - Tested on Android 2.3 device + ( ($this->is('UC Browser') || $this->is('Dolfin')) && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 ) || + // Kindle 3 and Fire - Tested on the built-in WebKit browser for each + ( $this->match('Kindle Fire') || + $this->is('Kindle') && $this->version('Kindle', self::VERSION_TYPE_FLOAT) >= 3.0 ) || + // Nook Color 1.4.1 - Tested on original Nook Color, not Nook Tablet + $this->is('AndroidOS') && $this->is('NookTablet') || + // Chrome Desktop 16-24 - Tested on OS X 10.7 and Windows 7 + $this->version('Chrome', self::VERSION_TYPE_FLOAT) >= 16 && !$isMobile || + // Safari Desktop 5-6 - Tested on OS X 10.7 and Windows 7 + $this->version('Safari', self::VERSION_TYPE_FLOAT) >= 5.0 && !$isMobile || + // Firefox Desktop 10-18 - Tested on OS X 10.7 and Windows 7 + $this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 10.0 && !$isMobile || + // Internet Explorer 7-9 - Tested on Windows XP, Vista and 7 + $this->version('IE', self::VERSION_TYPE_FLOAT) >= 7.0 && !$isMobile || + // Opera Desktop 10-12 - Tested on OS X 10.7 and Windows 7 + $this->version('Opera', self::VERSION_TYPE_FLOAT) >= 10 && !$isMobile + ) { + return self::MOBILE_GRADE_A; + } + + if ( + $this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) < 4.3 || + $this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) < 4.3 || + $this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) < 4.3 || + // Blackberry 5.0: Tested on the Storm 2 9550, Bold 9770 + $this->is('Blackberry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 5 && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) < 6 || + //Opera Mini (5.0-6.5) - Tested on iOS 3.2/4.3 and Android 2.3 + ($this->version('Opera Mini', self::VERSION_TYPE_FLOAT) >= 5.0 && $this->version('Opera Mini', self::VERSION_TYPE_FLOAT) <= 7.0 && + ($this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 || $this->is('iOS')) ) || + // Nokia Symbian^3 - Tested on Nokia N8 (Symbian^3), C7 (Symbian^3), also works on N97 (Symbian^1) + $this->match('NokiaN8|NokiaC7|N97.*Series60|Symbian/3') || + // @todo: report this (tested on Nokia N71) + $this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11 && $this->is('SymbianOS') + ) { + return self::MOBILE_GRADE_B; + } + + if ( + // Blackberry 4.x - Tested on the Curve 8330 + $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) <= 5.0 || + // Windows Mobile - Tested on the HTC Leo (WinMo 5.2) + $this->match('MSIEMobile|Windows CE.*Mobile') || $this->version('Windows Mobile', self::VERSION_TYPE_FLOAT) <= 5.2 || + // Tested on original iPhone (3.1), iPhone 3 (3.2) + $this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) <= 3.2 || + $this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) <= 3.2 || + $this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) <= 3.2 || + // Internet Explorer 7 and older - Tested on Windows XP + $this->version('IE', self::VERSION_TYPE_FLOAT) <= 7.0 && !$isMobile + ) { + return self::MOBILE_GRADE_C; + } + + // All older smartphone platforms and featurephones - Any device that doesn't support media queries + // will receive the basic, C grade experience. + return self::MOBILE_GRADE_C; + } + +} diff --git a/modman b/modman new file mode 100644 index 0000000..73b47a7 --- /dev/null +++ b/modman @@ -0,0 +1,20 @@ +app/code/community/Netresearch/OPS app/code/community/Netresearch/OPS +app/design/adminhtml/default/default/layout/ops.xml app/design/adminhtml/default/default/layout/ops.xml +app/design/adminhtml/default/default/template/ops app/design/adminhtml/default/default/template/ops +app/design/frontend/base/default/layout/ops.xml app/design/frontend/base/default/layout/ops.xml +app/design/frontend/base/default/template/ops app/design/frontend/base/default/template/ops +app/design/frontend/rwd/default/template/ops/customer/alias/list.phtml app/design/frontend/rwd/default/template/ops/customer/alias/list.phtml +app/etc/modules/Netresearch_OPS.xml app/etc/modules/Netresearch_OPS.xml +app/locale/de_DE/template/email/ops app/locale/de_DE/template/email/ops +app/locale/de_DE/Netresearch_OPS.csv app/locale/de_DE/Netresearch_OPS.csv +app/locale/en_US/template/email/ops app/locale/en_US/template/email/ops +app/locale/en_US/Netresearch_OPS.csv app/locale/en_US/Netresearch_OPS.csv +app/locale/fr_FR/Netresearch_OPS.csv app/locale/fr_FR/Netresearch_OPS.csv +app/locale/it_IT/Netresearch_OPS.csv app/locale/it_IT/Netresearch_OPS.csv +app/locale/nl_NL/Netresearch_OPS.csv app/locale/nl_NL/Netresearch_OPS.csv +js/netresearch/ops js/netresearch/ops +lib/MobileDetect/Mobile_Detect.php lib/MobileDetect/Mobile_Detect.php +skin/adminhtml/default/default/ops.css skin/adminhtml/default/default/ops.css +skin/frontend/base/default/css/ops.css skin/frontend/base/default/css/ops.css +skin/frontend/base/default/images/ops skin/frontend/base/default/images/ops +skin/frontend/rwd/default/css/ops.css skin/frontend/rwd/default/css/ops.css \ No newline at end of file diff --git a/skin/adminhtml/default/default/ops.css b/skin/adminhtml/default/default/ops.css new file mode 100644 index 0000000..4a81c0f --- /dev/null +++ b/skin/adminhtml/default/default/ops.css @@ -0,0 +1,46 @@ +.scoring { + padding: 2px; + margin-right: 4px; +} +th.scoring-G { + background: #00ff00; +} +th.scoring-O { + background: #ffaa00; +} +th.scoring-R { + background: #ff0000; + color: white; +} +table.scoring-G { + border: 1px solid #00ff00; +} +table.scoring-O { + border: 1px solid #ffaa00; +} +table.scoring .ops_scoring_description div { + background: white url(/skin/adminhtml/default/default/images/note_msg_icon.gif) no-repeat scroll 1px 1px; + border: 1px solid gray; + display: none; + margin: -23px -2px; + padding: 0 4px 0 22px; + position: absolute; +} +table.scoring .ops_scoring_description:hover div { + display: block; +} + +.ops_close_transaction_warning { + width: 35%; + margin-left:auto; + margin-right:0; + padding: 5px; + padding-left: 25px; + padding-left: 35px; + text-align: left; +} + +select.kwixo_shipping_select { + width: 250px; + margin-right: 10px; +} \ No newline at end of file diff --git a/skin/frontend/base/default/css/ops.css b/skin/frontend/base/default/css/ops.css new file mode 100644 index 0000000..7d4f17b --- /dev/null +++ b/skin/frontend/base/default/css/ops.css @@ -0,0 +1,299 @@ +.ops-payment-paypage #ie_cc { + margin-top: 20px; +} + +.ops-payment-paypage #ie_cc td { + text-align: center; +} + +td .ncol { + position: absolute; +} + +.ops-payment-paypage td .ncol { + position: inherit; + left: inherit; + width: auto; +} + +.ops-form-list label { + float: none; +} + +.ncoltable2 td { + text-align: center; +} + +.ncoltable2 td.ncolinput { + text-align: left; +} + +.ncoltable2 td.ncoltxtl2, .ncoltable1 td.ncoltxtl { + text-align: right; + padding-right: 5px; +} + +.ops-payment-paypage input.ncol { + background: none repeat scroll 0 0 #F18200; + border-color: #ED6502 #A04300 #A04300 #ED6502; + border-style: solid; + border-width: 1px; + color: #FFFFFF; + cursor: pointer; + font: bold 12px arial, helvetica, sans-serif; + padding: 0 7px 1px; + text-align: center !important; + white-space: nowrap; +} + +#payment_form_ops_alias li { + background: #FFFFFF; + border: 1px solid #DDDDDD; + padding: 12px 10px 0; +} + +#payment_form_ops_alias label { + display: inline-block; + margin-bottom: 10px; + padding-left: 8px; + text-align: left; + vertical-align: top; + width: 400px; +} + +.readonly { + color: #c6cbc7; +} + +#ops_review_form input { + width: 255px; +} + +#ops_placeform_iframe { + width: 100%; + min-height: 650px; + border: none; +} + +.ops_htp_iframe { + height: 475px; + width: 100%; + border: none; + margin-top: 10px; +} + +.ops_htp_loader { + text-align: center; +} + +.ops-subscription-notice { + background-color: lightyellow; + padding: 10px 10px; + border: yellow 2px solid; + margin: 10px auto; +} + +ul.ops-form-list.ops_card input[type=radio] { + display: none; +} + +ul.ops-form-list.ops_card input[type=radio] + label { + display: block; + cursor: pointer; + padding: 5px 5px 27px 5px; + vertical-align: top; + width: auto; + border: 1px solid #636363; + margin: 5px; +} + +ul.ops-form-list.ops_card input[type=radio] + label.new_alias_label { + display: block; + width: auto; + font-weight: bold; + margin-bottom: 0px; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label.new_alias_label { + border-bottom: none; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label, +ul.ops-form-list.ops_card input[type=radio]:checked + label.new_alias_label + li.insert_payment_details { + background-color: #f4f4f4; + padding: 4px 4px 0px 4px; +} + +li.insert_payment_details { + display: none; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label.new_alias_label + li.insert_payment_details { + border: 1px solid #636363; + border-top: none; + padding: 4px 4px 4px 4px; + margin: 0 5px 5px 5px; + display: block; +} + +ul.ops-form-list.ops_card input[type=radio] + label li.cvc { + height: 20px; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label li.cvc input { + height: 18px; + display: inline-block; + width: 140px; + vertical-align: middle; + line-height: 18px; + padding: 0px; + margin-left: 10px; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label li.cvc input + div.validation-advice { + float: right !important; +} + +#opc-payment.section .ops-form-list li img { + height: 20px; + float: none; + display: inline; + vertical-align: middle; +} + +img.ops-brandlogo { + display: inline-block; + height: 30px; +} + +.sp-methods dt > input.radio + label { + display: inline-block; +} + +/* OPS styles for payment methods */ +#co-payment-form .payment-logo { + height: 31px; + display: inline-block; + box-sizing: border-box; + margin-top: -3px; + padding: 0 5px 5px; +} + +#co-payment-form .payment-logo.left { + float: left; +} + +#co-payment-form .payment-logo.right { + float: right; +} + +#co-payment-form .payment-logo img { + height: 100%; +} + +/* OPS Styles for payment retry page */ +.ops-payment-retry #co-payment-form { + display: inline-block; + width: 65%; + vertical-align: top; + float: left; +} + +.ops-payment-retry .payment-information { + display: inline-block; + width: 33%; + vertical-align: top; + float: right; + border-left: 1px solid #aaa; + padding-left: 25px; +} + +.ops-payment-retry .payment-information .block-title { + border: none; + padding: 0; +} + +.ops-payment-retry #co-payment-form .section .step-title { + border: none; +} + +.ops-payment-retry #co-payment-form .step-title h2 { + margin: 0 0 0 20px; +} + +.ops-payment-retry .payment-information .billing-address, +.ops-payment-retry .payment-information .shipping-address { + padding-top: 10px; +} + +.ops-payment-retry .payment-information .billing-address span, +.ops-payment-retry .payment-information .shipping-address span { + display: block; +} + +.ops-payment-retry .payment-information .billing-address span.zipcode, +.ops-payment-retry .payment-information .shipping-address span.zipcode, +.ops-payment-retry .payment-information .billing-address span.city, +.ops-payment-retry .payment-information .shipping-address span.city { + display: inline-block; +} + +.ops-table-responsive { + width: 100%; + overflow-y: auto; + margin: 0 0 1em; +} + +.ops-table-responsive::-webkit-scrollbar { + -webkit-appearance: none; + width: 14px; + height: 14px; +} + +.ops-table-responsive::-webkit-scrollbar-thumb { + border-radius: 8px; + border: 3px solid #fff; + background-color: rgba(0, 0, 0, .3); +} + +@media screen and (max-width: 768px) { + .ops-payment-retry #co-payment-form { + display: inline-block; + width: 100%; + vertical-align: top; + float: none; + } + + .ops-payment-retry .payment-information { + width: 100%; + float: none; + border-left: none; + padding-left: 0; + border-bottom: 1px solid #aaa; + padding-bottom: 10px; + } + + .ops-payment-retry #co-payment-form .step-title h2 { + margin: 0; + } + + .ops-payment-retry .payment-information .billing-address { + margin-right: 30px; + } + + .ops-payment-retry .payment-information .billing-address, + .ops-payment-retry .payment-information .shipping-address { + display: inline-block; + vertical-align: top; + } +} + +@media screen and (max-width: 540px) { + .ops-payment-retry #payment-buttons-container button { + margin-bottom: 10px; + } + + .ops-payment-retry #payment-buttons-container button + button { + clear: left; + } +} diff --git a/skin/frontend/base/default/images/ops/alias/brands/AmericanExpress.png b/skin/frontend/base/default/images/ops/alias/brands/AmericanExpress.png new file mode 100644 index 0000000000000000000000000000000000000000..3c619aab2a4964e5957a8493127e4be3675e3374 GIT binary patch literal 119967 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 z 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

+F;QrZr%6D(Bk> zMVmIQj1AXeG=W7Fk$^lr6x`|+RZE=f%C4c2DP<*R29fQMHof4JQzlfUpxLYolb>c% zVLv)BSLSajmo#b#f&NlB7oC`zO---4rPdvHaBnod($1P{tt%p!_HIi}kFhUf)0LmO zD<9r#bnJW+wnyt~2t$Z##;!HWpMTEUPrU!~nlzoU4v|3s87Ai?M5I#_^$&E32%sB{ zY(Jp{AE>@Q{Xq&bpg`k^%Xz}?ablv7 z7Tid>)1Hdu+$kMP5YbEo1_gMi6o4_>3j=V@32@K?add6wBfecGB4rZPr8#U9cVNpJ z(+@>%XStzn1>GpsQ zMJygV24%`5&+n)Ql?cUFIR%LKuo^I=Kr}4{P@uNGg4^c-q=~eMnvTEE8eC@UT6>7E z&fGA1+P|7@&XZ}cqYjJbvoGT|+iv7zL9zu~T<40>ZKr@KT1PUi9?rI8(xO84LzGsg z0s1oBbU~Ng)B1YASs^XxAZ(y&c_0Qm$zdFjlL82-t;SN-KFv&JcgyYbo`nGy!&hw9 z7v&e6wf@&$|7t+zQSTG``1v}sRve%UA6q3l8OwcOC7@Q}LQx>EuUgs90@Avz0upA9 zk0nzmWu9=Va};0pzb>=10WDWZd5V&A45J-w1OJrX( z;2s-3XG2fyv!--kciZZrx6;+UO)e-Qoe}|z2&f>BLQcS% zHETWDP+i$bPx|F9Uq}p~5D*b_0d0nY`OHn4FLhh?l@D++OmPwlmi=DIxxZMfRmKyE?T_bk0>~%BAKF z*{6JKdAPa&V&HJ`d8b~udG(!-g{ZdSx-Z`T`9FCVB~Ylg2Q=h}jBI}8>dP

}%Dk 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 literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/alias/brands/Maestro.png b/skin/frontend/base/default/images/ops/alias/brands/Maestro.png new file mode 100644 index 0000000000000000000000000000000000000000..11a64e2b3205bf8f02bfc3d09b5bb683f4e66c21 GIT binary patch literal 29431 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 O00005PHt@T#Z-2k z={aX2l@ufqU~ypq004rtl$Z(t0P^(T;DZMJ4{1kQ0{?G=1ZD?_H_z$eopx^raANc>w`#b*w*66hT z`ya3h^X7m1REB#057=V;f6V`%&;P&Wsz^sS#Hx7L;{Z`4G#q4d1ko5-HF7+f8WCzR zII2>)v=j-IV>AE&N(3DZhydgt^${Zjh~lXvNM&H4O#txVAmk9Uu%w_(mXNeMFfb3`E=8{LM9!Im6a8MoS*`Wf=7rA2tXF67PTcR9pE^NLT;(o zkQovK2?vFZAO-1T*!qVz{=!?MGx?>Vf-A3drT#QQ3q!UiMPfLk2O;t+Avl6bRqR?^ zk`NgMofs5bOg4y!gomT4bGs#p#f<;2q#G4Uweb=>4}cmJ1Yk}C0gsv>oPb_X7J{;t zROf1H8oGr7k1SjY7N&=%3LqhB6+D0=%M|vB{sJ!|))=^Aeh;7{L)H#NWs`ydL1n5m zl&Uvxv8hiQIENDxBRnOb)j&vCF^y*miimJd;7%_TGEay=hQ>!0B{NKsjFRv~ARrS% ziIbSa)dM4i0f5>9sUo7CAyfa$)ZHa8N5C97U_dUHDTsXD$^=a##j@_#CC>&VbaK40 zjdqC|h*Ss>D6<5lQ9LvXcOHJg;Y`#7l(3;ljD`}e8Z5gtc-H|8XhH-PBxyNBG*~!Z zrYgS55F#lkNTj94d~7M?(Q=ZckW~ ?j4rRS;-j{;#SL<)?g7vTX26^@Fh`v1nmQ z3m>NVWojZgI7jq|F;F6s0rMYHK|9I@A|rFiXsTi<5NkEl5Awh~1Kg zV+8REqLx$AC7oRx&IDD$wp+PaE7ISgxrOe~kDB1;3JKgKDQ&}=Viw|yv*e=k>{S;f*J1j16Ab5exy;6|chTXenniGjMjnmUd1^Xdxm9s_~Kv zqB16eg)4%U0|E5}0LYeb=f{?8p_JyQS$s7KXdy;|gy)VR`v2&`aLH)mVBikRfCr=1 z8b>Fj_17b3fn!qvR`ruu+^7pJPPPLF)PaWooh6)AuR^8^PX#sdL;)e{`zZ(w8VST4 zb1sxJ$XXC{kjbbD@i8L&12qaRRrXHa{61E!U5hcZBOSEgDSkHZLAeKVqFbZ}=x z9U@KA=j4qxG&scFoZU90%2;qs8Jj{IUR1v@5IAt4lo4qEhfEEPz<_zp5l|5YfyM-YpP4YaTJRjg(GGSh z*y(HGWS_@&uI&Ena-Fk<0{pI(;C*qk32|xupcf?^z?XXJCD? z#U0luzbJQH5G!i3uPn@xGt}K=I~tau-rT`ficsx>1N^%_;$bUr6M0sJ2VRG}jpjF; z$t>j3iKj@iQSFFX44TUs@T_}$?PYLAGw%GZtLaOJmyGH*;_%f$2B#*0B_T3SwsoH+ zX}olH7Ya#;$9NC;6^UOjsS6W^BLpAxS1cv5!OY^E(fE7*=?aPmOCYs|W5XOd2|Jld z+$bbsNGcl82ObR?WXE<52M0Wd8;HqQLE@IEDl8u(QH~$!@Hsx8X6(9K!sko%?HQ;YH~d=k%+*{sYZsDSC%gn zB~Ew0j6QM|dp;xYx92xiBX28cqM=;>-TEFu>)4POo2Ld)*)6%k0e{2YfX9cqkRr4& zFeV?)8tlGy?Qd+ej9r;lhy_ZGWg-L8g#i|nL*SODZ^h^JULVvDWa|XMgKR26A(0>` z0qC^DxZ2C+=nzU$aMUOXXX*g@GKzIEW%0jbw0o{L1P$AY=`+1Nk6|8wn@uR&=Yc-o z&30k{Akc+h8tuNW=PQ!2`*twz<3Vv}{h69lmV@Z82nq_KFt}8J5QrB*NKN$=-u546 zXaS#;n;85Ul@M=u6nFqKj2c)u8*-%M9soX~m`Zgk0&C26$XaOt`9X3;|M?s0nvw4b z@)T53Nrt+l)Ddd$n$>D~;492)#=1~kdWv5ya^9H48;n@R5xp49CzGksg_bQ5h9@#?oq%2I!sX{3bogCbs^$QsjXlRPnDW2%Zn~~MC93= z*aGg{E7uAxvWlj6$*|5PUZ^zwBtr(#_I|mXWBpU+a#Wt{ZFukDvj5JsdL3;B0<5kf zKv*~i74w&8LKB|i>s!*eki#TG9?aeSbLaPDxc)~cQnufg`_nlIJ;p$$Fp-uuNz@Li z82q-JF%H)>o-NO>Xmm5QbxSCD$-w;AF?MZoshvNCiE9ea&5r(o)wg

bsjf6zqUY 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¥=>TZHAXLJk%x$!NlC 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 DTW9WF literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/alias/brands/VISA.png b/skin/frontend/base/default/images/ops/alias/brands/VISA.png new file mode 100644 index 0000000000000000000000000000000000000000..30fd6e1aaea4d8206154cd64236d33187c33a130 GIT binary patch literal 49753 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Ƴ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 JPDHLkV1jYVOYi^y literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/kwixo/apres_reception.jpg b/skin/frontend/base/default/images/ops/kwixo/apres_reception.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d89da0ec246ad2e513bcad839312b77672a93e2a GIT binary patch literal 4950 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%0DK^Qu0Gf zYOi#;7S{O7H6HcTq$sYoy&<4jZ-S?~1f9UNFjenU3oJg!+Z!$bT}d^4^K`o82d33F zR=3pUEu%$AUj?hFEl|DGsdqKrZqdneGsRV1vWlb4S?k7vEjmrkqfg{$K}N5QY~0?Y zu+dBY_9LC4Z!uFp?xZ~sI0ns1)~1w2{|IJEtw>%oxg)5LfvTP@IJk0L`hAbur6@$` zsSh&uHb3yNRTpzi{ebd7)=eh8KKop$4S(Gk)Krq-196|E*B0|7*C##%5~lB z)KIvgCSlCv$r<|ldss}IuUh`v_E;z!g~o2`Xj;Ek&M&hzTV%F@qx{w5ym__lSj4t1 zdTAOg|34&%j@hdx>c#C>+)=&RPjX$Y?7EAI<(<8=Gxa^}nfwh2PpFBTK)YJ$V*7Oa zcSY^q24u%*9Hm)vc#0qZ-v{^_DfHpzrcEgbbbRA3zOw$*ZY53m1Eh(%vL5Br-o2!p zE$hC0?oK$!pZ2bv2pDRL3E9Qtz6#pARXXNeywb?C^B&@#3MWQ7Pg_ACPdf`0dp!IT zPk80f3C*;k^yc;F2v){}X==Oj@6NSg#?_UdsayF0ycQOd7|5^0m+p6zQFbv-nGZkW z7VOlcNW1&7e*59VcK4E)>L$$$^;9PZqdN!-?}Nq-h1v2kWkaO0n*IW;;|jOwgH@!j zZPqin@h>6!pisHvx%scnZ65DCn*DhtX+oL%%QF}JCB|*C{n6T((Ygi_CUx2=9{*>S&NJPbIIHZ^WVfK>Fb;u zUUF(>tvYl;`gSDm&)ajESG_$cdUA{*B3xDLFNsQ-+r9YH&9gsjw~_NU2dkf`D$xxt zK1uPlq?Phql)r{>aH@H;62t<^A@RJfz6-9$X-8!<5SPzJ1sg>ty|KLXU1UZ}vxfYb zan*dxC+6B4Q>|~7)e}cXC*cmqIvHpCiRIaDbfz7{+u`apad|p42ixbluMgxuD)tHs&cWaBx2-Q1hcj?MlnCgIi@2c zqw-ouq0V&`zHMG@@0FlWwWbR|?$E^3(-(cRUDWkc=bm;xYPtJC;QC}>oRsVR;G<}2 z!t)B_L9zSOE=F--x1~-FXS3liBQ+H2Q5z!9gSn&%`T@)&;i34~UM$Qds?{mCFl>HV zEP8qSMIT8^&ox^cuCtC~d9(fXnK2fBM^4x4@LL1Rg5glxJA`<9KFaD?=IdtY;qel+ z`UW);hh`Y8cvmy%;KMwLR_;ORd6h4r3qT;7xH9tW?OW~c5-GXNfs1X1*`*iX`ZT@C z_w+A`xAz`ByMM6YLC8EkSbz_VoH znCuPEW^0A@2lo4c8+^qO)*mbtjSE}afgzS?_k1r@bW(6rQs)vkgPjA z%qRI%K#4SvyCN;7_#8VUSk>h?m3-<`s;}2()cAGdNb(Qsl`)FOlM1n6c13De&zzg$ zBu&ggSGqj5+Y{#_eHhD=xp~iD^xjd3*`;%*&iTAj-WYgWkb!dKe7XWO5p+v!KCRhc zxC;8G?S`50mrAf$Ot~AH;SpJs-Q8nCYqO^NH)}^cvU2!9cVSv(o1`6ogsa`J6ms}{ zvs9ViB@Q)88<$%QPl@Yn2>3d}HB1q$mGex~`@>gGi35YhNVjK!P@+JCIJH9Iy2E!w2} zVaFz^nYLEgTGADrKcBgU+&$+}c>UOlKOvYG(b+u}{(>wb^C=+?xLSPCa-rvc01o$s A^Z)<= literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/kwixo/credit.jpg b/skin/frontend/base/default/images/ops/kwixo/credit.jpg new file mode 100644 index 0000000000000000000000000000000000000000..59deaa19ddea8689f9e29a4bc5f09b29b7867d3f GIT binary patch literal 4814 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 GFaHBlg^L~l literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_BCMC.png b/skin/frontend/base/default/images/ops/logos/ops_BCMC.png new file mode 100644 index 0000000000000000000000000000000000000000..444c498bc99ff35f8fd63e638403c3f0800c07bb GIT binary patch literal 2306 zcmV+d3H|noP)(_`g8%^e{{R4h=>PzAFaQARU;qF*m;eA5Z<1fd zMgRZ^h)G02RCwCVn0a(m^%ciIZ<(27lF1GMvVjRv1OXv22tlGEU{P8D7pjzsrHDvr zIUE;kEw~g>K#pq#1VKa*8W*B+z#NW1Vw5#8h9m|O$V%2pX31pUy!Y!L15wPNZRHg1 zfA9CZ=f3Z|zwiCt`+JfQVhG@Ub!Xw!H6IqpMBRz#*IwEYF5#82ERu22fp^w*zRa8t~!!xFe0%_Pf&^$TZ3_R&b$ zb8saQny#5qQ&vEer1jAJ=SKAOGQ!SI9ZYEI`4f_ z)E(}u+SK;XNvT#E_8FSyEq^6Z4C!5L1J&s570Z>rZz_z>!*5#Hke9qhK__E?W zuvUwTqDUrHmL=Wm)4Ml9EGqr`Mj*SrB=-p;*sQYsKn2-fl(X^iNo@L}oa5itW7sUb z`CMUfUB6)px3E%EN!J!rxDSd1#S(}HD1XFHE#qQQJ$g;%tXQr@uZ#nm@{v3-*p=QG@w(c({@Aw(oK3zck+%2e*#QnDq z<=O$!Y}{MMq!Eeu%p;kaF@UA-AL52_seD)8!QC%@&i#|G;nfE+$t$bpNOc>lW{o}% zlQ^dmKL)Gm2F=Y*%4=Imhz=pItbxC+KR}~LLzckl@uA2vr<=5X7gvoEZmR> z0!~#mvta%HZh$|u9+@4Q6wTT{-AGeyI}c7DK~!iEuch|Jq{w6rjia&M!@$@u7TtCY zkU2Qy(<0&C`Ek`Nur?!Qjv zU|}^G*CsM*U@ZM?!Bn<5Nl)&@`;Secq{a?H^lVJ#8EZSv*j?gswhnifi2cK+4)tP5 zlj!;e>^ne_-~ydm`T_p}Jp*6>vIQw>5lEJwrosPWhS36*qmkWCe?$W?0bxI?5D6p$ z=m2zo#q{_tA7BO|AG-Kp1wd6MveyZrXMtM$V2e-2VHh%!YybjbC^Wcij7$6ui%s(b z3sglWTcFy%2w=TjUw)wH(wqR*@r1)EJ!kh1Oh7%Y6wWC9u(;T_QV+nSI^r5x&c9^0I8IrwHJ>~-Ne`jKK38^ zxds>wg#@Tgxzxxk#2q_d?P zV9)BNRW~te^-^$I{2V=(jxNO?F|ra|3V1u=jDy!!-AQIj>@QXwHQ*5_vY&rSfXpfa zv{2C1@40%8uVx>pnqLYe0d_a@9?oLkO;=qME$;z#P<$kv*$+|E?8GMo{UWV+b%SuL znRb_#W{0QSH`uJ=(+#vhS0F=x5L}fQb+P(RPp~eJhFch<^b7A^^JqS{Z0ult?s1--k#XU>S&vD*uASZ)&#~*+Y5%oi!Wtgg zQoz2FddlnV0Gw&>#MPxS@#!403n~HV()IJ=I6A!uAtTqyEGbF zoagyA+ufAbwDXUx2dQaw{pXntHY*Ss#Dj0_WAcb3d=*U$ii^aeD%7>QnEL!K;(Lb? z5oBU__EBtBGZ7(Xo_+ry1Nud9*XR@;-<(fqh=sjNr|`!&_K}(xMZpWx0jQ|AvwU*_ z<*qK4-Id0goFZZ@DtC-cB{?aY(R1JCx{NE>zIejV>uh6tCz%tk22JP9+)^gZ&7`Tr z!?DU{`uDO?*XAZvk-2xuFt&|J=BN*h>w z$MEj>tu8MlClDWH zrMkt*kh6vLVGEV2-fj>e3i}Wh~FWm`jVtM@W!L*2pA$ zTfV~8>FaJBs$Z2PRR-bFbk3F2;P?n0nKpu?UN!)xT{nPms|AZmrory!>+(hxKR$_D zh9_Wmd)alUij}i7i3m3P?_25p8TXHnKo)bi6n$rGHZ=LArM}lVU}p0F(##W!{Q0~ cK*PTW0M9E<3FAM=3;+NC07*qoM6N<$g18-3zyJUM literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_Masterpass.png b/skin/frontend/base/default/images/ops/logos/ops_Masterpass.png new file mode 100644 index 0000000000000000000000000000000000000000..b7a3f44485d255dae3de93f4e56d9a1c20ff6b1c GIT binary patch literal 1529 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 E2f;`)1ONa4 literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_belfiusDirectNet.png b/skin/frontend/base/default/images/ops/logos/ops_belfiusDirectNet.png new file mode 100644 index 0000000000000000000000000000000000000000..3c6a1c8d808b841a9fe0e85b43ebfcee75945403 GIT binary patch literal 2174 zcmV-^2!Z#BP)(_`g8%^e{{R4h=>PzAFaQARU;qF*m;eA5Z<1fd zMgRZ^1W80eRCwC#ms@O9#~H`Jxtz0SchBC}tnW71U@+!lFtG_G7^+f2Vp@_CDLfQt zRaI(f)Jv4ABvMcVRYKKDRin~2ElE^D+fdUsO#>8YD1=}x24fpzY>crTFJAB2J$rWd z?B(p4Gkq{=ORctf=|d#{rIBW&dHBtL{`$Td#5sqLi!gkQGy^*WI|DldJ0<$zq<7TW@x8gR%ZQ`&RO7uTOk z>luh^X^6xU(6qQ75?T`ShOuYig4&_7P|2&EUA^1RU%B$LxpQV;TQs+3&(*$x&$-?5 zwot$ac83Hmrwkv2?}8)>aD8A1CZh^0uAdK86=krcZTev2~qZZ z#aUbao>A%kbwpLxjD_!N#lgUSx5qQj0`z7woq2>Yvjbxc6k{;*26SET12vg$s;R0d zx_I^4tLHCwJzrT``~x+SJQGVM%)F7Ca5;kziB1CJ7A$R81cXdoG(rdvLO=)xgwXUD zFjY51+0O!xtT|EiaBF*THN9eVE9meD00Xc zRo}!8i2>pO1am+f5Q1DbUuaOZ)XqN~JM~*FnX+x(xbCQ76kZSb{7u`oY`WfmYxn>n zxS3FzCYU0Z%N@fQ7i>0Bb4vD%Q83q@>$tk(*k8}i2}fej*r*ulxZGv$8yxzQlF%NH z#MHM|G%ivnVzKHg*ZRIbEwg|LFGN$g5K^BbAqV0d0azx_4@5X3WGWnp17j>-6!6tr z7F+Ii%B!@rZj^*(RRZ8pszg5VkSP0wtyb0CISI?na;24a{T!oP)&}x|dN4VPsmCCHZ~6{^n(ktKwQB z(AnL8G;0*vra6l+LWS|TZQ`|KM*ssyfB-lk%QOYzk^ndd1PD0i$Sz4zJfXq0zMG=M zE|HP3@W`#<(G^c@TK{r*BKE^vHrwEIIwKrF0ssQ{Dhsn1w=4@d09c$MkH?+QWV4Xe zb(}R!AHtZb3C(_`w@(kv3a)oZ(!OLey&C`y-3KeLoI9qy+dUrv7Qp)O1OSLqKmq}v zm=rJvM$lL!p>8vob!Oy)@%f@%x?NLL{j0Y1Yvp&(UD}t+8(Rb_w8(DP21yhQgiwM~ z0)jv>a8QXLfXyZnM5%X9ZS{pax9^;~K5+YlX);$$RoT@^MM0wi2i&_diFxPj$oN;Dn0>GEm$< z2{OmfR2x!SO6o*&cmg7^c-D+qo#7z1#QqqRG7P7gHQKst#pohW09Z6Ny@UNnApj&0 zP~W)(6JryAvDj*>DgH*bf9#-pcG*t|C1XgKl z3m}y{OCKD~hwWwNY+cb_kSN*}Z9n%@X-;vM$HKevhV}juX8SC^5u3nHsqE2oZoF(= z?V|YkzHbU8ftLWF|0BH(5Ft>wt3hrc3|7_xrf0G5)6MTWm()C)jm9w&$W!w2x}}!M z_QiSz-^PB|=G>&ZKYq6RZIm@0qE4w<`AbK;LD7-)jnVHZ?Wea!e{=e~Ku94w7~V-j zo&_dj2$coArbvE~nN$uMn%+8c;GHi--Z;56^2?*Id^Bef0xOdRE0h1wNRv8pIvm~K9B>#6< zG*umuM1Xo^AomX?ak8@|-FdUqzMw)ED}v7hz>wf~CLPOWL($Gh-$D*iO`pH{7>Ja} z4>kNge|zFbAd=%1yS4*$*%L-2c1o%%D*^=E*VgisILqB{M&c)tBwS9Ny}r>lr+6_5 zdIk%Lq?Y(o$KU>eHS-mpft`V!ft`Wb%707*qoM6N<$f;wFD AhyVZp literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_cashU.png b/skin/frontend/base/default/images/ops/logos/ops_cashU.png new file mode 100644 index 0000000000000000000000000000000000000000..3c498240bfb4f61fce9a40c289f803b7bb9b7259 GIT binary patch literal 2218 zcmaJ@cUTj78jgspiYS8cD0&)$1#pv@NioGF7*ilXkPsc3otJ8xXwv!`qo>=GVUW``)k3GY10vz3isD zOh=(mc6@J65HjMBXVFv}A50(usTd{6l-A=OQx2# zn2thCR>_4Ty-46kld6?C@u&>Wq|_j26pHR?(uk#zupTXe!{sUlroXHLgOoqh0FdB_GBLSz@h650lN(DeXfX8DI39K$kr5Bs9DxK?u1`e!~ zYULWeT&+TnY7|S<20a6VF#YU;QX>$2QLNI92MP%pU=nKp2nPa6<)~j{(mH(*{9hVh zmDUNPG%yeZ>(mCV6v;=J>jW5y-Cr9T6-3z3)@tQQQp5_5T53?jDm|aWz#u0$nOsJr z!eJB&ypn{Kh-DHi0VGnf5;AoqmI#rkuuLi=tR#>o^n49ZVS!v00fImd1VRv($RhAS zHkZdGkqA@@MC43h`6``WtdhbLw&jTJaV+?kSQ=Xki}h-)P_0%>R6#(5TCdhcs5NLd zdo1f{w+(WYOl{P;kEZFf*Bn?Y-ww-oTD1~A7H69LD;x-T0tHWIu|Pb?8{^O7aM=_H zB7$5Vh0B95<5<~OVmFRm^MA1bLIxNulK&~1i7liON2gy}09kx#B3OlVjuvT=>}AOu z6w11Z&tVBoEj?zXVh(rit*j@#s{YdAU-R!5SuIpLo}c5*VaKl#G6Ni}4+4RK*-iFK zT@GX~<1tSyn&F)^msDl0;c!;8SVuna0dMYcxfdkhSZ|3fTJ5{(W2v#YxU6{9a3?MN z&KakQc`2DM&EX%OWSkhGziTKkCh7dmlHe`}<&=(z+gvn|-8QAw^H~3(SQl`{9f3;@ zKPb2yz}-4&Q}ljst|9ghKSH-{F0kfG*Mk+b)|QHA?^Y`+^P-!=6O&J-58Dr6em`8$ zD0S%BT?DUh9=wy{TiTOMO|^P><7xwNAb={vRsT2mf@B(-!4` zbqT5{;gU>mIQ!G2%f7GPFzr(twMT{n6Y2O`b$D}3!?D)jO%;X8XM%)# zM+of)ej*%M>b||qGqH2W$|+})OnsLh`0EZo@-4F~Uw*0o$0GKtfn63=FH*49} z1>arhE@+pxw3Q8O>Th28=RMWC^_Ox?ak~?jAAMZq^pi)spEIt({(=3lb!_N}REwfdP~H{j4DB7HQxIQmG^#^3yd zw>hAxG{NdpMorud`zME^lB1P_lOjVlR7+$^$}qh@`1#xd$9~$(j*+|09X}Q9Jk(U%0*BPF0v~x{p>3Fx>nm<>-^+E)~Pw?xEcD43tk_OOH#`Z|6_iw zZ9epHOP}-n?SYo=a~j1G*&(O>&C+cvf;!IAyVggWEy0^qPx`VhR?PNtT9?8%WVl^~ zeh-cuWRxTa4`r9c{a(`#);xDg`sTN!5=rEhxi!U^>)X-Ak5l{t z-RKKXoc6fUSu!>2*tQtKexK%PInn7q@{Kt~xQ>k_UdEKa8N8q94E1lE)9e@i$v9D^ zh?;|YTU!xPU3C7OI5Bsh+idscoR;TF$(_`g8%^e{{R4h=>PzAFaQARU;qF*m;eA5Z<1fd zMgRZ=G)Y83RCwCdm}_VpRTRg6_s-5tlC{}JOk)o}lsEDOSKS*ssEyef>w6vrcp^;j*7R1^%Y?9qHvpeI5dzPKf z&Tf+3!0LrVGPCF0^FQ~VJ?DQ`1wp|7WhILLsO^X9-Kz_D6zB(T0CK>0z#ibkdNvN8 zSP6ihr=Xt%jsY(MtAGW-BH#hw17HNWqFyKS0rnB#9l)ug(*qmRt?N zZzR~K=cs=#aDAh|_Sax@eOX}5K(2Z8-SUlko+s=yGi5NH9KrI8fH7Cg4txWGlg1n^ZoU>$M#w>+-} zo&_F{REP|)1K0(ulcvxDGy&Ee{HdA1+CZoD^rb>nZU&YCmjP|SK46pZf~_K!l&3r} zCH7l@^)(*MUvX)$R!B$>aHW8osh1Ck>;=9D4g-J4_4NYx0r%9IW`#JgZwt^`*7`b5 zuBK|>{6{%=N8HKo1XcohEDI6|Pzo#yw8T^>fbSbQf8OXg4$L%dC}nIs&+MU8>9epe zt<7`Em&W%`mxI;%c~U83l+D8Aq;W7cWt;%Gt^wyb(B2Lc6L9Wa$w9@!+PZTF$n3bgJ- z>rTMK_tW@(nnK}6a=C3dP6tg*oALeGq(Pco?o+frMpu^!^z2!1-8*rdew6A(>khPD z1{B2CNqm1O82R{(3&Adb7+PDq(Rvp_aMLUc^>x7T>`S}y@ckRfW=C+G#W>Dq1i_ja z8PmacBTXTdS_&K?2o_YZ8JCLThTi33^u88ml>n}mq0xLpgTO|qRV0%mz#)R5qk^vq zv1zHq4W5&!G`cubQbq35YcfO~qifYunBwRw@oYHDwo7iSzY@U%Iln|bbaD|O>x+~V87?3M}vZ32!9K}iD#fT!dprepLM0+#@nRv}#TQNr)`yV^0a>WJ{)FTMp07s(nJSl{86TlG=;4S6}p#X>{NQh~L z7&x2;LBtJ0gE&4Q9~KjNGZ>;=j9_dWj^Qdtc>HJx1tK7!NWvtH{#H)_L;@yZBb8&v zL0C|f$SY9_ZAkRx@)M)^bOFKD1z^ZQOh62wJU}kqCXs=1CgF=Nh|OoVNd({v1dV1A z{z@u{;}5W4DFjf76kEO>nGDcqM6v^wMkCt*_I6}@k{$NaY{?EFl?K{70$(2j7ELOM z1OwRXzQ)2oH*mMw0Uo5}9Z>Q_?Ju!}|JUC>VuhQ7{6qHqZdejUtHv zj+a?|!E-pEw?u~WBz(x5%_Lw7M3G1U+R@iJk{z9#9BFP;GTDPh_aNJ|*mMVb2QtNp z>`0yEvSEI#7?Pl~T*2R5`ggfARuCgtWHuxf#X*8~QdkUpDH#-fKNs3}^}cci-_OPI zyIc~M3~8pZ|JCT(EzCVL({HxL7T=5yNierdFfPuacQ>SL{CyXrQeRZGy>bwRcCE<4D zK6kalCR;)%A)Be`xAYMey#!uqyt`aljC=b|L%SuVH+VR+d3}c#8vRo)^Fi-cg}vQL zKc{UerUUdlgHI`+-t}nU61S^%IzQ@EH#D{}?`X3u%**>DFp#0K)XdPiuRD^QppvNT z>PVBC^>KwSUR^F59)3OeyW8>5(90|;^<6RLiEwVY&hzoT+P^Uzh4tgLq$3v(l&)$g z+%3I%p0GZ>b#W#?Ha3o)J+5?ia*nQhI1CzQBu>nIf5mQ0@ho!qTt&_B5=!aj!`vMG zX<*{Y&tt(IYtFRJ#bvr5+KO}0jdvc4(3lcypteI6cUEK`{By;dH{Gp7Q*(U__Hjhn z|Kj3&)GW;}d`H@?*Oo^2V;B?j`<2qjCi_)667cS1e`9K0f4`*c)LPdfRruDn%n0^^ zQ&vvXB2c_{L@$2iPqMnw@2x?)g?~BWvCfYZnv5wmzhyT+1}>UXzpGO_?WxG(m zzqE zQee{TjLxlcPIj z#2B~i59@VLUaa0CX6;&Z^}z&U;8rX%9c}5OdO0TjsyDhLec|(QquQ#pRJ9#?iBX>- zGTp6JDVbGH2qQ5A5UngPIFhrb09@Wgf-c473Tv9f3^cE6mev21);q_a9-5+?QM+LC zk~4d*xxD0BKc1@0kd?AJrkA}PQpUQ2Ir{B)*4S{f&0PEzp2GWTKJ{vw0+&wLnov4lu-g2v8OKXoGmAWZ)c&>K2rETT)1CuX> zI&#k@s9++Q^1(^*dJ)0jDo;IRY^`ydYkpD9g=?cF3zl0n3)Rl#efE`Hh*yjij>i}% zuT>D&m%#dK5BY1H`9&?9b$f^LVB)d9jO=wib$M2DvxD_+rvo0zW$1mE4I{$MJ-?r0 z75QuDcUk~3d7A{O>eWb0U3>lK3a^O~t>C3Gva=6OA8pUaCwrFXWbtlTpCxv-M-6c+ z|M}7*=CRG^OtEQ$bK5|Sr@_Dy@erq`@?=8TvwLL=geKKDC{&K)^D)h_X>Qw_t9~U{ zDJ@+M%6W|Q?#5CyoBY7T@ZkP));66cM>Q*avI1T_imFsi?!IYWWc6{f__35^nj9GS zo*(`u)N-Qhb)QPJWJM$1tHQW(G;Md6PFXogF`+Hzn_bNUi|=iZJNk5c=+XAzdSjvw zblvme>h6zq2ayjur}OI-&i)tGFSYT&>V4yZOFA6Eo!x5wN!RgvxXdkpLbr2%QsCOj zSX9o)J%F#duw;E2{@!zFyE|P7Nk6sO5Nnjnp0bGrL4@`d0Wbj z$p+~Dr)(p9>_Rkmy0y2zDZ1xp| literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_chinaUnionPay.png b/skin/frontend/base/default/images/ops/logos/ops_chinaUnionPay.png new file mode 100644 index 0000000000000000000000000000000000000000..2485859cb652e68883f902b203804f7b963dc94d GIT binary patch literal 3325 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 literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_directDebit.png b/skin/frontend/base/default/images/ops/logos/ops_directDebit.png new file mode 100644 index 0000000000000000000000000000000000000000..dfcf2b3e0e598762d3854cb34970c2ebd77d9b78 GIT binary patch literal 1863 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 e(#T0q15=#0(^39}khhk6-V#GxI+@m8HM{ literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_eDankort.png b/skin/frontend/base/default/images/ops/logos/ops_eDankort.png new file mode 100644 index 0000000000000000000000000000000000000000..9042be14e722f983eaf6bcbc0ce92650fd34aa90 GIT binary patch literal 1708 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 literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_iDeal.png b/skin/frontend/base/default/images/ops/logos/ops_iDeal.png new file mode 100644 index 0000000000000000000000000000000000000000..52bd538b57d69f752ba23cc8c9883bc90fe4c9d4 GIT binary patch literal 2859 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 J{YD5$`VWtN=qCUG literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_ingHomePay.png b/skin/frontend/base/default/images/ops/logos/ops_ingHomePay.png new file mode 100644 index 0000000000000000000000000000000000000000..16dcf49e6f37576f2730d5785dc8a20528cbd37c GIT binary patch literal 2187 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 z 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 Dq>Cm| literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_kbcOnline.png b/skin/frontend/base/default/images/ops/logos/ops_kbcOnline.png new file mode 100644 index 0000000000000000000000000000000000000000..d090f110301c1ccdb7171893c30cc3362f8584c3 GIT binary patch literal 1147 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 U$IXcOXzKL? z!#-QQ&}$(Mhg&QO63VcVguSaA7GU4fvY7yE@I*z?Xatgg8kKqo7pO+!AwZ&0CPFeu zsZL92hPXJKtrnI?qtT%u92KG=DXlgnlSYT3aX4;(NvBlpf>0nHN`$pM{AgVR9)Q(6 zd=w*;5~>qGNpMiQ9*Rg0ldIBqsn}|Kz$Soe;$Q?C2vq_mO|sU&G4b&4csbbIx=qFd z?@Z8LJp6l6(V4`<@2{ZIg@%40)*5C5g@uf4QtHSci9*(BtoM3nZ9rwv ze`S2s+8|HUL1YdjzAA7QA96CkmR{4kR~B0Vn{-C zfIwi)Ivw~l3f8I-qruObruVlNLVEafNG;YQ8sJ@=Iq*j`Fc~zufKL}tgdhdePv;95 zLKcMviUa8sia(Q$pT(*_61!Qf@c&}Tm>9CPNdBj2=C-g(v`#;?0JiwhM35Hi96i<| zCw>U`#o_F{CBi_t>F(p`;2`-Hk4;gdcW$s~%v1S(o_NLIm%(<9f#r{wKUTzCa9Cf# z46KH?%a5*<%L+#V)knL%a(F;($8@FLPeCq3NfV5Ukzb7){hhOo2`Jh)k~?~T<4jt{ z%;4MM8(kr{mp;XdFq}jZ%t)=~tlY5@t1FMkL$3Mg4cOEG!zk<79$xE8dTOnT4Y_l;qwo25cy_lyo z4>x4ymkkb@4a*)pEa+NUblq|c`FczH^1_VVR|Wg|qmNzv7hQ4M7UbdNUuBaex^!RUSingx;lr8Z?0DTbSX||d+qTX zn?j=HTFq|U)IWXhhaD^&8tN_$wzWO!Zji;P+K};=iQG7wu%oG|wzsR+71!;%6j$dw zHO+AUqdEL6aFxeFrNEL+zZ^e6m7dLFb;Nq-EzfX(>fV=%q zIINsO4md+I#n^i7-qgOz`G@Nj<_NKL@sg5eg=-ukYvHLI2c*|KjPl5QLPo}bF6G?I zy{~=SRS& z;DYx~$O3*oX>;plT)Cvr33q%EoI&WX$)2C{z+k&$_(lb{7^)pwvB39Vt0QyIt|^n6 z=Yf?Cj&GPl+~-Sry!v@Bx(eQ|3om~5m5YWT+vD0(BHQEr>acuUCWn0Hi0@R(dBqa0 zqbjE4g=GKPR^{T1)87{Oelff|7rbIuAC>0J8tJ2ndWZkq{Z-@OJo}6+i$~;Wnco1( zo;>BfUrOi?9Y{Gdax?ndrL0pqi88|5%NH&^yVu<6bbBmQ-1z)Y+=mC(W%-QemAD1w zPMofvHy+;TwFpV}^-7pRTs4UoxBjeS{Df>pApt2FX1SG|czF^=xhCyDtBlVEqwGL}9`z{*J%? E2f;`)1ONa4 literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_paypal.png b/skin/frontend/base/default/images/ops/logos/ops_paypal.png new file mode 100644 index 0000000000000000000000000000000000000000..da6e31d062cde06e139849c14088e69cc670ce2f GIT binary patch literal 1963 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 WziWa%w^8r-uSF_mVpUVsIsX7s`OG2! literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_pingPing.png b/skin/frontend/base/default/images/ops/logos/ops_pingPing.png new file mode 100644 index 0000000000000000000000000000000000000000..62d70196b7698f5ec5059b591a449353fe0a75cc GIT binary patch literal 1884 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 literal 0 HcmV?d00001 diff --git a/skin/frontend/base/default/images/ops/logos/ops_recurring_cc.png b/skin/frontend/base/default/images/ops/logos/ops_recurring_cc.png new file mode 100644 index 0000000000000000000000000000000000000000..3e98e3e91a0523779bb91974712472530845334d GIT binary patch literal 2293 zcmaJ@dpuO@8XotIB9&yPS`CRXV~iPQnIUF`lfl+(3CWmQ#-y3G%q$G4=;%gNE{UQd z(cW!EQJqcNk!w3eqm829QK^($si|nr(oXyQaXRa_*7`2L=Xu`u{odz~?|{Fr=REum zcpMHl&)bU~fQ>}#TQNr)`yV^0a>WJ{)FTMp07s(nJSl{86TlG=;4S6}p#X>{NQh~L z7&x2;LBtJ0gE&4Q9~KjNGZ>;=j9_dWj^Qdtc>HJx1tK7!NWvtH{#H)_L;@yZBb8&v zL0C|f$SY9_ZAkRx@)M)^bOFKD1z^ZQOh62wJU}kqCXs=1CgF=Nh|OoVNd({v1dV1A z{z@u{;}5W4DFjf76kEO>nGDcqM6v^wMkCt*_I6}@k{$NaY{?EFl?K{70$(2j7ELOM z1OwRXzQ)2oH*mMw0Uo5}9Z>Q_?Ju!}|JUC>VuhQ7{6qHqZdejUtHv zj+a?|!E-pEw?u~WBz(x5%_Lw7M3G1U+R@iJk{z9#9BFP;GTDPh_aNJ|*mMVb2QtNp z>`0yEvSEI#7?Pl~T*2R5`ggfARuCgtWHuxf#X*8~QdkUpDH#-fKNs3}^}cci-_OPI zyIc~M3~8pZ|JCT(EzCVL({HxL7T=5yNierdFfPuacQ>SL{CyXrQeRZGy>bwRcCE<4D zK6kalCR;)%A)Be`xAYMey#!uqyt`aljC=b|L%SuVH+VR+d3}c#8vRo)^Fi-cg}vQL zKc{UerUUdlgHI`+-t}nU61S^%IzQ@EH#D{}?`X3u%**>DFp#0K)XdPiuRD^QppvNT z>PVBC^>KwSUR^F59)3OeyW8>5(90|;^<6RLiEwVY&hzoT+P^Uzh4tgLq$3v(l&)$g z+%3I%p0GZ>b#W#?Ha3o)J+5?ia*nQhI1CzQBu>nIf5mQ0@ho!qTt&_B5=!aj!`vMG zX<*{Y&tt(IYtFRJ#bvr5+KO}0jdvc4(3lcypteI6cUEK`{By;dH{Gp7Q*(U__Hjhn z|Kj3&)GW;}d`H@?*Oo^2V;B?j`<2qjCi_)667cS1e`9K0f4`*c)LPdfRruDn%n0^^ zQ&vvXB2c_{L@$2iPqMnw@2x?)g?~BWvCfYZnv5wmzhyT+1}>UXzpGO_?WxG(m zzqE zQee{TjLxlcPIj z#2B~i59@VLUaa0CX6;&Z^}z&U;8rX%9c}5OdO0TjsyDhLec|(QquQ#pRJ9#?iBX>- zGTp6JDVbGH2qQ5A5UngPIFhrb09@Wgf-c473Tv9f3^cE6mev21);q_a9-5+?QM+LC zk~4d*xxD0BKc1@0kd?AJrkA}PQpUQ2Ir{B)*4S{f&0PEzp2GWTKJ{vw0+&wLnov4lu-g2v8OKXoGmAWZ)c&>K2rETT)1CuX> zI&#k@s9++Q^1(^*dJ)0jDo;IRY^`ydYkpD9g=?cF3zl0n3)Rl#efE`Hh*yjij>i}% zuT>D&m%#dK5BY1H`9&?9b$f^LVB)d9jO=wib$M2DvxD_+rvo0zW$1mE4I{$MJ-?r0 z75QuDcUk~3d7A{O>eWb0U3>lK3a^O~t>C3Gva=6OA8pUaCwrFXWbtlTpCxv-M-6c+ z|M}7*=CRG^OtEQ$bK5|Sr@_Dy@erq`@?=8TvwLL=geKKDC{&K)^D)h_X>Qw_t9~U{ zDJ@+M%6W|Q?#5CyoBY7T@ZkP));66cM>Q*avI1T_imFsi?!IYWWc6{f__35^nj9GS zo*(`u)N-Qhb)QPJWJM$1tHQW(G;Md6PFXogF`+Hzn_bNUi|=iZJNk5c=+XAzdSjvw zblvme>h6zq2ayjur}OI-&i)tGFSYT&>V4yZOFA6Eo!x5wN!RgvxXdkpLbr2%QsCOj zSX9o)J%F#duw;E2{@!zFyE|P7Nk6sO5Nnjnp0bGrL4@`d0Wbj z$p+~Dr)(p9>_Rkmy0y2zDZ1xp| literal 0 HcmV?d00001 diff --git a/skin/frontend/rwd/default/css/ops.css b/skin/frontend/rwd/default/css/ops.css new file mode 100644 index 0000000..17b4750 --- /dev/null +++ b/skin/frontend/rwd/default/css/ops.css @@ -0,0 +1,316 @@ +.ops-payment-paypage #ie_cc { + margin-top: 20px; +} + +.ops-payment-paypage #ie_cc td { + text-align: center; +} + +td .ncol { + position: absolute; +} + +.ops-payment-paypage td .ncol { + position: inherit; + left: inherit; + width: auto; +} + +.ops-form-list label { + float: none; +} + +.ncoltable2 td { + text-align: center; +} + +.ncoltable2 td.ncolinput { + text-align: left; +} + +.ncoltable2 td.ncoltxtl2, .ncoltable1 td.ncoltxtl { + text-align: right; + padding-right: 5px; +} + +.ops-payment-paypage input.ncol { + background: none repeat scroll 0 0 #F18200; + border-color: #ED6502 #A04300 #A04300 #ED6502; + border-style: solid; + border-width: 1px; + color: #FFFFFF; + cursor: pointer; + font: bold 12px arial, helvetica, sans-serif; + padding: 0 7px 1px; + text-align: center !important; + white-space: nowrap; +} + +#payment_form_ops_alias li { + background: #FFFFFF; + border: 1px solid #DDDDDD; + padding: 12px 10px 0; +} + +#payment_form_ops_alias label { + display: inline-block; + margin-bottom: 10px; + padding-left: 8px; + text-align: left; + vertical-align: top; + width: 400px; +} + +.readonly { + color: #c6cbc7; +} + +#ops_review_form input { + width: 255px; +} + +#ops_placeform_iframe { + width: 100%; + min-height: 650px; + border: none; +} + +.ops_htp_iframe { + height: 475px; + width: 100%; + border: none; + margin-top: 10px; +} + +.ops_htp_loader { + text-align: center; +} + +.ops-subscription-notice { + background-color: lightyellow; + padding: 10px 10px; + border: yellow 2px solid; + margin: 10px auto; +} + +ul.ops-form-list.ops_card input[type=radio] { + display: none; +} + +ul.ops-form-list.ops_card input[type=radio] + label { + display: block; + cursor: pointer; + padding: 5px 5px 27px 5px; + vertical-align: top; + width: auto; + border: 1px solid #636363; + margin: 5px; +} + +ul.ops-form-list.ops_card input[type=radio] + label.new_alias_label { + display: block; + width: auto; + font-weight: bold; + margin-bottom: 0px; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label.new_alias_label { + border-bottom: none; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label, +ul.ops-form-list.ops_card input[type=radio]:checked + label.new_alias_label + li.insert_payment_details { + background-color: #f4f4f4; + padding: 4px 4px 0px 4px; +} + +li.insert_payment_details { + display: none; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label.new_alias_label + li.insert_payment_details { + border: 1px solid #636363; + border-top: none; + padding: 4px 4px 4px 4px; + margin: 0 5px 5px 5px; + display: block; +} + +ul.ops-form-list.ops_card input[type=radio] + label li.cvc { + height: 20px; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label li.cvc input { + height: 18px; + display: inline-block; + width: 140px !important; + vertical-align: middle; + line-height: 18px; + padding: 0px; + margin-left: 10px; +} + +ul.ops-form-list.ops_card input[type=radio]:checked + label li.cvc input + div.validation-advice { + float: right !important; +} + +#opc-payment.section .ops-form-list li img { + height: 20px; + float: none; + display: inline; + vertical-align: middle; +} + +img.ops-brandlogo { + height: 1em; + display: inline-block; +} + +/* OPS styles for payment methods */ +#co-payment-form .payment-logo { + height: 31px; + display: inline-block; + box-sizing: border-box; + margin-top: -3px; + padding: 0 5px 5px; +} + +#co-payment-form .payment-logo.left { + float: left; +} + +#co-payment-form .payment-logo.right { + float: right; +} + +#co-payment-form .payment-logo img { + height: 100%; +} + +/* OPS Styles for payment retry page */ +.ops-payment-retry #co-payment-form { + display: inline-block; + width: 65%; + vertical-align: top; + float: left; +} + +.ops-payment-retry .payment-information { + display: inline-block; + width: 33%; + vertical-align: top; + float: right; + border-left: 1px solid #aaa; + padding-left: 25px; +} + +.ops-payment-retry .payment-information .block-title { + border: none; + padding: 0; +} + +.ops-payment-retry #co-payment-form .section .step-title { + border: none; +} + +.ops-payment-retry #co-payment-form .step-title h2 { + margin: 0 0 0 20px; +} + +.ops-payment-retry .payment-information .billing-address, +.ops-payment-retry .payment-information .shipping-address { + padding-top: 10px; +} + +.ops-payment-retry .payment-information .billing-address span, +.ops-payment-retry .payment-information .shipping-address span { + display: block; +} + +.ops-payment-retry .payment-information .billing-address span.zipcode, +.ops-payment-retry .payment-information .shipping-address span.zipcode, +.ops-payment-retry .payment-information .billing-address span.city, +.ops-payment-retry .payment-information .shipping-address span.city { + display: inline-block; +} + +table#ops-payment-information-table, +#ops-payment-information-table thead, +#ops-payment-information-table tbody, +#ops-payment-information-table th, +#ops-payment-information-table td, +#ops-payment-information-table tr { + display: block; +} + +#ops-payment-information-table thead tr { + position: absolute; + top: -9999px; + left: -9999px; +} + +#ops-payment-information-table tr { + border: 1px solid #ccc; +} + +#ops-payment-information-table td { + border: none; + border-bottom: 1px solid #eee; + position: relative; + padding-left: 50%; + max-width: 100%; + overflow: hidden; +} + +#ops-payment-information-table td:before { + position: absolute; + top: 10px; + left: 10px; + width: 45%; + padding-right: 10px; + white-space: nowrap; + content: attr(data-ops-title); + overflow: hidden; +} + +@media screen and (max-width: 768px) { + .ops-payment-retry #co-payment-form { + display: inline-block; + width: 100%; + vertical-align: top; + float: none; + } + + .ops-payment-retry .payment-information { + width: 100%; + float: none; + border-left: none; + padding-left: 0; + border-bottom: 1px solid #aaa; + padding-bottom: 10px; + } + + .ops-payment-retry #co-payment-form .step-title h2 { + margin: 0; + } + + .ops-payment-retry .payment-information .billing-address { + margin-right: 30px; + } + + .ops-payment-retry .payment-information .billing-address, + .ops-payment-retry .payment-information .shipping-address { + display: inline-block; + vertical-align: top; + } +} + +@media screen and (max-width: 540px) { + .ops-payment-retry #payment-buttons-container button { + margin-bottom: 10px; + } + + .ops-payment-retry #payment-buttons-container button + button { + clear: left; + } +}