+1
.gitignore
+1
.gitignore
+3
-3
CMakeLists.txt
+3
-3
CMakeLists.txt
···
7
7
set(CMAKE_CXX_EXTENSIONS OFF)
8
8
9
9
# Add executable
10
-
add_executable(transaction-parser src/transaction-parser.cpp)
10
+
add_executable(soapdump src/soapdump.cpp)
11
11
12
12
# Set compiler flags
13
-
target_compile_options(transaction-parser PRIVATE
13
+
target_compile_options(soapdump PRIVATE
14
14
-Wall
15
15
-Wextra
16
16
-Wpedantic
···
18
18
)
19
19
20
20
# Install target
21
-
install(TARGETS transaction-parser
21
+
install(TARGETS soapdump
22
22
RUNTIME DESTINATION bin
23
23
)
+4
-4
CRUSH.md
+4
-4
CRUSH.md
···
2
2
3
3
## Commands
4
4
- **Build with nix**: `nix build`
5
-
- **Run parser**: `./result/bin/transaction-parser <logfile>`
6
-
- **Test parser**: `./result/bin/transaction-parser -s <logfile>` (summary mode)
7
-
- **Help**: `./result/bin/transaction-parser --help`
8
-
- **Build with clang**: `clang++ -std=c++17 -O3 -o transaction-parser src/transaction-parser.cpp`
5
+
- **Run parser**: `./result/bin/soapdump <logfile>`
6
+
- **Test parser**: `./result/bin/soapdump -s <logfile>` (summary mode)
7
+
- **Help**: `./result/bin/soapdump --help`
8
+
- **Build with clang**: `clang++ -std=c++17 -O3 -o soapdump src/soapdump.cpp`
9
9
10
10
## Code Style
11
11
- **Language**: C++17
+7
-28
README.md
+7
-28
README.md
···
2
2
3
3
A high-performance PayPal SOAP log parser written in C++.
4
4
5
-

6
-
7
-
## Features
8
-
9
-
- Fast parsing of PayPal SOAP transaction logs
10
-
- Structured output for easy analysis
11
-
- Summary statistics mode
12
-
- Pipe-friendly output format
13
-
- Nix flake for reproducible builds
14
-
15
5
## Usage
16
6
17
7
```bash
18
8
# Get all transactions
19
-
./transaction-parser payments.log
9
+
soapdump payments.log
20
10
21
11
# Get only successful transactions
22
-
./transaction-parser payments.log | grep Success
12
+
soapdump payments.log | grep Success
23
13
24
14
# Count transactions by state
25
-
./transaction-parser payments.log | cut -d'|' -f8 | sort | uniq -c | sort -nr
15
+
soapdump payments.log | cut -d'|' -f8 | sort | uniq -c | sort -nr
26
16
27
17
# Find largest transaction
28
-
./transaction-parser payments.log | sort -t'|' -k2 -nr | head -1
18
+
soapdump payments.log | sort -t'|' -k2 -nr | head -1
29
19
30
20
# Get transactions over $500
31
-
./transaction-parser payments.log | awk -F'|' '$2 > 500'
21
+
soapdump payments.log | awk -F'|' '$2 > 500'
32
22
33
23
# Summary stats
34
-
./transaction-parser -s payments.log
24
+
soapdump -s payments.log
35
25
```
36
26
37
27
## Building
···
49
39
nix develop
50
40
```
51
41
52
-
### Using CMake
53
-
54
-
```bash
55
-
# Build the project
56
-
cmake -B build -S .
57
-
cmake --build build --config Release
58
-
59
-
# Run the parser
60
-
./build/transaction-parser payments.log
61
-
```
62
-
63
42
## Output Format
64
43
65
44
Tab-separated values with the following fields:
···
74
53
75
54
<p align="center">
76
55
<a href="https://github.com/taciturnaxolotl/soapdump/blob/main/LICENSE.md"><img src="https://img.shields.io/static/v1.svg?style=for-the-badge&label=License&message=MIT&logoColor=d9e0ee&colorA=363a4f&colorB=b7bdf8"/></a>
77
-
</p>
56
+
</p>
+5
-5
flake.nix
+5
-5
flake.nix
···
34
34
35
35
buildPhase = ''
36
36
mkdir -p bin
37
-
clang++ -std=c++17 -O3 -o bin/transaction-parser src/transaction-parser.cpp
37
+
clang++ -std=c++17 -O3 -o bin/soapdump src/soapdump.cpp
38
38
'';
39
39
40
40
installPhase = ''
41
41
mkdir -p $out/bin
42
-
cp bin/transaction-parser $out/bin/
42
+
cp bin/soapdump $out/bin/
43
43
'';
44
44
};
45
45
···
55
55
};
56
56
57
57
apps = rec {
58
-
transaction-parser = {
58
+
soapdump = {
59
59
type = "app";
60
-
program = "${self.packages.${system}.default}/bin/transaction-parser";
60
+
program = "${self.packages.${system}.default}/bin/soapdump";
61
61
drv = self.packages.${system}.default;
62
62
};
63
63
64
-
default = transaction-parser;
64
+
default = soapdump;
65
65
};
66
66
}
67
67
);
+352
src/soapdump.cpp
+352
src/soapdump.cpp
···
1
+
#include <cstdio>
2
+
#include <iostream>
3
+
#include <fstream>
4
+
#include <string>
5
+
#include <vector>
6
+
#include <map>
7
+
#include <regex>
8
+
#include <algorithm>
9
+
#include <numeric>
10
+
#include <iomanip>
11
+
#include <getopt.h>
12
+
13
+
// Transaction data structure
14
+
struct Transaction {
15
+
int transNum;
16
+
std::string amount;
17
+
std::string currency;
18
+
std::string firstName;
19
+
std::string lastName;
20
+
std::string street;
21
+
std::string city;
22
+
std::string state;
23
+
std::string zip;
24
+
std::string ccType;
25
+
std::string ccLast4;
26
+
std::string expMonth;
27
+
std::string expYear;
28
+
std::string cvv;
29
+
std::string transId;
30
+
std::string status;
31
+
std::string corrId;
32
+
std::string procAmount;
33
+
};
34
+
35
+
// Response data structure
36
+
struct Response {
37
+
std::string transId;
38
+
std::string status;
39
+
std::string corrId;
40
+
std::string procAmount;
41
+
};
42
+
43
+
// Function prototypes
44
+
void showHelp(const char* programName);
45
+
std::string extractXmlValue(const std::string& xml, const std::string& tag);
46
+
std::string extractXmlAttribute(const std::string& xml, const std::string& attribute);
47
+
std::vector<std::string> extractRequests(const std::string& logContent);
48
+
std::vector<std::string> extractResponses(const std::string& logContent);
49
+
std::vector<Response> parseResponses(const std::vector<std::string>& responseXmls);
50
+
std::vector<Transaction> parseTransactions(const std::vector<std::string>& requestXmls, const std::vector<Response>& responses);
51
+
void outputRawData(const std::vector<Transaction>& transactions);
52
+
void outputSummary(const std::vector<Transaction>& transactions);
53
+
54
+
int main(int argc, char* argv[]) {
55
+
// Default options
56
+
bool summaryOnly = false;
57
+
std::string logFile;
58
+
59
+
// Parse command line options
60
+
static struct option longOptions[] = {
61
+
{"help", no_argument, 0, 'h'},
62
+
{"summary", no_argument, 0, 's'},
63
+
{"raw", no_argument, 0, 'r'},
64
+
{0, 0, 0, 0}
65
+
};
66
+
67
+
int optionIndex = 0;
68
+
int opt;
69
+
while ((opt = getopt_long(argc, argv, "hsr", longOptions, &optionIndex)) != -1) {
70
+
switch (opt) {
71
+
case 'h':
72
+
showHelp(argv[0]);
73
+
return 0;
74
+
case 's':
75
+
summaryOnly = true;
76
+
break;
77
+
case 'r':
78
+
summaryOnly = false;
79
+
break;
80
+
case '?':
81
+
std::cerr << "Unknown option: " << static_cast<char>(optopt) << std::endl;
82
+
showHelp(argv[0]);
83
+
return 1;
84
+
default:
85
+
break;
86
+
}
87
+
}
88
+
89
+
// Get logfile name
90
+
if (optind < argc) {
91
+
logFile = argv[optind];
92
+
} else {
93
+
std::cerr << "Error: No logfile specified" << std::endl;
94
+
showHelp(argv[0]);
95
+
return 1;
96
+
}
97
+
98
+
// Check if file exists
99
+
std::ifstream file(logFile);
100
+
if (!file.is_open()) {
101
+
std::cerr << "Error: File '" << logFile << "' not found" << std::endl;
102
+
return 1;
103
+
}
104
+
105
+
// Read the entire file
106
+
std::string logContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
107
+
file.close();
108
+
109
+
// Extract requests and responses
110
+
std::vector<std::string> requestXmls = extractRequests(logContent);
111
+
std::vector<std::string> responseXmls = extractResponses(logContent);
112
+
113
+
// Parse responses
114
+
std::vector<Response> responses = parseResponses(responseXmls);
115
+
116
+
// Parse transactions
117
+
std::vector<Transaction> transactions = parseTransactions(requestXmls, responses);
118
+
119
+
// Output data
120
+
if (summaryOnly) {
121
+
outputSummary(transactions);
122
+
} else {
123
+
outputRawData(transactions);
124
+
}
125
+
126
+
return 0;
127
+
}
128
+
129
+
void showHelp(const char* programName) {
130
+
std::cout << "PayPal SOAP Log Parser\n\n";
131
+
std::cout << "USAGE:\n";
132
+
std::cout << " " << programName << " [OPTIONS] <logfile>\n\n";
133
+
std::cout << "OPTIONS:\n";
134
+
std::cout << " -h, --help Show this help message\n";
135
+
std::cout << " -s, --summary Show summary statistics only\n";
136
+
std::cout << " -r, --raw Output raw structured data (default)\n\n";
137
+
std::cout << "OUTPUT FORMAT:\n";
138
+
std::cout << " TRANS_NUM|AMOUNT|CURRENCY|FIRSTNAME|LASTNAME|STREET|CITY|STATE|ZIP|CCTYPE|CCLAST4|EXPMONTH|EXPYEAR|CVV|TRANSID|STATUS|CORRID|PROC_AMOUNT\n\n";
139
+
std::cout << "FIELD DESCRIPTIONS:\n";
140
+
std::cout << " TRANS_NUM - Transaction sequence number\n";
141
+
std::cout << " AMOUNT - Order total amount\n";
142
+
std::cout << " CURRENCY - Currency code (USD, etc)\n";
143
+
std::cout << " FIRSTNAME - Customer first name\n";
144
+
std::cout << " LASTNAME - Customer last name\n";
145
+
std::cout << " STREET - Street address\n";
146
+
std::cout << " CITY - City name\n";
147
+
std::cout << " STATE - State/Province code\n";
148
+
std::cout << " ZIP - Postal code\n";
149
+
std::cout << " CCTYPE - Credit card type (Visa, MasterCard, etc)\n";
150
+
std::cout << " CCLAST4 - Last 4 digits of credit card\n";
151
+
std::cout << " EXPMONTH - Card expiration month\n";
152
+
std::cout << " EXPYEAR - Card expiration year\n";
153
+
std::cout << " CVV - CVV code\n";
154
+
std::cout << " TRANSID - PayPal transaction ID\n";
155
+
std::cout << " STATUS - Transaction status (Success/Failure)\n";
156
+
std::cout << " CORRID - Correlation ID\n";
157
+
std::cout << " PROC_AMOUNT - Actually processed amount\n\n";
158
+
std::cout << "EXAMPLES:\n";
159
+
std::cout << " # Get all transactions\n";
160
+
std::cout << " " << programName << " payments.log\n\n";
161
+
std::cout << " # Get only successful transactions\n";
162
+
std::cout << " " << programName << " payments.log | grep Success\n\n";
163
+
std::cout << " # Count transactions by state\n";
164
+
std::cout << " " << programName << " payments.log | cut -d'|' -f8 | sort | uniq -c | sort -nr\n\n";
165
+
std::cout << " # Find largest transaction\n";
166
+
std::cout << " " << programName << " payments.log | sort -t'|' -k2 -nr | head -1\n\n";
167
+
std::cout << " # Get transactions over $500\n";
168
+
std::cout << " " << programName << " payments.log | awk -F'|' '$2 > 500'\n\n";
169
+
std::cout << " # Summary stats\n";
170
+
std::cout << " " << programName << " -s payments.log\n";
171
+
}
172
+
173
+
std::string extractXmlValue(const std::string& xml, const std::string& tag) {
174
+
std::regex pattern("<" + tag + "(?:[^>]*)>([^<]*)</" + tag + ">");
175
+
std::smatch match;
176
+
if (std::regex_search(xml, match, pattern) && match.size() > 1) {
177
+
return match[1].str();
178
+
}
179
+
return "";
180
+
}
181
+
182
+
std::string extractXmlAttribute(const std::string& xml, const std::string& attribute) {
183
+
std::regex pattern(attribute + "=\"([^\"]*)\"");
184
+
std::smatch match;
185
+
if (std::regex_search(xml, match, pattern) && match.size() > 1) {
186
+
return match[1].str();
187
+
}
188
+
return "";
189
+
}
190
+
191
+
std::vector<std::string> extractRequests(const std::string& logContent) {
192
+
std::vector<std::string> requests;
193
+
std::regex pattern("PPAPIService: Request: (.*)");
194
+
195
+
std::string::const_iterator searchStart(logContent.cbegin());
196
+
std::smatch match;
197
+
while (std::regex_search(searchStart, logContent.cend(), match, pattern)) {
198
+
if (match.size() > 1) {
199
+
requests.push_back(match[1].str());
200
+
}
201
+
searchStart = match.suffix().first;
202
+
}
203
+
204
+
return requests;
205
+
}
206
+
207
+
std::vector<std::string> extractResponses(const std::string& logContent) {
208
+
std::vector<std::string> responses;
209
+
std::regex pattern("PPAPIService: Response: <\\?.*\\?>(.*)");
210
+
211
+
std::string::const_iterator searchStart(logContent.cbegin());
212
+
std::smatch match;
213
+
while (std::regex_search(searchStart, logContent.cend(), match, pattern)) {
214
+
if (match.size() > 1) {
215
+
responses.push_back(match[1].str());
216
+
}
217
+
searchStart = match.suffix().first;
218
+
}
219
+
220
+
return responses;
221
+
}
222
+
223
+
std::vector<Response> parseResponses(const std::vector<std::string>& responseXmls) {
224
+
std::vector<Response> responses;
225
+
226
+
for (const auto& xml : responseXmls) {
227
+
Response response;
228
+
response.transId = extractXmlValue(xml, "TransactionID");
229
+
response.status = extractXmlValue(xml, "Ack");
230
+
response.corrId = extractXmlValue(xml, "CorrelationID");
231
+
response.procAmount = extractXmlValue(xml, "Amount");
232
+
233
+
responses.push_back(response);
234
+
}
235
+
236
+
return responses;
237
+
}
238
+
239
+
std::vector<Transaction> parseTransactions(const std::vector<std::string>& requestXmls, const std::vector<Response>& responses) {
240
+
std::vector<Transaction> transactions;
241
+
int transNum = 1;
242
+
243
+
for (size_t i = 0; i < requestXmls.size(); ++i) {
244
+
const auto& xml = requestXmls[i];
245
+
246
+
Transaction transaction;
247
+
transaction.transNum = transNum++;
248
+
249
+
// Extract request fields
250
+
transaction.amount = extractXmlValue(xml, "ebl:OrderTotal");
251
+
transaction.currency = extractXmlAttribute(xml, "currencyID");
252
+
transaction.firstName = extractXmlValue(xml, "ebl:FirstName");
253
+
transaction.lastName = extractXmlValue(xml, "ebl:LastName");
254
+
transaction.street = extractXmlValue(xml, "ebl:Street1");
255
+
transaction.city = extractXmlValue(xml, "ebl:CityName");
256
+
transaction.state = extractXmlValue(xml, "ebl:StateOrProvince");
257
+
transaction.zip = extractXmlValue(xml, "ebl:PostalCode");
258
+
transaction.ccType = extractXmlValue(xml, "ebl:CreditCardType");
259
+
transaction.ccLast4 = extractXmlValue(xml, "ebl:CreditCardLastFourDigits");
260
+
transaction.expMonth = extractXmlValue(xml, "ebl:ExpMonth");
261
+
transaction.expYear = extractXmlValue(xml, "ebl:ExpYear");
262
+
transaction.cvv = extractXmlValue(xml, "ebl:CVV2");
263
+
264
+
// Get corresponding response data
265
+
if (i < responses.size()) {
266
+
transaction.transId = responses[i].transId;
267
+
transaction.status = responses[i].status;
268
+
transaction.corrId = responses[i].corrId;
269
+
transaction.procAmount = responses[i].procAmount;
270
+
}
271
+
272
+
transactions.push_back(transaction);
273
+
}
274
+
275
+
return transactions;
276
+
}
277
+
278
+
void outputRawData(const std::vector<Transaction>& transactions) {
279
+
for (const auto& t : transactions) {
280
+
std::cout << t.transNum << "|"
281
+
<< t.amount << "|"
282
+
<< t.currency << "|"
283
+
<< t.firstName << "|"
284
+
<< t.lastName << "|"
285
+
<< t.street << "|"
286
+
<< t.city << "|"
287
+
<< t.state << "|"
288
+
<< t.zip << "|"
289
+
<< t.ccType << "|"
290
+
<< t.ccLast4 << "|"
291
+
<< t.expMonth << "|"
292
+
<< t.expYear << "|"
293
+
<< t.cvv << "|"
294
+
<< t.transId << "|"
295
+
<< t.status << "|"
296
+
<< t.corrId << "|"
297
+
<< t.procAmount << std::endl;
298
+
}
299
+
}
300
+
301
+
void outputSummary(const std::vector<Transaction>& transactions) {
302
+
std::cout << "=== SUMMARY ===" << std::endl;
303
+
304
+
// Count transactions
305
+
int total = transactions.size();
306
+
int successful = std::count_if(transactions.begin(), transactions.end(),
307
+
[](const Transaction& t) { return t.status == "Success"; });
308
+
309
+
std::cout << "Total Transactions: " << total << std::endl;
310
+
std::cout << "Successful: " << successful << std::endl;
311
+
std::cout << "Failed: " << (total - successful) << std::endl;
312
+
std::cout << std::endl;
313
+
314
+
// Top 5 states
315
+
std::map<std::string, int> stateCounts;
316
+
for (const auto& t : transactions) {
317
+
stateCounts[t.state]++;
318
+
}
319
+
320
+
std::cout << "Top 5 States by Transaction Count:" << std::endl;
321
+
std::vector<std::pair<std::string, int>> stateCountVec(stateCounts.begin(), stateCounts.end());
322
+
std::sort(stateCountVec.begin(), stateCountVec.end(),
323
+
[](const auto& a, const auto& b) { return a.second > b.second; });
324
+
325
+
int count = 0;
326
+
for (const auto& sc : stateCountVec) {
327
+
if (count++ >= 5) break;
328
+
std::cout << " " << sc.first << ": " << sc.second << std::endl;
329
+
}
330
+
std::cout << std::endl;
331
+
332
+
// Transaction amount stats
333
+
std::vector<double> amounts;
334
+
for (const auto& t : transactions) {
335
+
try {
336
+
amounts.push_back(std::stod(t.amount));
337
+
} catch (...) {
338
+
// Skip invalid amounts
339
+
}
340
+
}
341
+
342
+
if (!amounts.empty()) {
343
+
double totalAmount = std::accumulate(amounts.begin(), amounts.end(), 0.0);
344
+
double largest = *std::max_element(amounts.begin(), amounts.end());
345
+
double smallest = *std::min_element(amounts.begin(), amounts.end());
346
+
347
+
std::cout << "Transaction Amount Stats:" << std::endl;
348
+
std::cout << " Total: $" << std::fixed << std::setprecision(2) << totalAmount << std::endl;
349
+
std::cout << " Largest: $" << std::fixed << std::setprecision(2) << largest << std::endl;
350
+
std::cout << " Smallest: $" << std::fixed << std::setprecision(2) << smallest << std::endl;
351
+
}
352
+
}