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'] = '[email protected]';
+ $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'] = '[email protected]';
+ $init['contribution_tracking_id'] = mt_rand();
+ $gateway->loadDataAndReInit( $init );
+
+ $gateway->setDummyGatewayResponseCode( $code );
+ $result = $gateway->do_transaction( 'Confirm_CreditCard' );
+ $this->assertEquals( 1, count( $gateway->curled ), "Gateway
kept trying even with response code $code! MasterCard could fine us a thousand
bucks for that!" );
+ $this->assertEquals( false, $result->getCommunicationStatus(),
"Error code $code should mean status of do_transaction is false" );
+ $errors = $result->getErrors();
+ $this->assertFalse( empty( $errors ), 'Orphan adapter needs to
see the errors to consider it rectified' );
+ $finder = function( $error ) {
+ return $error->getErrorCode() == '1000001';
+ };
+ $this->assertNotEmpty( array_filter( $errors, $finder ),
'Orphan adapter needs error 1000001 to consider it rectified' );
+ $loglines = $this->getLogMatches( LogLevel::INFO, "/Got error
code $code, not retrying to avoid MasterCard fines./" );
+ $this->assertNotEmpty( $loglines, "GC Error $code is not
generating the expected payments log error" );
+ }
+
+ /**
+ * Make sure we're incorporating GET_ORDERSTATUS AVS and CVV responses
into
+ * fraud scores.
+ */
+ function testGetOrderstatusPostProcessFraud() {
+ $this->setMwGlobals( array(
+ 'wgDonationInterfaceEnableCustomFilters' => true,
+ 'wgIngenicoGatewayCustomFiltersFunctions' => array(
+ 'getCVVResult' => 10,
+ 'getAVSResult' => 30,
+ ),
+ ) );
+ $gateway = $this->getFreshGatewayObject( null, array (
'order_id_meta' => array ( 'generate' => FALSE ) ) );
+
+ $init = array_merge( $this->getDonorTestData(),
$this->dummy_utm_data );
+ $init['ffname'] = 'cc-vmad';
+ $init['order_id'] = '55555';
+ $init['email'] = '[email protected]';
+ $init['contribution_tracking_id'] = mt_rand();
+ $init['payment_method'] = 'cc';
+
+ $gateway->loadDataAndReInit( $init );
+ $gateway->setDummyGatewayResponseCode( '600_badCvv' );
+
+ $gateway->do_transaction( 'Confirm_CreditCard' );
+ $action = $gateway->getValidationAction();
+ $this->assertEquals( 'review', $action,
+ 'Orphan gateway should fraud fail on bad CVV and AVS' );
+
+ $exposed = TestingAccessWrapper::newFromObject( $gateway );
+ $this->assertEquals( 40, $exposed->risk_score,
+ 'Risk score was incremented correctly.' );
+ $message = 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' => '[email protected]',
+ 'gateway' => 'ingenico',
+ 'gateway_txn_id' => "txn-{$uniq}",
+ 'order_id' => "order-{$uniq}",
+ 'gateway_account' => 'default',
+ 'payment_method' => 'cc',
+ 'payment_submethod' => 'mc',
+ // Defaults to a magic 25 minutes ago, within the
process window.
+ 'date' => time() - 25 * 60,
+ 'amount' => 123,
+ 'currency' => 'EUR',
+ );
+ $this->pendingDb->storeMessage( $message );
+ return $message;
+ }
+
+ /**
+ * Assert whether we made exactly the expected gateway calls
+ *
+ * @param array $expected List of API action names, in the form they
appear
+ * in the <ACTION> tag.
+ */
+ protected function assertGatewayCallsExactly( $expected ) {
+ $expected_num_calls = count( $expected );
+ $this->assertEquals( $expected_num_calls, count(
$this->gateway->curled ),
+ "Ran exactly {$expected_num_calls} API calls" );
+ foreach ( $expected as $index => $action ) {
+ $this->assertRegExp( '/\b' . $action . '\b/',
$this->gateway->curled[$index],
+ "Call #" . ( $index + 1 ) . " was {$action}." );
+ }
+ }
+
+ /**
+ * Dump the entire database state, for debugging.
+ */
+ protected function debugDbContents() {
+ $result = $this->pendingDb->getDatabase()->query(
+ "select * from pending" );
+ $rows = $result->fetchAll( PDO::FETCH_ASSOC );
+ var_export($rows);
+ }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/IngenicoTest.php
b/tests/phpunit/Adapter/Ingenico/IngenicoTest.php
new file mode 100644
index 0000000..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'] = '[email protected]';
+
+ $gateway = $this->getFreshGatewayObject( $init );
+
+ $gateway->do_transaction( 'GET_ORDERSTATUS' );
+
+ $data = $gateway->getTransactionData();
+
+ $this->assertEquals( 'N', $data['CVVRESULT'], 'CVV Result not
loaded from XML response' );
+ }
+
+ /**
+ * Don't fraud-fail someone for bad CVV if GET_ORDERSTATUS
+ * comes back with STATUSID 25 and no CVVRESULT
+ * @group CvvResult
+ */
+ function testConfirmCreditCardStatus25() {
+ $init = $this->getDonorTestData();
+ $init['payment_method'] = 'cc';
+ $init['payment_submethod'] = 'visa';
+ $init['email'] = '[email protected]';
+
+ $this->setUpRequest( array( 'CVVRESULT' => 'M' ) );
+
+ $gateway = $this->getFreshGatewayObject( $init );
+ $gateway->setDummyGatewayResponseCode( '25' );
+
+ $gateway->do_transaction( 'Confirm_CreditCard' );
+ $action = $gateway->getValidationAction();
+ $this->assertEquals( 'process', $action, 'Gateway should not
fraud fail on STATUSID 25' );
+ }
+
+ /**
+ * Make sure we're incorporating GET_ORDERSTATUS AVS and CVV responses
into
+ * fraud scores.
+ */
+ function testGetOrderstatusPostProcessFraud() {
+ $this->setMwGlobals( array(
+ 'wgDonationInterfaceEnableCustomFilters' => true,
+ 'wgIngenicoGatewayCustomFiltersFunctions' => array(
+ 'getCVVResult' => 10,
+ 'getAVSResult' => 30,
+ ),
+ ) );
+
+ $init = $this->getDonorTestData();
+ $init['ffname'] = 'cc-vmad';
+ $init['order_id'] = '55555';
+ $init['email'] = '[email protected]';
+ $init['contribution_tracking_id'] = mt_rand();
+ $init['payment_method'] = 'cc';
+ $gateway = $this->getFreshGatewayObject( $init );
+
+ $gateway->setDummyGatewayResponseCode( '600_badCvv' );
+
+ $gateway->do_transaction( 'Confirm_CreditCard' );
+ $action = $gateway->getValidationAction();
+ $this->assertEquals( 'review', $action,
+ 'Orphan gateway should fraud fail on bad CVV and AVS' );
+
+ $exposed = TestingAccessWrapper::newFromObject( $gateway );
+ $this->assertEquals( 40, $exposed->risk_score,
+ 'Risk score was incremented correctly.' );
+ }
+
+ /**
+ * Ensure the Confirm_CreditCard transaction prefers CVVRESULT from the
XML
+ * over any value from the querystring
+ */
+ function testConfirmCreditCardPrefersXmlCvv() {
+ $init = $this->getDonorTestData();
+ $init['payment_method'] = 'cc';
+ $init['payment_submethod'] = 'visa';
+ $init['email'] = '[email protected]';
+
+ $this->setUpRequest( array( 'CVVRESULT' => 'M' ) );
+
+ $gateway = $this->getFreshGatewayObject( $init );
+
+ $gateway->do_transaction( 'Confirm_CreditCard' );
+
+ $this->assertEquals( 'N',
$gateway->getData_Unstaged_Escaped('cvv_result'), 'CVV Result not taken from
XML response' );
+ }
+
+ /**
+ * Make sure we record the actual amount charged, even if the donor has
+ * opened a new window and screwed up their session data.
+ */
+ function testConfirmCreditCardUpdatesAmount() {
+ $init = $this->getDonorTestData();
+ $init['payment_method'] = 'cc';
+ $init['payment_submethod'] = 'visa';
+ $init['email'] = '[email protected]';
+ // The values in session are not the values we originally used
+ // for 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'=>'[email protected]',
+ 'first_name'=>'Fakety',
+ 'format'=>'json',
+ 'gateway'=>'ingenico',
+ 'language'=>'en',
+ 'last_name'=>'Fake',
+ 'payment_method'=>'cc',
+ 'referrer'=>'http://en.wikipedia.org/wiki/Main_Page',
+ 'state_province'=>'MA',
+ 'street_address'=>'99 Fake St',
+ 'utm_campaign'=>'C14_en5C_dec_dsk_FR',
+ 'utm_medium'=>'sitenotice',
+ 'utm_source'=>'B14_120921_5C_lg_fnt_sans.no-LP.cc',
+ 'postal_code'=>'90210'
+ ) );
+
+ $gateway = new 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'] = '[email protected]';
+ $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'] = '[email protected]';
+ $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'] = '[email protected]';
+ $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'] = '[email protected]';
+ $init['order_id'] = mt_rand();
+ $session['Donor'] = $init;
+ $this->setUpRequest( $init, $session );
+ $gateway = $this->getFreshGatewayObject( array() );
+ $result = $gateway->processDonorReturn( array(
+ 'REF' => $init['order_id'],
+ 'CVVRESULT' => 'M',
+ 'AVSRESULT' => '0'
+ ) );
+ $this->assertFalse( $result->isFailed() );
+ $this->assertEmpty( $result->getErrors() );
+ // TODO inspect the queue message
+ }
+
+ public function testDonorReturnFailure() {
+ $init = $this->getDonorTestData();
+ $init['payment_method'] = 'cc';
+ $init['payment_submethod'] = 'visa';
+ $init['email'] = '[email protected]';
+ $init['order_id'] = mt_rand();
+ $session['Donor'] = $init;
+ $this->setUpRequest( $init, $session );
+ $gateway = $this->getFreshGatewayObject( array() );
+ $gateway->setDummyGatewayResponseCode( '430285' ); // invalid
card
+ $result = $gateway->processDonorReturn( array(
+ 'REF' => $init['order_id'],
+ 'CVVRESULT' => 'M',
+ 'AVSRESULT' => '0'
+ ) );
+ $this->assertTrue( $result->isFailed() );
+ }
+}
diff --git a/tests/phpunit/Adapter/Ingenico/RealTimeBankTransferIdealTest.php
b/tests/phpunit/Adapter/Ingenico/RealTimeBankTransferIdealTest.php
new file mode 100644
index 0000000..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' => '[email protected]',
+ );
+
+ //somewhere else?
+ $options = $this->getDonorTestData( 'ES' );
+ $options = array_merge( $options, $optionsForTestData );
+
+ $this->gatewayAdapter = $this->getFreshGatewayObject( $options
);
+
+ $this->assertTrue( $this->gatewayAdapter->validatedOK() );
+
+ $this->gatewayAdapter->do_transaction(
"INSERT_ORDERWITHPAYMENT" );
+ $action = $this->gatewayAdapter->getTransactionDataFormAction();
+ $this->assertEquals( "url_placeholder", $action, "The
formaction was not populated as expected (ideal)." );
+ }
+
+}
+
diff --git a/tests/phpunit/Adapter/Ingenico/RecurringTest.php
b/tests/phpunit/Adapter/Ingenico/RecurringTest.php
new file mode 100644
index 0000000..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'] = '[email protected]';
+ $donorTestData['order_id'] = mt_rand();
+ $session['Donor'] = $donorTestData;
+ // Mark the order as already popped out of the iframe
+ $session['order_status'][$donorTestData['order_id']] =
'liberated';
+ $request = array(
+ 'REF' => $donorTestData['order_id'],
+ 'CVVRESULT' => 'M',
+ 'AVSRESULT' => '0',
+ 'language' => 'fr', // FIXME: verifyFormOutput conflates request
with other stuff
+ );
+ $assertNodes = array(
+ 'headers' => array(
+ 'Location' => function( $location ) use (
$donorTestData ) {
+ // Do this after the real processing to
avoid side effects
+ $gateway =
$this->getFreshGatewayObject( $donorTestData );
+ $url = ResultPages::getThankYouPage(
$gateway );
+ $this->assertEquals( $url, $location );
+ }
+ )
+ );
+
+ $this->verifyFormOutput( 'IngenicoGatewayResult', $request,
$assertNodes, false, $session );
+ // Make sure we logged the expected cURL attempts
+ $messages = $this->getLogMatches( 'info', '/Preparing to send
GET_ORDERSTATUS transaction to Global Collect/' );
+ $this->assertNotEmpty( $messages );
+ $messages = $this->getLogMatches( 'info', '/Preparing to send
SET_PAYMENT transaction to Global Collect/' );
+ $this->assertNotEmpty( $messages );
+ }
+}
--
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 <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits