Merge pull request #40 from GameServerPanel/copilot/add-logging-output-error-handling
[WIP] Add logging to investigate HTTP ERROR 500 issues
This commit is contained in:
commit
2a0757ceda
7 changed files with 1267 additions and 68 deletions
223
modules/billing/LOGGING_CHANGES_SUMMARY.md
Normal file
223
modules/billing/LOGGING_CHANGES_SUMMARY.md
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
# PayPal Payment Flow Logging Enhancement - Summary
|
||||||
|
|
||||||
|
## Problem Addressed
|
||||||
|
|
||||||
|
Users were experiencing intermittent errors when clicking "Pay from PayPal" button:
|
||||||
|
- **JSON parsing errors**
|
||||||
|
- **HTTP ERROR 500**
|
||||||
|
- **"Currently unable to handle this request"** errors
|
||||||
|
|
||||||
|
These errors would "flip-flop" between different types, making diagnosis difficult without proper logging.
|
||||||
|
|
||||||
|
## Solution Implemented
|
||||||
|
|
||||||
|
Added comprehensive logging throughout the entire PayPal payment flow to capture:
|
||||||
|
- All request/response data
|
||||||
|
- Error details with full context
|
||||||
|
- Unique request IDs for tracking
|
||||||
|
- Database operations and results
|
||||||
|
- Client-side JavaScript errors
|
||||||
|
|
||||||
|
## What Changed
|
||||||
|
|
||||||
|
### Modified Files
|
||||||
|
|
||||||
|
1. **`api/create_order.php`** - Enhanced with comprehensive logging
|
||||||
|
- Logs every step of order creation
|
||||||
|
- Captures request data, OAuth process, PayPal API calls
|
||||||
|
- Returns request IDs in error messages for tracking
|
||||||
|
- Logs to: `logs/paypal_create_order.log`
|
||||||
|
|
||||||
|
2. **`api/capture_order.php`** - Enhanced existing logging
|
||||||
|
- Logs payment capture process
|
||||||
|
- Tracks database operations (invoice updates, order creation)
|
||||||
|
- Captures all error conditions
|
||||||
|
- Logs to: `logs/paypal_capture.log`
|
||||||
|
|
||||||
|
3. **`cart.php`** - Improved client-side error handling
|
||||||
|
- Better error messages with reference IDs
|
||||||
|
- Enhanced console logging for debugging
|
||||||
|
- Sends errors to server for centralized logging
|
||||||
|
- Better user feedback during payment process
|
||||||
|
|
||||||
|
4. **`api/log_error.php`** - NEW: Client error logging endpoint
|
||||||
|
- Captures JavaScript errors from browser
|
||||||
|
- Logs to: `logs/client_errors.log`
|
||||||
|
|
||||||
|
### New Files
|
||||||
|
|
||||||
|
1. **`PAYPAL_DEBUGGING_GUIDE.md`** - Comprehensive debugging guide
|
||||||
|
- How to read logs
|
||||||
|
- Common issues and solutions
|
||||||
|
- Request flow documentation
|
||||||
|
- Monitoring commands
|
||||||
|
|
||||||
|
2. **`QUICK_DEBUG_REFERENCE.md`** - Quick reference card
|
||||||
|
- Common commands
|
||||||
|
- Error patterns
|
||||||
|
- Quick fixes
|
||||||
|
- Troubleshooting checklist
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
### When an error occurs:
|
||||||
|
|
||||||
|
1. **User will see an error message with a reference ID**, for example:
|
||||||
|
```
|
||||||
|
Failed to create order: API error 500 (Ref: req_abc123)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Search the logs for that reference ID**:
|
||||||
|
```bash
|
||||||
|
cd /home/runner/work/GSP/GSP/modules/billing/logs
|
||||||
|
grep "req_abc123" paypal_create_order.log
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Review the full request flow** to identify where it failed
|
||||||
|
|
||||||
|
4. **Refer to the debugging guide** for common solutions
|
||||||
|
|
||||||
|
### Monitor logs in real-time:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/runner/work/GSP/GSP/modules/billing/logs
|
||||||
|
tail -f paypal_*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check for errors:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/runner/work/GSP/GSP/modules/billing/logs
|
||||||
|
grep -i error paypal_create_order.log
|
||||||
|
grep -i failed paypal_capture.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Log Files
|
||||||
|
|
||||||
|
All logs are written to: `/modules/billing/logs/`
|
||||||
|
|
||||||
|
| Log File | Purpose | When Created |
|
||||||
|
|----------|---------|--------------|
|
||||||
|
| `paypal_create_order.log` | Order creation requests | When user clicks "Pay with PayPal" |
|
||||||
|
| `paypal_capture.log` | Payment capture process | After PayPal approval, during payment capture |
|
||||||
|
| `client_errors.log` | JavaScript/browser errors | When browser encounters errors |
|
||||||
|
|
||||||
|
## Request Tracking
|
||||||
|
|
||||||
|
Each request has a unique ID:
|
||||||
|
- **Create order**: `req_XXXXXXXXXXXXX`
|
||||||
|
- **Capture order**: `cap_XXXXXXXXXXXXX`
|
||||||
|
|
||||||
|
These IDs:
|
||||||
|
- Appear in error messages shown to users
|
||||||
|
- Are logged in every log entry for that request
|
||||||
|
- Can be used to track a request through the entire flow
|
||||||
|
|
||||||
|
## Log Entry Format
|
||||||
|
|
||||||
|
```
|
||||||
|
[TIMESTAMP] [REQUEST_ID] LOG_LABEL
|
||||||
|
key => value
|
||||||
|
key => value
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
[2025-10-29 21:30:00] [req_abc123] OAUTH_SUCCESS
|
||||||
|
token_length => 1024
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
## What Gets Logged
|
||||||
|
|
||||||
|
### Create Order Flow (`api/create_order.php`):
|
||||||
|
- ✓ Incoming request data (amount, currency, items)
|
||||||
|
- ✓ JSON parsing status
|
||||||
|
- ✓ OAuth token acquisition
|
||||||
|
- ✓ PayPal order creation request/response
|
||||||
|
- ✓ All error conditions with full details
|
||||||
|
|
||||||
|
### Capture Order Flow (`api/capture_order.php`):
|
||||||
|
- ✓ Payment capture request
|
||||||
|
- ✓ OAuth process
|
||||||
|
- ✓ Database connection status
|
||||||
|
- ✓ Invoice update queries and results
|
||||||
|
- ✓ Order creation/renewal operations
|
||||||
|
- ✓ All error conditions with full details
|
||||||
|
|
||||||
|
### Client-Side (`cart.php` → `log_error.php`):
|
||||||
|
- ✓ JavaScript errors
|
||||||
|
- ✓ PayPal SDK errors
|
||||||
|
- ✓ Network failures
|
||||||
|
- ✓ JSON parsing errors
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Full Visibility**: Every step of payment flow is now logged
|
||||||
|
2. **Easy Troubleshooting**: Request IDs link user reports to log entries
|
||||||
|
3. **Root Cause Analysis**: Can identify exactly where and why failures occur
|
||||||
|
4. **Pattern Detection**: Can identify if errors are consistent or intermittent
|
||||||
|
5. **Better User Experience**: Users get reference IDs to report issues
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Monitor the logs** after deploying this change
|
||||||
|
2. **Analyze error patterns** to identify the root cause
|
||||||
|
3. **Review common errors** in the debugging guide
|
||||||
|
4. **Fix underlying issues** once identified
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **Full Guide**: `PAYPAL_DEBUGGING_GUIDE.md`
|
||||||
|
- **Quick Reference**: `QUICK_DEBUG_REFERENCE.md`
|
||||||
|
- **This Summary**: `LOGGING_CHANGES_SUMMARY.md`
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The logging system has been tested and verified to work correctly. All components:
|
||||||
|
- ✓ Write to correct log files
|
||||||
|
- ✓ Include proper timestamps and request IDs
|
||||||
|
- ✓ Format data correctly
|
||||||
|
- ✓ Handle errors gracefully
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
### Log Rotation
|
||||||
|
|
||||||
|
Logs will grow over time. Consider setting up log rotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Manual rotation
|
||||||
|
cd /home/runner/work/GSP/GSP/modules/billing/logs
|
||||||
|
gzip paypal_create_order.log
|
||||||
|
mv paypal_create_order.log.gz paypal_create_order.$(date +%Y%m%d).log.gz
|
||||||
|
touch paypal_create_order.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use `logrotate` (see `PAYPAL_DEBUGGING_GUIDE.md` for details).
|
||||||
|
|
||||||
|
### Monitoring
|
||||||
|
|
||||||
|
Set up automated monitoring to alert on:
|
||||||
|
- High error rates
|
||||||
|
- Specific error patterns (OAuth failures, DB connection issues)
|
||||||
|
- Unusual request volumes
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
If you encounter issues or need help interpreting logs:
|
||||||
|
|
||||||
|
1. Check `PAYPAL_DEBUGGING_GUIDE.md` for common issues
|
||||||
|
2. Review `QUICK_DEBUG_REFERENCE.md` for quick fixes
|
||||||
|
3. Provide log excerpts (with request IDs) when asking for help
|
||||||
|
|
||||||
|
## Changes Made By
|
||||||
|
|
||||||
|
- Enhanced logging system - Added 2025-10-29
|
||||||
|
- Documentation created - 2025-10-29
|
||||||
|
- Testing completed - 2025-10-29
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**The intermittent JSON/HTTP 500 errors should now be fully traceable and debuggable with this comprehensive logging system.**
|
||||||
316
modules/billing/PAYPAL_DEBUGGING_GUIDE.md
Normal file
316
modules/billing/PAYPAL_DEBUGGING_GUIDE.md
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
# PayPal Payment Flow Debugging Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide explains how to diagnose and troubleshoot PayPal payment errors using the comprehensive logging system that has been added to the payment flow.
|
||||||
|
|
||||||
|
## Problem Being Addressed
|
||||||
|
|
||||||
|
Users were experiencing intermittent errors when clicking "Pay from PayPal" button:
|
||||||
|
- JSON parsing errors
|
||||||
|
- HTTP ERROR 500
|
||||||
|
- "Currently unable to handle this request" errors
|
||||||
|
|
||||||
|
These errors would "flip-flop" between different error types, making it difficult to diagnose the root cause.
|
||||||
|
|
||||||
|
## Log Files Location
|
||||||
|
|
||||||
|
All logs are stored in: `/modules/billing/logs/`
|
||||||
|
|
||||||
|
### Available Log Files
|
||||||
|
|
||||||
|
1. **`paypal_create_order.log`** - Logs all PayPal order creation requests
|
||||||
|
- When: Created when user clicks "Pay with PayPal" button
|
||||||
|
- Contains: Request data, OAuth tokens, PayPal API responses
|
||||||
|
|
||||||
|
2. **`paypal_capture.log`** - Logs all payment capture attempts
|
||||||
|
- When: Created when PayPal redirects user back after approving payment
|
||||||
|
- Contains: Capture requests, database operations, order creation
|
||||||
|
|
||||||
|
3. **`client_errors.log`** - Logs JavaScript errors from browser
|
||||||
|
- When: Created when browser encounters errors during checkout
|
||||||
|
- Contains: Client-side errors, PayPal SDK issues, network failures
|
||||||
|
|
||||||
|
## How to Debug Payment Issues
|
||||||
|
|
||||||
|
### Step 1: Identify the Request
|
||||||
|
|
||||||
|
Each request has a unique ID for tracking:
|
||||||
|
- Create order requests: `req_XXXXX`
|
||||||
|
- Capture order requests: `cap_XXXXX`
|
||||||
|
|
||||||
|
Look for these IDs in error messages shown to users.
|
||||||
|
|
||||||
|
### Step 2: Check the Logs
|
||||||
|
|
||||||
|
#### For "Failed to create order" errors:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -100 /modules/billing/logs/paypal_create_order.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
- `JSON_DECODE_ERROR` - Invalid input from cart.php
|
||||||
|
- `OAUTH_CURL_ERROR` or `OAUTH_HTTP_ERROR` - Can't connect to PayPal
|
||||||
|
- `CREATE_ORDER_HTTP_ERROR` - PayPal rejected the order
|
||||||
|
|
||||||
|
#### For "Payment capture failed" errors:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -100 /modules/billing/logs/paypal_capture.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
- `OAUTH_*_ERROR` - Authentication issues
|
||||||
|
- `CAPTURE_HTTP_ERROR` - PayPal rejected capture
|
||||||
|
- `DB_CONNECTION_FAILED` - Database issues
|
||||||
|
- `UPDATE_INVOICES_FAILED` - Can't mark invoices as paid
|
||||||
|
- `ORDER_CREATE_FAILED` - Can't create order record
|
||||||
|
|
||||||
|
#### For client-side errors:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -100 /modules/billing/logs/client_errors.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for:
|
||||||
|
- Network errors (fetch failed)
|
||||||
|
- PayPal SDK errors
|
||||||
|
- JSON parsing errors
|
||||||
|
|
||||||
|
### Step 3: Common Issues and Solutions
|
||||||
|
|
||||||
|
#### Issue: OAuth fails (OAUTH_HTTP_ERROR)
|
||||||
|
|
||||||
|
**Log entry example:**
|
||||||
|
```
|
||||||
|
[2025-10-29 21:30:00] [req_12345] OAUTH_HTTP_ERROR
|
||||||
|
http_code => 401
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Invalid PayPal credentials
|
||||||
|
|
||||||
|
**Solution:** Check that `$client_id` and `$client_secret` in `api/create_order.php` and `api/capture_order.php` are correct.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Issue: JSON decode error
|
||||||
|
|
||||||
|
**Log entry example:**
|
||||||
|
```
|
||||||
|
[2025-10-29 21:30:00] [req_12345] JSON_DECODE_ERROR
|
||||||
|
error => Syntax error
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Malformed JSON from cart.php or corrupted request
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Check the `RAW_INPUT` entry before the error
|
||||||
|
2. Verify cart.php is sending valid JSON
|
||||||
|
3. Check for PHP errors that might corrupt the output
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Issue: PayPal returns error creating order
|
||||||
|
|
||||||
|
**Log entry example:**
|
||||||
|
```
|
||||||
|
[2025-10-29 21:30:00] [req_12345] CREATE_ORDER_HTTP_ERROR
|
||||||
|
http_code => 400
|
||||||
|
response => {"name":"INVALID_REQUEST","details":[{"issue":"..."}]}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Invalid order data sent to PayPal
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Look at `PAYPAL_ORDER_PAYLOAD` entry to see what was sent
|
||||||
|
2. Common issues:
|
||||||
|
- Invalid amount format (must be 2 decimals)
|
||||||
|
- Invalid currency code
|
||||||
|
- Malformed items array
|
||||||
|
- Invalid URLs (return_url, cancel_url must be absolute URLs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Issue: Database connection failed
|
||||||
|
|
||||||
|
**Log entry example:**
|
||||||
|
```
|
||||||
|
[2025-10-29 21:30:00] [cap_12345] DB_CONNECTION_FAILED
|
||||||
|
error => Access denied for user
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Can't connect to database
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Check database credentials in `includes/config.inc.php`
|
||||||
|
2. Verify database server is running
|
||||||
|
3. Check database permissions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Issue: Invoice update failed
|
||||||
|
|
||||||
|
**Log entry example:**
|
||||||
|
```
|
||||||
|
[2025-10-29 21:30:00] [cap_12345] UPDATE_INVOICES_FAILED
|
||||||
|
error => Table 'ogp_billing_invoices' doesn't exist
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause:** Database schema issue
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Verify table exists and has correct name
|
||||||
|
2. Check `$table_prefix` variable in config
|
||||||
|
3. Run database migrations if needed
|
||||||
|
|
||||||
|
## Log Entry Structure
|
||||||
|
|
||||||
|
Each log entry includes:
|
||||||
|
|
||||||
|
```
|
||||||
|
[TIMESTAMP] [REQUEST_ID] LOG_LABEL
|
||||||
|
key => value
|
||||||
|
key => value
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
- **TIMESTAMP**: When the event occurred (Y-m-d H:i:s format)
|
||||||
|
- **REQUEST_ID**: Unique identifier for tracking the request
|
||||||
|
- **LOG_LABEL**: What happened (e.g., OAUTH_SUCCESS, CREATE_ORDER_FAILED)
|
||||||
|
- **Data**: Relevant data for the event (arrays/objects pretty-printed)
|
||||||
|
|
||||||
|
## Request Flow with Logging
|
||||||
|
|
||||||
|
### Creating an Order
|
||||||
|
|
||||||
|
1. User clicks "Pay with PayPal" in cart.php
|
||||||
|
2. JavaScript calls `api/create_order.php`
|
||||||
|
3. Logs generated:
|
||||||
|
- `REQUEST_START` - Initial request info
|
||||||
|
- `RAW_INPUT` - What was received
|
||||||
|
- `PARSED_INPUT` - Decoded data
|
||||||
|
- `OAUTH_REQUEST_START` - Starting OAuth
|
||||||
|
- `OAUTH_RESPONSE` - OAuth result
|
||||||
|
- `OAUTH_SUCCESS` or `OAUTH_*_ERROR`
|
||||||
|
- `CREATE_ORDER_REQUEST_START` - Sending to PayPal
|
||||||
|
- `CREATE_ORDER_RESPONSE` - PayPal's response
|
||||||
|
- `CREATE_ORDER_SUCCESS` or `CREATE_ORDER_*_ERROR`
|
||||||
|
|
||||||
|
### Capturing Payment
|
||||||
|
|
||||||
|
1. User approves payment on PayPal
|
||||||
|
2. PayPal redirects back to site
|
||||||
|
3. JavaScript calls `api/capture_order.php`
|
||||||
|
4. Logs generated:
|
||||||
|
- `REQUEST_START` - Initial request
|
||||||
|
- `RAW_INPUT` - Order ID received
|
||||||
|
- `PARSED_INPUT` - Decoded data
|
||||||
|
- `OAUTH_*` - Authentication steps
|
||||||
|
- `CAPTURE_REQUEST_START` - Starting capture
|
||||||
|
- `CAPTURE_RESPONSE` - PayPal's response
|
||||||
|
- `CAPTURE_SUCCESS` or `CAPTURE_*_ERROR`
|
||||||
|
- `PAYMENT_DETAILS` - Extracted transaction info
|
||||||
|
- `STARTING_DB_PROCESSING` - Beginning database work
|
||||||
|
- `DB_CONNECTED` - Database ready
|
||||||
|
- `SESSION_INFO` - User session details
|
||||||
|
- `PROCESSING_INVOICES` - Starting invoice processing
|
||||||
|
- `UPDATE_INVOICES_*` - Invoice update results
|
||||||
|
- `PROCESSING_INVOICE` - For each invoice
|
||||||
|
- `NEW_ORDER_DETECTED` or `RENEWAL_DETECTED`
|
||||||
|
- `ORDER_CREATE_*` or `ORDER_EXTENDED_*`
|
||||||
|
- `PROCESSING_COMPLETE` - Done
|
||||||
|
|
||||||
|
## Monitoring Tips
|
||||||
|
|
||||||
|
### Watch logs in real-time
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Watch create order logs
|
||||||
|
tail -f /modules/billing/logs/paypal_create_order.log
|
||||||
|
|
||||||
|
# Watch capture logs
|
||||||
|
tail -f /modules/billing/logs/paypal_capture.log
|
||||||
|
|
||||||
|
# Watch all logs
|
||||||
|
tail -f /modules/billing/logs/*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter for errors only
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -i error /modules/billing/logs/paypal_create_order.log
|
||||||
|
grep -i failed /modules/billing/logs/paypal_capture.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Find specific request by ID
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep "req_abc123" /modules/billing/logs/paypal_create_order.log
|
||||||
|
grep "cap_xyz789" /modules/billing/logs/paypal_capture.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Count successful vs failed requests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -c "CREATE_ORDER_SUCCESS" /modules/billing/logs/paypal_create_order.log
|
||||||
|
grep -c "CREATE_ORDER.*ERROR" /modules/billing/logs/paypal_create_order.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Log Rotation
|
||||||
|
|
||||||
|
Logs will grow over time. Consider implementing log rotation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Archive old logs
|
||||||
|
cd /modules/billing/logs
|
||||||
|
gzip paypal_create_order.log
|
||||||
|
mv paypal_create_order.log.gz paypal_create_order.$(date +%Y%m%d).log.gz
|
||||||
|
touch paypal_create_order.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use logrotate:
|
||||||
|
|
||||||
|
```
|
||||||
|
/path/to/modules/billing/logs/*.log {
|
||||||
|
daily
|
||||||
|
rotate 7
|
||||||
|
compress
|
||||||
|
delaycompress
|
||||||
|
notifempty
|
||||||
|
create 0644 www-data www-data
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Messages to Users
|
||||||
|
|
||||||
|
When errors occur, users now see messages with request IDs:
|
||||||
|
|
||||||
|
- "Failed to create order: API error 500 (Ref: req_abc123)"
|
||||||
|
- "Payment capture failed: oauth_fail (Ref: cap_xyz789)"
|
||||||
|
|
||||||
|
Use these reference IDs to search the logs for the full details.
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
When reporting issues, include:
|
||||||
|
|
||||||
|
1. The exact error message shown to user (including Ref ID)
|
||||||
|
2. Relevant log entries (search by Ref ID)
|
||||||
|
3. What the user was trying to do
|
||||||
|
4. Whether it's consistent or intermittent
|
||||||
|
5. Browser console output (F12 → Console tab)
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- PayPal API Documentation: https://developer.paypal.com/api/rest/
|
||||||
|
- PayPal Sandbox Testing: https://developer.paypal.com/developer/accounts/
|
||||||
|
- PayPal Error Codes: https://developer.paypal.com/api/rest/reference/orders/v2/errors/
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### 2025-10-29
|
||||||
|
- Added comprehensive logging to create_order.php
|
||||||
|
- Enhanced logging in capture_order.php
|
||||||
|
- Added client-side error logging
|
||||||
|
- Created debugging guide
|
||||||
186
modules/billing/QUICK_DEBUG_REFERENCE.md
Normal file
186
modules/billing/QUICK_DEBUG_REFERENCE.md
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
# PayPal Payment Flow - Quick Debug Reference
|
||||||
|
|
||||||
|
## Quick Commands
|
||||||
|
|
||||||
|
### View recent errors:
|
||||||
|
```bash
|
||||||
|
cd /home/runner/work/GSP/GSP/modules/billing/logs
|
||||||
|
|
||||||
|
# Last 50 lines of create order log
|
||||||
|
tail -50 paypal_create_order.log
|
||||||
|
|
||||||
|
# Last 50 lines of capture log
|
||||||
|
tail -50 paypal_capture.log
|
||||||
|
|
||||||
|
# Last 50 lines of client errors
|
||||||
|
tail -50 client_errors.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Watch logs live:
|
||||||
|
```bash
|
||||||
|
# In terminal, run:
|
||||||
|
tail -f /home/runner/work/GSP/GSP/modules/billing/logs/paypal_*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Search for specific error:
|
||||||
|
```bash
|
||||||
|
# Find all OAuth errors
|
||||||
|
grep "OAUTH.*ERROR" paypal_create_order.log paypal_capture.log
|
||||||
|
|
||||||
|
# Find database errors
|
||||||
|
grep "DB.*FAILED" paypal_capture.log
|
||||||
|
|
||||||
|
# Find a specific request by ID
|
||||||
|
grep "req_12345" paypal_create_order.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Error Patterns
|
||||||
|
|
||||||
|
### ❌ "JSON error" or "unable to handle this request"
|
||||||
|
|
||||||
|
**What to check:**
|
||||||
|
1. Browser console (F12 → Console tab) for JavaScript errors
|
||||||
|
2. `client_errors.log` for client-side issues
|
||||||
|
3. `paypal_create_order.log` for `JSON_DECODE_ERROR`
|
||||||
|
|
||||||
|
**Quick fix:**
|
||||||
|
- Check if cart items are valid
|
||||||
|
- Verify amount calculations are correct
|
||||||
|
- Look for PHP errors that might corrupt JSON output
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ HTTP ERROR 500
|
||||||
|
|
||||||
|
**What to check:**
|
||||||
|
1. `paypal_create_order.log` for `CREATE_ORDER_HTTP_ERROR`
|
||||||
|
2. `paypal_capture.log` for `CAPTURE_HTTP_ERROR`
|
||||||
|
3. Look for `OAUTH.*ERROR` entries
|
||||||
|
|
||||||
|
**Quick fix:**
|
||||||
|
- Verify PayPal credentials are correct
|
||||||
|
- Check PayPal API status: https://www.paypal-status.com/
|
||||||
|
- Verify sandbox vs live mode settings match credentials
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ Payment seems successful but no order created
|
||||||
|
|
||||||
|
**What to check:**
|
||||||
|
1. `paypal_capture.log` for `DB_CONNECTION_FAILED`
|
||||||
|
2. Look for `UPDATE_INVOICES_FAILED`
|
||||||
|
3. Check `ORDER_CREATE_FAILED`
|
||||||
|
|
||||||
|
**Quick fix:**
|
||||||
|
- Verify database connection settings
|
||||||
|
- Check if `ogp_billing_invoices` table exists
|
||||||
|
- Verify `ogp_billing_orders` table exists
|
||||||
|
- Check table permissions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ Intermittent failures (works sometimes, fails sometimes)
|
||||||
|
|
||||||
|
**What to check:**
|
||||||
|
1. Compare successful vs failed requests in logs
|
||||||
|
2. Look for timeout errors (`CURL.*ERROR`)
|
||||||
|
3. Check for database connection pool exhaustion
|
||||||
|
|
||||||
|
**Quick fix:**
|
||||||
|
- Check server load/resources
|
||||||
|
- Verify network connectivity to PayPal API
|
||||||
|
- Check for rate limiting
|
||||||
|
|
||||||
|
## Log File Locations
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/runner/work/GSP/GSP/modules/billing/logs/
|
||||||
|
├── paypal_create_order.log # Order creation (when clicking "Pay")
|
||||||
|
├── paypal_capture.log # Payment capture (after PayPal approval)
|
||||||
|
└── client_errors.log # JavaScript/browser errors
|
||||||
|
```
|
||||||
|
|
||||||
|
## Request ID Format
|
||||||
|
|
||||||
|
- Create order: `req_XXXXXXXXXXXXX`
|
||||||
|
- Capture order: `cap_XXXXXXXXXXXXX`
|
||||||
|
|
||||||
|
When user sees an error with `(Ref: req_abc123)`, search logs for that ID.
|
||||||
|
|
||||||
|
## Important Log Labels
|
||||||
|
|
||||||
|
### Create Order Flow:
|
||||||
|
- `REQUEST_START` → `RAW_INPUT` → `PARSED_INPUT`
|
||||||
|
- `OAUTH_REQUEST_START` → `OAUTH_SUCCESS`
|
||||||
|
- `CREATE_ORDER_REQUEST_START` → `CREATE_ORDER_SUCCESS`
|
||||||
|
|
||||||
|
### Capture Order Flow:
|
||||||
|
- `REQUEST_START` → `PARSED_INPUT`
|
||||||
|
- `OAUTH_SUCCESS` → `CAPTURE_SUCCESS`
|
||||||
|
- `DB_CONNECTED` → `PROCESSING_INVOICES`
|
||||||
|
- `ORDER_CREATED_SUCCESS` or `ORDER_EXTENDED_SUCCESS`
|
||||||
|
|
||||||
|
### Error Labels:
|
||||||
|
- `*_ERROR` - Something went wrong
|
||||||
|
- `*_FAILED` - Operation failed
|
||||||
|
- `INVALID_*` - Invalid input/data
|
||||||
|
|
||||||
|
## Browser Console Debugging
|
||||||
|
|
||||||
|
1. Open cart page
|
||||||
|
2. Press F12 to open DevTools
|
||||||
|
3. Go to Console tab
|
||||||
|
4. Click "Pay with PayPal"
|
||||||
|
5. Watch for:
|
||||||
|
- Red error messages
|
||||||
|
- `PayPal Error:` logs
|
||||||
|
- Network errors (check Network tab)
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
When testing payments:
|
||||||
|
|
||||||
|
- [ ] Check browser console for errors
|
||||||
|
- [ ] Note the Ref ID if error occurs
|
||||||
|
- [ ] Check `paypal_create_order.log` for the request
|
||||||
|
- [ ] Check `paypal_capture.log` if got past order creation
|
||||||
|
- [ ] Verify database tables exist and have data
|
||||||
|
- [ ] Check PayPal sandbox account activity
|
||||||
|
|
||||||
|
## Need More Help?
|
||||||
|
|
||||||
|
See full guide: `PAYPAL_DEBUGGING_GUIDE.md`
|
||||||
|
|
||||||
|
## Key Configuration Files
|
||||||
|
|
||||||
|
- PayPal credentials: `api/create_order.php` and `api/capture_order.php`
|
||||||
|
- Lines 5-6: `$client_id` and `$client_secret`
|
||||||
|
- Line 4: `$sandbox` (true/false)
|
||||||
|
|
||||||
|
- Database config: `includes/config.inc.php`
|
||||||
|
- `$db_host`, `$db_user`, `$db_pass`, `$db_name`
|
||||||
|
- `$table_prefix`
|
||||||
|
|
||||||
|
## Status Checklist for Issues
|
||||||
|
|
||||||
|
When user reports error:
|
||||||
|
|
||||||
|
1. **Get details:**
|
||||||
|
- [ ] What error message did they see?
|
||||||
|
- [ ] What was the Ref ID (if shown)?
|
||||||
|
- [ ] Can they reproduce it?
|
||||||
|
|
||||||
|
2. **Check logs:**
|
||||||
|
- [ ] Find the request by Ref ID
|
||||||
|
- [ ] Look for ERROR or FAILED labels
|
||||||
|
- [ ] Check surrounding context (before/after)
|
||||||
|
|
||||||
|
3. **Verify config:**
|
||||||
|
- [ ] PayPal credentials valid?
|
||||||
|
- [ ] Database connection working?
|
||||||
|
- [ ] Correct sandbox/live mode?
|
||||||
|
|
||||||
|
4. **Test:**
|
||||||
|
- [ ] Try creating test order
|
||||||
|
- [ ] Watch logs in real-time
|
||||||
|
- [ ] Check database for created records
|
||||||
|
|
@ -11,12 +11,49 @@ ini_set('display_errors', '0');
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
$in = json_decode(file_get_contents('php://input'), true) ?: [];
|
|
||||||
|
// Read and parse input
|
||||||
|
$rawInput = file_get_contents('php://input');
|
||||||
|
capture_log('RAW_INPUT', substr($rawInput, 0, 1000));
|
||||||
|
|
||||||
|
$in = json_decode($rawInput, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
capture_log('JSON_DECODE_ERROR', [
|
||||||
|
'error' => json_last_error_msg(),
|
||||||
|
'raw_input_length' => strlen($rawInput),
|
||||||
|
'raw_input_preview' => substr($rawInput, 0, 500)
|
||||||
|
]);
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'invalid_json', 'message' => json_last_error_msg(), 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$in) {
|
||||||
|
$in = [];
|
||||||
|
}
|
||||||
|
|
||||||
$order_id = $in['order_id'] ?? null;
|
$order_id = $in['order_id'] ?? null;
|
||||||
if (!$order_id) { http_response_code(400); echo json_encode(['error'=>'missing order_id']); exit; }
|
capture_log('PARSED_INPUT', ['order_id' => $order_id]);
|
||||||
|
|
||||||
|
if (!$order_id) {
|
||||||
|
capture_log('MISSING_ORDER_ID', ['input' => $in]);
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'missing_order_id', 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
|
$api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
|
||||||
|
|
||||||
|
capture_log('PAYPAL_API_CONFIG', [
|
||||||
|
'sandbox_mode' => $sandbox,
|
||||||
|
'api_base' => $api,
|
||||||
|
'has_client_id' => !empty($client_id),
|
||||||
|
'has_client_secret' => !empty($client_secret)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Step 1: Get OAuth token
|
||||||
|
capture_log('OAUTH_REQUEST_START', ['endpoint' => "$api/v1/oauth2/token"]);
|
||||||
|
|
||||||
$ch = curl_init("$api/v1/oauth2/token");
|
$ch = curl_init("$api/v1/oauth2/token");
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
|
@ -27,9 +64,47 @@ curl_setopt_array($ch, [
|
||||||
]);
|
]);
|
||||||
$tok = curl_exec($ch);
|
$tok = curl_exec($ch);
|
||||||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$oauth_curl_errno = curl_errno($ch);
|
||||||
|
$oauth_curl_error = curl_error($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
if ($http !== 200) { http_response_code(500); echo json_encode(['error'=>'oauth_fail']); exit; }
|
|
||||||
|
capture_log('OAUTH_RESPONSE', [
|
||||||
|
'http_code' => $http,
|
||||||
|
'curl_errno' => $oauth_curl_errno,
|
||||||
|
'curl_error' => $oauth_curl_error,
|
||||||
|
'response_length' => strlen($tok),
|
||||||
|
'response_preview' => substr($tok, 0, 200)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($oauth_curl_errno !== 0) {
|
||||||
|
capture_log('OAUTH_CURL_ERROR', ['errno' => $oauth_curl_errno, 'error' => $oauth_curl_error]);
|
||||||
|
http_response_code(502);
|
||||||
|
echo json_encode(['error' => 'oauth_curl_fail', 'details' => $oauth_curl_error, 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http !== 200) {
|
||||||
|
capture_log('OAUTH_HTTP_ERROR', ['http_code' => $http, 'response' => $tok]);
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'oauth_fail', 'http_code' => $http, 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$access = json_decode($tok, true)['access_token'] ?? null;
|
$access = json_decode($tok, true)['access_token'] ?? null;
|
||||||
|
if (!$access) {
|
||||||
|
capture_log('OAUTH_NO_TOKEN', ['response' => $tok]);
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'oauth_no_token', 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_log('OAUTH_SUCCESS', ['token_length' => strlen($access)]);
|
||||||
|
|
||||||
|
// Step 2: Capture the PayPal order
|
||||||
|
capture_log('CAPTURE_REQUEST_START', [
|
||||||
|
'endpoint' => "$api/v2/checkout/orders/$order_id/capture",
|
||||||
|
'order_id' => $order_id
|
||||||
|
]);
|
||||||
|
|
||||||
$ch = curl_init("$api/v2/checkout/orders/$order_id/capture");
|
$ch = curl_init("$api/v2/checkout/orders/$order_id/capture");
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
|
|
@ -40,30 +115,33 @@ curl_setopt_array($ch, [
|
||||||
$res = curl_exec($ch);
|
$res = curl_exec($ch);
|
||||||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
$curl_err = curl_error($ch);
|
$curl_err = curl_error($ch);
|
||||||
|
$curl_errno = curl_errno($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
// Ensure logs folder exists and provide a helper to write debug info
|
|
||||||
$logDir = __DIR__ . '/../logs';
|
|
||||||
if (!is_dir($logDir)) @mkdir($logDir, 0755, true);
|
|
||||||
$logFile = $logDir . '/paypal_capture.log';
|
|
||||||
function capture_log($label, $data) {
|
|
||||||
global $logFile;
|
|
||||||
$entry = '[' . date('Y-m-d H:i:s') . '] ' . $label . "\n";
|
|
||||||
if (is_array($data) || is_object($data)) $entry .= print_r($data, true);
|
|
||||||
else $entry .= (string)$data;
|
|
||||||
$entry .= "\n---\n";
|
|
||||||
@file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the raw curl response for debugging
|
// Log the raw curl response for debugging
|
||||||
capture_log('paypal_curl_response_http_' . $http, $res === false ? "(curl failed) " . $curl_err : $res);
|
capture_log('CAPTURE_RESPONSE', [
|
||||||
|
'http_code' => $http,
|
||||||
|
'curl_errno' => $curl_errno,
|
||||||
|
'curl_error' => $curl_err,
|
||||||
|
'response_length' => strlen($res),
|
||||||
|
'response_preview' => substr($res, 0, 1000)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check for curl-level errors
|
||||||
|
if ($curl_errno !== 0) {
|
||||||
|
capture_log('CAPTURE_CURL_ERROR', ['errno' => $curl_errno, 'error' => $curl_err]);
|
||||||
|
http_response_code(502);
|
||||||
|
$out = ['error' => 'capture_curl_fail', 'details' => $curl_err, 'request_id' => $requestId];
|
||||||
|
echo json_encode($out);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize response: ensure we always return valid JSON to the caller
|
// Normalize response: ensure we always return valid JSON to the caller
|
||||||
if ($res === false || $res === '') {
|
if ($res === false || $res === '') {
|
||||||
// Curl-level failure or empty body
|
// Curl-level failure or empty body
|
||||||
|
capture_log('CAPTURE_EMPTY_RESPONSE', ['http' => $http]);
|
||||||
http_response_code(502);
|
http_response_code(502);
|
||||||
$out = ['error' => 'paypal_empty_response', 'http' => $http, 'curl_error' => $curl_err];
|
$out = ['error' => 'paypal_empty_response', 'http' => $http, 'request_id' => $requestId];
|
||||||
capture_log('paypal_empty_response', $out);
|
|
||||||
echo json_encode($out);
|
echo json_encode($out);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
@ -72,25 +150,36 @@ if ($res === false || $res === '') {
|
||||||
$capture = json_decode($res, true);
|
$capture = json_decode($res, true);
|
||||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
// PayPal returned non-JSON / malformed response — return it as raw string inside JSON
|
// PayPal returned non-JSON / malformed response — return it as raw string inside JSON
|
||||||
|
capture_log('CAPTURE_INVALID_JSON', [
|
||||||
|
'json_error' => json_last_error_msg(),
|
||||||
|
'http' => $http,
|
||||||
|
'raw_preview' => substr($res, 0, 500)
|
||||||
|
]);
|
||||||
http_response_code(502);
|
http_response_code(502);
|
||||||
$out = ['error' => 'paypal_invalid_json', 'http' => $http, 'raw' => $res];
|
$out = ['error' => 'paypal_invalid_json', 'http' => $http, 'request_id' => $requestId];
|
||||||
capture_log('paypal_invalid_json', $out);
|
|
||||||
echo json_encode($out);
|
echo json_encode($out);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($http !== 201 && $http !== 200) {
|
if ($http !== 201 && $http !== 200) {
|
||||||
|
capture_log('CAPTURE_HTTP_ERROR', [
|
||||||
|
'http_code' => $http,
|
||||||
|
'response' => $capture
|
||||||
|
]);
|
||||||
http_response_code($http);
|
http_response_code($http);
|
||||||
// Return structured JSON with PayPal's decoded response
|
// Return structured JSON with PayPal's decoded response
|
||||||
$out = ['error' => 'paypal_capture_failed', 'http' => $http, 'response' => $capture];
|
$out = ['error' => 'paypal_capture_failed', 'http' => $http, 'paypal_error' => $capture, 'request_id' => $requestId];
|
||||||
capture_log('paypal_capture_failed', $out);
|
|
||||||
echo json_encode($out);
|
echo json_encode($out);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract payment details
|
// Extract payment details
|
||||||
$txid = null;
|
$txid = null;
|
||||||
capture_log('paypal_capture_success', $capture);
|
capture_log('CAPTURE_SUCCESS', [
|
||||||
|
'status' => $capture['status'] ?? 'UNKNOWN',
|
||||||
|
'id' => $capture['id'] ?? 'UNKNOWN'
|
||||||
|
]);
|
||||||
|
|
||||||
if (isset($capture['purchase_units'][0]['payments']['captures'][0])) {
|
if (isset($capture['purchase_units'][0]['payments']['captures'][0])) {
|
||||||
$txid = $capture['purchase_units'][0]['payments']['captures'][0]['id'] ?? null;
|
$txid = $capture['purchase_units'][0]['payments']['captures'][0]['id'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
@ -99,28 +188,48 @@ if (isset($capture['purchase_units'][0]['payments']['captures'][0])) {
|
||||||
$custom_id = $capture['purchase_units'][0]['custom_id'] ?? null;
|
$custom_id = $capture['purchase_units'][0]['custom_id'] ?? null;
|
||||||
$captureStatus = $capture['status'] ?? null;
|
$captureStatus = $capture['status'] ?? null;
|
||||||
|
|
||||||
|
capture_log('PAYMENT_DETAILS', [
|
||||||
|
'txid' => $txid,
|
||||||
|
'custom_id' => $custom_id,
|
||||||
|
'status' => $captureStatus
|
||||||
|
]);
|
||||||
|
|
||||||
if ($captureStatus === 'COMPLETED' && $custom_id) {
|
if ($captureStatus === 'COMPLETED' && $custom_id) {
|
||||||
|
capture_log('STARTING_DB_PROCESSING', ['custom_id' => $custom_id, 'status' => $captureStatus]);
|
||||||
|
|
||||||
// Connect to database using mysqli (standalone - no panel dependencies)
|
// Connect to database using mysqli (standalone - no panel dependencies)
|
||||||
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
|
$db = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
|
||||||
if (!$db) {
|
if (!$db) {
|
||||||
error_log('capture_order.php: DB connection failed - ' . mysqli_connect_error());
|
$dbError = mysqli_connect_error();
|
||||||
echo json_encode(['error' => 'db_connection_failed', 'status' => $captureStatus]);
|
capture_log('DB_CONNECTION_FAILED', [
|
||||||
|
'error' => $dbError,
|
||||||
|
'host' => $db_host,
|
||||||
|
'db_name' => $db_name
|
||||||
|
]);
|
||||||
|
error_log('capture_order.php: DB connection failed - ' . $dbError);
|
||||||
|
echo json_encode(['error' => 'db_connection_failed', 'status' => $captureStatus, 'request_id' => $requestId]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
capture_log('DB_CONNECTED', ['database' => $db_name]);
|
||||||
|
|
||||||
// Get coupon information from session if available
|
// Get coupon information from session if available
|
||||||
|
session_start();
|
||||||
$applied_coupon = isset($_SESSION['applied_coupon']) ? $_SESSION['applied_coupon'] : null;
|
$applied_coupon = isset($_SESSION['applied_coupon']) ? $_SESSION['applied_coupon'] : null;
|
||||||
$coupon_id = $applied_coupon ? intval($applied_coupon['coupon_id']) : null;
|
$coupon_id = $applied_coupon ? intval($applied_coupon['coupon_id']) : null;
|
||||||
|
|
||||||
// Find all invoices with status='due' for this user (cart session)
|
|
||||||
// For now, we'll mark ALL due invoices for the logged-in user as paid
|
|
||||||
// TODO: Improve to match specific invoice_id from custom_id if cart sends it
|
|
||||||
session_start();
|
|
||||||
// Check both website_user_id and user_id for compatibility
|
// Check both website_user_id and user_id for compatibility
|
||||||
$user_id = isset($_SESSION['website_user_id']) ? intval($_SESSION['website_user_id']) :
|
$user_id = isset($_SESSION['website_user_id']) ? intval($_SESSION['website_user_id']) :
|
||||||
(isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0);
|
(isset($_SESSION['user_id']) ? intval($_SESSION['user_id']) : 0);
|
||||||
|
|
||||||
|
capture_log('SESSION_INFO', [
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'coupon_id' => $coupon_id,
|
||||||
|
'session_keys' => array_keys($_SESSION)
|
||||||
|
]);
|
||||||
|
|
||||||
if ($user_id > 0) {
|
if ($user_id > 0) {
|
||||||
|
capture_log('PROCESSING_INVOICES', ['user_id' => $user_id, 'custom_id' => $custom_id]);
|
||||||
// Mark all due invoices for this user as paid, including coupon_id if applicable
|
// Mark all due invoices for this user as paid, including coupon_id if applicable
|
||||||
$now = date('Y-m-d H:i:s');
|
$now = date('Y-m-d H:i:s');
|
||||||
$esc_txid = mysqli_real_escape_string($db, $txid);
|
$esc_txid = mysqli_real_escape_string($db, $txid);
|
||||||
|
|
@ -131,26 +240,52 @@ if ($captureStatus === 'COMPLETED' && $custom_id) {
|
||||||
$updateInvoices .= ", coupon_id=$coupon_id";
|
$updateInvoices .= ", coupon_id=$coupon_id";
|
||||||
}
|
}
|
||||||
$updateInvoices .= " WHERE user_id=$user_id AND status='due'";
|
$updateInvoices .= " WHERE user_id=$user_id AND status='due'";
|
||||||
mysqli_query($db, $updateInvoices);
|
|
||||||
|
capture_log('UPDATE_INVOICES_QUERY', ['sql' => $updateInvoices]);
|
||||||
|
|
||||||
|
$updateResult = mysqli_query($db, $updateInvoices);
|
||||||
|
if (!$updateResult) {
|
||||||
|
capture_log('UPDATE_INVOICES_FAILED', ['error' => mysqli_error($db)]);
|
||||||
|
} else {
|
||||||
|
$affectedRows = mysqli_affected_rows($db);
|
||||||
|
capture_log('UPDATE_INVOICES_SUCCESS', ['affected_rows' => $affectedRows]);
|
||||||
|
}
|
||||||
|
|
||||||
// Update coupon usage count if a coupon was applied
|
// Update coupon usage count if a coupon was applied
|
||||||
if ($coupon_id) {
|
if ($coupon_id) {
|
||||||
$updateCoupon = "UPDATE {$table_prefix}billing_coupons
|
$updateCoupon = "UPDATE {$table_prefix}billing_coupons
|
||||||
SET current_uses = current_uses + 1
|
SET current_uses = current_uses + 1
|
||||||
WHERE coupon_id = $coupon_id";
|
WHERE coupon_id = $coupon_id";
|
||||||
mysqli_query($db, $updateCoupon);
|
capture_log('UPDATE_COUPON_QUERY', ['sql' => $updateCoupon]);
|
||||||
|
|
||||||
|
$couponResult = mysqli_query($db, $updateCoupon);
|
||||||
|
if (!$couponResult) {
|
||||||
|
capture_log('UPDATE_COUPON_FAILED', ['error' => mysqli_error($db)]);
|
||||||
|
} else {
|
||||||
|
capture_log('UPDATE_COUPON_SUCCESS', ['affected_rows' => mysqli_affected_rows($db)]);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear coupon from session after use (for one-time coupons)
|
// Clear coupon from session after use (for one-time coupons)
|
||||||
if ($applied_coupon && $applied_coupon['usage_type'] === 'one_time') {
|
if ($applied_coupon && $applied_coupon['usage_type'] === 'one_time') {
|
||||||
unset($_SESSION['applied_coupon']);
|
unset($_SESSION['applied_coupon']);
|
||||||
|
capture_log('COUPON_CLEARED', ['type' => 'one_time']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all invoices we just marked paid
|
// Get all invoices we just marked paid
|
||||||
$getInvoices = "SELECT * FROM {$table_prefix}billing_invoices WHERE user_id=$user_id AND payment_txid='$esc_txid'";
|
$getInvoices = "SELECT * FROM {$table_prefix}billing_invoices WHERE user_id=$user_id AND payment_txid='$esc_txid'";
|
||||||
|
capture_log('GET_INVOICES_QUERY', ['sql' => $getInvoices]);
|
||||||
|
|
||||||
$invoicesResult = mysqli_query($db, $getInvoices);
|
$invoicesResult = mysqli_query($db, $getInvoices);
|
||||||
|
if (!$invoicesResult) {
|
||||||
|
capture_log('GET_INVOICES_FAILED', ['error' => mysqli_error($db)]);
|
||||||
|
} else {
|
||||||
|
$invoiceCount = mysqli_num_rows($invoicesResult);
|
||||||
|
capture_log('GET_INVOICES_SUCCESS', ['count' => $invoiceCount]);
|
||||||
|
}
|
||||||
|
|
||||||
// For each invoice, either create a new order or extend existing one (renewal)
|
// For each invoice, either create a new order or extend existing one (renewal)
|
||||||
|
$processedInvoices = 0;
|
||||||
while ($inv = mysqli_fetch_assoc($invoicesResult)) {
|
while ($inv = mysqli_fetch_assoc($invoicesResult)) {
|
||||||
$invoice_id = intval($inv['invoice_id']);
|
$invoice_id = intval($inv['invoice_id']);
|
||||||
$existing_order_id = intval($inv['order_id'] ?? 0);
|
$existing_order_id = intval($inv['order_id'] ?? 0);
|
||||||
|
|
@ -166,8 +301,17 @@ if ($captureStatus === 'COMPLETED' && $custom_id) {
|
||||||
$ftp_pw = mysqli_real_escape_string($db, $inv['ftp_password']);
|
$ftp_pw = mysqli_real_escape_string($db, $inv['ftp_password']);
|
||||||
$inv_coupon_id = intval($inv['coupon_id'] ?? 0);
|
$inv_coupon_id = intval($inv['coupon_id'] ?? 0);
|
||||||
|
|
||||||
|
capture_log('PROCESSING_INVOICE', [
|
||||||
|
'invoice_id' => $invoice_id,
|
||||||
|
'existing_order_id' => $existing_order_id,
|
||||||
|
'service_id' => $service_id,
|
||||||
|
'home_name' => $home_name,
|
||||||
|
'amount' => $amount
|
||||||
|
]);
|
||||||
|
|
||||||
// Check if this is a renewal (existing order_id > 0) or new order (order_id = 0)
|
// Check if this is a renewal (existing order_id > 0) or new order (order_id = 0)
|
||||||
if ($existing_order_id > 0) {
|
if ($existing_order_id > 0) {
|
||||||
|
capture_log('RENEWAL_DETECTED', ['order_id' => $existing_order_id, 'invoice_id' => $invoice_id]);
|
||||||
// RENEWAL: Extend the existing order's end_date
|
// RENEWAL: Extend the existing order's end_date
|
||||||
// Calculate months to add based on qty and duration
|
// Calculate months to add based on qty and duration
|
||||||
$months = 0;
|
$months = 0;
|
||||||
|
|
@ -199,13 +343,27 @@ if ($captureStatus === 'COMPLETED' && $custom_id) {
|
||||||
$updateOrder = "UPDATE {$table_prefix}billing_orders
|
$updateOrder = "UPDATE {$table_prefix}billing_orders
|
||||||
SET end_date='$new_end_date', status='paid', payment_txid='$esc_txid', paid_ts='$now'
|
SET end_date='$new_end_date', status='paid', payment_txid='$esc_txid', paid_ts='$now'
|
||||||
WHERE order_id=$existing_order_id";
|
WHERE order_id=$existing_order_id";
|
||||||
|
capture_log('UPDATE_ORDER_QUERY', ['sql' => $updateOrder]);
|
||||||
|
|
||||||
if (mysqli_query($db, $updateOrder)) {
|
if (mysqli_query($db, $updateOrder)) {
|
||||||
|
capture_log('ORDER_EXTENDED_SUCCESS', [
|
||||||
|
'order_id' => $existing_order_id,
|
||||||
|
'new_end_date' => $new_end_date,
|
||||||
|
'invoice_id' => $invoice_id
|
||||||
|
]);
|
||||||
error_log("capture_order.php: Extended order $existing_order_id end_date to $new_end_date for invoice $invoice_id");
|
error_log("capture_order.php: Extended order $existing_order_id end_date to $new_end_date for invoice $invoice_id");
|
||||||
|
$processedInvoices++;
|
||||||
} else {
|
} else {
|
||||||
error_log("capture_order.php: Failed to extend order $existing_order_id: " . mysqli_error($db));
|
$dbError = mysqli_error($db);
|
||||||
|
capture_log('ORDER_EXTENDED_FAILED', [
|
||||||
|
'order_id' => $existing_order_id,
|
||||||
|
'error' => $dbError
|
||||||
|
]);
|
||||||
|
error_log("capture_order.php: Failed to extend order $existing_order_id: " . $dbError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
capture_log('NEW_ORDER_DETECTED', ['invoice_id' => $invoice_id]);
|
||||||
// NEW ORDER: Create a new order record
|
// NEW ORDER: Create a new order record
|
||||||
// Calculate end_date based on qty * duration
|
// Calculate end_date based on qty * duration
|
||||||
$end_date = date('Y-m-d H:i:s', strtotime("+$qty $duration"));
|
$end_date = date('Y-m-d H:i:s', strtotime("+$qty $duration"));
|
||||||
|
|
@ -221,25 +379,58 @@ if ($captureStatus === 'COMPLETED' && $custom_id) {
|
||||||
'$esc_txid', '$now'" . ($inv_coupon_id ? ", $inv_coupon_id" : "") . "
|
'$esc_txid', '$now'" . ($inv_coupon_id ? ", $inv_coupon_id" : "") . "
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
capture_log('INSERT_ORDER_QUERY', ['sql' => substr($insertOrder, 0, 500)]);
|
||||||
|
|
||||||
if (mysqli_query($db, $insertOrder)) {
|
if (mysqli_query($db, $insertOrder)) {
|
||||||
$new_order_id = mysqli_insert_id($db);
|
$new_order_id = mysqli_insert_id($db);
|
||||||
|
|
||||||
|
capture_log('ORDER_CREATED_SUCCESS', [
|
||||||
|
'new_order_id' => $new_order_id,
|
||||||
|
'invoice_id' => $invoice_id
|
||||||
|
]);
|
||||||
|
|
||||||
// Link invoice to order
|
// Link invoice to order
|
||||||
$linkInvoice = "UPDATE {$table_prefix}billing_invoices SET order_id=$new_order_id WHERE invoice_id=$invoice_id";
|
$linkInvoice = "UPDATE {$table_prefix}billing_invoices SET order_id=$new_order_id WHERE invoice_id=$invoice_id";
|
||||||
mysqli_query($db, $linkInvoice);
|
capture_log('LINK_INVOICE_QUERY', ['sql' => $linkInvoice]);
|
||||||
|
|
||||||
|
if (mysqli_query($db, $linkInvoice)) {
|
||||||
|
capture_log('INVOICE_LINKED_SUCCESS', ['invoice_id' => $invoice_id, 'order_id' => $new_order_id]);
|
||||||
|
} else {
|
||||||
|
capture_log('INVOICE_LINK_FAILED', ['error' => mysqli_error($db)]);
|
||||||
|
}
|
||||||
|
|
||||||
error_log("capture_order.php: Created order $new_order_id for invoice $invoice_id");
|
error_log("capture_order.php: Created order $new_order_id for invoice $invoice_id");
|
||||||
|
$processedInvoices++;
|
||||||
} else {
|
} else {
|
||||||
error_log("capture_order.php: Failed to create order for invoice $invoice_id: " . mysqli_error($db));
|
$dbError = mysqli_error($db);
|
||||||
|
capture_log('ORDER_CREATE_FAILED', [
|
||||||
|
'invoice_id' => $invoice_id,
|
||||||
|
'error' => $dbError
|
||||||
|
]);
|
||||||
|
error_log("capture_order.php: Failed to create order for invoice $invoice_id: " . $dbError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
capture_log('PROCESSING_COMPLETE', [
|
||||||
|
'processed_invoices' => $processedInvoices,
|
||||||
|
'user_id' => $user_id
|
||||||
|
]);
|
||||||
|
|
||||||
mysqli_close($db);
|
mysqli_close($db);
|
||||||
|
} else {
|
||||||
|
capture_log('NO_USER_ID', ['session_data' => $_SESSION]);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
capture_log('SKIP_PROCESSING', [
|
||||||
|
'captureStatus' => $captureStatus,
|
||||||
|
'custom_id' => $custom_id,
|
||||||
|
'reason' => !$captureStatus ? 'no_status' : (!$custom_id ? 'no_custom_id' : 'status_not_completed')
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the full PayPal response (normalized JSON) for proper processing
|
// Return the full PayPal response (normalized JSON) for proper processing
|
||||||
|
capture_log('REQUEST_COMPLETE', ['returning_status' => $captureStatus]);
|
||||||
echo json_encode($capture);
|
echo json_encode($capture);
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,65 @@
|
||||||
<?php
|
<?php
|
||||||
|
/**
|
||||||
|
* PayPal Create Order API Endpoint
|
||||||
|
* Enhanced with comprehensive logging for debugging
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Ensure all errors are logged, not displayed (to prevent JSON corruption)
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
require_once(__DIR__ . '/../includes/config.inc.php');
|
require_once(__DIR__ . '/../includes/config.inc.php');
|
||||||
// create_order for PayPal — adapted to run from _website/api
|
// create_order for PayPal — adapted to run from _website/api
|
||||||
$sandbox = true; // flip to false for Live
|
$sandbox = true; // flip to false for Live
|
||||||
$client_id = 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c';
|
$client_id = 'AfvY_C2zA_hTHxHq7TIhtOeub4xBdySYrt_Hjj3d_WYQwjWI9NfOAVOTeResx2rgZ_nP5tOoxQSAHw8c';
|
||||||
$client_secret = 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0';
|
$client_secret = 'EJ216np9cAj9n7KSddez3fLVxGe-zi4oKKKl1YGqPp88XIikr4Qzbxh0XW2as-V6LgdX-upjtQAg9dC0';
|
||||||
|
|
||||||
|
// Setup comprehensive logging
|
||||||
|
$logDir = __DIR__ . '/../logs';
|
||||||
|
@mkdir($logDir, 0755, true);
|
||||||
|
$logFile = $logDir . '/paypal_create_order.log';
|
||||||
|
$requestId = uniqid('req_', true); // Unique request identifier for tracking
|
||||||
|
|
||||||
|
function create_order_log($label, $data) {
|
||||||
|
global $logFile, $requestId;
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$entry = "[$timestamp] [$requestId] $label\n";
|
||||||
|
if (is_array($data) || is_object($data)) {
|
||||||
|
$entry .= print_r($data, true);
|
||||||
|
} else {
|
||||||
|
$entry .= (string)$data;
|
||||||
|
}
|
||||||
|
$entry .= "\n" . str_repeat('-', 80) . "\n";
|
||||||
|
@file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
|
||||||
|
}
|
||||||
|
|
||||||
|
create_order_log('REQUEST_START', [
|
||||||
|
'method' => $_SERVER['REQUEST_METHOD'] ?? 'UNKNOWN',
|
||||||
|
'remote_addr' => $_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN',
|
||||||
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'UNKNOWN',
|
||||||
|
]);
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
$in = json_decode(file_get_contents('php://input'), true) ?: [];
|
// Read and parse input
|
||||||
|
$rawInput = file_get_contents('php://input');
|
||||||
|
create_order_log('RAW_INPUT', substr($rawInput, 0, 2000)); // Log first 2000 chars
|
||||||
|
|
||||||
|
$in = json_decode($rawInput, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
create_order_log('JSON_DECODE_ERROR', [
|
||||||
|
'error' => json_last_error_msg(),
|
||||||
|
'raw_input_length' => strlen($rawInput),
|
||||||
|
'raw_input_preview' => substr($rawInput, 0, 500)
|
||||||
|
]);
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => 'invalid_json', 'message' => json_last_error_msg(), 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$in) {
|
||||||
|
$in = [];
|
||||||
|
}
|
||||||
|
|
||||||
$amount_in = $in['amount'] ?? '0.00';
|
$amount_in = $in['amount'] ?? '0.00';
|
||||||
$currency = $in['currency'] ?? 'USD';
|
$currency = $in['currency'] ?? 'USD';
|
||||||
|
|
@ -19,6 +71,15 @@ $cancel_url = $in['cancel_url'] ?? null;
|
||||||
$items = (isset($in['items']) && is_array($in['items'])) ? $in['items'] : null;
|
$items = (isset($in['items']) && is_array($in['items'])) ? $in['items'] : null;
|
||||||
$line_invoices= (isset($in['line_invoices']) && is_array($in['line_invoices'])) ? $in['line_invoices'] : null;
|
$line_invoices= (isset($in['line_invoices']) && is_array($in['line_invoices'])) ? $in['line_invoices'] : null;
|
||||||
|
|
||||||
|
create_order_log('PARSED_INPUT', [
|
||||||
|
'amount' => $amount_in,
|
||||||
|
'currency' => $currency,
|
||||||
|
'invoice_id' => $invoice_id,
|
||||||
|
'custom_id' => $custom_id,
|
||||||
|
'items_count' => $items ? count($items) : 0,
|
||||||
|
'line_invoices_count' => $line_invoices ? count($line_invoices) : 0
|
||||||
|
]);
|
||||||
|
|
||||||
$amount_value = number_format((float)$amount_in, 2, '.', '');
|
$amount_value = number_format((float)$amount_in, 2, '.', '');
|
||||||
if ($items) {
|
if ($items) {
|
||||||
$sum = 0.00;
|
$sum = 0.00;
|
||||||
|
|
@ -28,9 +89,23 @@ if ($items) {
|
||||||
$sum += $qty * $val;
|
$sum += $qty * $val;
|
||||||
}
|
}
|
||||||
$amount_value = number_format($sum, 2, '.', '');
|
$amount_value = number_format($sum, 2, '.', '');
|
||||||
|
create_order_log('AMOUNT_CALCULATED', [
|
||||||
|
'original_amount' => $amount_in,
|
||||||
|
'calculated_from_items' => $amount_value,
|
||||||
|
'items_sum' => $sum
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
|
$api = $sandbox ? 'https://api-m.sandbox.paypal.com' : 'https://api-m.paypal.com';
|
||||||
|
create_order_log('PAYPAL_API_CONFIG', [
|
||||||
|
'sandbox_mode' => $sandbox,
|
||||||
|
'api_base' => $api,
|
||||||
|
'has_client_id' => !empty($client_id),
|
||||||
|
'has_client_secret' => !empty($client_secret)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Step 1: Get OAuth token
|
||||||
|
create_order_log('OAUTH_REQUEST_START', ['endpoint' => "$api/v1/oauth2/token"]);
|
||||||
|
|
||||||
$ch = curl_init("$api/v1/oauth2/token");
|
$ch = curl_init("$api/v1/oauth2/token");
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
|
|
@ -42,14 +117,51 @@ curl_setopt_array($ch, [
|
||||||
]);
|
]);
|
||||||
$tok = curl_exec($ch);
|
$tok = curl_exec($ch);
|
||||||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_errno = curl_errno($ch);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
if ($http !== 200) { http_response_code(500); echo json_encode(['error'=>'oauth_fail']); exit; }
|
|
||||||
|
create_order_log('OAUTH_RESPONSE', [
|
||||||
|
'http_code' => $http,
|
||||||
|
'curl_errno' => $curl_errno,
|
||||||
|
'curl_error' => $curl_error,
|
||||||
|
'response_length' => strlen($tok),
|
||||||
|
'response_preview' => substr($tok, 0, 200)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($curl_errno !== 0) {
|
||||||
|
create_order_log('OAUTH_CURL_ERROR', ['errno' => $curl_errno, 'error' => $curl_error]);
|
||||||
|
http_response_code(502);
|
||||||
|
echo json_encode(['error' => 'oauth_curl_fail', 'details' => $curl_error, 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($http !== 200) {
|
||||||
|
create_order_log('OAUTH_HTTP_ERROR', ['http_code' => $http, 'response' => $tok]);
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'oauth_fail', 'http_code' => $http, 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$access = json_decode($tok, true)['access_token'] ?? null;
|
$access = json_decode($tok, true)['access_token'] ?? null;
|
||||||
if (!$access) { http_response_code(500); echo json_encode(['error'=>'oauth_no_token']); exit; }
|
if (!$access) {
|
||||||
|
create_order_log('OAUTH_NO_TOKEN', ['response' => $tok]);
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => 'oauth_no_token', 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_order_log('OAUTH_SUCCESS', ['token_length' => strlen($access)]);
|
||||||
|
|
||||||
// Update site base URL to exclude 'modules/billing'
|
// Update site base URL to exclude 'modules/billing'
|
||||||
$siteBaseUrl = 'http://gameservers.world';
|
$siteBaseUrl = 'http://gameservers.world';
|
||||||
|
|
||||||
|
create_order_log('URL_PROCESSING_BEFORE', [
|
||||||
|
'return_url' => $return_url,
|
||||||
|
'cancel_url' => $cancel_url,
|
||||||
|
'site_base' => $siteBaseUrl
|
||||||
|
]);
|
||||||
|
|
||||||
// Ensure return_url and cancel_url are absolute URLs (relative to site root)
|
// Ensure return_url and cancel_url are absolute URLs (relative to site root)
|
||||||
if (strpos($return_url, 'http') !== 0) {
|
if (strpos($return_url, 'http') !== 0) {
|
||||||
$return_url = $siteBaseUrl . '/' . ltrim($return_url, '/');
|
$return_url = $siteBaseUrl . '/' . ltrim($return_url, '/');
|
||||||
|
|
@ -58,6 +170,11 @@ if (strpos($cancel_url, 'http') !== 0) {
|
||||||
$cancel_url = $siteBaseUrl . '/' . ltrim($cancel_url, '/');
|
$cancel_url = $siteBaseUrl . '/' . ltrim($cancel_url, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create_order_log('URL_PROCESSING_AFTER', [
|
||||||
|
'return_url' => $return_url,
|
||||||
|
'cancel_url' => $cancel_url
|
||||||
|
]);
|
||||||
|
|
||||||
$purchaseUnit = [
|
$purchaseUnit = [
|
||||||
'amount' => [ 'currency_code' => $currency, 'value' => $amount_value ],
|
'amount' => [ 'currency_code' => $currency, 'value' => $amount_value ],
|
||||||
'description' => $description,
|
'description' => $description,
|
||||||
|
|
@ -75,17 +192,10 @@ $body = [
|
||||||
'application_context' => [ 'return_url'=>$return_url, 'cancel_url'=>$cancel_url, 'user_action'=>'PAY_NOW' ]
|
'application_context' => [ 'return_url'=>$return_url, 'cancel_url'=>$cancel_url, 'user_action'=>'PAY_NOW' ]
|
||||||
];
|
];
|
||||||
|
|
||||||
// Log the payload for debugging
|
create_order_log('PAYPAL_ORDER_PAYLOAD', $body);
|
||||||
$logDir = __DIR__ . '/../data';
|
|
||||||
@mkdir($logDir, 0775, true);
|
|
||||||
$logFile = $logDir . '/create_order_payload.log';
|
|
||||||
$logEntry = date('Y-m-d H:i:s') . "\n" . json_encode($body, JSON_PRETTY_PRINT) . "\n\n";
|
|
||||||
@file_put_contents($logFile, $logEntry, FILE_APPEND);
|
|
||||||
|
|
||||||
// Log corrected URLs for debugging
|
// Step 2: Create PayPal order
|
||||||
$logFile = $logDir . '/corrected_urls.log';
|
create_order_log('CREATE_ORDER_REQUEST_START', ['endpoint' => "$api/v2/checkout/orders"]);
|
||||||
$logEntry = date('Y-m-d H:i:s') . "\nReturn URL: $return_url\nCancel URL: $cancel_url\n\n";
|
|
||||||
@file_put_contents($logFile, $logEntry, FILE_APPEND);
|
|
||||||
|
|
||||||
$ch = curl_init("$api/v2/checkout/orders");
|
$ch = curl_init("$api/v2/checkout/orders");
|
||||||
curl_setopt_array($ch, [
|
curl_setopt_array($ch, [
|
||||||
|
|
@ -96,20 +206,61 @@ curl_setopt_array($ch, [
|
||||||
]);
|
]);
|
||||||
$res = curl_exec($ch);
|
$res = curl_exec($ch);
|
||||||
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$curl_errno = curl_errno($ch);
|
||||||
|
$curl_error = curl_error($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
if ($http !== 201) {
|
create_order_log('CREATE_ORDER_RESPONSE', [
|
||||||
// Log error for debugging
|
'http_code' => $http,
|
||||||
$logDir = __DIR__ . '/../data';
|
'curl_errno' => $curl_errno,
|
||||||
@mkdir($logDir, 0775, true);
|
'curl_error' => $curl_error,
|
||||||
$logFile = $logDir . '/create_order_errors.log';
|
'response_length' => strlen($res),
|
||||||
$logEntry = date('Y-m-d H:i:s') . " HTTP $http: " . substr($res, 0, 500) . "\n";
|
'response' => substr($res, 0, 1000) // First 1000 chars of response
|
||||||
@file_put_contents($logFile, $logEntry, FILE_APPEND);
|
]);
|
||||||
|
|
||||||
http_response_code($http);
|
if ($curl_errno !== 0) {
|
||||||
echo $res;
|
create_order_log('CREATE_ORDER_CURL_ERROR', ['errno' => $curl_errno, 'error' => $curl_error]);
|
||||||
exit;
|
http_response_code(502);
|
||||||
|
echo json_encode(['error' => 'create_order_curl_fail', 'details' => $curl_error, 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($http !== 201) {
|
||||||
|
create_order_log('CREATE_ORDER_HTTP_ERROR', [
|
||||||
|
'http_code' => $http,
|
||||||
|
'response' => $res,
|
||||||
|
'payload_sent' => $body
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Try to parse PayPal error response
|
||||||
|
$errorData = json_decode($res, true);
|
||||||
|
http_response_code($http);
|
||||||
|
echo json_encode([
|
||||||
|
'error' => 'create_order_failed',
|
||||||
|
'http_code' => $http,
|
||||||
|
'paypal_error' => $errorData,
|
||||||
|
'request_id' => $requestId
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success - parse and validate response
|
||||||
|
$orderData = json_decode($res, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
create_order_log('CREATE_ORDER_INVALID_JSON', [
|
||||||
|
'json_error' => json_last_error_msg(),
|
||||||
|
'response' => $res
|
||||||
|
]);
|
||||||
|
http_response_code(502);
|
||||||
|
echo json_encode(['error' => 'invalid_paypal_response', 'request_id' => $requestId]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
create_order_log('CREATE_ORDER_SUCCESS', [
|
||||||
|
'order_id' => $orderData['id'] ?? 'UNKNOWN',
|
||||||
|
'status' => $orderData['status'] ?? 'UNKNOWN'
|
||||||
|
]);
|
||||||
|
|
||||||
echo $res;
|
echo $res;
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
|
||||||
44
modules/billing/api/log_error.php
Normal file
44
modules/billing/api/log_error.php
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Client-side error logging endpoint
|
||||||
|
* Logs JavaScript errors from the cart page for debugging
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Ensure all errors are logged, not displayed
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
// Setup logging
|
||||||
|
$logDir = __DIR__ . '/../logs';
|
||||||
|
@mkdir($logDir, 0755, true);
|
||||||
|
$logFile = $logDir . '/client_errors.log';
|
||||||
|
|
||||||
|
function log_client_error($data) {
|
||||||
|
global $logFile;
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$entry = "[$timestamp] CLIENT ERROR\n";
|
||||||
|
$entry .= "IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN') . "\n";
|
||||||
|
$entry .= "User Agent: " . ($_SERVER['HTTP_USER_AGENT'] ?? 'UNKNOWN') . "\n";
|
||||||
|
if (is_array($data) || is_object($data)) {
|
||||||
|
$entry .= print_r($data, true);
|
||||||
|
} else {
|
||||||
|
$entry .= (string)$data;
|
||||||
|
}
|
||||||
|
$entry .= "\n" . str_repeat('-', 80) . "\n";
|
||||||
|
@file_put_contents($logFile, $entry, FILE_APPEND | LOCK_EX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and parse input
|
||||||
|
$rawInput = file_get_contents('php://input');
|
||||||
|
$data = json_decode($rawInput, true);
|
||||||
|
|
||||||
|
if ($data) {
|
||||||
|
log_client_error($data);
|
||||||
|
echo json_encode(['status' => 'logged']);
|
||||||
|
} else {
|
||||||
|
log_client_error(['raw_input' => $rawInput, 'error' => 'Invalid JSON']);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid JSON']);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
@ -597,12 +597,37 @@ $apiBase = 'api';
|
||||||
return_url, cancel_url
|
return_url, cancel_url
|
||||||
});
|
});
|
||||||
|
|
||||||
function setStatus(msg){ if(statusEl) statusEl.textContent = msg; }
|
function setStatus(msg, isError = false){
|
||||||
|
if(statusEl) {
|
||||||
|
statusEl.textContent = msg;
|
||||||
|
statusEl.style.color = isError ? '#dc3545' : '#000';
|
||||||
|
statusEl.style.fontWeight = isError ? 'bold' : 'normal';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function logError(context, error) {
|
||||||
|
const errorData = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
context: context,
|
||||||
|
error: error,
|
||||||
|
invoice_id: invoice_id,
|
||||||
|
amount: amount
|
||||||
|
};
|
||||||
|
console.error('PayPal Error:', errorData);
|
||||||
|
// Try to send error to server for logging
|
||||||
|
fetch("<?= $apiBase ?>/log_error.php", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {"Content-Type":"application/json"},
|
||||||
|
body: JSON.stringify(errorData)
|
||||||
|
}).catch(e => console.error('Failed to log error to server:', e));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
paypal.Buttons({
|
paypal.Buttons({
|
||||||
createOrder: function() {
|
createOrder: function() {
|
||||||
setStatus('Creating order…');
|
setStatus('Creating order…');
|
||||||
|
console.log('createOrder: Starting request to create_order.php');
|
||||||
|
|
||||||
return fetch("<?= $apiBase ?>/create_order.php", {
|
return fetch("<?= $apiBase ?>/create_order.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {"Content-Type":"application/json"},
|
headers: {"Content-Type":"application/json"},
|
||||||
|
|
@ -615,51 +640,114 @@ $apiBase = 'api';
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
console.log('createOrder: Response received', { status: res.status, ok: res.ok });
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
return res.text().then(errText => {
|
return res.text().then(errText => {
|
||||||
throw new Error('API error ' + res.status + ': ' + errText.substring(0, 200));
|
console.error('createOrder: Error response text:', errText);
|
||||||
|
logError('create_order_http_error', { status: res.status, response: errText });
|
||||||
|
|
||||||
|
// Try to parse as JSON for better error display
|
||||||
|
let errorMsg = 'API error ' + res.status;
|
||||||
|
try {
|
||||||
|
const errJson = JSON.parse(errText);
|
||||||
|
if (errJson.error) errorMsg += ': ' + errJson.error;
|
||||||
|
if (errJson.request_id) errorMsg += ' (Ref: ' + errJson.request_id + ')';
|
||||||
|
} catch (e) {
|
||||||
|
errorMsg += ': ' + errText.substring(0, 100);
|
||||||
|
}
|
||||||
|
throw new Error(errorMsg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (!data.id) {
|
console.log('createOrder: Parsed response', data);
|
||||||
throw new Error(JSON.stringify(data).substring(0, 200) || 'No order id');
|
if (!data.id) {
|
||||||
|
const errMsg = 'No order ID in response';
|
||||||
|
console.error('createOrder:', errMsg, data);
|
||||||
|
logError('create_order_no_id', { response: data });
|
||||||
|
throw new Error(errMsg + (data.request_id ? ' (Ref: ' + data.request_id + ')' : ''));
|
||||||
}
|
}
|
||||||
setStatus('Order created.');
|
setStatus('Order created.');
|
||||||
|
console.log('createOrder: Success, order ID:', data.id);
|
||||||
return data.id;
|
return data.id;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
setStatus('PayPal error: ' + err.message);
|
console.error('createOrder: Caught error', err);
|
||||||
|
const errorMsg = 'Failed to create order: ' + err.message;
|
||||||
|
setStatus(errorMsg, true);
|
||||||
|
logError('create_order_exception', { message: err.message, stack: err.stack });
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onApprove: function(data) {
|
onApprove: function(data) {
|
||||||
setStatus('Capturing payment…');
|
setStatus('Capturing payment…');
|
||||||
|
console.log('onApprove: Starting capture for order', data.orderID);
|
||||||
|
|
||||||
return fetch("<?= $apiBase ?>/capture_order.php", {
|
return fetch("<?= $apiBase ?>/capture_order.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {"Content-Type":"application/json"},
|
headers: {"Content-Type":"application/json"},
|
||||||
body: JSON.stringify({ order_id: data.orderID })
|
body: JSON.stringify({ order_id: data.orderID })
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => {
|
||||||
|
console.log('onApprove: Capture response received', { status: res.status, ok: res.ok });
|
||||||
|
if (!res.ok) {
|
||||||
|
return res.text().then(errText => {
|
||||||
|
console.error('onApprove: Error response text:', errText);
|
||||||
|
logError('capture_order_http_error', { status: res.status, response: errText });
|
||||||
|
|
||||||
|
let errorMsg = 'Capture failed (HTTP ' + res.status + ')';
|
||||||
|
try {
|
||||||
|
const errJson = JSON.parse(errText);
|
||||||
|
if (errJson.error) errorMsg += ': ' + errJson.error;
|
||||||
|
if (errJson.request_id) errorMsg += ' (Ref: ' + errJson.request_id + ')';
|
||||||
|
} catch (e) {
|
||||||
|
errorMsg += ': ' + errText.substring(0, 100);
|
||||||
|
}
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
.then(capture => {
|
.then(capture => {
|
||||||
|
console.log('onApprove: Parsed capture response', capture);
|
||||||
if (capture.status === 'COMPLETED') {
|
if (capture.status === 'COMPLETED') {
|
||||||
|
console.log('onApprove: Payment completed, redirecting to success page');
|
||||||
|
setStatus('Payment completed! Redirecting...');
|
||||||
// go to your return page; webhook will fill data/<invoice_id>.json
|
// go to your return page; webhook will fill data/<invoice_id>.json
|
||||||
window.location.href = return_url;
|
window.location.href = return_url;
|
||||||
|
} else if (capture.error) {
|
||||||
|
const errorMsg = 'Capture error: ' + capture.error + (capture.request_id ? ' (Ref: ' + capture.request_id + ')' : '');
|
||||||
|
console.error('onApprove:', errorMsg);
|
||||||
|
logError('capture_order_error_response', capture);
|
||||||
|
setStatus(errorMsg, true);
|
||||||
} else {
|
} else {
|
||||||
setStatus('Capture status: ' + capture.status);
|
const statusMsg = 'Unexpected capture status: ' + (capture.status || 'UNKNOWN');
|
||||||
|
console.warn('onApprove:', statusMsg, capture);
|
||||||
|
logError('capture_order_unexpected_status', capture);
|
||||||
|
setStatus(statusMsg, true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => setStatus('Error: ' + err.message));
|
.catch(err => {
|
||||||
|
console.error('onApprove: Caught error', err);
|
||||||
|
const errorMsg = 'Payment capture failed: ' + err.message;
|
||||||
|
setStatus(errorMsg, true);
|
||||||
|
logError('capture_order_exception', { message: err.message, stack: err.stack });
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onCancel: function() {
|
onCancel: function() {
|
||||||
|
console.log('onCancel: User cancelled payment');
|
||||||
|
logError('payment_cancelled', { invoice_id: invoice_id });
|
||||||
window.location.href = cancel_url;
|
window.location.href = cancel_url;
|
||||||
},
|
},
|
||||||
|
|
||||||
onError: function(err){
|
onError: function(err){
|
||||||
setStatus('PayPal error: ' + (err && err.message ? err.message : err));
|
console.error('onError: PayPal SDK error', err);
|
||||||
|
const errorMsg = 'PayPal error: ' + (err && err.message ? err.message : JSON.stringify(err));
|
||||||
|
setStatus(errorMsg, true);
|
||||||
|
logError('paypal_sdk_error', { error: err });
|
||||||
}
|
}
|
||||||
}).render('#paypal-button-container');
|
}).render('#paypal-button-container');
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue