···
11
+
#include <unordered_map>
14
+
// Transaction data structure
15
+
struct Transaction {
18
+
std::string currency;
19
+
std::string firstName;
20
+
std::string lastName;
26
+
std::string ccLast4;
27
+
std::string expMonth;
28
+
std::string expYear;
30
+
std::string transId;
33
+
std::string procAmount;
36
+
// Response data structure
38
+
std::string transId;
41
+
std::string procAmount;
44
+
// Function prototypes
45
+
void showHelp(const char* programName);
46
+
std::string extractXmlValue(const std::string& xml, const std::string& tag);
47
+
std::string extractXmlAttribute(const std::string& xml, const std::string& attribute);
48
+
std::vector<std::string> extractRequests(const std::string& logContent);
49
+
std::vector<std::string> extractResponses(const std::string& logContent);
50
+
std::vector<Response> parseResponses(const std::vector<std::string>& responseXmls);
51
+
std::vector<Transaction> parseTransactions(const std::vector<std::string>& requestXmls, const std::vector<Response>& responses);
52
+
void outputRawData(const std::vector<Transaction>& transactions);
53
+
void outputSummary(const std::vector<Transaction>& transactions);
55
+
int main(int argc, char* argv[]) {
57
+
bool summaryOnly = false;
58
+
std::string logFile;
60
+
// Parse command line options
61
+
static struct option longOptions[] = {
62
+
{"help", no_argument, 0, 'h'},
63
+
{"summary", no_argument, 0, 's'},
64
+
{"raw", no_argument, 0, 'r'},
68
+
int optionIndex = 0;
70
+
while ((opt = getopt_long(argc, argv, "hsr", longOptions, &optionIndex)) != -1) {
79
+
summaryOnly = false;
82
+
std::cerr << "Unknown option: " << static_cast<char>(optopt) << std::endl;
91
+
if (optind < argc) {
92
+
logFile = argv[optind];
94
+
std::cerr << "Error: No logfile specified" << std::endl;
99
+
// Check if file exists
100
+
std::ifstream file(logFile);
101
+
if (!file.is_open()) {
102
+
std::cerr << "Error: File '" << logFile << "' not found" << std::endl;
106
+
// Read the entire file
107
+
std::string logContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
110
+
// Extract requests and responses
111
+
std::vector<std::string> requestXmls = extractRequests(logContent);
112
+
std::vector<std::string> responseXmls = extractResponses(logContent);
115
+
std::vector<Response> responses = parseResponses(responseXmls);
117
+
// Parse transactions
118
+
std::vector<Transaction> transactions = parseTransactions(requestXmls, responses);
122
+
outputSummary(transactions);
124
+
outputRawData(transactions);
130
+
void showHelp(const char* programName) {
131
+
std::cout << "PayPal SOAP Log Parser\n\n";
132
+
std::cout << "USAGE:\n";
133
+
std::cout << " " << programName << " [OPTIONS] <logfile>\n\n";
134
+
std::cout << "OPTIONS:\n";
135
+
std::cout << " -h, --help Show this help message\n";
136
+
std::cout << " -s, --summary Show summary statistics only\n";
137
+
std::cout << " -r, --raw Output raw structured data (default)\n\n";
138
+
std::cout << "OUTPUT FORMAT (tab-separated):\n";
139
+
std::cout << " TRANS_NUM|AMOUNT|CURRENCY|FIRSTNAME|LASTNAME|STREET|CITY|STATE|ZIP|CCTYPE|CCLAST4|EXPMONTH|EXPYEAR|CVV|TRANSID|STATUS|CORRID|PROC_AMOUNT\n\n";
140
+
std::cout << "FIELD DESCRIPTIONS:\n";
141
+
std::cout << " TRANS_NUM - Transaction sequence number\n";
142
+
std::cout << " AMOUNT - Order total amount\n";
143
+
std::cout << " CURRENCY - Currency code (USD, etc)\n";
144
+
std::cout << " FIRSTNAME - Customer first name\n";
145
+
std::cout << " LASTNAME - Customer last name\n";
146
+
std::cout << " STREET - Street address\n";
147
+
std::cout << " CITY - City name\n";
148
+
std::cout << " STATE - State/Province code\n";
149
+
std::cout << " ZIP - Postal code\n";
150
+
std::cout << " CCTYPE - Credit card type (Visa, MasterCard, etc)\n";
151
+
std::cout << " CCLAST4 - Last 4 digits of credit card\n";
152
+
std::cout << " EXPMONTH - Card expiration month\n";
153
+
std::cout << " EXPYEAR - Card expiration year\n";
154
+
std::cout << " CVV - CVV code\n";
155
+
std::cout << " TRANSID - PayPal transaction ID\n";
156
+
std::cout << " STATUS - Transaction status (Success/Failure)\n";
157
+
std::cout << " CORRID - Correlation ID\n";
158
+
std::cout << " PROC_AMOUNT - Actually processed amount\n\n";
159
+
std::cout << "EXAMPLES:\n";
160
+
std::cout << " # Get all transactions\n";
161
+
std::cout << " " << programName << " payments.log\n\n";
162
+
std::cout << " # Get only successful transactions\n";
163
+
std::cout << " " << programName << " payments.log | grep Success\n\n";
164
+
std::cout << " # Count transactions by state\n";
165
+
std::cout << " " << programName << " payments.log | cut -d'|' -f8 | sort | uniq -c | sort -nr\n\n";
166
+
std::cout << " # Find largest transaction\n";
167
+
std::cout << " " << programName << " payments.log | sort -t'|' -k2 -nr | head -1\n\n";
168
+
std::cout << " # Get transactions over $500\n";
169
+
std::cout << " " << programName << " payments.log | awk -F'|' '$2 > 500'\n\n";
170
+
std::cout << " # Summary stats\n";
171
+
std::cout << " " << programName << " -s payments.log\n";
174
+
std::string extractXmlValue(const std::string& xml, const std::string& tag) {
175
+
std::regex pattern("<" + tag + "(?:[^>]*)>([^<]*)</" + tag + ">");
177
+
if (std::regex_search(xml, match, pattern) && match.size() > 1) {
178
+
return match[1].str();
183
+
std::string extractXmlAttribute(const std::string& xml, const std::string& attribute) {
184
+
std::regex pattern(attribute + "=\"([^\"]*)\"");
186
+
if (std::regex_search(xml, match, pattern) && match.size() > 1) {
187
+
return match[1].str();
192
+
std::vector<std::string> extractRequests(const std::string& logContent) {
193
+
std::vector<std::string> requests;
194
+
std::regex pattern("PPAPIService: Request: (.*)");
196
+
std::string::const_iterator searchStart(logContent.cbegin());
198
+
while (std::regex_search(searchStart, logContent.cend(), match, pattern)) {
199
+
if (match.size() > 1) {
200
+
requests.push_back(match[1].str());
202
+
searchStart = match.suffix().first;
208
+
std::vector<std::string> extractResponses(const std::string& logContent) {
209
+
std::vector<std::string> responses;
210
+
std::regex pattern("PPAPIService: Response: <\\?.*\\?>(.*)");
212
+
std::string::const_iterator searchStart(logContent.cbegin());
214
+
while (std::regex_search(searchStart, logContent.cend(), match, pattern)) {
215
+
if (match.size() > 1) {
216
+
responses.push_back(match[1].str());
218
+
searchStart = match.suffix().first;
224
+
std::vector<Response> parseResponses(const std::vector<std::string>& responseXmls) {
225
+
std::vector<Response> responses;
227
+
for (const auto& xml : responseXmls) {
229
+
response.transId = extractXmlValue(xml, "TransactionID");
230
+
response.status = extractXmlValue(xml, "Ack");
231
+
response.corrId = extractXmlValue(xml, "CorrelationID");
232
+
response.procAmount = extractXmlValue(xml, "Amount");
234
+
responses.push_back(response);
240
+
std::vector<Transaction> parseTransactions(const std::vector<std::string>& requestXmls, const std::vector<Response>& responses) {
241
+
std::vector<Transaction> transactions;
244
+
for (size_t i = 0; i < requestXmls.size(); ++i) {
245
+
const auto& xml = requestXmls[i];
247
+
Transaction transaction;
248
+
transaction.transNum = transNum++;
250
+
// Extract request fields
251
+
transaction.amount = extractXmlValue(xml, "ebl:OrderTotal");
252
+
transaction.currency = extractXmlAttribute(xml, "currencyID");
253
+
transaction.firstName = extractXmlValue(xml, "ebl:FirstName");
254
+
transaction.lastName = extractXmlValue(xml, "ebl:LastName");
255
+
transaction.street = extractXmlValue(xml, "ebl:Street1");
256
+
transaction.city = extractXmlValue(xml, "ebl:CityName");
257
+
transaction.state = extractXmlValue(xml, "ebl:StateOrProvince");
258
+
transaction.zip = extractXmlValue(xml, "ebl:PostalCode");
259
+
transaction.ccType = extractXmlValue(xml, "ebl:CreditCardType");
260
+
transaction.ccLast4 = extractXmlValue(xml, "ebl:CreditCardLastFourDigits");
261
+
transaction.expMonth = extractXmlValue(xml, "ebl:ExpMonth");
262
+
transaction.expYear = extractXmlValue(xml, "ebl:ExpYear");
263
+
transaction.cvv = extractXmlValue(xml, "ebl:CVV2");
265
+
// Get corresponding response data
266
+
if (i < responses.size()) {
267
+
transaction.transId = responses[i].transId;
268
+
transaction.status = responses[i].status;
269
+
transaction.corrId = responses[i].corrId;
270
+
transaction.procAmount = responses[i].procAmount;
273
+
transactions.push_back(transaction);
276
+
return transactions;
279
+
void outputRawData(const std::vector<Transaction>& transactions) {
280
+
for (const auto& t : transactions) {
281
+
std::cout << t.transNum << "|"
283
+
<< t.currency << "|"
284
+
<< t.firstName << "|"
285
+
<< t.lastName << "|"
291
+
<< t.ccLast4 << "|"
292
+
<< t.expMonth << "|"
293
+
<< t.expYear << "|"
295
+
<< t.transId << "|"
298
+
<< t.procAmount << std::endl;
302
+
void outputSummary(const std::vector<Transaction>& transactions) {
303
+
std::cout << "=== SUMMARY ===" << std::endl;
305
+
// Count transactions
306
+
int total = transactions.size();
307
+
int successful = std::count_if(transactions.begin(), transactions.end(),
308
+
[](const Transaction& t) { return t.status == "Success"; });
310
+
std::cout << "Total Transactions: " << total << std::endl;
311
+
std::cout << "Successful: " << successful << std::endl;
312
+
std::cout << "Failed: " << (total - successful) << std::endl;
313
+
std::cout << std::endl;
316
+
std::map<std::string, int> stateCounts;
317
+
for (const auto& t : transactions) {
318
+
stateCounts[t.state]++;
321
+
std::cout << "Top 5 States by Transaction Count:" << std::endl;
322
+
std::vector<std::pair<std::string, int>> stateCountVec(stateCounts.begin(), stateCounts.end());
323
+
std::sort(stateCountVec.begin(), stateCountVec.end(),
324
+
[](const auto& a, const auto& b) { return a.second > b.second; });
327
+
for (const auto& sc : stateCountVec) {
328
+
if (count++ >= 5) break;
329
+
std::cout << " " << sc.first << ": " << sc.second << std::endl;
331
+
std::cout << std::endl;
333
+
// Transaction amount stats
334
+
std::vector<double> amounts;
335
+
for (const auto& t : transactions) {
337
+
amounts.push_back(std::stod(t.amount));
339
+
// Skip invalid amounts
343
+
if (!amounts.empty()) {
344
+
double totalAmount = std::accumulate(amounts.begin(), amounts.end(), 0.0);
345
+
double largest = *std::max_element(amounts.begin(), amounts.end());
346
+
double smallest = *std::min_element(amounts.begin(), amounts.end());
348
+
std::cout << "Transaction Amount Stats:" << std::endl;
349
+
std::cout << " Total: $" << std::fixed << std::setprecision(2) << totalAmount << std::endl;
350
+
std::cout << " Largest: $" << std::fixed << std::setprecision(2) << largest << std::endl;
351
+
std::cout << " Smallest: $" << std::fixed << std::setprecision(2) << smallest << std::endl;