-
Notifications
You must be signed in to change notification settings - Fork 68
/
Copy pathingest-fa-payments
executable file
·227 lines (185 loc) · 7.56 KB
/
ingest-fa-payments
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#!/usr/bin/php
<?
// Note: This script must be run as a user with a $HOME directory, otherwise Firefox won't be able to start with a profile.
/**
* FA is unreliable in the email notifications it sends. They are often missing.
* This script gets a list of FA transactions directly from their website.
* It tracks the last transaction it saw in a temp file and won't go past that.
* If there is no temp file, it gets all transactions from today, and writes the temp file with the last transaction it saw.
* Any transactions that the script finds and which don't already exist, are added to the database as pending payments.
* After that, the `/scripts/process-pending-payments` script will pick them up and do accounting/patron logic.
*/
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Firefox\FirefoxDriver;
use Facebook\WebDriver\Firefox\FirefoxOptions;
use Facebook\WebDriver\WebDriverElement;
use function Safe\file_get_contents;
use function Safe\file_put_contents;
use function Safe\preg_replace;
use function Safe\putenv;
use function Safe\set_time_limit;
require_once('/standardebooks.org/web/lib/Core.php');
// Disable script timeout because Selenium is very slow.
set_time_limit(0);
// Initialize the Selenium driver.
putenv('WEBDRIVER_FIREFOX_DRIVER=' . SITE_ROOT . '/config/selenium/geckodriver-0.31.0');
$firefoxOptions = new FirefoxOptions();
$firefoxOptions->addArguments(['-headless']); // WARNING: Only one dash!
$capabilities = DesiredCapabilities::firefox();
$capabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions);
$driver = null;
$log = new Log(DONATIONS_LOG_FILE_PATH);
$faUsername = get_cfg_var('se.secrets.fractured_atlas.username');
$faPassword = get_cfg_var('se.secrets.fractured_atlas.password');
$lastSeenTransactionId = null;
$firstTransactionId = null;
$transactionFilePath = '/tmp/last-fa-donation';
$transactionIds = [];
$today = NOW->format('n/j/Y');
$faItemsPerPage = 20; // How many items are on a full page of FA results?
// General plan: Read /tmp/last-fa-donation to see what the last transaction ID was that we processed.
// If /tmp/last-fa-donation doesn't exist, get all transactions from today and create the file.
function InsertTransaction(string $transactionId): bool{
$exists = Db::QueryBool('SELECT exists(
select *
from
( select 1
from Payments
where TransactionId = ?
union select 1
from PendingPayments
where TransactionId = ? ) x
)',
[$transactionId, $transactionId]);
if(!$exists){
Db::Query('INSERT into PendingPayments
(Created,
Processor,
TransactionId)
values (utc_timestamp(),
?,
?)',
[Enums\PaymentProcessorType::FracturedAtlas, $transactionId]);
return true;
}
return false;
}
try{
$log->Write('Ingesting FA donations...');
$driver = FirefoxDriver::start($capabilities);
if(file_exists($transactionFilePath)){
$lastSeenTransactionId = trim(file_get_contents($transactionFilePath));
if($lastSeenTransactionId == ''){
$lastSeenTransactionId = null;
}
}
if($lastSeenTransactionId === null){
$log->Write('No last transaction ID, checking everything from ' . NOW->format('Y-m-d'));
}
else{
$log->Write('Checking from last transaction ID ' . $lastSeenTransactionId);
}
$page = 1;
$getMoreTransactions = true;
while($getMoreTransactions){
if($page > 5){
// Safety valve for runaway logic.
throw new Exception('Error: went past page 5 of Fractured Atlas results.');
}
$log->Write('Getting page ' . $page . ' of transactions');
$driver->get('https://fundraising.fracturedatlas.org/admin/general_support/donations?page=' . $page);
// Check if we need to log in to FA.
// Wait until the <body> element is visible, then check the current URL.
$driver->wait(20, 250)->until(WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::xpath('/html/body')));
if(stripos($driver->getCurrentUrl(), 'auth0.com')){
$log->Write('Logging in to Fractured Atlas ...');
// We were redirected to the login page, so try to log in.
/** @var WebDriverElement $emailField */
$emailField = $driver->wait(20, 250)->until(WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::xpath('//input[@type="email"]')));
/** @var WebDriverElement $passwordField */
$passwordField = $driver->wait(20, 250)->until(WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::xpath('//input[@type="password"]')));
/** @var WebDriverElement $submitButton */
$submitButton = $driver->wait(20, 250)->until(WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::xpath('//button[@type="submit"]')));
// Fill out and submit the form.
$emailField->sendKeys($faUsername);
$passwordField->sendKeys($faPassword);
$submitButton->click();
}
// Wait until the page finishes loading.
// We have to expand the row before we can select its contents, so click the 'expand' button once it's visible.
try{
$toggleButton = $driver->wait(20, 250)->until(WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::xpath('//button[contains(@class, "button-toggle")]')));
}
catch(Exception){
$log->Write('Error: Couldn\'t load donation list.');
continue;
}
// If the last seen transaction ID is null, get everything from today.
if($lastSeenTransactionId === null){
$elements = $driver->findElements(WebDriverBy::xpath('//td[preceding-sibling::th[normalize-space(.) = "ID"]][parent::tr[preceding-sibling::tr[./td[normalize-space(.) = "' . $today . '"]]]]'));
if(sizeof($elements) < $faItemsPerPage){
$getMoreTransactions = false;
}
for($i = 0; $i < sizeof($elements); $i++){
$td = $elements[$i];
/** @var string $transactionId */
$transactionId = $td->getDomProperty('textContent');
$transactionId = trim($transactionId);
if($transactionId === ''){
continue;
}
if($i == 0 && $page == 1){
$firstTransactionId = $transactionId;
}
if(InsertTransaction($transactionId)){
$log->Write('Inserting transaction ' . $transactionId);
}
}
}
else{
// Last seen transaction ID is not null, get everything from that ID.
// Get a list of transaction IDs on the page.
$elements = $driver->findElements(WebDriverBy::xpath('//td[preceding-sibling::th[normalize-space(.) = "ID"]]'));
for($i = 0; $i < sizeof($elements); $i++){
$td = $elements[$i];
/** @var string $transactionId */
$transactionId = $td->getDomProperty('textContent');
$transactionId = trim($transactionId);
if($transactionId === ''){
continue;
}
if($i == 0 && $page == 1){
$firstTransactionId = $transactionId;
}
if($transactionId == $lastSeenTransactionId){
$getMoreTransactions = false;
break;
}
if(InsertTransaction($transactionId)){
$log->Write('Inserting transaction ' . $transactionId);
}
}
}
$page = $page + 1;
}
if($firstTransactionId !== null){
file_put_contents($transactionFilePath, $firstTransactionId);
}
$log->Write('Done.');
}
catch(Exception $ex){
$exceptionString = vds($ex);
$log->Write('Error: Uncaught exception: ' . $exceptionString);
$em = new Email(true);
$em->To = ADMIN_EMAIL_ADDRESS;
$em->Subject = 'Ingesting FA donations failed';
$em->Body = Template::EmailDonationProcessingFailed(['exception' => preg_replace('/^/m', "\t", $exceptionString)]);
$em->TextBody = Template::EmailDonationProcessingFailedText(['exception' => preg_replace('/^/m', "\t", $exceptionString)]);
$em->Send();
throw $ex;
}
finally{
$driver?->quit();
}