Ejegg has uploaded a new change for review. https://gerrit.wikimedia.org/r/312563
Change subject: WIP use ct_id to find completed, avoid race ...................................................................... WIP use ct_id to find completed, avoid race We don't want to drop pending messages just because the payments-init table says 'completed'. We can consult payments-init for failed donations, but we should check a different table to determine whether a completed donation has actually been imported. TODO: test for the completed condition. We'll want a matching patch for the fredge consumer in the crm repo. So, now the contribution tracking schema lives in 3 places... Sorry! Bug: T141477 Change-Id: I39d958b17347728873e6e3f416b32b0f97f781da --- A Core/DataStores/ContributionTrackingDatabase.php M Core/DataStores/PaymentsInitialDatabase.php M Core/QueueConsumers/PendingQueueConsumer.php A Schema/mysql/004_CreateContributionTrackingTable.sql A Schema/sqlite/004_CreateContributionTrackingTable.sql M SmashPig.yaml M Tests/PendingQueueConsumerTest.php M Tests/data/config_smashpig_db.yaml 8 files changed, 123 insertions(+), 27 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/wikimedia/fundraising/SmashPig refs/changes/63/312563/1 diff --git a/Core/DataStores/ContributionTrackingDatabase.php b/Core/DataStores/ContributionTrackingDatabase.php new file mode 100644 index 0000000..c908a01 --- /dev/null +++ b/Core/DataStores/ContributionTrackingDatabase.php @@ -0,0 +1,39 @@ +<?php +namespace SmashPig\Core\DataStores; + +use PDO; + +class ContributionTrackingDatabase extends SmashPigDatabase { + + public function contributionExistsForTrackingId( $id ) { + $record = $this->fetchRecordByContributionTrackingId( $id ); + return $record && !empty( $record['contribution_id'] ); + } + + public function fetchRecordByContributionTrackingId( $id ) { + $prepared = self::$db->prepare( ' + SELECT * FROM contribution_tracking + WHERE id = :id' ); + $prepared->bindValue( ':id', $id, PDO::PARAM_INT ); + $prepared->execute(); + $row = $prepared->fetch( PDO::FETCH_ASSOC ); + if ( !$row ) { + return null; + } + return $row; + } + + /** + * @return string Key in configuration pointing to backing PDO object + */ + protected function getConfigKey() { + return 'data-store/contribution-tracking-db'; + } + + /** + * @return string Name of file (no directory) containing table creation SQL + */ + protected function getTableScriptFile() { + return '004_CreateContributionTrackingTable.sql'; + } +} diff --git a/Core/DataStores/PaymentsInitialDatabase.php b/Core/DataStores/PaymentsInitialDatabase.php index d18701f..834b04e 100644 --- a/Core/DataStores/PaymentsInitialDatabase.php +++ b/Core/DataStores/PaymentsInitialDatabase.php @@ -18,13 +18,13 @@ * isTransactionFinalizedByGatewayOrderId?? * @return boolean */ - public function isTransactionFinalized( $message ) { + public function isTransactionFailed( $message ) { $message = $this->fetchMessageByGatewayOrderId( $message['gateway'], $message['order_id'] ); if ( $message === null ) { return false; } - if ( in_array( $message['payments_final_status'], array( 'failed', 'complete' ) ) ) { + if ( $message['payments_final_status'] === 'failed' ) { return true; } return false; diff --git a/Core/QueueConsumers/PendingQueueConsumer.php b/Core/QueueConsumers/PendingQueueConsumer.php index 733d9b7..ac2f8b8 100644 --- a/Core/QueueConsumers/PendingQueueConsumer.php +++ b/Core/QueueConsumers/PendingQueueConsumer.php @@ -1,5 +1,6 @@ <?php namespace SmashPig\Core\QueueConsumers; +use SmashPig\Core\DataStores\ContributionTrackingDatabase; use SmashPig\Core\DataStores\PaymentsInitialDatabase; use SmashPig\Core\DataStores\PendingDatabase; use SmashPig\Core\Logging\Logger; @@ -16,18 +17,28 @@ */ protected $paymentsInitialDatabase; + /** + * @var ContributionTrackingDatabase + */ + protected $contributionTrackingDatabase; + public function __construct( $queueName, $timeLimit, $messageLimit ) { parent::__construct( $queueName, $timeLimit, $messageLimit ); $this->pendingDatabase = PendingDatabase::get(); $this->paymentsInitialDatabase = PaymentsInitialDatabase::get(); + $this->contributionTrackingDatabase = ContributionTrackingDatabase::get(); } public function processMessage( $message ) { $logIdentifier = "message with gateway {$message['gateway']}" . " and order ID {$message['order_id']}"; - if ( $this->paymentsInitialDatabase->isTransactionFinalized( $message ) ) { - // Throw the message out if it's already completed or failed, and - // exists in the fredge database. + if ( + $this->paymentsInitialDatabase->isTransactionFailed( $message ) || + $this->contributionTrackingDatabase->contributionExistsForTrackingId( + $message['contribution_tracking_id'] + ) + ) { + // Throw the message out if it's already completed or failed Logger::info( "Skipping finalized $logIdentifier" ); } else { Logger::info( "Storing $logIdentifier in database" ); diff --git a/Schema/mysql/004_CreateContributionTrackingTable.sql b/Schema/mysql/004_CreateContributionTrackingTable.sql new file mode 100644 index 0000000..02fd0b0 --- /dev/null +++ b/Schema/mysql/004_CreateContributionTrackingTable.sql @@ -0,0 +1,27 @@ +CREATE TABLE IF NOT EXISTS `contribution_tracking` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `contribution_id` int(10) unsigned DEFAULT NULL, + `form_amount` varchar(20) DEFAULT NULL, + `usd_amount` decimal(20,2) DEFAULT NULL, + `note` text, + `referrer` varchar(4096) DEFAULT NULL, + `anonymous` tinyint(1) unsigned DEFAULT NULL, + `utm_source` varchar(128) DEFAULT NULL, + `utm_medium` varchar(128) DEFAULT NULL, + `utm_campaign` varchar(128) DEFAULT NULL, + `utm_key` varchar(128) DEFAULT NULL, + `payments_form` varchar(128) DEFAULT NULL, + `optout` tinyint(1) unsigned DEFAULT NULL, + `language` varchar(8) DEFAULT NULL, + `country` varchar(2) DEFAULT NULL, + `ts` char(14) DEFAULT NULL, + `owa_session` varbinary(255) DEFAULT NULL, + `owa_ref` int(11) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `contribution_id` (`contribution_id`), + KEY `ts` (`ts`), + KEY `utm_source_id` (`utm_source`), + KEY `utm_medium_id` (`utm_medium`), + KEY `utm_campaign_id` (`utm_campaign`), + KEY `utm_campsign_id` (`utm_campaign`) +) ENGINE=InnoDB AUTO_INCREMENT=23320382 DEFAULT CHARSET=utf8 diff --git a/Schema/sqlite/004_CreateContributionTrackingTable.sql b/Schema/sqlite/004_CreateContributionTrackingTable.sql new file mode 100644 index 0000000..83b829a --- /dev/null +++ b/Schema/sqlite/004_CreateContributionTrackingTable.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS `contribution_tracking` ( + `id` int(10) NOT NULL PRIMARY KEY, + `contribution_id` int(10) DEFAULT NULL, + `form_amount` varchar(20) DEFAULT NULL, + `usd_amount` decimal(20,2) DEFAULT NULL, + `note` text, + `referrer` varchar(4096) DEFAULT NULL, + `anonymous` tinyint(1) DEFAULT NULL, + `utm_source` varchar(128) DEFAULT NULL, + `utm_medium` varchar(128) DEFAULT NULL, + `utm_campaign` varchar(128) DEFAULT NULL, + `utm_key` varchar(128) DEFAULT NULL, + `payments_form` varchar(128) DEFAULT NULL, + `optout` tinyint(1) DEFAULT NULL, + `language` varchar(8) DEFAULT NULL, + `country` varchar(2) DEFAULT NULL, + `ts` char(14) DEFAULT NULL, + `owa_session` varbinary(255) DEFAULT NULL, + `owa_ref` int(11) DEFAULT NULL +) diff --git a/SmashPig.yaml b/SmashPig.yaml index f078a36..dece735 100644 --- a/SmashPig.yaml +++ b/SmashPig.yaml @@ -46,6 +46,11 @@ constructor-parameters: - 'mysql:host=127.0.0.1;dbname=fredge' + contribution-tracking-db: + class: PDO + constructor-parameters: + - 'mysql:host=127.0.0.1;dbname=drupal' + recurring: class: SmashPig\Core\DataStores\MultiQueueWriter constructor-parameters: diff --git a/Tests/PendingQueueConsumerTest.php b/Tests/PendingQueueConsumerTest.php index 5c7aecc..fae4657 100644 --- a/Tests/PendingQueueConsumerTest.php +++ b/Tests/PendingQueueConsumerTest.php @@ -3,6 +3,7 @@ namespace SmashPig\Tests; use SmashPig\Core\Context; +use SmashPig\Core\DataStores\ContributionTrackingDatabase; use SmashPig\Core\DataStores\PaymentsInitialDatabase; use SmashPig\Core\DataStores\PendingDatabase; use SmashPig\Core\QueueConsumers\PendingQueueConsumer; @@ -19,6 +20,11 @@ */ protected $paymentsInitialDb; + /** + * @var ContributionTrackingDatabase + */ + protected $contributionTrackingDb; + public function setUp() { parent::setUp(); // Merge db and queue test configs. @@ -32,6 +38,8 @@ $this->pendingDb->createTable(); $this->paymentsInitialDb = PaymentsInitialDatabase::get(); $this->paymentsInitialDb->createTable(); + $this->contributionTrackingDb = ContributionTrackingDatabase::get(); + $this->contributionTrackingDb->createTable(); } public function tearDown() { @@ -93,28 +101,6 @@ /** * We refuse to consume a message and drop it if the corresponding - * payments_initial row is complete. - */ - public function testPendingMessageInitialComplete() { - $initRow = PaymentsInitialDatabaseTest::generateTestMessage(); - $initRow['payments_final_status'] = 'complete'; - - $this->paymentsInitialDb->storeMessage( $initRow ); - - $message = self::generatePendingMessageFromInitial( $initRow ); - $consumer = new PendingQueueConsumer( 'pending', 1000, 1000 ); - - $consumer->processMessage( $message ); - - $fetched = $this->pendingDb->fetchMessageByGatewayOrderId( - $message['gateway'], $message['order_id'] ); - - $this->assertNull( $fetched, - 'Message consumed and not stored in the pending database.' ); - } - - /** - * We refuse to consume a message and drop it if the corresponding * payments_initial row is failed. */ public function testPendingMessageInitialFailed() { @@ -139,6 +125,7 @@ $message = array( 'gateway' => 'test', 'date' => time(), + 'contribution_tracking_id' => mt_rand(), 'order_id' => mt_rand(), 'cousin' => 'itt', 'kookiness' => mt_rand(), @@ -158,6 +145,7 @@ 'gateway' => $initialRow['gateway'], 'date' => $initialRow['date'], 'order_id' => $initialRow['order_id'], + 'contribution_tracking_id' => $initialRow['contribution_tracking_id'], 'cousin' => 'itt', 'kookiness' => mt_rand(), ); diff --git a/Tests/data/config_smashpig_db.yaml b/Tests/data/config_smashpig_db.yaml index e3bd4f3..dad34a9 100644 --- a/Tests/data/config_smashpig_db.yaml +++ b/Tests/data/config_smashpig_db.yaml @@ -14,3 +14,9 @@ class: PDO constructor-parameters: - 'sqlite::memory:' + + contribution-tracking-db: + class: PDO + constructor-parameters: + - 'sqlite::memory:' + -- To view, visit https://gerrit.wikimedia.org/r/312563 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I39d958b17347728873e6e3f416b32b0f97f781da Gerrit-PatchSet: 1 Gerrit-Project: wikimedia/fundraising/SmashPig Gerrit-Branch: master Gerrit-Owner: Ejegg <eeggles...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits