Ejegg has uploaded a new change for review. ( 
https://gerrit.wikimedia.org/r/364143 )

Change subject: WIP Ingenico gateway mimics GlobalCollect
......................................................................

WIP Ingenico gateway mimics GlobalCollect

Just swaps out the API calls and response parsing.
TODO: actually parse stuff

Change-Id: I4c02abd190b1ea27c1921f37196481d87cd0162a
---
M gateway_common/donation.api.php
A ingenico_gateway/ingenico.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
10 files changed, 2,019 insertions(+), 11 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/DonationInterface 
refs/changes/43/364143/1

diff --git a/gateway_common/donation.api.php b/gateway_common/donation.api.php
index c90896b..2a4f908 100644
--- a/gateway_common/donation.api.php
+++ b/gateway_common/donation.api.php
@@ -37,17 +37,23 @@
                        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' );
+               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;
                }
 
                // $normalizedData = $gatewayObj->getData_Unstaged_Escaped();
diff --git a/ingenico_gateway/ingenico.adapter.php 
b/ingenico_gateway/ingenico.adapter.php
new file mode 100644
index 0000000..1718bd9
--- /dev/null
+++ b/ingenico_gateway/ingenico.adapter.php
@@ -0,0 +1,68 @@
+<?php
+
+use SmashPig\PaymentProviders\PaymentProviderFactory;
+
+class IngenicoAdapter extends GlobalCollectAdapter {
+       const GATEWAY_NAME = 'Ingenico';
+       const IDENTIFIER = 'ingenico';
+       // For now, use the globals from the GlobalCollect adapter
+
+       public function getCommunicationType() {
+               return 'namevalue';
+       }
+
+       // Not using this, so override parent
+       public function buildRequestNameValueString() {}
+
+       public function getResponseType() {
+               return 'json';
+       }
+
+       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':
+                               $payment = new Payment(
+                                       $this->getData_Staged( 'amount' ),
+                                       $this->getData_Staged( 'currency' ),
+                                       $this->getData_Staged( 'order_id' ),
+                                       $this->getData_Staged( 'locale' ),
+                                       $this->getData_Staged( 'country' ),
+                                       array(
+                                               'payment_product' => 
$this->getData_Staged( 'payment_product' ),
+                                       )
+                               );
+
+                               $result = $provider->createHostedPayment( 
$payment );
+                               $this->transaction_response->setRawResponse( 
json_encode( $result ) );
+                               return true;
+                       default:
+                               return false;
+               }
+       }
+
+       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 ) {
+               return $response;
+       }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/IngenicoApiTest.php 
b/tests/phpunit/Adapter/Ingenico/IngenicoApiTest.php
new file mode 100644
index 0000000..1d4f1c8
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoApiTest.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ * @group IngenicoApi
+ * @group medium
+ */
+
+class IngenicoApiTest extends ApiTestCase {
+
+       public function testGoodSubmit() {
+               $init = DonationInterfaceTestCase::getDonorTestData();
+               $init['email'] = 'g...@innocent.com';
+               $init['payment_method'] = 'cc';
+               $init['gateway'] = 'ingenico';
+               $init['action'] = 'donate';
+
+               $apiResult = $this->doApiRequest( $init );
+               $result = $apiResult[0]['result'];
+               $orderId = $result['orderid'];
+
+               $this->assertEquals( 'url_placeholder', $result['formaction'], 
'GC API not setting formaction' );
+               $this->assertTrue( is_numeric( $orderId ), 'Ingenico API not 
setting numeric order ID' );
+               $this->assertTrue( $result['status'], 'Ingenico API result 
status should be true' );
+               preg_match( 
"/Special:IngenicoGatewayResult\?order_id={$orderId}\$/", $result['returnurl'], 
$match );
+               $this->assertNotEmpty( $match, 'Ingenico API not setting proper 
return url' );
+       }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/IngenicoFormLoadTest.php 
b/tests/phpunit/Adapter/Ingenico/IngenicoFormLoadTest.php
new file mode 100644
index 0000000..9cb8bb4
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoFormLoadTest.php
@@ -0,0 +1,270 @@
+<?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 DonationInterfaceTestCase {
+       public function setUp() {
+               parent::setUp();
+
+               $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'),
+                               ),
+                       ),
+               ) );
+       }
+
+       public function testIngenicoFormLoad() {
+               $init = $this->getDonorTestData( 'US' );
+               unset( $init['order_id'] );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['ffname'] = 'cc-vmad';
+
+               $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';
+
+               $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';
+
+               $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;
+
+               $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;
+
+               $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..546e43e
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoOrphanAdapterTest.php
@@ -0,0 +1,208 @@
+<?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\Configuration;
+use SmashPig\Core\Context;
+use SmashPig\CrmLink\Messages\SourceFields;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ *
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ * @group OrphanSlayer
+ */
+class DonationInterface_Adapter_Ingenico_Orphans_IngenicoTest extends 
DonationInterfaceTestCase {
+       public function setUp() {
+               parent::setUp();
+
+               $config = Configuration::createForView( 'ingenico' );
+               Context::initWithLogger( $config );
+
+               $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->testAdapterClass = 'TestingIngenicoOrphanAdapter';
+               $this->dummy_utm_data = array (
+                       'utm_source' => 'dummy_source',
+                       'utm_campaign' => 'dummy_campaign',
+                       'utm_medium' => 'dummy_medium',
+                       'date' => time(),
+               );
+       }
+
+       public function testConstructor() {
+
+               $options = $this->getDonorTestData();
+               $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'] = 'innoc...@clean.com';
+               $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->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'] = 'innoc...@manichean.com';
+               $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 = DonationQueue::instance()->pop( 'payments-antifraud' 
);
+               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..ee27b85
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoOrphanRectifierTest.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.
+ *
+ */
+
+use SmashPig\Core\Context;
+use SmashPig\Core\DataStores\PendingDatabase;
+use SmashPig\Tests\SmashPigDatabaseTestConfiguration;
+
+/**
+ * @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->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' => 'TestingIngenicoOrphanAdapter',
+                               'ingenico_orphan' => 
'TestingIngenicoOrphanAdapter',
+                       ),
+               ) );
+
+               $config = SmashPigDatabaseTestConfiguration::instance();
+               Context::init( $config );
+
+               $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' => 'test+...@eff.org',
+                       '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..fc35370
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/IngenicoTest.php
@@ -0,0 +1,638 @@
+<?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 
DonationInterfaceTestCase {
+       public function setUp() {
+               parent::setUp();
+
+               $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->testAdapterClass = 'TestingIngenicoAdapter';
+       }
+
+       /**
+        * testnormalizeOrderID
+        * Non-exhaustive integration tests to verify that order_id
+        * normalization works as expected with different settings and
+        * conditions in theIngenico adapter
+        * @covers GatewayAdapter::normalizeOrderID
+        */
+       public function testNormalizeOrderID() {
+               $request = $this->getDonorTestData();
+               $externalData = $this->getDonorTestData();
+               $session = array( 'Donor' => $this->getDonorTestData() );
+
+               //no order_id from anywhere, explicit no generate
+               $gateway = $this->getFreshGatewayObject( $externalData, 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->assertNull( $gateway->getData_Unstaged_Escaped( 
'order_id' ), 'Failed asserting that an absent order id is left as null, when 
not generating our own' );
+
+               //no order_id from anywhere, explicit generate
+               $gateway = $this->getFreshGatewayObject( $externalData, array ( 
'order_id_meta' => array ( 'generate' => TRUE ) ) );
+               $this->assertTrue( $gateway->getOrderIDMeta( 'generate' ), 'The 
order_id meta generate setting override is not working properly. Self order_id 
generation may be broken.' );
+               $this->assertInternalType( 'numeric', 
$gateway->getData_Unstaged_Escaped( 'order_id' ), 'Generated order_id is not 
numeric, which it should be for Ingenico' );
+
+               // conflicting order_id in request and session, default GC 
generation
+               $request['order_id'] = '55555';
+               $session['Donor']['order_id'] = '44444';
+               $this->setUpRequest( $request, $session );
+               $gateway = new TestingIngenicoAdapter();
+               $this->assertEquals( '55555', 
$gateway->getData_Unstaged_Escaped( 'order_id' ), 'Ingenico gateway is 
preferring session data over the request. Session should be secondary.' );
+
+               // conflicting order_id in request and session, garbage data in 
request, default GC generation
+               $request['order_id'] = 'nonsense!';
+               $this->setUpRequest( $request, $session );
+               $gateway = new TestingIngenicoAdapter();
+               $this->assertEquals( '44444', 
$gateway->getData_Unstaged_Escaped( 'order_id' ), 'Ingenico gateway is not 
ignoring nonsensical order_id candidates' );
+
+               // order_id in session, default GC generation
+               unset( $request['order_id'] );
+               $this->setUpRequest( $request, $session );
+               $gateway = new TestingIngenicoAdapter();
+               $this->assertEquals( '44444', 
$gateway->getData_Unstaged_Escaped( 'order_id' ), 'Ingenico gateway is not 
recognizing the session order_id' );
+
+               // conflicting order_id in external data, request and session, 
explicit GC generation, batch mode
+               $request['order_id'] = '33333';
+               $externalData['order_id'] = '22222';
+               $this->setUpRequest( $request, $session );
+               $gateway = $this->getFreshGatewayObject( $externalData, array ( 
'order_id_meta' => array ( 'generate' => true ), 'batch_mode' => true ) );
+               $this->assertEquals( $externalData['order_id'], 
$gateway->getData_Unstaged_Escaped( 'order_id' ), 'Failed asserting that an 
extrenally provided order id is being honored in batch mode' );
+
+               //make sure that decimal numbers are rejected by GC. Should be 
a toss and regen
+               $externalData['order_id'] = '2143.0';
+               unset( $request['order_id'] );
+               unset( $session['Donor']['order_id'] );
+               $this->setUpRequest( $request, $session );
+               //conflicting order_id in external data, request and session, 
explicit GC generation, batch mode
+               $gateway = $this->getFreshGatewayObject( $externalData, array ( 
'order_id_meta' => array ( 'generate' => true, 'disallow_decimals' => true ), 
'batch_mode' => true ) );
+               $this->assertNotEquals( $externalData['order_id'], 
$gateway->getData_Unstaged_Escaped( 'order_id' ), 'Failed assering that a 
decimal order_id was regenerated, when disallow_decimals is true' );
+       }
+
+       /**
+        * 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 INSERT_ORDERWITHPAYMENT.
+        */
+       function testOrderIDRetrieval() {
+               $init = $this->getDonorTestData();
+               unset( $init['order_id'] );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+
+               //no order_id from anywhere, explicit generate
+               $gateway = $this->getFreshGatewayObject( $init, array ( 
'order_id_meta' => array ( 'generate' => FALSE ) ) );
+               $this->assertNull( $gateway->getData_Unstaged_Escaped( 
'order_id' ), 'Ungenerated order_id is not null. The rest of this test is 
broken.' );
+
+               $gateway->do_transaction( 'INSERT_ORDERWITHPAYMENT' );
+
+               $this->assertNotNull( $gateway->getData_Unstaged_Escaped( 
'order_id' ), 'No order_id was retrieved from INSERT_ORDERWITHPAYMENT' );
+       }
+
+       /**
+        * Just run the GET_ORDERSTATUS transaction and make sure we load the 
data
+        */
+       function testGetOrderStatus() {
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = 'innoc...@safedomain.org';
+
+               $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() {
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = 'innoc...@safedomain.org';
+
+               $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->setMwGlobals( array(
+                       'wgDonationInterfaceEnableCustomFilters' => true,
+                       'wgIngenicoGatewayCustomFiltersFunctions' => array(
+                               'getCVVResult' => 10,
+                               'getAVSResult' => 30,
+                       ),
+               ) );
+
+               $init = $this->getDonorTestData();
+               $init['ffname'] = 'cc-vmad';
+               $init['order_id'] = '55555';
+               $init['email'] = 'innoc...@manichean.com';
+               $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 testConfirmCreditCardPrefersXmlCvv() {
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = 'innoc...@safedomain.org';
+
+               $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() {
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = 'innoc...@safedomain.org';
+               // The values in session are not the values we originally used
+               // for INSERT_ORDERWITHPAYMENT
+               $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'  );
+       }
+
+       /**
+        * testDefineVarMap
+        *
+        * This is tested with a bank transfer from Spain.
+        *
+        * @covers IngenicoAdapter::__construct
+        * @covers IngenicoAdapter::defineVarMap
+        */
+       public function testDefineVarMap() {
+
+               $gateway = $this->getFreshGatewayObject( self::$initial_vars );
+
+               $var_map = array(
+                       'ORDERID' => 'order_id',
+                       'AMOUNT' => 'amount',
+                       'CURRENCYCODE' => 'currency',
+                       'LANGUAGECODE' => 'language',
+                       'COUNTRYCODE' => 'country',
+                       'MERCHANTREFERENCE' => 'contribution_tracking_id',
+                       'RETURNURL' => 'returnto',
+                       'IPADDRESS' => 'server_ip',
+                       'ISSUERID' => 'issuer_id',
+                       'PAYMENTPRODUCTID' => 'payment_product',
+                       'CVV' => 'cvv',
+                       'EXPIRYDATE' => 'expiration',
+                       'CREDITCARDNUMBER' => 'card_num',
+                       'FIRSTNAME' => 'first_name',
+                       'SURNAME' => 'last_name',
+                       'STREET' => 'street_address',
+                       'CITY' => 'city',
+                       'STATE' => 'state_province',
+                       'ZIP' => 'postal_code',
+                       'EMAIL' => 'email',
+                       'ACCOUNTHOLDER' => 'account_holder',
+                       'ACCOUNTNAME' => 'account_name',
+                       'ACCOUNTNUMBER' => 'account_number',
+                       'ADDRESSLINE1E' => 'address_line_1e',
+                       'ADDRESSLINE2' => 'address_line_2',
+                       'ADDRESSLINE3' => 'address_line_3',
+                       'ADDRESSLINE4' => 'address_line_4',
+                       'ATTEMPTID' => 'attempt_id',
+                       'AUTHORISATIONID' => 'authorization_id',
+                       'BANKACCOUNTNUMBER' => 'bank_account_number',
+                       'BANKAGENZIA' => 'bank_agenzia',
+                       'BANKCHECKDIGIT' => 'bank_check_digit',
+                       'BANKCODE' => 'bank_code',
+                       'BANKFILIALE' => 'bank_filiale',
+                       'BANKNAME' => 'bank_name',
+                       'BRANCHCODE' => 'branch_code',
+                       'COUNTRYCODEBANK' => 'country_code_bank',
+                       'COUNTRYDESCRIPTION' => 'country_description',
+                       'CUSTOMERBANKCITY' => 'customer_bank_city',
+                       'CUSTOMERBANKSTREET' => 'customer_bank_street',
+                       'CUSTOMERBANKNUMBER' => 'customer_bank_number',
+                       'CUSTOMERBANKZIP' => 'customer_bank_zip',
+                       'DATECOLLECT' => 'date_collect',
+                       'DESCRIPTOR' => 'descriptor',
+                       'DIRECTDEBITTEXT' => 'direct_debit_text',
+                       'DOMICILIO' => 'domicilio',
+                       'EFFORTID' => 'effort_id',
+                       'IBAN' => 'iban',
+                       'IPADDRESSCUSTOMER' => 'user_ip',
+                       'PAYMENTREFERENCE' => 'payment_reference',
+                       'PROVINCIA' => 'provincia',
+                       'SPECIALID' => 'special_id',
+                       'SWIFTCODE' => 'swift_code',
+                       'TRANSACTIONTYPE' => 'transaction_type',
+                       'FISCALNUMBER' => 'fiscal_number',
+                       'AVSRESULT' => 'avs_result',
+                       'CVVRESULT' => 'cvv_result',
+               );
+
+               $exposed = TestingAccessWrapper::newFromObject( $gateway );
+               $this->assertEquals( $var_map, $exposed->var_map );
+       }
+
+       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' donor's language was inproperly set. Should be 'no'" );
+       }
+
+       public function testLanguageFallbackStaging() {
+               $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() {
+               $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 ) {
+               $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'=>'faketyf...@gmail.com',
+                       '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 TestingIngenicoAdapter();
+               $gateway->setDummyGatewayResponseCode( 'Exception' );
+               try {
+                       $gateway->do_transaction( 'INSERT_ORDERWITHPAYMENT' );
+               }
+               catch ( Exception $e ) {
+                       // totally expected this
+               }
+               $first = $gateway->curled[0];
+               //simulate another request coming in before we get anything 
back from GC
+               $anotherGateway = new TestingIngenicoAdapter();
+               $anotherGateway->do_transaction( 'INSERT_ORDERWITHPAYMENT' );
+               $second = $anotherGateway->curled[0];
+               $this->assertFalse( $first == $second, 'Two calls to the api 
did the same thing');
+       }
+
+       /**
+        * 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 ) {
+               $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 return an iframe result with normal data
+        */
+       function testDoPaymentSuccess() {
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = 'innoc...@clean.com';
+               $init['ffname'] = 'cc-vmad';
+               unset( $init['order_id'] );
+
+               $gateway = $this->getFreshGatewayObject( $init );
+               $result = $gateway->doPayment();
+               $this->assertEmpty( $result->isFailed(), 'PaymentResult should 
not be failed' );
+               $this->assertEmpty( $result->getErrors(), 'PaymentResult should 
have no errors' );
+               $this->assertEquals( 'url_placeholder', $result->getIframe(), 
'PaymentResult should have iframe set' );
+       }
+
+       /**
+        * doPayment should recover from an attempt to use a duplicate order ID.
+        */
+       function testDuplicateOrderId() {
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = 'innoc...@localhost.net';
+               $init['ffname'] = 'cc-vmad';
+               unset( $init['order_id'] );
+
+               $gateway = $this->getFreshGatewayObject( $init );
+               $orig_id = $gateway->getData_Unstaged_Escaped( 'order_id' );
+               $gateway->setDummyGatewayResponseCode( function ( $gateway ) 
use ( $orig_id ) {
+                       if ( $gateway->getData_Unstaged_Escaped( 'order_id' ) 
=== $orig_id ) {
+                               return 'duplicate';
+                       } else {
+                               return null;
+                       }
+               } );
+               $result = $gateway->doPayment();
+               $this->assertEmpty( $result->isFailed(), 'PaymentResult should 
not be failed' );
+               $this->assertEmpty( $result->getErrors(), 'PaymentResult should 
have no errors' );
+               $this->assertNotEquals( $gateway->getData_Unstaged_Escaped( 
'order_id' ), $orig_id,
+                       'Order ID regenerated in DonationData.' );
+               $this->assertNotEquals( $gateway->session_getData( 'order_id' 
), $orig_id,
+                       'Order ID regenerated in session.' );
+       }
+
+       /**
+        * doPayment should recover from Ingenico-side timeouts.
+        */
+       function testTimeoutRecover() {
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = 'innoc...@localhost.net';
+               $init['ffname'] = 'cc-vmad';
+
+               $gateway = $this->getFreshGatewayObject( $init );
+               $gateway->setDummyGatewayResponseCode( '11000400' );
+               $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() {
+               $init = $this->getDonorTestData( 'FR' );
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = 'innoc...@localhost.net';
+               $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() {
+               $init = $this->getDonorTestData();
+               $init['payment_method'] = 'cc';
+               $init['payment_submethod'] = 'visa';
+               $init['email'] = 'innoc...@localhost.net';
+               $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..87297af
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/RealTimeBankTransferIdealTest.php
@@ -0,0 +1,372 @@
+<?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\Configuration;
+use SmashPig\Core\Context;
+
+/**
+ * 
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ * @group RealTimeBankTransfer
+ */
+class DonationInterface_Adapter_Ingenico_RealTimeBankTransferIdealTest extends 
DonationInterfaceTestCase {
+       /**
+        * @var PHPUnit_Framework_MockObject_MockObject
+        */
+       protected $bankPaymentProvider;
+
+       public function setUp() {
+               parent::setUp();
+
+               $config = Configuration::createForView( 'ingenico' );
+               Context::initWithLogger( $config ); // gets torn down in parent
+
+               $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' => 'nob...@wikimedia.org',
+               );
+
+               //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..b40730f
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/RecurringTest.php
@@ -0,0 +1,142 @@
+<?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 
DonationInterfaceTestCase {
+
+       /**
+        * @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->testAdapterClass = 'TestingIngenicoAdapter';
+       }
+
+       public function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals( array(
+                       'wgIngenicoGatewayEnabled' => true,
+               ) );
+       }
+
+       /**
+        * 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..803699c
--- /dev/null
+++ b/tests/phpunit/Adapter/Ingenico/ResultSwitcherTest.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @group Fundraising
+ * @group DonationInterface
+ * @group Ingenico
+ */
+class DonationInterface_Adapter_Ingenico_ResultSwitcherTest extends 
DonationInterfaceTestCase {
+       protected $testAdapterClass = 'TestingIngenicoAdapter';
+
+       public function setUp() {
+               parent::setUp();
+
+               $this->setMwGlobals(
+                       array(
+                               'wgIngenicoGatewayEnabled' => true,
+                       )
+               );
+       }
+
+       /**
+        * Assuming we've popped out of the frame, does processing succeed?
+        */
+       public function testResultSwitcherLiberatedSuccess() {
+               $donorTestData = $this->getDonorTestData( 'FR' );
+               $donorTestData['payment_method'] = 'cc';
+               $donorTestData['payment_submethod'] = 'visa';
+               $donorTestData['email'] = 'innoc...@localhost.net';
+               $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 );
+    }
+}

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: I4c02abd190b1ea27c1921f37196481d87cd0162a
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/DonationInterface
Gerrit-Branch: master
Gerrit-Owner: Ejegg <ej...@ejegg.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to