jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/364143 )

Change subject: Add Ingenico Connect gateway based on GlobalCollect
......................................................................


Add Ingenico Connect gateway based on GlobalCollect

Swaps out curl_transaction for API calls and does json response parsing.

Shows an Ingenico Connect hosted checkout form!

TODO: parse errors, remove debug code added to verifyFormOutput

Bug: T163946
Change-Id: I4c02abd190b1ea27c1921f37196481d87cd0162a
---
M DonationInterface.class.php
M extension.json
M gateway_common/donation.api.php
A ingenico_gateway/IngenicoOrphanRectifier.php
A ingenico_gateway/config/country_fields.yaml
A ingenico_gateway/config/currencies.yaml
A ingenico_gateway/config/data_constraints.yaml
A ingenico_gateway/config/error_map.yaml
A ingenico_gateway/config/payment_methods.yaml
A ingenico_gateway/config/payment_submethods.yaml
A ingenico_gateway/config/transformers.yaml
A ingenico_gateway/config/var_map.yaml
A ingenico_gateway/ingenico.adapter.php
A ingenico_gateway/ingenico_gateway.alias.php
A ingenico_gateway/ingenico_gateway.body.php
A ingenico_gateway/ingenico_resultswitcher.body.php
A ingenico_gateway/orphan.adapter.php
A tests/phpunit/Adapter/Ingenico/IngenicoApiTest.php
A tests/phpunit/Adapter/Ingenico/IngenicoFormLoadTest.php
A tests/phpunit/Adapter/Ingenico/IngenicoOrphanAdapterTest.php
A tests/phpunit/Adapter/Ingenico/IngenicoOrphanRectifierTest.php
A tests/phpunit/Adapter/Ingenico/IngenicoTest.php
A tests/phpunit/Adapter/Ingenico/RealTimeBankTransferIdealTest.php
A tests/phpunit/Adapter/Ingenico/RecurringTest.php
A tests/phpunit/Adapter/Ingenico/ResultSwitcherTest.php
A tests/phpunit/BaseIngenicoTestCase.php
M tests/phpunit/DonationInterfaceTestCase.php
M tests/phpunit/TestConfiguration.php
28 files changed, 2,287 insertions(+), 16 deletions(-)

Approvals:
  Mepps: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/DonationInterface.class.php b/DonationInterface.class.php
index 8f014e6..4282c2b 100644
--- a/DonationInterface.class.php
+++ b/DonationInterface.class.php
@@ -69,6 +69,7 @@
 
                $wgAutoloadClasses['DonationInterfaceTestCase'] = $testDir . 
'DonationInterfaceTestCase.php';
                $wgAutoloadClasses['DonationInterfaceApiTestCase'] = $testDir . 
'DonationInterfaceApiTestCase.php';
+               $wgAutoloadClasses['BaseIngenicoTestCase'] = $testDir . 
'BaseIngenicoTestCase.php';
                $wgAutoloadClasses['MockAmazonClient'] = $testDir . 
'includes/MockAmazonClient.php';
                $wgAutoloadClasses['MockAmazonResponse'] = $testDir . 
'includes/MockAmazonResponse.php';
                $wgAutoloadClasses['TestingAdyenAdapter'] = $testDir . 
'includes/test_gateway/TestingAdyenAdapter.php';
diff --git a/extension.json b/extension.json
index 39afef2..9c0058a 100644
--- a/extension.json
+++ b/extension.json
@@ -26,6 +26,8 @@
                "SystemStatus": "SystemStatus",
                "GlobalCollectGateway": "GlobalCollectGateway",
                "GlobalCollectGatewayResult": "GlobalCollectGatewayResult",
+               "IngenicoGateway": "IngenicoGateway",
+               "IngenicoGatewayResult": "IngenicoGatewayResult",
                "AmazonGateway": "AmazonGateway",
                "AdyenGateway": "AdyenGateway",
                "AdyenGatewayResult": "AdyenGatewayResult",
@@ -57,6 +59,7 @@
                "GatewayAliases": "DonationInterface.alias.php",
                "AmazonGatewayAlias": "amazon_gateway/amazon_gateway.alias.php",
                "GlobalCollectGatewayAlias": 
"globalcollect_gateway/globalcollect_gateway.alias.php",
+               "IngenicoGatewayAlias": 
"ingenico_gateway/ingenico_gateway.alias.php",
                "AdyenGatewayAlias": "adyen_gateway/adyen_gateway.alias.php",
                "AstroPayGatewayAlias": 
"astropay_gateway/astropay_gateway.alias.php",
                "PaypalGatewayAlias": "paypal_gateway/paypal_gateway.alias.php"
@@ -115,8 +118,13 @@
                "GlobalCollectGateway": 
"globalcollect_gateway/globalcollect_gateway.body.php",
                "GlobalCollectGatewayResult": 
"globalcollect_gateway/globalcollect_resultswitcher.body.php",
                "GlobalCollectAdapter": 
"globalcollect_gateway/globalcollect.adapter.php",
+               "IngenicoGateway": "ingenico_gateway/ingenico_gateway.body.php",
+               "IngenicoGatewayResult": 
"ingenico_gateway/ingenico_resultswitcher.body.php",
+               "IngenicoAdapter": "ingenico_gateway/ingenico.adapter.php",
                "GlobalCollectOrphanAdapter": 
"globalcollect_gateway/orphan.adapter.php",
                "GlobalCollectOrphanRectifier": 
"globalcollect_gateway/GlobalCollectOrphanRectifier.php",
+               "IngenicoOrphanAdapter": "ingenico_gateway/orphan.adapter.php",
+               "IngenicoOrphanRectifier": 
"ingenico_gateway/IngenicoOrphanRectifier.php",
                "IngenicoFinancialNumber": 
"globalcollect_gateway/IngenicoFinancialNumber.php",
                "IngenicoLanguage": 
"globalcollect_gateway/IngenicoLanguage.php",
                "IngenicoMethodCodec": 
"globalcollect_gateway/IngenicoMethodCodec.php",
@@ -424,6 +432,7 @@
                "DonationInterfaceMinfraudErrorScore": 50,
                "DonationInterfaceEnableBannerHistoryLog": false,
                "GlobalCollectGatewayEnabled": false,
+               "IngenicoGatewayEnabled": false,
                "AmazonGatewayEnabled": false,
                "AdyenGatewayEnabled": false,
                "AstroPayGatewayEnabled": false,
@@ -440,6 +449,8 @@
                "DonationInterfaceGatewayAdapters": {
                        "globalcollect": "GlobalCollectAdapter",
                        "globalcollect_orphan": "GlobalCollectOrphanAdapter",
+                       "ingenico": "IngenicoAdapter",
+                       "ingenico_orphan": "IngenicoOrphanAdapter",
                        "amazon": "AmazonAdapter",
                        "adyen": "AdyenAdapter",
                        "astropay": "AstroPayAdapter",
diff --git a/gateway_common/donation.api.php b/gateway_common/donation.api.php
index 107f62c..16a6e7a 100644
--- a/gateway_common/donation.api.php
+++ b/gateway_common/donation.api.php
@@ -37,20 +37,27 @@
                        return;
                }
 
-               if ( $this->gateway == 'globalcollect' ) {
-                       switch ( $method ) {
-                               // TODO: add other iframe payment methods
-                               case 'cc':
-                                       $result = $gatewayObj->do_transaction( 
'INSERT_ORDERWITHPAYMENT' );
-                                       break;
-                               default:
-                                       $result = $gatewayObj->do_transaction( 
'TEST_CONNECTION' );
-                       }
-               } elseif ( $this->gateway == 'adyen' ) {
-                       $result = $gatewayObj->do_transaction( 'donate' );
-               } elseif ( $this->gateway === 'paypal_ec' ) {
-                       $gatewayObj->doPayment();
-                       $result = $gatewayObj->getTransactionResponse();
+               switch( $this->gateway ) {
+                       case 'globalcollect':
+                               switch ( $method ) {
+                                       // TODO: add other iframe payment 
methods
+                                       case 'cc':
+                                               $result = 
$gatewayObj->do_transaction( 'INSERT_ORDERWITHPAYMENT' );
+                                               break;
+                                       default:
+                                               $result = 
$gatewayObj->do_transaction( 'TEST_CONNECTION' );
+                               }
+                               break;
+                       case 'ingenico':
+                               $result = $gatewayObj->do_transaction( 
'createHostedCheckout' );
+                               break;
+                       case 'adyen':
+                               $result = $gatewayObj->do_transaction( 'donate' 
);
+                               break;
+                       case 'paypal_ec':
+                               $gatewayObj->doPayment();
+                               $result = $gatewayObj->getTransactionResponse();
+                               break;
                }
 
                // $normalizedData = $gatewayObj->getData_Unstaged_Escaped();
diff --git a/ingenico_gateway/IngenicoOrphanRectifier.php 
b/ingenico_gateway/IngenicoOrphanRectifier.php
new file mode 100644
index 0000000..47273d7
--- /dev/null
+++ b/ingenico_gateway/IngenicoOrphanRectifier.php
@@ -0,0 +1,5 @@
+<?php
+
+class IngenicoOrphanRectifier extends GlobalCollectOrphanRectifier {
+
+}
\ No newline at end of file
diff --git a/ingenico_gateway/config/country_fields.yaml 
b/ingenico_gateway/config/country_fields.yaml
new file mode 120000
index 0000000..b45ff73
--- /dev/null
+++ b/ingenico_gateway/config/country_fields.yaml
@@ -0,0 +1 @@
+../../globalcollect_gateway/config/country_fields.yaml
\ No newline at end of file
diff --git a/ingenico_gateway/config/currencies.yaml 
b/ingenico_gateway/config/currencies.yaml
new file mode 120000
index 0000000..8234aeb
--- /dev/null
+++ b/ingenico_gateway/config/currencies.yaml
@@ -0,0 +1 @@
+../../globalcollect_gateway/config/currencies.yaml
\ No newline at end of file
diff --git a/ingenico_gateway/config/data_constraints.yaml 
b/ingenico_gateway/config/data_constraints.yaml
new file mode 100644
index 0000000..166021f
--- /dev/null
+++ b/ingenico_gateway/config/data_constraints.yaml
@@ -0,0 +1,62 @@
+# General fields
+# AMOUNT: N12
+amount:
+    type: numeric
+    length: 12
+# city: AN40
+city:
+    type: alphanumeric
+    length: 40
+# countryCode: AN2
+country:
+    type: alphanumeric
+    length: 2
+# currency: AN3
+currency:
+    type: alphanumeric
+    length: 3
+# emailAddress: AN70
+email:
+    type: alphanumeric
+    length: 70
+# firstName: AN15
+first_name:
+    type: alphanumeric
+    length: 15
+# IPADDRESS: AN32
+user_ip:
+    type: alphanumeric
+    length: 32
+# locale: AN5
+language:
+    type: alphanumeric
+    length: 5
+# merchantReference: N10
+order_id:
+    type: numeric
+    length: 10
+# paymentProductId
+payment_product:
+    type: numeric
+    length: 5
+# returnUrl: AN512
+returnto:
+    type: alphanumeric
+    length: 512
+# state: AN35
+state_province:
+    type: alphanumeric
+    length: 35
+# street: AN50
+street_address:
+    type: alphanumeric
+    length: 50
+# surname: AN35
+last_name:
+    type: alphanumeric
+    length: 35
+# zip: AN10
+postal_code:
+    type: alphanumeric
+    length: 10
+
diff --git a/ingenico_gateway/config/error_map.yaml 
b/ingenico_gateway/config/error_map.yaml
new file mode 120000
index 0000000..a5ef117
--- /dev/null
+++ b/ingenico_gateway/config/error_map.yaml
@@ -0,0 +1 @@
+../../globalcollect_gateway/config/error_map.yaml
\ No newline at end of file
diff --git a/ingenico_gateway/config/payment_methods.yaml 
b/ingenico_gateway/config/payment_methods.yaml
new file mode 120000
index 0000000..ef8aeeb
--- /dev/null
+++ b/ingenico_gateway/config/payment_methods.yaml
@@ -0,0 +1 @@
+../../globalcollect_gateway/config/payment_methods.yaml
\ No newline at end of file
diff --git a/ingenico_gateway/config/payment_submethods.yaml 
b/ingenico_gateway/config/payment_submethods.yaml
new file mode 120000
index 0000000..40198da
--- /dev/null
+++ b/ingenico_gateway/config/payment_submethods.yaml
@@ -0,0 +1 @@
+../../globalcollect_gateway/config/payment_submethods.yaml
\ No newline at end of file
diff --git a/ingenico_gateway/config/transformers.yaml 
b/ingenico_gateway/config/transformers.yaml
new file mode 100644
index 0000000..f8d14c7
--- /dev/null
+++ b/ingenico_gateway/config/transformers.yaml
@@ -0,0 +1,14 @@
+# Core
+- Amount
+- DonorEmail
+- DonorFullName
+- AmountInCents
+- StreetAddress
+
+# Ingenico
+- FiscalNumber
+- ContributionTrackingPlusUnique
+- IngenicoFinancialNumber
+- DonorLocale
+- IngenicoMethodCodec
+- IngenicoReturntoHelper
diff --git a/ingenico_gateway/config/var_map.yaml 
b/ingenico_gateway/config/var_map.yaml
new file mode 100644
index 0000000..79c88d8
--- /dev/null
+++ b/ingenico_gateway/config/var_map.yaml
@@ -0,0 +1,16 @@
+# TODO: move this all into SmashPig
+isRecurring: recurring
+locale: language
+amount: amount
+currencyCode: currency
+paymentProductId: payment_product
+city: city
+countryCode: country
+state: state_province
+street: street_address
+zip: postal_code
+emailAddress: email
+firstName: first_name
+surname: last_name
+merchantReference: order_id
+hostedCheckoutId: gateway_session_id
diff --git a/ingenico_gateway/ingenico.adapter.php 
b/ingenico_gateway/ingenico.adapter.php
new file mode 100644
index 0000000..bc4bd45
--- /dev/null
+++ b/ingenico_gateway/ingenico.adapter.php
@@ -0,0 +1,172 @@
+<?php
+
+use SmashPig\PaymentProviders\PaymentProviderFactory;
+
+class IngenicoAdapter extends GlobalCollectAdapter {
+       const GATEWAY_NAME = 'Ingenico';
+       const IDENTIFIER = 'ingenico';
+       const GLOBAL_PREFIX = 'wgIngenicoGateway';
+
+       public function getCommunicationType() {
+               return 'array';
+       }
+
+       public function getResponseType() {
+               return 'json';
+       }
+
+       public function defineTransactions() {
+               parent::defineTransactions();
+               $this->transactions['createHostedCheckout'] = array(
+                       'request' => array(
+                               'hostedCheckoutSpecificInput' => array(
+                                       'isRecurring',
+                                       'locale',
+                                       'paymentProductFilters' => array(
+                                               'restrictTo' => array(
+                                                       'products' => array(
+                                                               // HACK! this 
array should be a simple
+                                                               // list of 
payment ids, not an associative array
+                                                               // so... use 
'null' to flag that?
+                                                               
'paymentProductId' => null
+                                                       )
+                                               )
+                                       ),
+                                       'returnUrl',
+                                       'showResultPage',
+                                       // 'tokens', // we don't store user 
accounts or tokens here
+                                       // 'variant', // For a/b testing of 
iframe
+                               ),
+                               'order' => array(
+                                       'amountOfMoney' => array(
+                                               'amount',
+                                               'currencyCode',
+                                       ),
+                                       'customer' => array(
+                                               'billingAddress' => array(
+                                                       'city',
+                                                       'countryCode',
+                                                       // 'houseNumber' // 
hmm, hope this isn't used for fraud detection!
+                                                       'state',
+                                                       // 'stateCode', // 
should we use this instead?
+                                                       'street',
+                                                       'zip',
+                                               ),
+                                               'contactDetails' => array(
+                                                       'emailAddress'
+                                               ),
+                                               // 'fiscalNumber' // only 
required for boletos & Brazil paypal
+                                               'locale', // used for 
redirection to 3rd parties
+                                               'personalInformation' => array(
+                                                       'name' => array(
+                                                               'firstName',
+                                                               'surname',
+                                                       )
+                                               )
+                                       ),
+                                       /*'items' => array(
+                                               array(
+                                                       'amountOfMoney' => 
array(
+                                                               'amount',
+                                                               'currencyCode',
+                                                       ),
+                                                       'invoiceData' => array(
+                                                               'description'
+                                                       )
+                                               )
+                                       ),*/
+                                       'references' => array(
+                                               'descriptor', // First 22+ 
chars appear on card statement
+                                               'merchantReference', // unique, 
string(30)
+                                       )
+                               )
+                       ),
+                       'values' => array(
+                               'returnUrl' => $returnTitle = 
Title::newFromText( 'Special:IngenicoGatewayResult' )
+                                       ->getFullURL( false, false, 
PROTO_CURRENT ),
+                               'showResultPage' => 'false',
+                               'descriptor' => WmfFramework::formatMessage( 
'donate_interface-donation-description' ),
+                       ),
+                       'response' => array(
+                               'hostedCheckoutId'
+                       )
+               );
+       }
+
+       /**
+        * Sets up the $order_id_meta array.
+        * Should contain the following keys/values:
+        * 'alt_locations' => array( $dataset_name, $dataset_key ) //ordered
+        * 'type' => numeric, or alphanumeric
+        * 'length' => $max_charlen
+        */
+       public function defineOrderIDMeta() {
+               $this->order_id_meta = array (
+                       'alt_locations' => array (),
+                       'ct_id' => TRUE,
+                       'generate' => TRUE,
+               );
+       }
+
+       public function curl_transaction( $data ) {
+               $email = $this->getData_Unstaged_Escaped( 'email' );
+               $this->logger->info( "Making API call for donor $email" );
+
+               $filterResult = $this->runSessionVelocityFilter();
+               if ( $filterResult === false ) {
+                       return false;
+               }
+
+               $provider = $this->getPaymentProvider();
+               switch( $this->getCurrentTransaction() ) {
+                       case 'createHostedCheckout':
+                               $result = $provider->createHostedPayment( $data 
);
+                               $this->transaction_response->setRawResponse( 
json_encode( $result ) );
+                               return true;
+                       default:
+                               return false;
+               }
+       }
+
+       public function getBasedir() {
+               return __DIR__;
+       }
+
+       public function do_transaction( $transaction ) {
+               // If this is not our first call, get a fresh order ID
+               // FIXME: This is repeated in four places. Maybe always 
regenerate in incrementSequenceNumber?
+               if ( $this->session_getData( 'sequence' ) ) {
+                       $this->regenerateOrderID();
+               }
+               if ( $transaction === 'createHostedCheckout' ) {
+                       $this->incrementSequenceNumber();
+               }
+               $result = parent::do_transaction( $transaction );
+               // Add things to session which may have been retrieved from API
+               $this->session_addDonorData();
+               return $result;
+       }
+
+       protected function getPaymentProvider() {
+               $method = $this->getData_Unstaged_Escaped( 'payment_method' );
+               return PaymentProviderFactory::getProviderForMethod( $method );
+       }
+
+       public function parseResponseCommunicationStatus( $response ) {
+               return true;
+       }
+
+       public function parseResponseErrors( $response ) {
+               return array();
+       }
+
+       public function parseResponseData( $response ) {
+               if ( isset( $response['partialRedirectUrl'] ) ) {
+                       $provider = $this->getPaymentProvider();
+                       $response['FORMACTION'] = 
$provider->getHostedPaymentUrl(
+                               $response['partialRedirectUrl']
+                       );
+               }
+               return $response;
+       }
+}
diff --git a/ingenico_gateway/ingenico_gateway.alias.php 
b/ingenico_gateway/ingenico_gateway.alias.php
new file mode 100644
index 0000000..cf74199
--- /dev/null
+++ b/ingenico_gateway/ingenico_gateway.alias.php
@@ -0,0 +1,9 @@
+<?php
+
+$specialPageAliases = array();
+
+/** English */
+$specialPageAliases['en'] = array(
+       'IngenicoGateway' => array( 'IngenicoGateway' ),
+       'IngenicoGatewayResult' => array( 'IngenicoGatewayResult' ),
+);
diff --git a/ingenico_gateway/ingenico_gateway.body.php 
b/ingenico_gateway/ingenico_gateway.body.php
new file mode 100644
index 0000000..7f85f25
--- /dev/null
+++ b/ingenico_gateway/ingenico_gateway.body.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * Wikimedia Foundation
+ *
+ * LICENSE
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/**
+ * IngenicoGateway
+ *
+ */
+class IngenicoGateway extends GlobalCollectGateway {
+
+       protected $gatewayIdentifier = IngenicoAdapter::IDENTIFIER;
+
+}
diff --git a/ingenico_gateway/ingenico_resultswitcher.body.php 
b/ingenico_gateway/ingenico_resultswitcher.body.php
new file mode 100644
index 0000000..38c901d
--- /dev/null
+++ b/ingenico_gateway/ingenico_resultswitcher.body.php
@@ -0,0 +1,7 @@
+<?php
+
+class IngenicoGatewayResult extends GlobalCollectGatewayResult {
+
+       protected $gatewayIdentifier = IngenicoAdapter::IDENTIFIER;
+
+}
diff --git a/ingenico_gateway/orphan.adapter.php 
b/ingenico_gateway/orphan.adapter.php
new file mode 100644
index 0000000..07c5989
--- /dev/null
+++ b/ingenico_gateway/orphan.adapter.php
@@ -0,0 +1,5 @@
+<?php
+
+class IngenicoOrphanAdapter extends IngenicoAdapter {
+
+}
diff --git a/tests/phpunit/Adapter/Ingenico/IngenicoApiTest.php 
b/tests/phpunit/Adapter/Ingenico/IngenicoApiTest.php
new file mode 100644
index 0000000..8a89e1e
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoApiTest.php
@@ -0,0 +1,148 @@
+<?php
+
+use SmashPig\Core\DataStores\QueueWrapper;
+use SmashPig\CrmLink\Messages\SourceFields;
+use SmashPig\Tests\TestingContext;
+use SmashPig\Tests\TestingProviderConfiguration;
+
+/**
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ * @group IngenicoApi
+ * @group DonationInterfaceApi
+ * @group medium
+ */
+
+class IngenicoApiTest extends DonationInterfaceApiTestCase {
+
+       protected $hostedCheckoutProvider;
+
+       protected $partialUrl;
+
+       public function setUp() {
+               parent::setUp();
+               $ctx = TestingContext::get();
+               $globalConfig = $ctx->getGlobalConfiguration();
+
+               $providerConfig = 
TestingProviderConfiguration::createForProvider(
+                       'ingenico', $globalConfig
+               );
+               $ctx->providerConfigurationOverride = $providerConfig;
+
+               $this->hostedCheckoutProvider = $this->getMockBuilder(
+                       
'SmashPig\PaymentProviders\Ingenico\HostedCheckoutProvider'
+               )->disableOriginalConstructor()->getMock();
+
+               $providerConfig->overrideObjectInstance( 'payment-provider/cc', 
$this->hostedCheckoutProvider );
+               $this->partialUrl = 
'poweredbyglobalcollect.com/pay8915-53ebca407e6b4a1dbd086aad4f10354d:' .
+                       
'8915-28e5b79c889641c8ba770f1ba576c1fe:9798f4c44ac6406e8288494332d1daa0';
+       }
+
+       public function testGoodSubmit() {
+               $init = DonationInterfaceTestCase::getDonorTestData();
+               $init['email'] = '[email protected]';
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['gateway'] = 'ingenico';
+               $init['action'] = 'donate';
+
+               $this->hostedCheckoutProvider->expects( $this->once() )
+                       ->method( 'createHostedPayment' )->with(
+                               $this->callback( function( $actual ) {
+                                       $hcsi = array(
+                                               'locale' => 'en_US',
+                                               'paymentProductFilters' => 
array(
+                                                       'restrictTo' => array(
+                                                               'products' => 
array(
+                                                                       1
+                                                               )
+                                                       )
+                                               ),
+                                               'showResultPage' => 'false'
+                                       );
+                                       $this->assertArraySubset( $hcsi, 
$actual['hostedCheckoutSpecificInput'] );
+                                       $this->assertRegExp(
+                                               
'/Special:IngenicoGatewayResult/',
+                                               
$actual['hostedCheckoutSpecificInput']['returnUrl']
+                                       );
+                                       $order = array(
+                                               'amountOfMoney' => array(
+                                                       'currencyCode' => 'USD',
+                                                       'amount' => 155
+                                               ),
+                                               'customer' => array(
+                                                       'billingAddress' => 
array(
+                                                               'countryCode' 
=> 'US',
+                                                               'city' => 'San 
Francisco',
+                                                               'state' => 'CA',
+                                                               'zip' => 
'94105',
+                                                               'street' => 
'123 Fake Street'
+                                                       ),
+                                                       'contactDetails' => 
array(
+                                                               'emailAddress' 
=> '[email protected]'
+                                                       ),
+                                                       'locale' => 'en_US',
+                                                       'personalInformation' 
=> array(
+                                                               'name' => array(
+                                                                       
'firstName' => 'Firstname',
+                                                                       
'surname' => 'Surname'
+                                                               )
+                                                       )
+                                               )
+                                       );
+                                       $this->assertArraySubset( $order, 
$actual['order'] );
+                                       $this->assertTrue( is_numeric( 
$actual['order']['references']['merchantReference'] ) );
+                                       return true;
+                               } )
+                       )
+                       ->willReturn(
+                               array(
+                                       'partialRedirectUrl' => 
$this->partialUrl,
+                                       'hostedCheckoutId' => 
'8915-28e5b79c889641c8ba770f1ba576c1fe',
+                                       'RETURNMAC' => 
'f5b66cf9-c64c-4c8d-8171-b47205c89a56'
+                               )
+                       );
+
+               $this->hostedCheckoutProvider->expects( $this->once() )
+                       ->method( 'getHostedPaymentUrl' )->with(
+                               $this->equalTo( $this->partialUrl )
+                       )->willReturn( 'https://wmf-pay.' . $this->partialUrl );
+
+               $apiResult = $this->doApiRequest( $init );
+               $result = $apiResult[0]['result'];
+
+               $this->assertEquals(
+                       'https://wmf-pay.' . $this->partialUrl,
+                       $result['formaction'],
+                       'Ingenico API not setting formaction'
+               );
+               $this->assertTrue( $result['status'], 'Ingenico API result 
status should be true' );
+
+               $message = QueueWrapper::getQueue( 'pending' )->pop();
+               $this->assertNotNull( $message, 'Not sending a message to the 
pending queue' );
+               SourceFields::removeFromMessage( $message );
+               $expected = array(
+                       'fee' => 0,
+                       'utm_source' => '..cc',
+                       'language' => 'en',
+                       'email' => '[email protected]',
+                       'first_name' => 'Firstname',
+                       'last_name' => 'Surname',
+                       'country' => 'US',
+                       'gateway' => 'ingenico',
+                       'recurring' => '',
+                       'payment_method' => 'cc',
+                       'payment_submethod' => 'visa',
+                       'currency' => 'USD',
+                       'gross' => 1.55,
+                       'user_ip' => '127.0.0.1',
+                       'street_address' => '123 Fake Street',
+                       'city' => 'San Francisco',
+                       'state_province' => 'CA',
+                       'postal_code' => '94105',
+                       'gateway_session_id' => 
'8915-28e5b79c889641c8ba770f1ba576c1fe'
+               );
+               $this->assertArraySubset( $expected, $message );
+       }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/IngenicoFormLoadTest.php 
b/tests/phpunit/Adapter/Ingenico/IngenicoFormLoadTest.php
new file mode 100644
index 0000000..b73e122
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoFormLoadTest.php
@@ -0,0 +1,218 @@
+<?php
+/**
+ * Wikimedia Foundation
+ *
+ * LICENSE
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/**
+ *
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ */
+class IngenicoFormLoadTest extends BaseIngenicoTestCase {
+
+       public function testIngenicoFormLoad() {
+               $init = $this->getDonorTestData( 'US' );
+               unset( $init['order_id'] );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['ffname'] = 'cc-vmad';
+               $init['gateway'] = 'ingenico';
+
+               $assertNodes = array (
+                       'submethod-mc' => array (
+                               'nodename' => 'input'
+                       ),
+                       'selected-amount' => array (
+                               'nodename' => 'span',
+                               'innerhtmlmatches' => '/^\s*' .
+                                       str_replace( '$', '\$',
+                                               Amount::format( 1.55, 'USD', 
$init['language'] . '_' . $init['country'] )
+                                       ).
+                                       '\s*$/',
+                       ),
+                       'state_province' => array (
+                               'nodename' => 'select',
+                               'selected' => 'CA',
+                       ),
+               );
+
+               $this->verifyFormOutput( 'IngenicoGateway', $init, 
$assertNodes, true );
+       }
+
+       function testIngenicoFormLoad_FR() {
+               $init = $this->getDonorTestData( 'FR' );
+               unset( $init['order_id'] );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['ffname'] = 'cc-vmaj';
+               $init['gateway'] = 'ingenico';
+
+               $assertNodes = array (
+                       'selected-amount' => array (
+                               'nodename' => 'span',
+                               'innerhtmlmatches' => '/^\s*' .
+                                       Amount::format( 1.55, 'EUR', 
$init['language'] . '_' . $init['country'] ) .
+                                       '\s*$/',
+                       ),
+                       'first_name' => array (
+                               'nodename' => 'input',
+                               'value' => 'Prénom',
+                       ),
+                       'last_name' => array (
+                               'nodename' => 'input',
+                               'value' => 'Nom',
+                       ),
+                       'country' => array (
+                               'nodename' => 'input',
+                               'value' => 'FR',
+                       ),
+               );
+
+               $this->verifyFormOutput( 'IngenicoGateway', $init, 
$assertNodes, true );
+       }
+
+       /**
+        * Ensure that form loads for Italy
+        */
+       public function testIngenicoFormLoad_IT() {
+               $init = $this->getDonorTestData( 'IT' );
+               unset( $init['order_id'] );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['ffname'] = 'cc-vmaj';
+               $init['gateway'] = 'ingenico';
+
+               $assertNodes = array (
+                       'selected-amount' => array (
+                               'nodename' => 'span',
+                               'innerhtmlmatches' => '/^\s*' .
+                                       Amount::format( 1.55, 'EUR', 
$init['language'] . '_' . $init['country'] ) .
+                                       '\s*$/',
+                       ),
+                       'first_name' => array (
+                               'nodename' => 'input',
+                               'placeholder' => wfMessage( 
'donate_interface-donor-first_name')->inLanguage( 'it' )->text(),
+                       ),
+                       'last_name' => array (
+                               'nodename' => 'input',
+                               'placeholder' => wfMessage( 
'donate_interface-donor-last_name')->inLanguage( 'it' )->text(),
+                       ),
+                       'informationsharing' => array (
+                               'nodename' => 'p',
+                               'innerhtml' => wfMessage( 
'donate_interface-informationsharing', '.*' )->inLanguage( 'it' )->text(),
+                       ),
+                       'country' => array (
+                               'nodename' => 'input',
+                               'value' => 'IT',
+                       ),
+               );
+
+               $this->verifyFormOutput( 'IngenicoGateway', $init, 
$assertNodes, true );
+       }
+
+       /**
+        * Make sure Belgian form loads in all of that country's supported 
languages
+        * @dataProvider belgiumLanguageProvider
+        */
+       public function testIngenicoFormLoad_BE( $language ) {
+               $init = $this->getDonorTestData( 'BE' );
+               unset( $init['order_id'] );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['ffname'] = 'cc-vmaj';
+               $init['language'] = $language;
+               $init['gateway'] = 'ingenico';
+
+               $assertNodes = array (
+                       'selected-amount' => array (
+                               'nodename' => 'span',
+                               'innerhtmlmatches' => '/^\s*' .
+                                       Amount::format( 1.55, 'EUR', 
$init['language'] . '_' . $init['country'] ) .
+                                       '\s*$/',
+                       ),
+                       'first_name' => array (
+                               'nodename' => 'input',
+                               'placeholder' => wfMessage( 
'donate_interface-donor-first_name')->inLanguage( $language )->text(),
+                       ),
+                       'last_name' => array (
+                               'nodename' => 'input',
+                               'placeholder' => wfMessage( 
'donate_interface-donor-last_name')->inLanguage( $language )->text(),
+                       ),
+                       'informationsharing' => array (
+                               'nodename' => 'p',
+                               'innerhtml' => wfMessage( 
'donate_interface-informationsharing', '.*' )->inLanguage( $language )->text(),
+                       ),
+                       'country' => array (
+                               'nodename' => 'input',
+                               'value' => 'BE',
+                       ),
+               );
+
+               $this->verifyFormOutput( 'IngenicoGateway', $init, 
$assertNodes, true );
+       }
+
+       /**
+        * Make sure Canadian CC form loads in English and French
+        * @dataProvider canadaLanguageProvider
+        */
+       public function testIngenicoFormLoad_CA( $language ) {
+               $init = $this->getDonorTestData( 'CA' );
+               unset( $init['order_id'] );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['ffname'] = 'cc-vma';
+               $init['language'] = $language;
+               $init['gateway'] = 'ingenico';
+
+               $assertNodes = array (
+                       'selected-amount' => array (
+                               'nodename' => 'span',
+                               'innerhtmlmatches' => '/^\s*' .
+                                       str_replace( '$', '\$',
+                                               Amount::format( 1.55, 'CAD', 
$init['language'] . '_' . $init['country'] )
+                                       ) .
+                                       '\s*$/',
+                       ),
+                       'first_name' => array (
+                               'nodename' => 'input',
+                               'placeholder' => wfMessage( 
'donate_interface-donor-first_name')->inLanguage( $language )->text(),
+                       ),
+                       'last_name' => array (
+                               'nodename' => 'input',
+                               'placeholder' => wfMessage( 
'donate_interface-donor-last_name')->inLanguage( $language )->text(),
+                       ),
+                       'informationsharing' => array (
+                               'nodename' => 'p',
+                               'innerhtml' => wfMessage( 
'donate_interface-informationsharing', '.*' )->inLanguage( $language )->text(),
+                       ),
+                       'state_province' => array (
+                               'nodename' => 'select',
+                               'selected' => 'SK',
+                       ),
+                       'postal_code' => array (
+                               'nodename' => 'input',
+                               'value' => $init['postal_code'],
+                       ),
+                       'country' => array (
+                               'nodename' => 'input',
+                               'value' => 'CA',
+                       ),
+               );
+
+               $this->verifyFormOutput( 'IngenicoGateway', $init, 
$assertNodes, true );
+       }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/IngenicoOrphanAdapterTest.php 
b/tests/phpunit/Adapter/Ingenico/IngenicoOrphanAdapterTest.php
new file mode 100644
index 0000000..10042f7
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoOrphanAdapterTest.php
@@ -0,0 +1,212 @@
+<?php
+/**
+ * Wikimedia Foundation
+ *
+ * LICENSE
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+use Psr\Log\LogLevel;
+use SmashPig\Core\DataStores\QueueWrapper;
+use SmashPig\CrmLink\Messages\SourceFields;
+use SmashPig\Tests\TestingContext;
+use SmashPig\Tests\TestingProviderConfiguration;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ *
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ * @group OrphanSlayer
+ */
+class DonationInterface_Adapter_Ingenico_Orphans_IngenicoTest extends 
DonationInterfaceTestCase {
+       public function setUp() {
+               parent::setUp();
+               $this->markTestSkipped( 'Orphan adapter not yet implemented' );
+
+               TestingContext::get()->providerConfigurationOverride =
+                       TestingProviderConfiguration::createForProvider(
+                               'ingenico',
+                               $this->smashPigGlobalConfig
+                       );
+
+               $this->setMwGlobals( array(
+                       'wgIngenicoGatewayEnabled' => true,
+                       'wgDonationInterfaceAllowedHtmlForms' => array(
+                               'cc-vmad' => array(
+                                       'gateway' => 'ingenico',
+                                       'payment_methods' => array('cc' => 
array( 'visa', 'mc', 'amex', 'discover' )),
+                                       'countries' => array(
+                                               '+' => array( 'US', ),
+                                       ),
+                               ),
+                       ),
+               ) );
+       }
+
+       /**
+        * @param $name string The name of the test case
+        * @param $data array Any parameters read from a dataProvider
+        * @param $dataName string|int The name or index of the data set
+        */
+       function __construct( $name = null, array $data = array(), $dataName = 
'' ) {
+               parent::__construct( $name, $data, $dataName );
+               $this->dummy_utm_data = array (
+                       'utm_source' => 'dummy_source',
+                       'utm_campaign' => 'dummy_campaign',
+                       'utm_medium' => 'dummy_medium',
+                       'date' => time(),
+               );
+       }
+
+       public function testConstructor() {
+
+               $class = $this->testAdapterClass;
+
+               $gateway = $this->getFreshGatewayObject();
+
+               $this->assertInstanceOf( $class, $gateway );
+
+               $this->verifyNoLogErrors();
+       }
+
+
+       public function testBatchOrderID_generate() {
+
+               //no data on construct, generate Order IDs
+               $gateway = $this->getFreshGatewayObject( null, array ( 
'order_id_meta' => array ( 'generate' => TRUE ) ) );
+               $this->assertTrue( $gateway->getOrderIDMeta( 'generate' ), 'The 
order_id meta generate setting override is not working properly. Order_id 
generation may be broken.' );
+               $this->assertNotNull( $gateway->getData_Unstaged_Escaped( 
'order_id' ), 'Failed asserting that an absent order id is not left as null, 
when generating our own' );
+
+               $data = array_merge( $this->getDonorTestData(), 
$this->dummy_utm_data );
+               $data['order_id'] = '55555';
+
+               //now, add data and check that we didn't kill the oid. Still 
generating.
+               $gateway->loadDataAndReInit( $data );
+               $this->assertEquals( $gateway->getData_Unstaged_Escaped( 
'order_id' ), '55555', 'loadDataAndReInit failed to stick OrderID' );
+
+               $data['order_id'] = '444444';
+               $gateway->loadDataAndReInit( $data );
+               $this->assertEquals( $gateway->getData_Unstaged_Escaped( 
'order_id' ), '444444', 'loadDataAndReInit failed to stick OrderID' );
+
+               $this->verifyNoLogErrors();
+       }
+
+       public function testBatchOrderID_no_generate() {
+
+               //no data on construct, do not generate Order IDs
+               $gateway = $this->getFreshGatewayObject( null, array ( 
'order_id_meta' => array ( 'generate' => FALSE ) ) );
+               $this->assertFalse( $gateway->getOrderIDMeta( 'generate' ), 
'The order_id meta generate setting override is not working properly. Deferred 
order_id generation may be broken.' );
+               $this->assertEmpty( $gateway->getData_Unstaged_Escaped( 
'order_id' ), 'Failed asserting that an absent order id is left as null, when 
not generating our own' );
+
+               $data = array_merge( $this->getDonorTestData(), 
$this->dummy_utm_data );
+               $data['order_id'] = '66666';
+
+               //now, add data and check that we didn't kill the oid. Still 
not generating
+               $gateway->loadDataAndReInit( $data );
+               $this->assertEquals( $gateway->getData_Unstaged_Escaped( 
'order_id' ), '66666', 'loadDataAndReInit failed to stick OrderID' );
+
+               $data['order_id'] = '777777';
+               $gateway->loadDataAndReInit( $data );
+               $this->assertEquals( $gateway->getData_Unstaged_Escaped( 
'order_id' ), '777777', 'loadDataAndReInit failed to stick OrderID on second 
batch item' );
+
+               $this->verifyNoLogErrors();
+       }
+
+       /**
+        * Tests to make sure that certain error codes returned from GC will
+        * trigger order cancellation, even if retryable errors also exist.
+        * @dataProvider mcNoRetryCodeProvider
+        */
+       public function testNoMastercardFinesForRepeatOnBadCodes( $code ) {
+               $gateway = $this->getFreshGatewayObject( null, array ( 
'order_id_meta' => array ( 'generate' => FALSE ) ) );
+
+               //Toxic card should not retry, even if there's an order id 
collision
+               $init = array_merge( $this->getDonorTestData(), 
$this->dummy_utm_data );
+               $init['ffname'] = 'cc-vmad';
+               $init['order_id'] = '55555';
+               $init['email'] = '[email protected]';
+               $init['contribution_tracking_id'] = mt_rand();
+               $gateway->loadDataAndReInit( $init );
+
+               $gateway->setDummyGatewayResponseCode( $code );
+               $result = $gateway->do_transaction( 'Confirm_CreditCard' );
+               $this->assertEquals( 1, count( $gateway->curled ), "Gateway 
kept trying even with response code $code!  MasterCard could fine us a thousand 
bucks for that!" );
+               $this->assertEquals( false, $result->getCommunicationStatus(), 
"Error code $code should mean status of do_transaction is false" );
+               $errors = $result->getErrors();
+               $this->assertFalse( empty( $errors ), 'Orphan adapter needs to 
see the errors to consider it rectified' );
+               $finder = function( $error ) {
+                       return $error->getErrorCode() == '1000001';
+               };
+               $this->assertNotEmpty( array_filter( $errors, $finder ), 
'Orphan adapter needs error 1000001 to consider it rectified' );
+               $loglines = $this->getLogMatches( LogLevel::INFO, "/Got error 
code $code, not retrying to avoid MasterCard fines./" );
+               $this->assertNotEmpty( $loglines, "GC Error $code is not 
generating the expected payments log error" );
+       }
+
+       /**
+        * Make sure we're incorporating GET_ORDERSTATUS AVS and CVV responses 
into
+        * fraud scores.
+        */
+       function testGetOrderstatusPostProcessFraud() {
+               $this->markTestSkipped( 'OrderStatus not yet implemented' );
+               $this->setMwGlobals( array(
+                       'wgDonationInterfaceEnableCustomFilters' => true,
+                       'wgIngenicoGatewayCustomFiltersFunctions' => array(
+                               'getCVVResult' => 10,
+                               'getAVSResult' => 30,
+                       ),
+               ) );
+               $gateway = $this->getFreshGatewayObject( null, array ( 
'order_id_meta' => array ( 'generate' => FALSE ) ) );
+
+               $init = array_merge( $this->getDonorTestData(), 
$this->dummy_utm_data );
+               $init['ffname'] = 'cc-vmad';
+               $init['order_id'] = '55555';
+               $init['email'] = '[email protected]';
+               $init['contribution_tracking_id'] = mt_rand();
+               $init['payment_method'] = 'cc';
+
+               $gateway->loadDataAndReInit( $init );
+               $gateway->setDummyGatewayResponseCode( '600_badCvv' );
+
+               $gateway->do_transaction( 'Confirm_CreditCard' );
+               $action = $gateway->getValidationAction();
+               $this->assertEquals( 'review', $action,
+                       'Orphan gateway should fraud fail on bad CVV and AVS' );
+
+               $exposed = TestingAccessWrapper::newFromObject( $gateway );
+               $this->assertEquals( 40, $exposed->risk_score,
+                       'Risk score was incremented correctly.' );
+               $message = QueueWrapper::getQueue( 'payments-antifraud' 
)->pop();
+               SourceFields::removeFromMessage( $message );
+               $expected = array(
+                       'validation_action' => 'review',
+                       'risk_score' => 40,
+                       'score_breakdown' => array(
+                               // FIXME: need to enable utm / email / country 
checks ???
+                               'initial' => 0,
+                               'getCVVResult' => 10,
+                               'getAVSResult' => 30,
+                       ),
+                       'user_ip' => null, // FIXME
+                       'gateway_txn_id' => '55555',
+                       'date' => $message['date'],
+                       'server' => gethostname(),
+                       'gateway' => 'ingenico',
+                       'contribution_tracking_id' => 
$gateway->getData_Unstaged_Escaped( 'contribution_tracking_id' ),
+                       'order_id' => $gateway->getData_Unstaged_Escaped( 
'order_id' ),
+                       'payment_method' => 'cc',
+               );
+               $this->assertEquals( $expected, $message );
+       }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/IngenicoOrphanRectifierTest.php 
b/tests/phpunit/Adapter/Ingenico/IngenicoOrphanRectifierTest.php
new file mode 100644
index 0000000..4926c49
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoOrphanRectifierTest.php
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * Wikimedia Foundation
+ *
+ * LICENSE
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+use SmashPig\Core\DataStores\PendingDatabase;
+
+/**
+ * @covers IngenicoOrphanRectifier
+ *
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ * @group OrphanSlayer
+ */
+class DonationInterface_Adapter_Ingenico_Orphan_Rectifier_Test
+       extends DonationInterfaceTestCase
+{
+       // TODO: Give vulgar names.
+       // FIXME: Is 25 the normal unauthorized status?  Use the common one, 
whatever that is.
+       const STATUS_PENDING = 25;
+       const STATUS_PENDING_POKE = 600;
+       const STATUS_COMPLETE = 800;
+
+       // Arbitrary configuration for testing time logic.
+       const TIME_BUFFER = 60;
+       const TARGET_EXECUTE_TIME = 1200;
+
+       public $pendingDb;
+
+       public function setUp() {
+               parent::setUp();
+               $this->markTestSkipped( 'Orphan rectifier not yet implemented' 
);
+
+               $this->setMwGlobals( array(
+                       'wgDonationInterfaceOrphanCron' => array(
+                               'enable' => true,
+                               'target_execute_time' => 
self::TARGET_EXECUTE_TIME,
+                               'time_buffer' => self::TIME_BUFFER,
+                       ),
+                       'wgIngenicoGatewayEnabled' => true,
+                       'wgDonationInterfaceGatewayAdapters' => array(
+                               // We include the regular adapter in order to 
pass gateway validation D:
+                               'ingenico' => 'IngenicoOrphanAdapter',
+                               'ingenico_orphan' => 'IngenicoOrphanAdapter',
+                       ),
+               ) );
+
+
+               $this->pendingDb = PendingDatabase::get();
+
+               // Create the schema.
+               $this->pendingDb->createTable();
+       }
+
+       /**
+        * When leaving a message unprocessed and pending, don't try to process 
it
+        * again.
+        */
+       public function testProcessOrphansStatusPending() {
+               $orphan_pending = $this->createOrphan();
+
+               $rectifier = new IngenicoOrphanRectifier();
+               $this->gateway = $rectifier->getAdapter();
+               $this->gateway->setDummyGatewayResponseCode( 
self::STATUS_PENDING );
+               $rectifier->processOrphans();
+
+               $fetched = $this->pendingDb->fetchMessageByGatewayOrderId(
+                       'ingenico', $orphan_pending['order_id'] );
+               $this->assertNull( $fetched,
+                       'Message was popped.' );
+
+               $this->assertGatewayCallsExactly( array(
+                       'GET_ORDERSTATUS'
+               ) );
+       }
+
+       /**
+        * If a message is waiting for the API kiss of death, perform it.
+        */
+       public function testProcessOrphansStatusPendingPoke() {
+               $orphan_pending_poke = $this->createOrphan();
+
+               $rectifier = new IngenicoOrphanRectifier();
+               $this->gateway = $rectifier->getAdapter();
+               $this->gateway->setDummyGatewayResponseCode( 
self::STATUS_PENDING_POKE );
+               $rectifier->processOrphans();
+
+               $fetched = $this->pendingDb->fetchMessageByGatewayOrderId(
+                       'ingenico', $orphan_pending_poke['order_id'] );
+               $this->assertNull( $fetched,
+                       'Message was popped' );
+
+               $this->assertGatewayCallsExactly( array(
+                       'GET_ORDERSTATUS',
+                       'SET_PAYMENT',
+               ) );
+
+               // TODO: test that we sent a completion message
+       }
+
+       /**
+        * Report a completed transaction.
+        */
+       public function testProcessOrphansStatusComplete() {
+
+               $orphan_complete = $this->createOrphan();
+
+               $rectifier = new IngenicoOrphanRectifier();
+               $this->gateway = $rectifier->getAdapter();
+               $this->gateway->setDummyGatewayResponseCode( 
self::STATUS_COMPLETE );
+               $rectifier->processOrphans();
+
+               $fetched = $this->pendingDb->fetchMessageByGatewayOrderId(
+                       'ingenico', $orphan_complete['order_id'] );
+               $this->assertNull( $fetched,
+                       'Message was popped' );
+
+               $this->assertGatewayCallsExactly( array(
+                       'GET_ORDERSTATUS',
+               ) );
+
+               // TODO: test that we sent a completion message
+       }
+
+       /**
+        * Don't process recent messages.
+        */
+       public function testTooRecentMessage() {
+               $orphan_complete = $this->createOrphan( array(
+                       'date' => time() - self::TIME_BUFFER + 30,
+               ) );
+
+               $rectifier = new IngenicoOrphanRectifier();
+               $this->gateway = $rectifier->getAdapter();
+               $rectifier->processOrphans();
+
+               $fetched = $this->pendingDb->fetchMessageByGatewayOrderId(
+                       'ingenico', $orphan_complete['order_id'] );
+               $this->assertNotNull( $fetched,
+                       'Message was not popped' );
+
+               $this->assertGatewayCallsExactly( array() );
+
+               // TODO: Test that we:
+               // * Logged the "done with old messages" line.
+       }
+
+       /**
+        * Create an orphaned tranaction and store it to the pending database.
+        *
+        * TODO: Reuse SmashPigBaseTest#createMessage
+        */
+       public function createOrphan( $overrides = array() ) {
+               $uniq = mt_rand();
+               $message = $overrides + array(
+                       'contribution_tracking_id' => $uniq,
+                       'first_name' => 'Flighty',
+                       'last_name' => 'Dono',
+                       'email' => '[email protected]',
+                       'gateway' => 'ingenico',
+                       'gateway_txn_id' => "txn-{$uniq}",
+                       'order_id' => "order-{$uniq}",
+                       'gateway_account' => 'default',
+                       'payment_method' => 'cc',
+                       'payment_submethod' => 'mc',
+                       // Defaults to a magic 25 minutes ago, within the 
process window.
+                       'date' => time() - 25 * 60,
+                       'amount' => 123,
+                       'currency' => 'EUR',
+               );
+               $this->pendingDb->storeMessage( $message );
+               return $message;
+       }
+
+       /**
+        * Assert whether we made exactly the expected gateway calls
+        *
+        * @param array $expected List of API action names, in the form they 
appear
+        * in the <ACTION> tag.
+        */
+       protected function assertGatewayCallsExactly( $expected ) {
+               $expected_num_calls = count( $expected );
+               $this->assertEquals( $expected_num_calls, count( 
$this->gateway->curled ),
+                       "Ran exactly {$expected_num_calls} API calls" );
+               foreach ( $expected as $index => $action ) {
+                       $this->assertRegExp( '/\b' . $action . '\b/', 
$this->gateway->curled[$index],
+                               "Call #" . ( $index + 1 ) . " was {$action}." );
+               }
+       }
+
+       /**
+        * Dump the entire database state, for debugging.
+        */
+       protected function debugDbContents() {
+               $result = $this->pendingDb->getDatabase()->query(
+                       "select * from pending" );
+               $rows = $result->fetchAll( PDO::FETCH_ASSOC );
+               var_export($rows);
+       }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/IngenicoTest.php 
b/tests/phpunit/Adapter/Ingenico/IngenicoTest.php
new file mode 100644
index 0000000..38fbf27
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoTest.php
@@ -0,0 +1,477 @@
+<?php
+/**
+ * Wikimedia Foundation
+ *
+ * LICENSE
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+use Psr\Log\LogLevel;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ *
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ */
+class DonationInterface_Adapter_Ingenico_IngenicoTest extends 
BaseIngenicoTestCase {
+
+       protected $partialUrl;
+
+       protected $hostedCheckoutCreateResponse;
+
+       public function setUp() {
+               parent::setUp();
+
+               $this->partialUrl = 
'poweredbyglobalcollect.com/pay8915-53ebca407e6b4a1dbd086aad4f10354d:' .
+                       
'8915-28e5b79c889641c8ba770f1ba576c1fe:9798f4c44ac6406e8288494332d1daa0';
+
+               $this->hostedCheckoutCreateResponse = array(
+                       'partialRedirectUrl' => $this->partialUrl,
+                       'hostedCheckoutId' => 
'8915-28e5b79c889641c8ba770f1ba576c1fe',
+                       'RETURNMAC' => 'f5b66cf9-c64c-4c8d-8171-b47205c89a56'
+               );
+       }
+
+       /**
+        * Non-exhaustive integration tests to verify that order_id, when in
+        * self-generation mode, won't regenerate until it is told to.
+        * @covers GatewayAdapter::normalizeOrderID
+        * @covers GatewayAdapter::regenerateOrderID
+        */
+       function testStickyGeneratedOrderID() {
+               $init = self::$initial_vars;
+               unset( $init['order_id'] );
+
+               //no order_id from anywhere, explicit generate
+               $gateway = $this->getFreshGatewayObject( $init, array ( 
'order_id_meta' => array ( 'generate' => TRUE ) ) );
+               $this->assertNotNull( $gateway->getData_Unstaged_Escaped( 
'order_id' ), 'Generated order_id is null. The rest of this test is broken.' );
+               $original_order_id = $gateway->getData_Unstaged_Escaped( 
'order_id' );
+
+               $gateway->normalizeOrderID();
+               $this->assertEquals( $original_order_id, 
$gateway->getData_Unstaged_Escaped( 'order_id' ), 'Re-normalized order_id has 
changed without explicit regeneration.' );
+
+               //this might look a bit strange, but we need to be able to 
generate valid order_ids without making them stick to anything.
+               $gateway->generateOrderID();
+               $this->assertEquals( $original_order_id, 
$gateway->getData_Unstaged_Escaped( 'order_id' ), 'function generateOrderID 
auto-changed the selected order ID. Not cool.' );
+
+               $gateway->regenerateOrderID();
+               $this->assertNotEquals( $original_order_id, 
$gateway->getData_Unstaged_Escaped( 'order_id' ), 'Re-normalized order_id has 
not changed, after explicit regeneration.' );
+       }
+
+       /**
+        * Integration test to verify that order_id can be retrieved from
+        * performing an createHostedCheckout.
+        */
+       function testGatewaySessionRetrieval() {
+               $init = $this->getDonorTestData();
+               unset( $init['order_id'] );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $this->hostedCheckoutProvider->expects( $this->once() )
+                       ->method( 'createHostedPayment' )
+                       ->willReturn(
+                               $this->hostedCheckoutCreateResponse
+                       );
+               //no order_id from anywhere, explicit generate
+               $gateway = $this->getFreshGatewayObject( $init );
+               $gateway->do_transaction( 'createHostedCheckout' );
+
+               $this->assertNotNull(
+                       $gateway->getData_Unstaged_Escaped( 
'gateway_session_id' ),
+                       'No gateway_session_id was retrieved from 
createHostedCheckout'
+               );
+       }
+
+       /**
+        * Just run the GET_ORDERSTATUS transaction and make sure we load the 
data
+        */
+       function testGetOrderStatus() {
+               $this->markTestSkipped( 'OrderStatus not implemented' );
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = '[email protected]';
+
+               $gateway = $this->getFreshGatewayObject( $init );
+
+               $gateway->do_transaction( 'GET_ORDERSTATUS' );
+
+               $data = $gateway->getTransactionData();
+
+               $this->assertEquals( 'N', $data['CVVRESULT'], 'CVV Result not 
loaded from XML response' );
+       }
+
+       /**
+        * Don't fraud-fail someone for bad CVV if GET_ORDERSTATUS
+        * comes back with STATUSID 25 and no CVVRESULT
+        * @group CvvResult
+        */
+       function testConfirmCreditCardStatus25() {
+               $this->markTestSkipped( 'OrderStatus not implemented' );
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = '[email protected]';
+
+               $this->setUpRequest( array( 'CVVRESULT' => 'M' ) );
+
+               $gateway = $this->getFreshGatewayObject( $init );
+               $gateway->setDummyGatewayResponseCode( '25' );
+
+               $gateway->do_transaction( 'Confirm_CreditCard' );
+               $action = $gateway->getValidationAction();
+               $this->assertEquals( 'process', $action, 'Gateway should not 
fraud fail on STATUSID 25' );
+       }
+
+       /**
+        * Make sure we're incorporating GET_ORDERSTATUS AVS and CVV responses 
into
+        * fraud scores.
+        */
+       function testGetOrderstatusPostProcessFraud() {
+               $this->markTestSkipped( 'OrderStatus not implemented' );
+               $this->setMwGlobals( array(
+                       'wgDonationInterfaceEnableCustomFilters' => true,
+                       'wgIngenicoGatewayCustomFiltersFunctions' => array(
+                               'getCVVResult' => 10,
+                               'getAVSResult' => 30,
+                       ),
+               ) );
+
+               $init = $this->getDonorTestData();
+               $init['ffname'] = 'cc-vmad';
+               $init['order_id'] = '55555';
+               $init['email'] = '[email protected]';
+               $init['contribution_tracking_id'] = mt_rand();
+               $init['payment_method'] = 'cc';
+               $gateway = $this->getFreshGatewayObject( $init );
+
+               $gateway->setDummyGatewayResponseCode( '600_badCvv' );
+
+               $gateway->do_transaction( 'Confirm_CreditCard' );
+               $action = $gateway->getValidationAction();
+               $this->assertEquals( 'review', $action,
+                       'Orphan gateway should fraud fail on bad CVV and AVS' );
+
+               $exposed = TestingAccessWrapper::newFromObject( $gateway );
+               $this->assertEquals( 40, $exposed->risk_score,
+                       'Risk score was incremented correctly.' );
+       }
+
+       /**
+        * Ensure the Confirm_CreditCard transaction prefers CVVRESULT from the 
XML
+        * over any value from the querystring
+        */
+       function testConfirmCreditCardPrefersApiCvv() {
+               $this->markTestSkipped( 'OrderStatus not implemented' );
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = '[email protected]';
+
+               $this->setUpRequest( array( 'CVVRESULT' => 'M' ) );
+
+               $gateway = $this->getFreshGatewayObject( $init );
+
+               $gateway->do_transaction( 'Confirm_CreditCard' );
+
+               $this->assertEquals( 'N', 
$gateway->getData_Unstaged_Escaped('cvv_result'), 'CVV Result not taken from 
XML response' );
+       }
+
+       /**
+        * Make sure we record the actual amount charged, even if the donor has
+        * opened a new window and screwed up their session data.
+        */
+       function testConfirmCreditCardUpdatesAmount() {
+               $this->markTestSkipped( 'OrderStatus not implemented' );
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = '[email protected]';
+               // The values in session are not the values we originally used
+               // for createHostedCheckout
+               $init['amount'] = '12.50';
+               $init['currency'] = 'USD';
+
+               $gateway = $this->getFreshGatewayObject( $init );
+
+               $amount = $gateway->getData_Unstaged_Escaped( 'amount' );
+               $currency = $gateway->getData_Unstaged_Escaped( 'currency' );
+               $this->assertEquals( '12.50', $amount );
+               $this->assertEquals( 'USD', $currency );
+
+               $gateway->do_transaction( 'Confirm_CreditCard' );
+
+               $amount = $gateway->getData_Unstaged_Escaped( 'amount' );
+               $currency = $gateway->getData_Unstaged_Escaped( 'currency' );
+               $this->assertEquals( '23.45', $amount, 'Not recording correct 
amount' );
+               $this->assertEquals( 'EUR', $currency, 'Not recording correct 
currency'  );
+       }
+
+       public function testLanguageStaging() {
+               $options = $this->getDonorTestData( 'NO' );
+               $options['payment_method'] = 'cc';
+               $options['payment_submethod'] = 'visa';
+               $gateway = $this->getFreshGatewayObject( $options );
+
+               $exposed = TestingAccessWrapper::newFromObject( $gateway );
+               $exposed->stageData();
+
+               $this->assertEquals( $exposed->getData_Staged( 'language' ), 
'no_NO', "'NO' donor's language was improperly set. Should be 'no_NO'" );
+       }
+
+       public function testLanguageFallbackStaging() {
+               $this->markTestSkipped( 'Do we have to fall back with Connect?' 
);
+               $options = $this->getDonorTestData( 'Catalonia' );
+               $options['payment_method'] = 'cc';
+               $options['payment_submethod'] = 'visa';
+               $gateway = $this->getFreshGatewayObject( $options );
+
+               $exposed = TestingAccessWrapper::newFromObject( $gateway );
+               $exposed->stageData();
+
+               // Requesting the fallback language from the gateway.
+               $this->assertEquals( 'en', $exposed->getData_Staged( 'language' 
) );
+       }
+
+       /**
+        * Make sure unstaging functions don't overwrite core donor data.
+        */
+       public function testAddResponseData_underzealous() {
+               $options = $this->getDonorTestData( 'Catalonia' );
+               $options['payment_method'] = 'cc';
+               $options['payment_submethod'] = 'visa';
+               $gateway = $this->getFreshGatewayObject( $options );
+
+               // This will set staged_data['language'] = 'en'.
+               $exposed = TestingAccessWrapper::newFromObject( $gateway );
+               $exposed->stageData();
+
+               $ctid = mt_rand();
+
+               $gateway->addResponseData( array(
+                       'contribution_tracking_id' => $ctid . '.1',
+               ) );
+
+               $exposed = TestingAccessWrapper::newFromObject( $gateway );
+               // Desired vars were written into normalized data.
+               $this->assertEquals( $ctid, $exposed->dataObj->getVal( 
'contribution_tracking_id' ) );
+
+               // Language was not overwritten.
+               $this->assertEquals( 'ca', $exposed->dataObj->getVal( 
'language' ) );
+       }
+
+       /**
+        * Tests to make sure that certain error codes returned from GC will or
+        * will not create payments error loglines.
+        */
+       function testCCLogsOnGatewayError() {
+               $this->markTestSkipped( 'order status not implemented' );
+               $init = $this->getDonorTestData( 'US' );
+               unset( $init['order_id'] );
+               $init['ffname'] = 'cc-vmad';
+
+               //this should not throw any payments errors: Just an invalid 
card.
+               $gateway = $this->getFreshGatewayObject( $init );
+               $gateway->setDummyGatewayResponseCode( '430285' );
+               $gateway->do_transaction( 'GET_ORDERSTATUS' );
+               $this->verifyNoLogErrors();
+
+               //Now test one we want to throw a payments error
+               $gateway = $this->getFreshGatewayObject( $init );
+               $gateway->setDummyGatewayResponseCode( '21000050' );
+               $gateway->do_transaction( 'GET_ORDERSTATUS' );
+               $loglines = $this->getLogMatches( LogLevel::ERROR, 
'/Investigation required!/' );
+               $this->assertNotEmpty( $loglines, 'GC Error 21000050 is not 
generating the expected payments log error' );
+
+               //Reset logs
+               $this->testLogger->messages = array();
+
+               //Most irritating version of 20001000 - They failed to enter an 
expiration date on GC's form. This should log some specific info, but not an 
error.
+               $gateway = $this->getFreshGatewayObject( $init );
+               $gateway->setDummyGatewayResponseCode( '20001000-expiry' );
+               $gateway->do_transaction( 'GET_ORDERSTATUS' );
+               $this->verifyNoLogErrors();
+               $loglines = $this->getLogMatches( LogLevel::INFO, 
'/processResponse:.*EXPIRYDATE/' );
+               $this->assertNotEmpty( $loglines, 'GC Error 20001000-expiry is 
not generating the expected payments log line' );
+       }
+
+       /**
+        * Tests to make sure that certain error codes returned from GC will
+        * trigger order cancellation, even if retryable errors also exist.
+        * @dataProvider mcNoRetryCodeProvider
+        */
+       public function testNoMastercardFinesForRepeatOnBadCodes( $code ) {
+               $this->markTestSkipped( 'OrderStatus not implemented' );
+               $init = $this->getDonorTestData( 'US' );
+               unset( $init['order_id'] );
+               $init['ffname'] = 'cc-vmad';
+               //Make it not look like an orphan
+               $this->setUpRequest( array(
+                       'CVVRESULT' => 'M',
+                       'AVSRESULT' => '0'
+               ) );
+
+               //Toxic card should not retry, even if there's an order id 
collision
+               $gateway = $this->getFreshGatewayObject( $init );
+               $gateway->setDummyGatewayResponseCode( $code );
+               $gateway->do_transaction( 'Confirm_CreditCard' );
+               $this->assertEquals( 1, count( $gateway->curled ), "Gateway 
kept trying even with response code $code!  MasterCard could fine us a thousand 
bucks for that!" );
+       }
+
+       /**
+        * Tests that two API requests don't send the same order ID and merchant
+        * reference.  This was the case when users doubleclicked and we were
+        * using the last 5 digits of time in seconds as a suffix.  We want to 
see
+        * what happens when a 2nd request comes in while the 1st is still 
waiting
+        * for a CURL response, so here we fake that situation by having CURL 
throw
+        * an exception during the 1st response.
+        */
+       public function testNoDupeOrderId( ) {
+               $this->setUpRequest( array(
+                       'action'=>'donate',
+                       'amount'=>'3.00',
+                       'card_type'=>'amex',
+                       'city'=>'Hollywood',
+                       'contribution_tracking_id'=>'22901382',
+                       'country'=>'US',
+                       'currency'=>'USD',
+                       'email'=>'[email protected]',
+                       'first_name'=>'Fakety',
+                       'format'=>'json',
+                       'gateway'=>'ingenico',
+                       'language'=>'en',
+                       'last_name'=>'Fake',
+                       'payment_method'=>'cc',
+                       'referrer'=>'http://en.wikipedia.org/wiki/Main_Page',
+                       'state_province'=>'MA',
+                       'street_address'=>'99 Fake St',
+                       'utm_campaign'=>'C14_en5C_dec_dsk_FR',
+                       'utm_medium'=>'sitenotice',
+                       'utm_source'=>'B14_120921_5C_lg_fnt_sans.no-LP.cc',
+                       'postal_code'=>'90210'
+               ) );
+
+               $gateway = new IngenicoAdapter();
+               $calls = [];
+               $this->hostedCheckoutProvider->expects( $this->exactly( 2 ) )
+                       ->method( 'createHostedPayment' )
+                       ->with( $this->callback( function( $arg ) use ( &$calls 
) {
+                               $calls[] = $arg;
+                               if ( count( $calls ) === 2 ) {
+                                       $this->assertFalse( $calls[0] === 
$calls[1], 'Two calls to the api did the same thing' );
+                               }
+                               return true;
+                       } ) )
+                       ->will( $this->onConsecutiveCalls(
+                               $this->throwException( new Exception( 'test' ) 
),
+                               $this->returnValue( 
$this->hostedCheckoutCreateResponse )
+                       ) );
+               try {
+                       $gateway->do_transaction( 'createHostedCheckout' );
+               }
+               catch ( Exception $e ) {
+                       // totally expected this
+               }
+
+               //simulate another request coming in before we get anything 
back from GC
+               $anotherGateway = new IngenicoAdapter();
+               $anotherGateway->do_transaction( 'createHostedCheckout' );
+
+       }
+
+       /**
+        * Tests to see that we don't claim we're going to retry when we aren't
+        * going to. For GC, we really only want to retry on code 300620
+        * @dataProvider benignNoRetryCodeProvider
+        */
+       public function testNoClaimRetryOnBoringCodes( $code ) {
+               $this->markTestSkipped( 'OrderStatus not implemented' );
+               $init = $this->getDonorTestData( 'US' );
+               unset( $init['order_id'] );
+               $init['ffname'] = 'cc-vmad';
+               //Make it not look like an orphan
+               $this->setUpRequest( array(
+                       'CVVRESULT' => 'M',
+                       'AVSRESULT' => '0'
+               ) );
+
+               $gateway = $this->getFreshGatewayObject( $init );
+               $gateway->setDummyGatewayResponseCode( $code );
+               $exposed = TestingAccessWrapper::newFromObject( $gateway );
+               $start_id = $exposed->getData_Staged( 'order_id' );
+               $gateway->do_transaction( 'Confirm_CreditCard' );
+               $finish_id = $exposed->getData_Staged( 'order_id' );
+               $loglines = $this->getLogMatches( LogLevel::INFO, '/Repeating 
transaction on request for vars:/' );
+               $this->assertEmpty( $loglines, "Log says we are going to repeat 
the transaction for code $code, but that is not true" );
+               $this->assertEquals( $start_id, $finish_id, "Needlessly 
regenerated order id for code $code ");
+       }
+
+       /**
+        * doPayment should recover from Ingenico-side timeouts.
+        */
+       function testTimeoutRecover() {
+               $this->markTestSkipped( 'SetPayment not implemented' );
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = '[email protected]';
+               $init['ffname'] = 'cc-vmad';
+
+               $gateway = $this->getFreshGatewayObject( $init );
+
+               $gateway->do_transaction( 'SET_PAYMENT' );
+               $loglines = $this->getLogMatches( LogLevel::INFO, '/Repeating 
transaction for timeout/' );
+               $this->assertNotEmpty( $loglines, "Log does not say we retried 
for timeout." );
+       }
+
+       public function testDonorReturnSuccess() {
+               $this->markTestSkipped( 'SetPayment not implemented' );
+               $init = $this->getDonorTestData( 'FR' );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = '[email protected]';
+               $init['order_id'] = mt_rand();
+               $session['Donor'] = $init;
+               $this->setUpRequest( $init, $session );
+               $gateway = $this->getFreshGatewayObject( array() );
+               $result = $gateway->processDonorReturn( array(
+                       'REF' => $init['order_id'],
+                       'CVVRESULT' => 'M',
+                       'AVSRESULT' => '0'
+               ) );
+               $this->assertFalse( $result->isFailed() );
+               $this->assertEmpty( $result->getErrors() );
+               // TODO inspect the queue message
+       }
+
+       public function testDonorReturnFailure() {
+               $this->markTestSkipped( 'OrderStatus not implemented' );
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = '[email protected]';
+               $init['order_id'] = mt_rand();
+               $session['Donor'] = $init;
+               $this->setUpRequest( $init, $session );
+               $gateway = $this->getFreshGatewayObject( array() );
+               $gateway->setDummyGatewayResponseCode( '430285' ); // invalid 
card
+               $result = $gateway->processDonorReturn( array(
+                       'REF' => $init['order_id'],
+                       'CVVRESULT' => 'M',
+                       'AVSRESULT' => '0'
+               ) );
+               $this->assertTrue( $result->isFailed() );
+       }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/RealTimeBankTransferIdealTest.php 
b/tests/phpunit/Adapter/Ingenico/RealTimeBankTransferIdealTest.php
new file mode 100644
index 0000000..8caf7ac
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/RealTimeBankTransferIdealTest.php
@@ -0,0 +1,378 @@
+<?php
+/**
+ * Wikimedia Foundation
+ *
+ * LICENSE
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+use SmashPig\Tests\TestingContext;
+use SmashPig\Tests\TestingProviderConfiguration;
+
+/**
+ *
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ * @group RealTimeBankTransfer
+ */
+class DonationInterface_Adapter_Ingenico_RealTimeBankTransferIdealTest extends 
BaseIngenicoTestCase {
+       /**
+        * @var PHPUnit_Framework_MockObject_MockObject
+        */
+       protected $bankPaymentProvider;
+
+       public function setUp() {
+               parent::setUp();
+               $this->markTestSkipped( 'RTBT not implemented' );
+               $config = TestingProviderConfiguration::createForProvider(
+                       'ingenico',
+                       $this->smashPigGlobalConfig
+               );
+               TestingContext::get()->providerConfigurationOverride = $config;
+
+               $this->bankPaymentProvider = $this->getMockBuilder(
+                       
'\SmashPig\PaymentProviders\Ingenico\BankPaymentProvider'
+               )->disableOriginalConstructor()->getMock();
+
+               $config->overrideObjectInstance( 'payment-provider/rtbt', 
$this->bankPaymentProvider );
+
+               $this->bankPaymentProvider->method( 'getBankList' )
+                       ->willReturn(
+                               array(
+                                       'Test1234' => 'Test Bank 1234',
+                                       'Test5678' => 'Test Bank 5678',
+                               )
+                       );
+
+               $this->setMwGlobals(
+                       array(
+                               'wgIngenicoGatewayEnabled' => true,
+                               'wgDonationInterfaceAllowedHtmlForms' => array(
+                                       'rtbt-ideal' => array(
+                                               'gateway' => 'ingenico',
+                                               'payment_methods' => array( 
'rtbt' => 'rtbt_ideal' ),
+                                               'countries' => array( '+' => 
'NL' ),
+                                               'currencies' => array( '+' => 
'EUR' ),
+                                       ),
+                               ),
+                       )
+               );
+       }
+
+       /**
+        * Test for ideal form loading
+        */
+       public function testIngenicoFormLoad_rtbt_Ideal() {
+               $init = $this->getDonorTestData( 'NL' );
+               unset( $init['order_id'] );
+               $init['payment_method'] = 'rtbt';
+               $init['ffname'] = 'rtbt-ideal';
+
+               $assertNodes = array(
+                       'amount' => array(
+                               'nodename' => 'input',
+                               'value' => '1.55',
+                       ),
+                       'currency' => array(
+                               'nodename' => 'select',
+                               'selected' => 'EUR',
+                       ),
+                       'country' => array(
+                               'nodename' => 'input',
+                               'value' => 'NL',
+                       ),
+                       'issuer_id' => array(
+                               'innerhtmlmatches' => '/Test Bank 1234/'
+                       )
+               );
+
+               $this->verifyFormOutput( 'IngenicoGateway', $init, 
$assertNodes, true );
+       }
+
+       /**
+        * testBuildRequestXmlWithIssuerId21
+        *
+        * Rabobank: 21
+        *
+        * @covers GatewayAdapter::__construct
+        * @covers GatewayAdapter::setCurrentTransaction
+        * @covers GatewayAdapter::buildRequestXML
+        * @covers GatewayAdapter::getData_Unstaged_Escaped
+        */
+       public function testBuildRequestXmlWithIssuerId21() {
+
+               $optionsForTestData = array(
+                       'form_name' => 'TwoStepAmount',
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'payment_product_id' => 809,
+                       'issuer_id' => 21,
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+               unset( $options['payment_product_id'] );
+
+               $this->buildRequestXmlForIngenico( $optionsForTestData, 
$options );
+       }
+
+       /**
+        * testBuildRequestXmlWithIssuerId31
+        *
+        * ABN AMRO: 31
+        *
+        * @covers GatewayAdapter::__construct
+        * @covers GatewayAdapter::setCurrentTransaction
+        * @covers GatewayAdapter::buildRequestXML
+        * @covers GatewayAdapter::getData_Unstaged_Escaped
+        */
+       public function testBuildRequestXmlWithIssuerId31() {
+
+               $optionsForTestData = array(
+                       'form_name' => 'TwoStepAmount',
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'payment_product_id' => 809,
+                       'issuer_id' => 31,
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+               unset( $options['payment_product_id'] );
+
+               $this->buildRequestXmlForIngenico( $optionsForTestData, 
$options );
+       }
+
+       /**
+        * testBuildRequestXmlWithIssuerId91
+        *
+        * Rabobank: 21
+        *
+        * @covers GatewayAdapter::__construct
+        * @covers GatewayAdapter::setCurrentTransaction
+        * @covers GatewayAdapter::buildRequestXML
+        * @covers GatewayAdapter::getData_Unstaged_Escaped
+        */
+       public function testBuildRequestXmlWithIssuerId91() {
+
+               $optionsForTestData = array(
+                       'form_name' => 'TwoStepAmount',
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'payment_product_id' => 809,
+                       'issuer_id' => 21,
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+               unset( $options['payment_product_id'] );
+
+               $this->buildRequestXmlForIngenico( $optionsForTestData, 
$options );
+       }
+
+       /**
+        * testBuildRequestXmlWithIssuerId161
+        *
+        * Van Lanschot Bankiers: 161
+        *
+        * @covers GatewayAdapter::__construct
+        * @covers GatewayAdapter::setCurrentTransaction
+        * @covers GatewayAdapter::buildRequestXML
+        * @covers GatewayAdapter::getData_Unstaged_Escaped
+        */
+       public function testBuildRequestXmlWithIssuerId161() {
+
+               $optionsForTestData = array(
+                       'form_name' => 'TwoStepAmount',
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'payment_product_id' => 809,
+                       'issuer_id' => 161,
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+               unset( $options['payment_product_id'] );
+
+               $this->buildRequestXmlForIngenico( $optionsForTestData, 
$options );
+       }
+
+       /**
+        * testBuildRequestXmlWithIssuerId511
+        *
+        * Triodos Bank: 511
+        *
+        * @covers GatewayAdapter::__construct
+        * @covers GatewayAdapter::setCurrentTransaction
+        * @covers GatewayAdapter::buildRequestXML
+        * @covers GatewayAdapter::getData_Unstaged_Escaped
+        */
+       public function testBuildRequestXmlWithIssuerId511() {
+
+               $optionsForTestData = array(
+                       'form_name' => 'TwoStepAmount',
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'payment_product_id' => 809,
+                       'issuer_id' => 511,
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+               unset( $options['payment_product_id'] );
+
+               $this->buildRequestXmlForIngenico( $optionsForTestData, 
$options );
+       }
+
+       /**
+        * testBuildRequestXmlWithIssuerId721
+        *
+        * ING: 721
+        *
+        * @covers GatewayAdapter::__construct
+        * @covers GatewayAdapter::setCurrentTransaction
+        * @covers GatewayAdapter::buildRequestXML
+        * @covers GatewayAdapter::getData_Unstaged_Escaped
+        */
+       public function testBuildRequestXmlWithIssuerId721() {
+
+               $optionsForTestData = array(
+                       'form_name' => 'TwoStepAmount',
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'payment_product_id' => 809,
+                       'issuer_id' => 721,
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+               unset( $options['payment_product_id'] );
+
+               $this->buildRequestXmlForIngenico( $optionsForTestData, 
$options );
+       }
+
+       /**
+        * testBuildRequestXmlWithIssuerId751
+        *
+        * SNS Bank: 751
+        *
+        * @covers GatewayAdapter::__construct
+        * @covers GatewayAdapter::setCurrentTransaction
+        * @covers GatewayAdapter::buildRequestXML
+        * @covers GatewayAdapter::getData_Unstaged_Escaped
+        */
+       public function testBuildRequestXmlWithIssuerId751() {
+
+               $optionsForTestData = array(
+                       'form_name' => 'TwoStepAmount',
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'payment_product_id' => 809,
+                       'issuer_id' => 751,
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+               unset( $options['payment_product_id'] );
+
+               $this->buildRequestXmlForIngenico( $optionsForTestData, 
$options );
+       }
+
+       /**
+        * testBuildRequestXmlWithIssuerId761
+        *
+        * ASN Bank: 761
+        *
+        * @covers GatewayAdapter::__construct
+        * @covers GatewayAdapter::setCurrentTransaction
+        * @covers GatewayAdapter::buildRequestXML
+        * @covers GatewayAdapter::getData_Unstaged_Escaped
+        */
+       public function testBuildRequestXmlWithIssuerId761() {
+
+               $optionsForTestData = array(
+                       'form_name' => 'TwoStepAmount',
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'payment_product_id' => 809,
+                       'issuer_id' => 761,
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+               unset( $options['payment_product_id'] );
+
+               $this->buildRequestXmlForIngenico( $optionsForTestData, 
$options );
+       }
+
+       /**
+        * testBuildRequestXmlWithIssuerId771
+        *
+        * RegioBank: 771
+        *
+        * @covers GatewayAdapter::__construct
+        * @covers GatewayAdapter::setCurrentTransaction
+        * @covers GatewayAdapter::buildRequestXML
+        * @covers GatewayAdapter::getData_Unstaged_Escaped
+        */
+       public function testBuildRequestXmlWithIssuerId771() {
+
+               $optionsForTestData = array(
+                       'form_name' => 'TwoStepAmount',
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'payment_product_id' => 809,
+                       'issuer_id' => 771,
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+               unset( $options['payment_product_id'] );
+
+               $this->buildRequestXmlForIngenico( $optionsForTestData, 
$options );
+       }
+
+       public function testFormAction() {
+
+               $optionsForTestData = array(
+                       'payment_method' => 'rtbt',
+                       'payment_submethod' => 'rtbt_ideal',
+                       'issuer_id' => 771,
+                       // Email is required for RTBT.
+                       'email' => '[email protected]',
+               );
+
+               //somewhere else?
+               $options = $this->getDonorTestData( 'ES' );
+               $options = array_merge( $options, $optionsForTestData );
+
+               $this->gatewayAdapter = $this->getFreshGatewayObject( $options 
);
+
+               $this->assertTrue( $this->gatewayAdapter->validatedOK() );
+
+               $this->gatewayAdapter->do_transaction( 
"INSERT_ORDERWITHPAYMENT" );
+               $action = $this->gatewayAdapter->getTransactionDataFormAction();
+               $this->assertEquals( "url_placeholder", $action, "The 
formaction was not populated as expected (ideal)." );
+       }
+
+}
+
diff --git a/tests/phpunit/Adapter/Ingenico/RecurringTest.php 
b/tests/phpunit/Adapter/Ingenico/RecurringTest.php
new file mode 100644
index 0000000..20e2bf5
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/RecurringTest.php
@@ -0,0 +1,135 @@
+<?php
+/**
+ * Wikimedia Foundation
+ *
+ * LICENSE
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/**
+ *
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ * @group Recurring
+ */
+class DonationInterface_Adapter_Ingenico_RecurringTest extends 
BaseIngenicoTestCase {
+
+       public function setUp() {
+               parent::setUp();
+               $this->markTestSkipped( 'Recurring not implemented' );
+       }
+
+       /**
+        * Can make a recurring payment
+        *
+        * @covers IngenicoAdapter::transactionRecurring_Charge
+        */
+       public function testRecurringCharge() {
+               $init = array(
+                       'amount' => '2345',
+                       'effort_id' => 2,
+                       'order_id' => '9998890004',
+                       'currency' => 'EUR',
+                       'payment_product' => '',
+               );
+               $gateway = $this->getFreshGatewayObject( $init );
+
+               $gateway->setDummyGatewayResponseCode( 'recurring-OK' );
+
+               $result = $gateway->do_transaction( 'Recurring_Charge' );
+
+               $this->assertTrue( $result->getCommunicationStatus() );
+               $this->assertRegExp( '/SET_PAYMENT/', $result->getRawResponse() 
);
+       }
+
+       /**
+        * Can make a recurring payment
+        *
+        * @covers IngenicoAdapter::transactionRecurring_Charge
+        */
+       public function testDeclinedRecurringCharge() {
+               $init = array(
+                       'amount' => '2345',
+                       'effort_id' => 2,
+                       'order_id' => '9998890004',
+                       'currency' => 'EUR',
+                       'payment_product' => '',
+               );
+               $gateway = $this->getFreshGatewayObject( $init );
+
+               $gateway->setDummyGatewayResponseCode( 'recurring-declined' );
+
+               $result = $gateway->do_transaction( 'Recurring_Charge' );
+
+               $this->assertRegExp(
+                       '/GET_ORDERSTATUS/',
+                       $result->getRawResponse(),
+                       'Stopped after GET_ORDERSTATUS.'
+               );
+               $this->assertEquals(
+                       2,
+                       count( $gateway->curled ),
+                       'Expected 2 API calls'
+               );
+               $this->assertEquals( FinalStatus::FAILED, 
$gateway->getFinalStatus() );
+       }
+
+       /**
+        * Throw errors if the payment is incomplete
+        *
+        * @covers IngenicoAdapter::transactionRecurring_Charge
+        */
+       public function testRecurringTimeout() {
+               $init = array(
+                       'amount' => '2345',
+                       'effort_id' => 2,
+                       'order_id' => '9998890004',
+                       'currency' => 'EUR',
+                       'payment_product' => '',
+               );
+               $gateway = $this->getFreshGatewayObject( $init );
+
+               $gateway->setDummyGatewayResponseCode( 'recurring-timeout' );
+
+               $result = $gateway->do_transaction( 'Recurring_Charge' );
+
+               $this->assertFalse( $result->getCommunicationStatus() );
+               $this->assertRegExp( '/GET_ORDERSTATUS/', 
$result->getRawResponse() );
+               // FIXME: This is a little funky--the transaction is actually 
pending-poke.
+               $this->assertEquals( FinalStatus::FAILED, 
$gateway->getFinalStatus() );
+       }
+
+       /**
+        * Can resume a recurring payment
+        *
+        * @covers IngenicoAdapter::transactionRecurring_Charge
+        */
+       public function testRecurringResume() {
+               $init = array(
+                       'amount' => '2345',
+                       'effort_id' => 2,
+                       'order_id' => '9998890004',
+                       'currency' => 'EUR',
+                       'payment_product' => '',
+               );
+               $gateway = $this->getFreshGatewayObject( $init );
+
+               $gateway->setDummyGatewayResponseCode( 'recurring-resume' );
+
+               $result = $gateway->do_transaction( 'Recurring_Charge' );
+
+               $this->assertTrue( $result->getCommunicationStatus() );
+               $this->assertRegExp( '/SET_PAYMENT/', $result->getRawResponse() 
);
+       }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/ResultSwitcherTest.php 
b/tests/phpunit/Adapter/Ingenico/ResultSwitcherTest.php
new file mode 100644
index 0000000..71072fd
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/ResultSwitcherTest.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ */
+class DonationInterface_Adapter_Ingenico_ResultSwitcherTest extends 
BaseIngenicoTestCase {
+
+       /**
+        * Assuming we've popped out of the frame, does processing succeed?
+        */
+       public function testResultSwitcherLiberatedSuccess() {
+               $this->markTestSkipped( 'ResultSwitcher not implemented' );
+               $donorTestData = $this->getDonorTestData( 'FR' );
+               $donorTestData['payment_method'] = 'cc';
+               $donorTestData['payment_submethod'] = 'visa';
+               $donorTestData['email'] = '[email protected]';
+               $donorTestData['order_id'] = mt_rand();
+               $session['Donor'] = $donorTestData;
+               // Mark the order as already popped out of the iframe
+               $session['order_status'][$donorTestData['order_id']] = 
'liberated';
+               $request = array(
+                       'REF' => $donorTestData['order_id'],
+                       'CVVRESULT' => 'M',
+                       'AVSRESULT' => '0',
+                       'language' => 'fr', // FIXME: verifyFormOutput 
conflates request with other stuff
+               );
+               $assertNodes = array(
+                       'headers' => array(
+                               'Location' => function( $location ) use ( 
$donorTestData ) {
+                                       // Do this after the real processing to 
avoid side effects
+                                       $gateway = 
$this->getFreshGatewayObject( $donorTestData );
+                                       $url = ResultPages::getThankYouPage( 
$gateway );
+                                       $this->assertEquals( $url, $location );
+                               }
+                       )
+               );
+
+               $this->verifyFormOutput( 'IngenicoGatewayResult', $request, 
$assertNodes, false, $session );
+               // Make sure we logged the expected cURL attempts
+               $messages = $this->getLogMatches( 'info', '/Preparing to send 
GET_ORDERSTATUS transaction to Global Collect/' );
+               $this->assertNotEmpty( $messages );
+               $messages = $this->getLogMatches( 'info', '/Preparing to send 
SET_PAYMENT transaction to Global Collect/' );
+               $this->assertNotEmpty( $messages );
+       }
+}
diff --git a/tests/phpunit/BaseIngenicoTestCase.php 
b/tests/phpunit/BaseIngenicoTestCase.php
new file mode 100644
index 0000000..53b0618
--- /dev/null
+++ b/tests/phpunit/BaseIngenicoTestCase.php
@@ -0,0 +1,84 @@
+<?php
+
+use SmashPig\Core\Context;
+use SmashPig\PaymentProviders\Ingenico\HostedCheckoutProvider;
+use SmashPig\Tests\TestingContext;
+use SmashPig\Tests\TestingProviderConfiguration;
+
+class BaseIngenicoTestCase extends DonationInterfaceTestCase {
+
+       /**
+        * @var HostedCheckoutProvider
+        */
+       protected $hostedCheckoutProvider;
+
+       protected $testAdapterClass = 'IngenicoAdapter';
+
+       protected function setUp() {
+               parent::setUp();
+               $providerConfig = $this->setSmashPigProvider( 'ingenico' );
+
+               $this->hostedCheckoutProvider = $this->getMockBuilder(
+                       
'SmashPig\PaymentProviders\Ingenico\HostedCheckoutProvider'
+               )->disableOriginalConstructor()->getMock();
+
+               $providerConfig->overrideObjectInstance(
+                       'payment-provider/cc',
+                       $this->hostedCheckoutProvider
+               );
+
+               $vmad_countries = array( 'US', );
+               $vmaj_countries = array(
+                       'AD', 'AT', 'AU', 'BE', 'BH', 'DE', 'EC', 'ES', 'FI', 
'FR', 'GB',
+                       'GF', 'GR', 'HK', 'IE', 'IT', 'JP', 'KR', 'LU', 'MY', 
'NL', 'PR',
+                       'PT', 'SG', 'SI', 'SK', 'TH', 'TW',
+               );
+               $vma_countries = array(
+                       'AE', 'AL', 'AN', 'AR', 'BG', 'CA', 'CH', 'CN', 'CR', 
'CY', 'CZ', 'DK',
+                       'DZ', 'EE', 'EG', 'JO', 'KE', 'HR', 'HU', 'IL', 'KW', 
'KZ', 'LB', 'LI',
+                       'LK', 'LT', 'LV', 'MA', 'MT', 'NO', 'NZ', 'OM', 'PK', 
'PL', 'QA', 'RO',
+                       'RU', 'SA', 'SE', 'TN', 'TR', 'UA',
+               );
+               $this->setMwGlobals( array(
+                       'wgIngenicoGatewayEnabled' => true,
+                       'wgDonationInterfaceAllowedHtmlForms' => array(
+                               'cc-vmad' => array(
+                                       'gateway' => 'ingenico',
+                                       'payment_methods' => array('cc' => 
array( 'visa', 'mc', 'amex', 'discover' )),
+                                       'countries' => array(
+                                               '+' => $vmad_countries,
+                                       ),
+                               ),
+                               'cc-vmaj' => array(
+                                       'gateway' => 'ingenico',
+                                       'payment_methods' => array('cc' => 
array( 'visa', 'mc', 'amex', 'jcb' )),
+                                       'countries' => array(
+                                               '+' => $vmaj_countries,
+                                       ),
+                               ),
+                               'cc-vma' => array(
+                                       'gateway' => 'ingenico',
+                                       'payment_methods' => array('cc' => 
array( 'visa', 'mc', 'amex' )),
+                                       'countries' => array(
+                                               // Array merge with cc-vmaj as 
fallback in case 'j' goes down
+                                               // Array merge with cc-vmad as 
fallback in case 'd' goes down
+                                               '+' => array_merge(
+                                                       $vmaj_countries,
+                                                       $vmad_countries,
+                                                       $vma_countries
+                                               ),
+                                       ),
+                               ),
+                               'rtbt-sofo' => array(
+                                       'gateway' => 'ingenico',
+                                       'countries' => array(
+                                               '+' => array( 'AT', 'BE', 'CH', 
'DE' ),
+                                               '-' => 'GB'
+                                       ),
+                                       'currencies' => array( '+' => 'EUR' ),
+                                       'payment_methods' => array('rtbt' => 
'rtbt_sofortuberweisung'),
+                               ),
+                       ),
+               ) );
+       }
+}
diff --git a/tests/phpunit/DonationInterfaceTestCase.php 
b/tests/phpunit/DonationInterfaceTestCase.php
index 12ea84b..145c63e 100644
--- a/tests/phpunit/DonationInterfaceTestCase.php
+++ b/tests/phpunit/DonationInterfaceTestCase.php
@@ -19,6 +19,7 @@
 use SmashPig\Core\Context;
 use SmashPig\Tests\TestingContext;
 use SmashPig\Tests\TestingGlobalConfiguration;
+use SmashPig\Tests\TestingProviderConfiguration;
 use Wikimedia\TestingAccessWrapper;
 
 /**
@@ -107,6 +108,18 @@
                RequestContext::getMain()->setLanguage( $language );
                // BackCompat
                $this->setMwGlobals( 'wgLang', 
RequestContext::getMain()->getLanguage() );
+       }
+
+       /**
+        * @param string $provider
+        * @return TestingProviderConfiguration
+        */
+       protected function setSmashPigProvider( $provider ) {
+               $providerConfig = 
TestingProviderConfiguration::createForProvider(
+                       $provider, $this->smashPigGlobalConfig
+               );
+               TestingContext::get()->providerConfigurationOverride = 
$providerConfig;
+               return $providerConfig;
        }
 
        /**
@@ -584,7 +597,8 @@
                        unset( $perform_these_checks['headers'] );
 
                        $input_node = $dom_thingy->getElementById( $id );
-                       $this->assertNotNull( $input_node, "Couldn't find the 
'$id' element" );
+                       $this->assertNotNull( $input_node, "Couldn't find the 
'$id' element in html. Log entries: \n" .
+                               print_r( 
DonationLoggerFactory::$overrideLogger->messages, true ) . 
"\n\nHTML:\n$form_html" );
                        foreach ( $checks as $name => $expected ) {
                                switch ( $name ) {
                                        case 'nodename':
diff --git a/tests/phpunit/TestConfiguration.php 
b/tests/phpunit/TestConfiguration.php
index 8763cbf..8a0bd95 100644
--- a/tests/phpunit/TestConfiguration.php
+++ b/tests/phpunit/TestConfiguration.php
@@ -84,6 +84,7 @@
        $wgDonationInterfaceCustomFiltersSrcRules,
        $wgDonationInterfaceCustomFiltersFunctions,
        $wgGlobalCollectGatewayCustomFiltersFunctions,
+       $wgIngenicoGatewayCustomFiltersFunctions,
        $wgDonationInterfaceCountryMap,
        $wgDonationInterfaceUtmCampaignMap,
        $wgDonationInterfaceUtmSourceMap,
@@ -95,6 +96,7 @@
 
 $wgDonationInterfaceGatewayAdapters = array(
        'globalcollect'=> 'TestingGlobalCollectAdapter',
+       'ingenico' => 'IngenicoAdapter',
        'amazon'=> 'TestingAmazonAdapter',
        'adyen'=> 'TestingAdyenAdapter',
        'astropay'=> 'TestingAstroPayAdapter',
@@ -116,7 +118,6 @@
 $wgGlobalCollectGatewayAccountInfo['test'] = array(
        'MerchantID' => 'test',
 );
-
 
 /** Paypal **/
 $wgPaypalGatewayAccountInfo = array();
@@ -222,6 +223,8 @@
        'getAVSResult' => 25,
 ) + $customFilters;
 
+$wgIngenicoGatewayCustomFiltersFunctions = 
$wgGlobalCollectGatewayCustomFiltersFunctions;
+
 $wgDonationInterfaceCountryMap = array (
        'US' => 40,
        'CA' => 15,

-- 
To view, visit https://gerrit.wikimedia.org/r/364143
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I4c02abd190b1ea27c1921f37196481d87cd0162a
Gerrit-PatchSet: 35
Gerrit-Project: mediawiki/extensions/DonationInterface
Gerrit-Branch: master
Gerrit-Owner: Ejegg <[email protected]>
Gerrit-Reviewer: AndyRussG <[email protected]>
Gerrit-Reviewer: Cdentinger <[email protected]>
Gerrit-Reviewer: Eileen <[email protected]>
Gerrit-Reviewer: Ejegg <[email protected]>
Gerrit-Reviewer: Katie Horn <[email protected]>
Gerrit-Reviewer: Mepps <[email protected]>
Gerrit-Reviewer: XenoRyet <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to