diff --git a/README.md b/README.md index c0c67e3..d823b74 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,10 @@ A complete transaction reporting service for Global Payments, providing interact ## Available Implementations -- [.NET Core](./dotnet/) - ASP.NET Core web application -- [Go](./go/) - Go HTTP server application -- [Java](./java/) - Jakarta EE servlet-based web application -- [Node.js](./nodejs/) - Express.js web application -- [PHP](./php/) - PHP web application -- [Python](./python/) - Flask web application +- [.NET Core](./dotnet/) - ([Preview](https://githubbox.com/globalpayments-samples/reporting-service/tree/main/dotnet)) - ASP.NET Core web application +- [Java](./java/) - ([Preview](https://githubbox.com/globalpayments-samples/reporting-service/tree/main/java)) - Jakarta EE servlet-based web application +- [Node.js](./nodejs/) - ([Preview](https://githubbox.com/globalpayments-samples/reporting-service/tree/main/nodejs)) - Express.js web application +- [PHP](./php/) - ([Preview](https://githubbox.com/globalpayments-samples/reporting-service/tree/main/php)) - PHP web application ## Features @@ -47,7 +45,7 @@ Each implementation includes comprehensive reporting features: ## Quick Start -1. **Choose your language** - Navigate to any implementation directory (nodejs, python, php, java, dotnet, go) +1. **Choose your language** - Navigate to any implementation directory (nodejs, php, java, dotnet) 2. **Set up credentials** - Copy `.env.sample` to `.env` and add your Global Payments API keys 3. **Install dependencies** - Run the installation command for your language (see individual READMEs) 4. **Start the server** - Execute `./run.sh` or use the language-specific run command @@ -71,7 +69,7 @@ This reporting service is ideal for: - Global Payments account with API credentials - Development environment for your chosen language -- Package manager (npm, pip, composer, maven, dotnet, go mod) +- Package manager (npm, composer, maven, dotnet) ## Docker Support @@ -87,10 +85,8 @@ docker-compose up # Services will be available at: # - Node.js: http://localhost:8001 -# - Python: http://localhost:8002 # - PHP: http://localhost:8003 # - Java: http://localhost:8004 -# - Go: http://localhost:8005 # - .NET: http://localhost:8006 ``` diff --git a/docker-compose.yml b/docker-compose.yml index 6017d28..0e05b4c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,25 +18,6 @@ services: retries: 3 start_period: 30s - python: - build: - context: ./python - dockerfile: Dockerfile - ports: - - "8002:8000" - environment: - - PORT=8000 - - PUBLIC_API_KEY=${PUBLIC_API_KEY} - - SECRET_API_KEY=${SECRET_API_KEY} - networks: - - payments - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8000/config"] - interval: 10s - timeout: 5s - retries: 3 - start_period: 30s - php: build: context: ./php @@ -73,25 +54,6 @@ services: retries: 3 start_period: 60s - go: - build: - context: ./go - dockerfile: Dockerfile - ports: - - "8005:8000" - environment: - - PORT=8000 - - PUBLIC_API_KEY=${PUBLIC_API_KEY} - - SECRET_API_KEY=${SECRET_API_KEY} - networks: - - payments - healthcheck: - test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8000/config"] - interval: 10s - timeout: 5s - retries: 3 - start_period: 30s - dotnet: build: context: ./dotnet diff --git a/docker-run.sh b/docker-run.sh index 57e13e8..83fe04f 100755 --- a/docker-run.sh +++ b/docker-run.sh @@ -52,7 +52,7 @@ build_images() { start_services() { echo -e "${BLUE}🚀 Starting payment services...${NC}" - docker-compose up -d nodejs python php java go dotnet + docker-compose up -d nodejs php java dotnet echo -e "${YELLOW}⏳ Waiting for services to be healthy...${NC}" docker-compose ps @@ -61,10 +61,8 @@ start_services() { echo "" echo "Services available at:" echo " Node.js: http://localhost:8001" - echo " Python: http://localhost:8002" echo " PHP: http://localhost:8003" echo " Java: http://localhost:8004" - echo " Go: http://localhost:8005" echo " .NET: http://localhost:8006" } @@ -82,7 +80,7 @@ run_tests() { # Start services if not running echo -e "${YELLOW}📋 Ensuring services are running...${NC}" - docker-compose up -d nodejs python php java go dotnet + docker-compose up -d nodejs php java dotnet # Wait for services to be healthy echo -e "${YELLOW}⏳ Waiting for services to be ready...${NC}" @@ -99,7 +97,7 @@ run_tests() { run_single_test() { local impl=$1 if [ -z "$impl" ]; then - echo -e "${RED}❌ Please specify implementation: nodejs, python, php, java, go, or dotnet${NC}" + echo -e "${RED}❌ Please specify implementation: nodejs, php, java, or dotnet${NC}" exit 1 fi diff --git a/dotnet/.codesandbox/tasks.json b/dotnet/.codesandbox/tasks.json new file mode 100644 index 0000000..d4e9eb0 --- /dev/null +++ b/dotnet/.codesandbox/tasks.json @@ -0,0 +1,29 @@ +{ + // These tasks will run in order when initializing your CodeSandbox project. + "setupTasks": [ + { + "name": "Install Dependencies", + "command": "dotnet restore" + }, + { + "name": "Create .env", + "command": "cp .env.sample .env" + } + ], + + // These tasks can be run from CodeSandbox. Running one will open a log in the app. + "tasks": { + "start": { + "name": "start", + "command": "dotnet watch -- run", + "runAtStart": true, + "preview": { + "port": 8000 + } + }, + "install": { + "name": "install dependencies", + "command": "dotnet restore" + } + } +} diff --git a/dotnet/.codesandbox/template.json b/dotnet/.codesandbox/template.json new file mode 100644 index 0000000..d70df61 --- /dev/null +++ b/dotnet/.codesandbox/template.json @@ -0,0 +1,7 @@ +{ + "title": "Global Payments C#", + "description": "The easiest way to get started from Docker in CodeSandbox", + "iconUrl": "https://globalpayments-samples.github.io/assets/img/g-symbol-dev-brand.png", + "tags": ["global-payments", "sample", "csharp"], + "published": false +} \ No newline at end of file diff --git a/dotnet/.devcontainer/devcontainer.json b/dotnet/.devcontainer/devcontainer.json new file mode 100644 index 0000000..fffb453 --- /dev/null +++ b/dotnet/.devcontainer/devcontainer.json @@ -0,0 +1,4 @@ +{ + "name": "Devcontainer", + "image": "mcr.microsoft.com/devcontainers/dotnet" +} diff --git a/dotnet/.env.sample b/dotnet/.env.sample index 9b3b549..e8057f3 100644 --- a/dotnet/.env.sample +++ b/dotnet/.env.sample @@ -1,4 +1,6 @@ # API Keys for Global Payments # Replace these sample values with your actual API keys -PUBLIC_API_KEY=pkapi_your_public_key_here -SECRET_API_KEY=skapi_your_secret_key_here + +# API Platform (GP API) +GP_API_APP_ID=4gPqnGBkppGYvoE5UX9EWQlotTxGUDbs #gitleaks:allow +GP_API_APP_KEY=FQyJA5VuEQfcji2M #gitleaks:allow \ No newline at end of file diff --git a/go/.env.sample b/go/.env.sample deleted file mode 100644 index 9b3b549..0000000 --- a/go/.env.sample +++ /dev/null @@ -1,4 +0,0 @@ -# API Keys for Global Payments -# Replace these sample values with your actual API keys -PUBLIC_API_KEY=pkapi_your_public_key_here -SECRET_API_KEY=skapi_your_secret_key_here diff --git a/go/.gitignore b/go/.gitignore deleted file mode 100644 index c79514e..0000000 --- a/go/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Environment files -.env - -# IDE specific files -.idea -.vscode -*.swp -*.swo - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes diff --git a/go/Dockerfile b/go/Dockerfile deleted file mode 100644 index e8399cc..0000000 --- a/go/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM golang:1.23-alpine AS builder - -WORKDIR /app - -# Copy go mod files and download dependencies -COPY go.mod go.sum ./ -RUN go mod download - -# Copy source code and build -COPY . . -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . - -# Runtime stage -FROM alpine:latest - -WORKDIR /app - -# Install ca-certificates for HTTPS calls -RUN apk --no-cache add ca-certificates - -# Copy the binary from builder stage -COPY --from=builder /app/main . -COPY --from=builder /app/static ./static - -# Create non-root user -RUN addgroup -g 1001 -S appuser && \ - adduser -S appuser -u 1001 -USER appuser - -EXPOSE 8000 - -CMD ["./main"] \ No newline at end of file diff --git a/go/README.md b/go/README.md deleted file mode 100644 index d713b66..0000000 --- a/go/README.md +++ /dev/null @@ -1,196 +0,0 @@ -# Global Payments Go Integration - -A comprehensive Go implementation for Global Payments payment processing and transaction reporting using the Global Payments Go SDK. - -## Requirements - -- Go 1.21 or later -- Global Payments account and API credentials - -## Quick Start - -### 1. Configure Credentials - -Copy `.env.sample` to `.env` and add your credentials: - -```properties -# Payment Processing (Portico API) -PUBLIC_API_KEY=pkapi_cert_your_public_key -SECRET_API_KEY=skapi_cert_your_secret_key - -# Transaction Reporting (GP-API) -GP_API_APP_ID=your_app_id_here -GP_API_APP_KEY=your_app_key_here -GP_API_ENVIRONMENT=TEST # or PRODUCTION -``` - -### 2. Install Dependencies - -```bash -go mod download -``` - -### 3. Start the Server - -```bash -go run main.go -``` - -The application will start on `http://localhost:8888` - -### 4. Verify Setup - -Open your browser to: -- **Web Interface**: http://localhost:8888/ -- **Get Public Key**: http://localhost:8888/config -- **Reporting Config**: http://localhost:8888/reports/config - -## Features - -### ✅ Payment Processing -- Process card payments with hosted fields tokenization -- Support for billing address verification (AVS) -- Real-time payment processing with Global Payments Portico API - -### ✅ Transaction Reporting -- Search and filter transactions -- Export data (JSON, CSV, XML) -- Generate settlement, dispute, and deposit reports -- Transaction analytics and summaries - -## API Endpoints - -### Payment Processing - -**Get Configuration** -```bash -curl http://localhost:8888/config -``` - -**Process Payment** -```bash -curl -X POST http://localhost:8888/process-payment \ - -H "Content-Type: application/json" \ - -d '{"payment_token":"YOUR_TOKEN","billing_zip":"12345","amount":"25.00"}' -``` - -### Transaction Reporting - -See [REPORTING_README.md](REPORTING_README.md) for complete reporting API documentation. - -**Quick Example - Search Transactions** -```bash -curl "http://localhost:8888/reports/search?start_date=2025-09-01&page_size=10" -``` - -## Project Structure - -``` -go/ -├── main.go # Main application -├── reporting_service.go # Reporting service logic -├── reports.go # Reporting API endpoints -├── main_reporting.go # Reporting server setup -├── static/ -│ └── index.html # Payment form UI -├── go.mod # Go dependencies -├── .env # Your credentials (not in git) -├── .env.sample # Credential template -├── README.md # This file -└── REPORTING_README.md # Reporting API documentation -``` - -## Usage - -### Processing a Payment - -1. Open http://localhost:8888/ in your browser -2. Use test card: `4263970000005262` -3. Enter any future expiration date -4. Enter CVV: `123` -5. Enter billing ZIP code -6. Click "Pay $10.00" - -### Searching Transactions - -After processing payments, search for them: - -```bash -curl "http://localhost:8888/reports/search?page_size=20" -``` - -### Exporting Data - -Export transactions to CSV: - -```bash -curl "http://localhost:8888/reports/export?format=csv&start_date=2025-09-01" -o transactions.csv -``` - -## Testing - -### Test Card Numbers - -| Card Type | Number | CVV | Expiry | -|-----------|--------|-----|--------| -| Visa | 4263970000005262 | 123 | Any future date | -| Mastercard | 5425230000004415 | 123 | Any future date | -| Amex | 374101000000608 | 1234 | Any future date | - -### Testing Workflow - -1. **Process a payment** using the web interface -2. **Verify transaction** appears in reporting: - ```bash - curl "http://localhost:8888/reports/search" - ``` -3. **Get transaction details**: - ```bash - curl "http://localhost:8888/reports/detail?transaction_id=TRN_xxx" - ``` - -## Environment Variables - -| Variable | Required | Description | -|----------|----------|-------------| -| `PUBLIC_API_KEY` | Yes | Portico public API key for payment processing | -| `SECRET_API_KEY` | Yes | Portico secret API key for payment processing | -| `GP_API_APP_ID` | Yes | GP-API App ID for reporting | -| `GP_API_APP_KEY` | Yes | GP-API App Key for reporting | -| `GP_API_ENVIRONMENT` | No | TEST or PRODUCTION (default: TEST) | - -## Troubleshooting - -**Port already in use?** -```bash -# Modify the port in main.go or main_reporting.go -``` - -**Missing credentials error?** -- Ensure `.env` file exists in the `go/` directory -- Verify all required credentials are set -- Check for trailing spaces in credential values - -**Payment processing fails?** -- Verify `PUBLIC_API_KEY` and `SECRET_API_KEY` are correct -- Ensure using test credentials for test environment - -**Reporting not working?** -- Verify `GP_API_APP_ID` and `GP_API_APP_KEY` are correct -- These are different from payment processing credentials - -**Module errors?** -- Run `go mod tidy` to clean up dependencies -- Ensure Go 1.21+ is installed - -## Documentation - -- **Payment Processing**: See API Endpoints section above -- **Transaction Reporting**: See [REPORTING_README.md](REPORTING_README.md) -- **Global Payments Docs**: https://developer.globalpay.com - -## Support - -For issues or questions: -- Global Payments Developer Portal: https://developer.globalpay.com -- SDK Documentation: https://github.com/globalpayments/go-sdk diff --git a/go/REPORTING_README.md b/go/REPORTING_README.md deleted file mode 100644 index de5246f..0000000 --- a/go/REPORTING_README.md +++ /dev/null @@ -1,276 +0,0 @@ -# Global Payments Reporting API - Go - -A comprehensive reporting service for Global Payments transactions with search, filtering, analytics, and data export capabilities. - -## Quick Start - -### 1. Configure Environment - -Create a `.env` file in the go directory: - -```properties -GP_API_APP_ID=your_app_id_here -GP_API_APP_KEY=your_app_key_here -GP_API_ENVIRONMENT=TEST # or PRODUCTION -``` - -### 2. Install Dependencies & Start Server - -```bash -cd go -go mod tidy -go run reporting_service.go reports.go main_reporting.go -``` - -The API will be available at `http://localhost:8080/reports` - -### 3. Verify Configuration - -```bash -curl "http://localhost:8080/reports/config" -``` - -## API Endpoints - -All endpoints use `/reports/{action}` or `/reports?action={action_name}` and support both GET and POST requests. - -### Search Transactions - -Search and filter transactions with pagination. - -```bash -curl "http://localhost:8080/reports/search?start_date=2025-09-01&end_date=2025-09-30&page_size=20" -``` - -**Parameters**: -- `page` - Page number (default: 1) -- `page_size` - Results per page (default: 10, max: 100) -- `start_date` - Start date (YYYY-MM-DD) -- `end_date` - End date (YYYY-MM-DD) -- `transaction_id` - Specific transaction ID -- `payment_type` - Payment type filter -- `status` - Transaction status -- `amount_min` - Minimum amount -- `amount_max` - Maximum amount -- `card_last_four` - Last 4 digits of card - -### Get Transaction Details - -Retrieve detailed information for a specific transaction. - -```bash -curl "http://localhost:8080/reports/detail?transaction_id=TRN_123456" -``` - -**Required**: `transaction_id` - -### Settlement Report - -Get settlement information for a date range. - -```bash -curl "http://localhost:8080/reports/settlement?start_date=2025-09-01&end_date=2025-09-30" -``` - -**Parameters**: -- `page`, `page_size` - Pagination -- `start_date`, `end_date` - Date range - -### Export Transactions - -Export transaction data in JSON, CSV, or XML format. - -**CSV Export**: -```bash -curl "http://localhost:8080/reports/export?format=csv&start_date=2025-09-01&end_date=2025-09-30" -o transactions.csv -``` - -**XML Export**: -```bash -curl "http://localhost:8080/reports/export?format=xml&start_date=2025-09-01&end_date=2025-09-30" -o transactions.xml -``` - -**JSON Export**: -```bash -curl "http://localhost:8080/reports/export?format=json&start_date=2025-09-01&end_date=2025-09-30" -``` - -**Parameters**: -- `format` - Export format: `json`, `csv`, or `xml` (required) -- Plus all search filters - -### Summary Statistics - -Get aggregate statistics for transactions. - -```bash -curl "http://localhost:8080/reports/summary?start_date=2025-09-01&end_date=2025-09-30" -``` - -**Returns**: -- Total transaction count -- Total amount -- Average amount -- Status breakdown -- Payment type breakdown - -### Dispute Report - -Get dispute information with filtering. - -```bash -curl "http://localhost:8080/reports/disputes?start_date=2025-09-01&end_date=2025-09-30" -``` - -**Parameters**: -- `page`, `page_size` - Pagination -- `start_date`, `end_date` - Date range -- `stage` - Dispute stage -- `status` - Dispute status - -**Get Dispute Details**: -```bash -curl "http://localhost:8080/reports/dispute/DIS_123456" -``` - -### Deposit Report - -Get deposit information and details. - -```bash -curl "http://localhost:8080/reports/deposits?start_date=2025-09-01&end_date=2025-09-30" -``` - -**Parameters**: -- `page`, `page_size` - Pagination -- `start_date`, `end_date` - Date range -- `deposit_id` - Filter by deposit ID -- `status` - Deposit status - -**Get Deposit Details**: -```bash -curl "http://localhost:8080/reports/deposit/DEP_123456" -``` - -### Declined Transactions Report - -Get declined transactions with analysis. - -```bash -curl "http://localhost:8080/reports/declines?start_date=2025-09-01&end_date=2025-09-30" -``` - -**Returns** transaction data plus decline analysis: -- Decline reasons breakdown -- Card type breakdown -- Hourly decline patterns - -### Comprehensive Date Range Report - -Get a combined report across all transaction types. - -```bash -curl "http://localhost:8080/reports/date-range?start_date=2025-09-01&end_date=2025-09-30&transaction_limit=100" -``` - -**Parameters**: -- `start_date`, `end_date` - Date range -- `transaction_limit` - Max transactions (default: 100, max: 1000) -- `settlement_limit` - Max settlements (default: 50, max: 500) -- `dispute_limit` - Max disputes (default: 25, max: 100) -- `deposit_limit` - Max deposits (default: 25, max: 100) - -**Returns**: -- Transactions -- Settlements -- Disputes -- Deposits -- Comprehensive summary - -### Batch Report - -Get batch report information. - -```bash -curl "http://localhost:8080/reports/batches?start_date=2025-09-01&end_date=2025-09-30" -``` - -## Response Format - -### Success Response - -```json -{ - "success": true, - "data": { - "transactions": [...], - "pagination": { - "page": 1, - "page_size": 10, - "total_count": 42 - } - }, - "timestamp": "2025-10-01 12:00:00" -} -``` - -### Error Response - -```json -{ - "success": false, - "error": { - "code": "ERROR_CODE", - "message": "Error description", - "timestamp": "2025-10-01 12:00:00" - } -} -``` - -## Common Use Cases - -### Daily Transaction Report - -```bash -curl "http://localhost:8080/reports/summary?start_date=2025-09-30&end_date=2025-09-30" -``` - -### Find Specific Transaction - -```bash -curl "http://localhost:8080/reports/search?transaction_id=TRN_abc123" -``` - -### Export Monthly Transactions - -```bash -curl "http://localhost:8080/reports/export?format=csv&start_date=2025-09-01&end_date=2025-09-30" -o september_transactions.csv -``` - -### Analyze Declines - -```bash -curl "http://localhost:8080/reports/declines?start_date=2025-09-01&end_date=2025-09-30" -``` - -### Check Settlement Status - -```bash -curl "http://localhost:8080/reports/settlement?start_date=2025-09-30" -``` - -## Notes - -- **Date Format**: All dates must be in `YYYY-MM-DD` format -- **Pagination**: Page size is limited to 100 items maximum -- **Export Limits**: Exports are capped at 1000 transactions -- **Timestamps**: All response timestamps use `YYYY-MM-DD HH:mm:ss` format -- **CORS**: Enabled for cross-origin requests - -## API Documentation - -View complete API documentation: - -```bash -curl http://localhost:8080/reports -``` diff --git a/go/go.mod b/go/go.mod deleted file mode 100644 index cae050b..0000000 --- a/go/go.mod +++ /dev/null @@ -1,19 +0,0 @@ -module github.com/globalpayments/card-payments-go - -go 1.23.4 - -require ( - github.com/globalpayments/go-sdk v1.1.3 - github.com/gorilla/mux v1.8.1 - github.com/joho/godotenv v1.5.1 -) - -require ( - github.com/antchfx/xmlquery v1.3.18 // indirect - github.com/antchfx/xpath v1.2.4 // indirect - github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/shopspring/decimal v1.3.1 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/text v0.7.0 // indirect -) diff --git a/go/go.sum b/go/go.sum deleted file mode 100644 index e0de448..0000000 --- a/go/go.sum +++ /dev/null @@ -1,56 +0,0 @@ -github.com/antchfx/xmlquery v1.3.18 h1:FSQ3wMuphnPPGJOFhvc+cRQ2CT/rUj4cyQXkJcjOwz0= -github.com/antchfx/xmlquery v1.3.18/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA= -github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY= -github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= -github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= -github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/globalpayments/go-sdk v1.1.3 h1:7K966xL/GL3SHSGwOiEX+QxPK5jbJvuh/Mn0WcwNCPA= -github.com/globalpayments/go-sdk v1.1.3/go.mod h1:DCA8qrRhmOFbcN4akP+Rhw1WiLTf45duuO2mX6jOwhU= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/go/main.go b/go/main.go deleted file mode 100644 index ed6b469..0000000 --- a/go/main.go +++ /dev/null @@ -1,218 +0,0 @@ -// Package main implements a card payment processing server using the Global Payments SDK. -// It provides endpoints for configuration and payment processing, handling tokenized -// card data to ensure secure payment processing. -package main - -import ( - "context" - "encoding/json" - "log" - "net/http" - "os" - "regexp" - "strconv" - - "github.com/globalpayments/go-sdk/api" - "github.com/globalpayments/go-sdk/api/entities/base" - "github.com/globalpayments/go-sdk/api/entities/transactions" - "github.com/globalpayments/go-sdk/api/paymentmethods" - "github.com/globalpayments/go-sdk/api/serviceconfigs" - "github.com/globalpayments/go-sdk/api/utils/stringutils" - "github.com/joho/godotenv" -) - -// Config represents the configuration response sent to the client -type Config struct { - PublicApiKey string `json:"publicApiKey"` -} - -// Response represents a standardized API response -type Response struct { - Success bool `json:"success"` - Message string `json:"message,omitempty"` - Data interface{} `json:"data,omitempty"` - Error *ErrorInfo `json:"error,omitempty"` -} - -// ErrorInfo represents error details in the response -type ErrorInfo struct { - Code string `json:"code"` - Details string `json:"details"` -} - -// PaymentRequest represents the expected payment processing request payload -type PaymentRequest struct { - PaymentToken string `json:"payment_token"` - BillingZip string `json:"billing_zip"` -} - -// sanitizePostalCode removes invalid characters from the postal code input. -// It only allows alphanumeric characters and hyphens, limiting the length to 10 characters. -// This handles both US (12345, 12345-6789) and international postal codes. -func sanitizePostalCode(postalCode string) string { - // Remove any characters that aren't alphanumeric or hyphen - reg := regexp.MustCompile("[^a-zA-Z0-9-]") - sanitized := reg.ReplaceAllString(postalCode, "") - // Limit length to 10 characters - if len(sanitized) > 10 { - return sanitized[:10] - } - return sanitized -} - -// handleConfig handles the /config endpoint -func handleConfig(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - response := Response{ - Success: true, - Data: Config{ - PublicApiKey: os.Getenv("PUBLIC_API_KEY"), - }, - } - json.NewEncoder(w).Encode(response) -} - -// handlePayment handles the /process-payment endpoint -func handlePayment(w http.ResponseWriter, r *http.Request) { - // Ensure endpoint only accepts POST requests - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - // Parse and validate the form data - if err := r.ParseForm(); err != nil { - http.Error(w, "Error parsing form data", http.StatusBadRequest) - return - } - - // Extract payment information from form - paymentToken := r.Form.Get("payment_token") - billingZip := r.Form.Get("billing_zip") - amountStr := r.Form.Get("amount") - - // Validate required fields are present - if paymentToken == "" || billingZip == "" || amountStr == "" { - w.Header().Set("Content-Type", "application/json") - errorResponse := Response{ - Success: false, - Message: "Payment processing failed", - Error: &ErrorInfo{ - Code: "VALIDATION_ERROR", - Details: "Missing required fields", - }, - } - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(errorResponse) - return - } - - // Validate and parse amount - amount, err := strconv.ParseFloat(amountStr, 64) - if err != nil || amount <= 0 { - w.Header().Set("Content-Type", "application/json") - errorResponse := Response{ - Success: false, - Message: "Payment processing failed", - Error: &ErrorInfo{ - Code: "VALIDATION_ERROR", - Details: "Amount must be a positive number", - }, - } - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(errorResponse) - return - } - - // Initialize payment data using tokenized card information - card := paymentmethods.NewCreditCardDataWithToken(paymentToken) - - // Create billing address for AVS verification - address := base.NewAddress(sanitizePostalCode(billingZip)) - - // Configure the payment transaction using the provided amount - amountStr = strconv.FormatFloat(amount, 'f', 2, 64) - val, _ := stringutils.ToDecimalAmount(amountStr) - transaction := card.ChargeWithAmount(val) - transaction.WithAllowDuplicates(true) - transaction.WithCurrency("USD") - transaction.WithAddress(address) - - ctx := context.Background() - response, err := api.ExecuteGateway[transactions.Transaction](ctx, transaction) - if err != nil { - w.Header().Set("Content-Type", "application/json") - errorResponse := Response{ - Success: false, - Message: "Internal server error", - Error: &ErrorInfo{ - Code: "SERVER_ERROR", - Details: err.Error(), - }, - } - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(errorResponse) - return - } - - // Check for successful response code - if response.GetResponseCode() != "00" { - w.Header().Set("Content-Type", "application/json") - errorResponse := Response{ - Success: false, - Message: "Payment processing failed", - Error: &ErrorInfo{ - Code: "PAYMENT_DECLINED", - Details: response.GetResponseMessage(), - }, - } - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(errorResponse) - return - } - - w.Header().Set("Content-Type", "application/json") - successResponse := Response{ - Success: true, - Message: "Payment successful! Transaction ID: " + response.GetTransactionId(), - Data: map[string]string{ - "transactionId": response.GetTransactionId(), - }, - } - json.NewEncoder(w).Encode(successResponse) -} - -func main() { - // Initialize environment configuration - err := godotenv.Load() - if err != nil { - log.Fatal("Error loading .env file") - } - - // Configure Global Payments SDK with credentials and settings - config := serviceconfigs.NewPorticoConfig() - config.SecretApiKey = os.Getenv("SECRET_API_KEY") - config.DeveloperId = "000000" - config.VersionNumber = "0000" - config.ServiceUrl = "https://cert.api2.heartlandportico.com" - - err = api.ConfigureService(config, "default") - if err != nil { - log.Fatal("Error configuring Global Payments service:", err) - } - - // Set up routes - http.Handle("/", http.FileServer(http.Dir("static"))) - http.Handle("/config", http.HandlerFunc(handleConfig)) - http.Handle("/process-payment", http.HandlerFunc(handlePayment)) - - // Get port from environment variable or use default - port := os.Getenv("PORT") - if port == "" { - port = "8000" - } - - log.Printf("Server starting on http://localhost:%s", port) - log.Printf("Server also accessible at http://127.0.0.1:%s", port) - log.Fatal(http.ListenAndServe("0.0.0.0:"+port, nil)) -} diff --git a/go/main_reporting.go b/go/main_reporting.go deleted file mode 100644 index f988362..0000000 --- a/go/main_reporting.go +++ /dev/null @@ -1,77 +0,0 @@ -// Package main implements an example server that combines payment processing -// and reporting functionality using the Global Payments SDK. -// -// This example shows how to integrate the reporting service alongside -// the existing payment processing endpoints. -// -// Usage: -// To run the reporting service only: -// go run reporting_service.go reports.go main_reporting.go -// -// The server will start on port 8080 by default (configurable via PORT env var) -package main - -import ( - "log" - "net/http" - "os" - - "github.com/gorilla/mux" - "github.com/joho/godotenv" -) - -func main() { - // Load environment variables - if err := godotenv.Load(); err != nil { - log.Println("Warning: .env file not found, using system environment variables") - } - - // Create router - router := mux.NewRouter() - - // Initialize reporting API routes - if err := InitializeReportingAPI(router); err != nil { - log.Fatalf("Failed to initialize reporting API: %v", err) - } - - // Optional: Add a health check endpoint - router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"status":"healthy","service":"reporting-api"}`)) - }).Methods("GET") - - // Get port from environment or use default - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - // Start server - log.Printf("=================================================") - log.Printf("Global Payments Reporting API Server") - log.Printf("=================================================") - log.Printf("Server starting on http://localhost:%s", port) - log.Printf("API Documentation: http://localhost:%s/reports", port) - log.Printf("Health Check: http://localhost:%s/health", port) - log.Printf("=================================================") - log.Printf("") - log.Printf("Available Endpoints:") - log.Printf(" GET /reports - API documentation") - log.Printf(" GET /reports/config - Configuration status") - log.Printf(" GET /reports/search - Search transactions") - log.Printf(" GET /reports/detail - Transaction details") - log.Printf(" GET /reports/settlement - Settlement report") - log.Printf(" GET /reports/export - Export transactions") - log.Printf(" GET /reports/summary - Summary statistics") - log.Printf(" GET /reports/disputes - Dispute report") - log.Printf(" GET /reports/deposits - Deposit report") - log.Printf(" GET /reports/batches - Batch report") - log.Printf(" GET /reports/declines - Declined transactions") - log.Printf(" GET /reports/date-range - Date range report") - log.Printf("=================================================") - - if err := http.ListenAndServe("0.0.0.0:"+port, router); err != nil { - log.Fatalf("Server failed to start: %v", err) - } -} \ No newline at end of file diff --git a/go/reporting_service.go b/go/reporting_service.go deleted file mode 100644 index 09f6363..0000000 --- a/go/reporting_service.go +++ /dev/null @@ -1,1048 +0,0 @@ -// Package main provides the core reporting service functionality for Global Payments. -// This service class provides comprehensive reporting functionality including -// search, filtering, dispute management, deposits, batches, and data export. -package main - -import ( - "context" - "encoding/csv" - "encoding/json" - "encoding/xml" - "fmt" - "os" - "strconv" - "strings" - "time" - - "github.com/globalpayments/go-sdk/api" - "github.com/globalpayments/go-sdk/api/builders" - "github.com/globalpayments/go-sdk/api/entities/enums/channel" - "github.com/globalpayments/go-sdk/api/entities/enums/environment" - "github.com/globalpayments/go-sdk/api/entities/enums/paymenttype" - "github.com/globalpayments/go-sdk/api/entities/enums/transactionstatus" - "github.com/globalpayments/go-sdk/api/entities/reporting" - "github.com/globalpayments/go-sdk/api/serviceconfigs" - "github.com/joho/godotenv" -) - -// ReportingService provides methods for transaction reporting, searching, and data export -type ReportingService struct { - isConfigured bool -} - -// TransactionInfo represents formatted transaction data -type TransactionInfo struct { - TransactionID string `json:"transaction_id" csv:"transaction_id" xml:"transaction_id"` - Timestamp string `json:"timestamp" csv:"timestamp" xml:"timestamp"` - Amount float64 `json:"amount" csv:"amount" xml:"amount"` - Currency string `json:"currency" csv:"currency" xml:"currency"` - Status string `json:"status" csv:"status" xml:"status"` - PaymentMethod string `json:"payment_method" csv:"payment_method" xml:"payment_method"` - CardLastFour string `json:"card_last_four" csv:"card_last_four" xml:"card_last_four"` - AuthCode string `json:"auth_code" csv:"auth_code" xml:"auth_code"` - ReferenceNumber string `json:"reference_number" csv:"reference_number" xml:"reference_number"` -} - -// TransactionDetails represents detailed transaction information -type TransactionDetails struct { - TransactionID string `json:"transaction_id"` - Timestamp string `json:"timestamp"` - Amount float64 `json:"amount"` - Currency string `json:"currency"` - Status string `json:"status"` - PaymentMethod string `json:"payment_method"` - CardDetails map[string]string `json:"card_details"` - AuthCode string `json:"auth_code"` - ReferenceNumber string `json:"reference_number"` - GatewayResponseCode string `json:"gateway_response_code"` - GatewayResponseMessage string `json:"gateway_response_message"` -} - -// SettlementInfo represents settlement data -type SettlementInfo struct { - SettlementID string `json:"settlement_id"` - SettlementDate string `json:"settlement_date"` - TransactionCount int `json:"transaction_count"` - TotalAmount float64 `json:"total_amount"` - Currency string `json:"currency"` - Status string `json:"status"` -} - -// DisputeInfo represents dispute data -type DisputeInfo struct { - DisputeID string `json:"dispute_id"` - TransactionID string `json:"transaction_id"` - CaseNumber string `json:"case_number"` - DisputeStage string `json:"dispute_stage"` - DisputeStatus string `json:"dispute_status"` - CaseAmount float64 `json:"case_amount"` - Currency string `json:"currency"` - ReasonCode string `json:"reason_code"` - ReasonDescription string `json:"reason_description"` - CaseTime string `json:"case_time"` - LastAdjustmentTime string `json:"last_adjustment_time"` -} - -// DepositInfo represents deposit data -type DepositInfo struct { - DepositID string `json:"deposit_id"` - DepositDate string `json:"deposit_date"` - DepositReference string `json:"deposit_reference"` - DepositStatus string `json:"deposit_status"` - DepositAmount float64 `json:"deposit_amount"` - Currency string `json:"currency"` - MerchantNumber string `json:"merchant_number"` - MerchantHierarchy string `json:"merchant_hierarchy"` - SalesCount int `json:"sales_count"` - SalesAmount float64 `json:"sales_amount"` - RefundsCount int `json:"refunds_count"` - RefundsAmount float64 `json:"refunds_amount"` -} - -// BatchInfo represents batch data -type BatchInfo struct { - BatchID string `json:"batch_id"` - SequenceNumber string `json:"sequence_number"` - TransactionCount int `json:"transaction_count"` - TotalAmount float64 `json:"total_amount"` - Currency string `json:"currency"` - BatchStatus string `json:"batch_status"` - CloseTime string `json:"close_time"` - OpenTime string `json:"open_time"` -} - -// Pagination represents pagination information -type Pagination struct { - Page int `json:"page"` - PageSize int `json:"page_size"` - TotalCount int `json:"total_count"` - OriginalTotalCount int `json:"original_total_count,omitempty"` -} - -// NewReportingService creates a new reporting service instance -func NewReportingService() (*ReportingService, error) { - service := &ReportingService{} - if err := service.configureSDK(); err != nil { - return nil, fmt.Errorf("failed to configure SDK: %w", err) - } - service.isConfigured = true - return service, nil -} - -// configureSDK initializes the Global Payments SDK for GP-API -func (rs *ReportingService) configureSDK() error { - // Load environment variables - if err := godotenv.Load(); err != nil { - // Not a fatal error - env vars might be set externally - } - - // Validate required environment variables - appID := os.Getenv("GP_API_APP_ID") - appKey := os.Getenv("GP_API_APP_KEY") - - if appID == "" || appKey == "" { - return fmt.Errorf("missing required environment variables: GP_API_APP_ID and GP_API_APP_KEY") - } - - // Configure GP-API - config := serviceconfigs.NewGpApiConfig() - config.AppId = appID - config.AppKey = appKey - config.Environment = environment.TEST // Change to PRODUCTION for live - config.Channel = channel.CardNotPresent - - return api.ConfigureService(config, "default") -} - -// SearchTransactions searches transactions with filters and pagination -func (rs *ReportingService) SearchTransactions(filters map[string]interface{}) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - ctx := context.Background() - - // Extract pagination parameters - page := getIntValue(filters, "page", 1) - pageSize := getIntValue(filters, "page_size", 10) - if pageSize > 100 { - pageSize = 100 - } - - // Build search criteria - searchBuilder := builders.NewReportBuilder(builders.TransactionReportType) - searchBuilder.WithPaging(page, pageSize) - - // Apply date range filters - if startDate, ok := filters["start_date"].(string); ok && startDate != "" { - if date, err := time.Parse("2006-01-02", startDate); err == nil { - searchBuilder.WithStartDate(date) - } - } - - if endDate, ok := filters["end_date"].(string); ok && endDate != "" { - if date, err := time.Parse("2006-01-02", endDate); err == nil { - searchBuilder.WithEndDate(date) - } - } - - // Apply transaction ID filter - if txnID, ok := filters["transaction_id"].(string); ok && txnID != "" { - searchBuilder.WithTransactionId(txnID) - } - - // Apply payment type filter - if paymentTypeStr, ok := filters["payment_type"].(string); ok && paymentTypeStr != "" { - if pt := mapPaymentType(paymentTypeStr); pt != "" { - searchBuilder.WithPaymentType(paymenttype.PaymentType(pt)) - } - } - - // Apply amount range filters - if amountMin, ok := filters["amount_min"].(string); ok && amountMin != "" { - if amt, err := strconv.ParseFloat(amountMin, 64); err == nil { - searchBuilder.WithAmount(fmt.Sprintf("%.2f", amt)) - } - } - - if amountMax, ok := filters["amount_max"].(string); ok && amountMax != "" { - if amt, err := strconv.ParseFloat(amountMax, 64); err == nil { - searchBuilder.WithAmount(fmt.Sprintf("%.2f", amt)) - } - } - - // Apply card last four filter - if cardLastFour, ok := filters["card_last_four"].(string); ok && cardLastFour != "" { - searchBuilder.WithCardNumberLastFour(cardLastFour) - } - - // Execute search - response, err := api.ExecuteGateway[reporting.PagedResult](ctx, searchBuilder) - if err != nil { - return nil, fmt.Errorf("transaction search failed: %w", err) - } - - // Format transaction list - transactions := formatTransactionList(response.Results) - - // Apply client-side status filtering if needed - if status, ok := filters["status"].(string); ok && status != "" { - transactions = filterByStatus(transactions, strings.ToUpper(status)) - } - - return map[string]interface{}{ - "success": true, - "data": map[string]interface{}{ - "transactions": transactions, - "pagination": Pagination{ - Page: page, - PageSize: pageSize, - TotalCount: len(transactions), - OriginalTotalCount: response.TotalRecordCount, - }, - }, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// GetTransactionDetails retrieves detailed information for a specific transaction -func (rs *ReportingService) GetTransactionDetails(transactionID string) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - ctx := context.Background() - - searchBuilder := builders.NewReportBuilder(builders.TransactionReportType) - searchBuilder.WithTransactionId(transactionID) - - response, err := api.ExecuteGateway[reporting.TransactionSummary](ctx, searchBuilder) - if err != nil { - return nil, fmt.Errorf("failed to retrieve transaction details: %w", err) - } - - details := formatTransactionDetails(response) - - return map[string]interface{}{ - "success": true, - "data": details, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// GetSettlementReport generates settlement report for a date range -func (rs *ReportingService) GetSettlementReport(params map[string]interface{}) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - ctx := context.Background() - - page := getIntValue(params, "page", 1) - pageSize := getIntValue(params, "page_size", 50) - if pageSize > 100 { - pageSize = 100 - } - - searchBuilder := builders.NewReportBuilder(builders.SettlementReportType) - searchBuilder.WithPaging(page, pageSize) - - // Apply date range - if startDate, ok := params["start_date"].(string); ok && startDate != "" { - if date, err := time.Parse("2006-01-02", startDate); err == nil { - searchBuilder.WithStartDate(date) - } - } - - if endDate, ok := params["end_date"].(string); ok && endDate != "" { - if date, err := time.Parse("2006-01-02", endDate); err == nil { - searchBuilder.WithEndDate(date) - } - } - - response, err := api.ExecuteGateway[reporting.PagedResult](ctx, searchBuilder) - if err != nil { - return nil, fmt.Errorf("settlement report generation failed: %w", err) - } - - settlements := formatSettlementList(response.Results) - summary := generateSettlementSummary(settlements) - - return map[string]interface{}{ - "success": true, - "data": map[string]interface{}{ - "settlements": settlements, - "summary": summary, - "pagination": Pagination{ - Page: page, - PageSize: pageSize, - TotalCount: response.TotalRecordCount, - }, - }, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// GetDisputeReport retrieves dispute reports with filtering and pagination -func (rs *ReportingService) GetDisputeReport(filters map[string]interface{}) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - ctx := context.Background() - - page := getIntValue(filters, "page", 1) - pageSize := getIntValue(filters, "page_size", 10) - if pageSize > 100 { - pageSize = 100 - } - - searchBuilder := builders.NewReportBuilder(builders.DisputeReportType) - searchBuilder.WithPaging(page, pageSize) - - // Apply date range filters - if startDate, ok := filters["start_date"].(string); ok && startDate != "" { - if date, err := time.Parse("2006-01-02", startDate); err == nil { - searchBuilder.WithStartDate(date) - } - } - - if endDate, ok := filters["end_date"].(string); ok && endDate != "" { - if date, err := time.Parse("2006-01-02", endDate); err == nil { - searchBuilder.WithEndDate(date) - } - } - - // Apply dispute stage filter - if stage, ok := filters["stage"].(string); ok && stage != "" { - searchBuilder.WithDisputeStage(stage) - } - - // Apply dispute status filter - if status, ok := filters["status"].(string); ok && status != "" { - searchBuilder.WithDisputeStatus(status) - } - - response, err := api.ExecuteGateway[reporting.PagedResult](ctx, searchBuilder) - if err != nil { - return nil, fmt.Errorf("dispute report generation failed: %w", err) - } - - disputes := formatDisputeList(response.Results) - - return map[string]interface{}{ - "success": true, - "data": map[string]interface{}{ - "disputes": disputes, - "pagination": Pagination{ - Page: page, - PageSize: pageSize, - TotalCount: response.TotalRecordCount, - }, - }, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// GetDisputeDetails retrieves detailed information for a specific dispute -func (rs *ReportingService) GetDisputeDetails(disputeID string) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - ctx := context.Background() - - searchBuilder := builders.NewReportBuilder(builders.DisputeReportType) - searchBuilder.WithDisputeId(disputeID) - - response, err := api.ExecuteGateway[reporting.DisputeSummary](ctx, searchBuilder) - if err != nil { - return nil, fmt.Errorf("failed to retrieve dispute details: %w", err) - } - - details := formatDisputeDetails(response) - - return map[string]interface{}{ - "success": true, - "data": details, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// GetDepositReport retrieves deposit reports with filtering and pagination -func (rs *ReportingService) GetDepositReport(filters map[string]interface{}) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - ctx := context.Background() - - page := getIntValue(filters, "page", 1) - pageSize := getIntValue(filters, "page_size", 10) - if pageSize > 100 { - pageSize = 100 - } - - searchBuilder := builders.NewReportBuilder(builders.DepositReportType) - searchBuilder.WithPaging(page, pageSize) - - // Apply date range filters - if startDate, ok := filters["start_date"].(string); ok && startDate != "" { - if date, err := time.Parse("2006-01-02", startDate); err == nil { - searchBuilder.WithStartDate(date) - } - } - - if endDate, ok := filters["end_date"].(string); ok && endDate != "" { - if date, err := time.Parse("2006-01-02", endDate); err == nil { - searchBuilder.WithEndDate(date) - } - } - - // Apply deposit ID filter - if depositID, ok := filters["deposit_id"].(string); ok && depositID != "" { - searchBuilder.WithDepositReference(depositID) - } - - // Apply status filter - if status, ok := filters["status"].(string); ok && status != "" { - searchBuilder.WithDepositStatus(status) - } - - response, err := api.ExecuteGateway[reporting.PagedResult](ctx, searchBuilder) - if err != nil { - return nil, fmt.Errorf("deposit report generation failed: %w", err) - } - - deposits := formatDepositList(response.Results) - - return map[string]interface{}{ - "success": true, - "data": map[string]interface{}{ - "deposits": deposits, - "pagination": Pagination{ - Page: page, - PageSize: pageSize, - TotalCount: response.TotalRecordCount, - }, - }, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// GetDepositDetails retrieves detailed information for a specific deposit -func (rs *ReportingService) GetDepositDetails(depositID string) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - ctx := context.Background() - - searchBuilder := builders.NewReportBuilder(builders.DepositReportType) - searchBuilder.WithDepositReference(depositID) - - response, err := api.ExecuteGateway[reporting.DepositSummary](ctx, searchBuilder) - if err != nil { - return nil, fmt.Errorf("failed to retrieve deposit details: %w", err) - } - - details := formatDepositDetails(response) - - return map[string]interface{}{ - "success": true, - "data": details, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// GetBatchReport retrieves batch report with detailed transaction information -func (rs *ReportingService) GetBatchReport(filters map[string]interface{}) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - ctx := context.Background() - - searchBuilder := builders.NewReportBuilder(builders.BatchReportType) - - // Apply date range filters - if startDate, ok := filters["start_date"].(string); ok && startDate != "" { - if date, err := time.Parse("2006-01-02", startDate); err == nil { - searchBuilder.WithStartDate(date) - } - } - - if endDate, ok := filters["end_date"].(string); ok && endDate != "" { - if date, err := time.Parse("2006-01-02", endDate); err == nil { - searchBuilder.WithEndDate(date) - } - } - - response, err := api.ExecuteGateway[reporting.PagedResult](ctx, searchBuilder) - if err != nil { - return nil, fmt.Errorf("batch report generation failed: %w", err) - } - - batches := formatBatchList(response.Results) - summary := generateBatchSummary(batches) - - return map[string]interface{}{ - "success": true, - "data": map[string]interface{}{ - "batches": batches, - "summary": summary, - }, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// GetDeclinedTransactionsReport retrieves declined transactions report -func (rs *ReportingService) GetDeclinedTransactionsReport(filters map[string]interface{}) (map[string]interface{}, error) { - // Use transaction search with declined status filter - filters["status"] = "DECLINED" - result, err := rs.SearchTransactions(filters) - if err != nil { - return nil, fmt.Errorf("declined transactions report generation failed: %w", err) - } - - // Add decline analysis - if result["success"] == true { - data := result["data"].(map[string]interface{}) - transactions := data["transactions"].([]TransactionInfo) - data["decline_analysis"] = analyzeDeclines(transactions) - } - - return result, nil -} - -// GetDateRangeReport generates comprehensive date range report across all transaction types -func (rs *ReportingService) GetDateRangeReport(params map[string]interface{}) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - startDate := getStringValue(params, "start_date", time.Now().AddDate(0, 0, -30).Format("2006-01-02")) - endDate := getStringValue(params, "end_date", time.Now().Format("2006-01-02")) - - report := map[string]interface{}{ - "success": true, - "data": map[string]interface{}{ - "period": map[string]string{ - "start_date": startDate, - "end_date": endDate, - }, - "transactions": map[string]interface{}{}, - "settlements": map[string]interface{}{}, - "disputes": map[string]interface{}{}, - "deposits": map[string]interface{}{}, - "summary": map[string]interface{}{}, - }, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - } - - data := report["data"].(map[string]interface{}) - - // Get transactions for the period - transactionResult, err := rs.SearchTransactions(map[string]interface{}{ - "start_date": startDate, - "end_date": endDate, - "page_size": getIntValue(params, "transaction_limit", 100), - }) - if err == nil && transactionResult["success"] == true { - data["transactions"] = transactionResult["data"] - } - - // Get settlements for the period - settlementResult, err := rs.GetSettlementReport(map[string]interface{}{ - "start_date": startDate, - "end_date": endDate, - "page_size": getIntValue(params, "settlement_limit", 50), - }) - if err == nil && settlementResult["success"] == true { - data["settlements"] = settlementResult["data"] - } - - // Get disputes for the period - disputeResult, err := rs.GetDisputeReport(map[string]interface{}{ - "start_date": startDate, - "end_date": endDate, - "page_size": getIntValue(params, "dispute_limit", 25), - }) - if err == nil { - data["disputes"] = disputeResult["data"] - } else { - data["disputes"] = map[string]string{"error": fmt.Sprintf("Disputes not available: %v", err)} - } - - // Get deposits for the period - depositResult, err := rs.GetDepositReport(map[string]interface{}{ - "start_date": startDate, - "end_date": endDate, - "page_size": getIntValue(params, "deposit_limit", 25), - }) - if err == nil { - data["deposits"] = depositResult["data"] - } else { - data["deposits"] = map[string]string{"error": fmt.Sprintf("Deposits not available: %v", err)} - } - - // Generate comprehensive summary - data["summary"] = generateComprehensiveSummary(data) - - return report, nil -} - -// ExportTransactions exports transaction data in specified format -func (rs *ReportingService) ExportTransactions(filters map[string]interface{}, format string) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - // Get all transactions (increase limit for export) - filters["page_size"] = 1000 - result, err := rs.SearchTransactions(filters) - if err != nil { - return nil, fmt.Errorf("export failed: %w", err) - } - - data := result["data"].(map[string]interface{}) - transactions := data["transactions"].([]TransactionInfo) - - switch format { - case "csv": - return exportToCSV(transactions) - case "xml": - return exportToXML(transactions) - default: // json - return map[string]interface{}{ - "success": true, - "data": transactions, - "format": "json", - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil - } -} - -// GetSummaryStats generates summary statistics -func (rs *ReportingService) GetSummaryStats(params map[string]interface{}) (map[string]interface{}, error) { - if !rs.isConfigured { - return nil, fmt.Errorf("SDK is not properly configured") - } - - startDate := getStringValue(params, "start_date", time.Now().AddDate(0, 0, -30).Format("2006-01-02")) - endDate := getStringValue(params, "end_date", time.Now().Format("2006-01-02")) - - // Get transaction summary - result, err := rs.SearchTransactions(map[string]interface{}{ - "start_date": startDate, - "end_date": endDate, - "page_size": 1000, - }) - if err != nil { - return map[string]interface{}{ - "success": false, - "error": fmt.Sprintf("Failed to generate summary statistics: %v", err), - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, err - } - - data := result["data"].(map[string]interface{}) - transactions := data["transactions"].([]TransactionInfo) - stats := calculateSummaryStats(transactions) - - return map[string]interface{}{ - "success": true, - "data": stats, - "period": map[string]string{ - "start_date": startDate, - "end_date": endDate, - }, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// Helper functions - -func getIntValue(m map[string]interface{}, key string, defaultValue int) int { - if val, ok := m[key]; ok { - switch v := val.(type) { - case int: - return v - case float64: - return int(v) - case string: - if i, err := strconv.Atoi(v); err == nil { - return i - } - } - } - return defaultValue -} - -func getStringValue(m map[string]interface{}, key string, defaultValue string) string { - if val, ok := m[key]; ok { - if str, ok := val.(string); ok { - return str - } - } - return defaultValue -} - -func mapPaymentType(pt string) string { - mapping := map[string]string{ - "sale": string(paymenttype.SALE), - "refund": string(paymenttype.REFUND), - "authorize": string(paymenttype.AUTH), - "capture": string(paymenttype.CAPTURE), - } - return mapping[strings.ToLower(pt)] -} - -func formatTransactionList(results interface{}) []TransactionInfo { - // Note: Actual implementation depends on SDK response structure - // This is a placeholder that should be adapted based on actual SDK types - transactions := []TransactionInfo{} - - // TODO: Parse actual SDK response structure - // For now, return empty list - this should be implemented based on actual SDK - - return transactions -} - -func formatTransactionDetails(txn interface{}) TransactionDetails { - // TODO: Parse actual SDK transaction detail structure - return TransactionDetails{} -} - -func formatSettlementList(results interface{}) []SettlementInfo { - // TODO: Parse actual SDK settlement structure - return []SettlementInfo{} -} - -func formatDisputeList(results interface{}) []DisputeInfo { - // TODO: Parse actual SDK dispute structure - return []DisputeInfo{} -} - -func formatDepositList(results interface{}) []DepositInfo { - // TODO: Parse actual SDK deposit structure - return []DepositInfo{} -} - -func formatBatchList(results interface{}) []BatchInfo { - // TODO: Parse actual SDK batch structure - return []BatchInfo{} -} - -func formatDisputeDetails(dispute interface{}) map[string]interface{} { - // TODO: Parse actual SDK dispute detail structure - return map[string]interface{}{} -} - -func formatDepositDetails(deposit interface{}) map[string]interface{} { - // TODO: Parse actual SDK deposit detail structure - return map[string]interface{}{} -} - -func filterByStatus(transactions []TransactionInfo, status string) []TransactionInfo { - filtered := []TransactionInfo{} - for _, txn := range transactions { - if strings.ToUpper(txn.Status) == status { - filtered = append(filtered, txn) - } - } - return filtered -} - -func generateSettlementSummary(settlements []SettlementInfo) map[string]interface{} { - var totalAmount float64 - var totalTransactions int - - for _, s := range settlements { - totalAmount += s.TotalAmount - totalTransactions += s.TransactionCount - } - - avgAmount := 0.0 - if len(settlements) > 0 { - avgAmount = totalAmount / float64(len(settlements)) - } - - return map[string]interface{}{ - "total_settlements": len(settlements), - "total_amount": totalAmount, - "total_transactions": totalTransactions, - "average_settlement_amount": avgAmount, - } -} - -func generateBatchSummary(batches []BatchInfo) map[string]interface{} { - var totalAmount float64 - var totalTransactions int - statusCounts := make(map[string]int) - - for _, b := range batches { - totalAmount += b.TotalAmount - totalTransactions += b.TransactionCount - statusCounts[b.BatchStatus]++ - } - - avgAmount := 0.0 - if len(batches) > 0 { - avgAmount = totalAmount / float64(len(batches)) - } - - return map[string]interface{}{ - "total_batches": len(batches), - "total_amount": totalAmount, - "total_transactions": totalTransactions, - "average_batch_amount": avgAmount, - "status_breakdown": statusCounts, - } -} - -func calculateSummaryStats(transactions []TransactionInfo) map[string]interface{} { - var totalAmount float64 - statusCounts := make(map[string]int) - paymentTypeCounts := make(map[string]int) - - for _, txn := range transactions { - totalAmount += txn.Amount - statusCounts[txn.Status]++ - paymentTypeCounts[txn.PaymentMethod]++ - } - - avgAmount := 0.0 - if len(transactions) > 0 { - avgAmount = totalAmount / float64(len(transactions)) - } - - return map[string]interface{}{ - "total_transactions": len(transactions), - "total_amount": totalAmount, - "average_amount": avgAmount, - "status_breakdown": statusCounts, - "payment_type_breakdown": paymentTypeCounts, - } -} - -func analyzeDeclines(transactions []TransactionInfo) map[string]interface{} { - var totalAmount float64 - declineReasons := make(map[string]int) - cardTypes := make(map[string]int) - hourlyBreakdown := make(map[string]int) - - for _, txn := range transactions { - totalAmount += txn.Amount - - // Card type breakdown - cardTypes[txn.PaymentMethod]++ - - // Parse timestamp for hourly breakdown - if t, err := time.Parse("2006-01-02 15:04:05", txn.Timestamp); err == nil { - hour := t.Format("15") - hourlyBreakdown[hour]++ - } - } - - avgAmount := 0.0 - if len(transactions) > 0 { - avgAmount = totalAmount / float64(len(transactions)) - } - - return map[string]interface{}{ - "total_declined_transactions": len(transactions), - "total_declined_amount": totalAmount, - "average_declined_amount": avgAmount, - "decline_reasons": declineReasons, - "card_type_breakdown": cardTypes, - "hourly_breakdown": hourlyBreakdown, - } -} - -func generateComprehensiveSummary(reportData map[string]interface{}) map[string]interface{} { - summary := map[string]interface{}{ - "overview": map[string]interface{}{}, - "financial_summary": map[string]interface{}{}, - "operational_metrics": map[string]interface{}{}, - } - - // Transaction overview - if txnData, ok := reportData["transactions"].(map[string]interface{}); ok { - if txns, ok := txnData["transactions"].([]TransactionInfo); ok && len(txns) > 0 { - var totalAmount float64 - for _, txn := range txns { - totalAmount += txn.Amount - } - - overview := summary["overview"].(map[string]interface{}) - overview["transactions"] = map[string]interface{}{ - "count": len(txns), - "total_amount": totalAmount, - "average_amount": totalAmount / float64(len(txns)), - } - } - } - - return summary -} - -func exportToCSV(transactions []TransactionInfo) (map[string]interface{}, error) { - var builder strings.Builder - writer := csv.NewWriter(&builder) - - // Write header - header := []string{"Transaction ID", "Timestamp", "Amount", "Currency", "Status", "Payment Method", "Card Last Four", "Auth Code", "Reference Number"} - writer.Write(header) - - // Write data - for _, txn := range transactions { - record := []string{ - txn.TransactionID, - txn.Timestamp, - fmt.Sprintf("%.2f", txn.Amount), - txn.Currency, - txn.Status, - txn.PaymentMethod, - txn.CardLastFour, - txn.AuthCode, - txn.ReferenceNumber, - } - writer.Write(record) - } - - writer.Flush() - if err := writer.Error(); err != nil { - return nil, err - } - - filename := fmt.Sprintf("transactions_%s.csv", time.Now().Format("2006-01-02_15-04-05")) - - return map[string]interface{}{ - "success": true, - "data": builder.String(), - "format": "csv", - "filename": filename, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -func exportToXML(transactions []TransactionInfo) (map[string]interface{}, error) { - type TransactionList struct { - XMLName xml.Name `xml:"transactions"` - Transactions []TransactionInfo `xml:"transaction"` - } - - txnList := TransactionList{Transactions: transactions} - xmlData, err := xml.MarshalIndent(txnList, "", " ") - if err != nil { - return nil, err - } - - filename := fmt.Sprintf("transactions_%s.xml", time.Now().Format("2006-01-02_15-04-05")) - - return map[string]interface{}{ - "success": true, - "data": string(xmlData), - "format": "xml", - "filename": filename, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - }, nil -} - -// GetSDKConfigStatus returns current SDK configuration status -func GetSDKConfigStatus() map[string]interface{} { - appID := os.Getenv("GP_API_APP_ID") - appKey := os.Getenv("GP_API_APP_KEY") - - hasAppID := appID != "" - hasAppKey := appKey != "" - isConfigured := hasAppID && hasAppKey - - env := "Not configured" - if isConfigured { - env = "TEST" - } - - return map[string]interface{}{ - "configured": isConfigured, - "has_app_id": hasAppID, - "has_app_key": hasAppKey, - "environment": env, - "timestamp": time.Now().Format("2006-01-02 15:04:05"), - } -} - -// ValidateEnvironmentConfig validates environment configuration -func ValidateEnvironmentConfig() map[string]interface{} { - results := map[string]interface{}{ - "valid": true, - "errors": []string{}, - "warnings": []string{}, - } - - errors := []string{} - warnings := []string{} - - // Check required variables - required := []string{"GP_API_APP_ID", "GP_API_APP_KEY"} - for _, varName := range required { - if os.Getenv(varName) == "" { - errors = append(errors, fmt.Sprintf("Missing required environment variable: %s", varName)) - results["valid"] = false - } - } - - // Check legacy variables and warn if present - legacy := []string{"PUBLIC_API_KEY", "SECRET_API_KEY"} - for _, varName := range legacy { - if os.Getenv(varName) != "" { - warnings = append(warnings, fmt.Sprintf("Legacy variable %s found. GP-API uses GP_API_APP_ID and GP_API_APP_KEY.", varName)) - } - } - - results["errors"] = errors - results["warnings"] = warnings - - return results -} \ No newline at end of file diff --git a/go/reports.go b/go/reports.go deleted file mode 100644 index 0dec167..0000000 --- a/go/reports.go +++ /dev/null @@ -1,1077 +0,0 @@ -// Package main implements HTTP handler functions for Global Payments Reporting API. -// This file provides RESTful API endpoints for accessing Global Payments -// reporting functionality including transaction search, details, settlement -// reports, and data export capabilities. -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "strconv" - "strings" - "time" - - "github.com/gorilla/mux" -) - -// APIResponse represents a standardized API response -type APIResponse struct { - Success bool `json:"success"` - Data interface{} `json:"data,omitempty"` - Error *APIError `json:"error,omitempty"` - Timestamp string `json:"timestamp"` -} - -// APIError represents error details in the response -type APIError struct { - Code string `json:"code"` - Message string `json:"message"` - Timestamp string `json:"timestamp"` -} - -// APIEndpointInfo represents documentation for a single endpoint -type APIEndpointInfo struct { - URL string `json:"url"` - Method string `json:"method"` - Description string `json:"description"` - Parameters map[string]string `json:"parameters"` -} - -var reportingService *ReportingService - -// InitializeReportingAPI initializes the reporting service and sets up routes -func InitializeReportingAPI(router *mux.Router) error { - var err error - reportingService, err = NewReportingService() - if err != nil { - return fmt.Errorf("failed to initialize reporting service: %w", err) - } - - // Set up routes - router.HandleFunc("/reports", handleReports).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/search", handleSearch).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/detail", handleDetail).Methods("GET", "OPTIONS") - router.HandleFunc("/reports/settlement", handleSettlement).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/export", handleExport).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/summary", handleSummary).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/disputes", handleDisputes).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/dispute/{id}", handleDisputeDetail).Methods("GET", "OPTIONS") - router.HandleFunc("/reports/deposits", handleDeposits).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/deposit/{id}", handleDepositDetail).Methods("GET", "OPTIONS") - router.HandleFunc("/reports/batches", handleBatches).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/declines", handleDeclines).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/date-range", handleDateRange).Methods("GET", "POST", "OPTIONS") - router.HandleFunc("/reports/config", handleReportsConfig).Methods("GET", "OPTIONS") - - return nil -} - -// setJSONHeaders sets standard JSON response headers with CORS support -func setJSONHeaders(w http.ResponseWriter) { - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") -} - -// handlePreflight handles OPTIONS preflight requests -func handlePreflight(w http.ResponseWriter, r *http.Request) bool { - if r.Method == "OPTIONS" { - setJSONHeaders(w) - w.WriteHeader(http.StatusOK) - return true - } - return false -} - -// sendJSONResponse sends a JSON response with the specified status code -func sendJSONResponse(w http.ResponseWriter, data interface{}, statusCode int) { - setJSONHeaders(w) - w.WriteHeader(statusCode) - json.NewEncoder(w).Encode(data) -} - -// handleError sends an error response -func handleError(w http.ResponseWriter, message string, statusCode int, errorCode string) { - response := APIResponse{ - Success: false, - Error: &APIError{ - Code: errorCode, - Message: message, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - }, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - } - sendJSONResponse(w, response, statusCode) -} - -// getRequestParams extracts parameters from both GET and POST requests -func getRequestParams(r *http.Request) (map[string]interface{}, error) { - params := make(map[string]interface{}) - - // Get query parameters - for key, values := range r.URL.Query() { - if len(values) > 0 { - params[key] = values[0] - } - } - - // Get POST body parameters if applicable - if r.Method == "POST" { - contentType := r.Header.Get("Content-Type") - if strings.Contains(contentType, "application/json") { - var jsonParams map[string]interface{} - if err := json.NewDecoder(r.Body).Decode(&jsonParams); err == nil { - for key, value := range jsonParams { - params[key] = value - } - } - } else if err := r.ParseForm(); err == nil { - for key, values := range r.Form { - if len(values) > 0 { - params[key] = values[0] - } - } - } - } - - return params, nil -} - -// validateRequiredParams validates that required parameters are present -func validateRequiredParams(params map[string]interface{}, required []string) error { - for _, param := range required { - if val, ok := params[param]; !ok || val == "" { - return fmt.Errorf("missing required parameter: %s", param) - } - } - return nil -} - -// validateDateFormat validates date format -func validateDateFormat(dateStr string) error { - if dateStr == "" { - return nil - } - _, err := time.Parse("2006-01-02", dateStr) - if err != nil { - return fmt.Errorf("invalid date format. Use YYYY-MM-DD") - } - return nil -} - -// removeEmptyParams removes empty string values from params -func removeEmptyParams(params map[string]interface{}) map[string]interface{} { - cleaned := make(map[string]interface{}) - for key, value := range params { - if str, ok := value.(string); ok && str != "" { - cleaned[key] = value - } else if !ok { - cleaned[key] = value - } - } - return cleaned -} - -// handleReports handles the main /reports endpoint (API documentation) -func handleReports(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - // Check if action parameter is provided for backward compatibility - params, _ := getRequestParams(r) - if action, ok := params["action"].(string); ok && action != "" { - // Route based on action parameter (PHP-style compatibility) - handleActionBasedRouting(w, r, action, params) - return - } - - // Default: show API documentation - endpoints := map[string]APIEndpointInfo{ - "search": { - URL: "/reports/search", - Method: "GET/POST", - Description: "Search transactions with filters and pagination", - Parameters: map[string]string{ - "page": "Page number (default: 1)", - "page_size": "Results per page (default: 10, max: 100)", - "start_date": "Start date (YYYY-MM-DD)", - "end_date": "End date (YYYY-MM-DD)", - "transaction_id": "Specific transaction ID", - "payment_type": "Payment type (sale, refund, authorize, capture)", - "status": "Transaction status", - "amount_min": "Minimum amount", - "amount_max": "Maximum amount", - "card_last_four": "Last 4 digits of card", - }, - }, - "detail": { - URL: "/reports/detail?transaction_id={id}", - Method: "GET", - Description: "Get detailed transaction information", - Parameters: map[string]string{ - "transaction_id": "Transaction ID (required)", - }, - }, - "settlement": { - URL: "/reports/settlement", - Method: "GET/POST", - Description: "Get settlement report", - Parameters: map[string]string{ - "page": "Page number (default: 1)", - "page_size": "Results per page (default: 50, max: 100)", - "start_date": "Start date (YYYY-MM-DD)", - "end_date": "End date (YYYY-MM-DD)", - }, - }, - "export": { - URL: "/reports/export?format={json|csv|xml}", - Method: "GET/POST", - Description: "Export transaction data", - Parameters: map[string]string{ - "format": "Export format (json, csv, or xml)", - "...filters": "Same filters as search endpoint", - }, - }, - "summary": { - URL: "/reports/summary", - Method: "GET/POST", - Description: "Get summary statistics", - Parameters: map[string]string{ - "start_date": "Start date (YYYY-MM-DD)", - "end_date": "End date (YYYY-MM-DD)", - }, - }, - "disputes": { - URL: "/reports/disputes", - Method: "GET/POST", - Description: "Get dispute report", - Parameters: map[string]string{ - "page": "Page number (default: 1)", - "page_size": "Results per page (default: 10, max: 100)", - "start_date": "Start date (YYYY-MM-DD)", - "end_date": "End date (YYYY-MM-DD)", - "stage": "Dispute stage", - "status": "Dispute status", - }, - }, - "deposits": { - URL: "/reports/deposits", - Method: "GET/POST", - Description: "Get deposit report", - Parameters: map[string]string{ - "page": "Page number (default: 1)", - "page_size": "Results per page (default: 10, max: 100)", - "start_date": "Start date (YYYY-MM-DD)", - "end_date": "End date (YYYY-MM-DD)", - "deposit_id": "Specific deposit ID", - "status": "Deposit status", - }, - }, - "batches": { - URL: "/reports/batches", - Method: "GET/POST", - Description: "Get batch report", - Parameters: map[string]string{ - "start_date": "Start date (YYYY-MM-DD)", - "end_date": "End date (YYYY-MM-DD)", - }, - }, - "declines": { - URL: "/reports/declines", - Method: "GET/POST", - Description: "Get declined transactions report", - Parameters: map[string]string{ - "page": "Page number (default: 1)", - "page_size": "Results per page (default: 10, max: 100)", - "start_date": "Start date (YYYY-MM-DD)", - "end_date": "End date (YYYY-MM-DD)", - "payment_type": "Payment type", - "amount_min": "Minimum amount", - "amount_max": "Maximum amount", - "card_last_four": "Last 4 digits of card", - }, - }, - "date_range": { - URL: "/reports/date-range", - Method: "GET/POST", - Description: "Get comprehensive date range report", - Parameters: map[string]string{ - "start_date": "Start date (YYYY-MM-DD)", - "end_date": "End date (YYYY-MM-DD)", - "transaction_limit": "Max transactions to retrieve (default: 100, max: 1000)", - "settlement_limit": "Max settlements to retrieve (default: 50, max: 500)", - "dispute_limit": "Max disputes to retrieve (default: 25, max: 100)", - "deposit_limit": "Max deposits to retrieve (default: 25, max: 100)", - }, - }, - "config": { - URL: "/reports/config", - Method: "GET", - Description: "Get API configuration and status", - Parameters: map[string]string{}, - }, - } - - response := APIResponse{ - Success: true, - Data: map[string]interface{}{ - "name": "Global Payments Reporting API", - "version": "1.0.0", - "description": "RESTful API for Global Payments transaction reporting and analytics", - "endpoints": endpoints, - }, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - } - - sendJSONResponse(w, response, http.StatusOK) -} - -// handleActionBasedRouting routes requests based on action parameter (PHP compatibility) -func handleActionBasedRouting(w http.ResponseWriter, r *http.Request, action string, params map[string]interface{}) { - switch action { - case "search": - handleSearchWithParams(w, r, params) - case "detail": - handleDetailWithParams(w, r, params) - case "settlement": - handleSettlementWithParams(w, r, params) - case "export": - handleExportWithParams(w, r, params) - case "summary": - handleSummaryWithParams(w, r, params) - case "disputes": - handleDisputesWithParams(w, r, params) - case "dispute_detail": - handleDisputeDetailWithParams(w, r, params) - case "deposits": - handleDepositsWithParams(w, r, params) - case "deposit_detail": - handleDepositDetailWithParams(w, r, params) - case "batches": - handleBatchesWithParams(w, r, params) - case "declines": - handleDeclinesWithParams(w, r, params) - case "date_range": - handleDateRangeWithParams(w, r, params) - case "config": - handleReportsConfig(w, r) - default: - handleError(w, fmt.Sprintf("Invalid action: %s", action), http.StatusBadRequest, "INVALID_ACTION") - } -} - -// handleSearch handles transaction search requests -func handleSearch(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleSearchWithParams(w, r, params) -} - -func handleSearchWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - // Build filters - filters := make(map[string]interface{}) - - // Pagination - if page, ok := params["page"].(string); ok { - if p, err := strconv.Atoi(page); err == nil { - filters["page"] = p - } - } - if pageSize, ok := params["page_size"].(string); ok { - if ps, err := strconv.Atoi(pageSize); err == nil && ps <= 100 { - filters["page_size"] = ps - } - } - - // Date filters - if startDate, ok := params["start_date"].(string); ok { - if err := validateDateFormat(startDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - filters["start_date"] = startDate - } - - if endDate, ok := params["end_date"].(string); ok { - if err := validateDateFormat(endDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - filters["end_date"] = endDate - } - - // Other filters - filterKeys := []string{"transaction_id", "payment_type", "status", "amount_min", "amount_max", "card_last_four"} - for _, key := range filterKeys { - if val, ok := params[key]; ok { - filters[key] = val - } - } - - filters = removeEmptyParams(filters) - - result, err := reportingService.SearchTransactions(filters) - if err != nil { - handleError(w, fmt.Sprintf("Transaction search failed: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleDetail handles transaction detail requests -func handleDetail(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleDetailWithParams(w, r, params) -} - -func handleDetailWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - if err := validateRequiredParams(params, []string{"transaction_id"}); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - - transactionID := params["transaction_id"].(string) - result, err := reportingService.GetTransactionDetails(transactionID) - if err != nil { - handleError(w, fmt.Sprintf("Failed to retrieve transaction details: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleSettlement handles settlement report requests -func handleSettlement(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleSettlementWithParams(w, r, params) -} - -func handleSettlementWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - settlementParams := make(map[string]interface{}) - - // Pagination - if page, ok := params["page"].(string); ok { - if p, err := strconv.Atoi(page); err == nil { - settlementParams["page"] = p - } - } - if pageSize, ok := params["page_size"].(string); ok { - if ps, err := strconv.Atoi(pageSize); err == nil && ps <= 100 { - settlementParams["page_size"] = ps - } - } - - // Date filters - if startDate, ok := params["start_date"].(string); ok { - if err := validateDateFormat(startDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - settlementParams["start_date"] = startDate - } - - if endDate, ok := params["end_date"].(string); ok { - if err := validateDateFormat(endDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - settlementParams["end_date"] = endDate - } - - settlementParams = removeEmptyParams(settlementParams) - - result, err := reportingService.GetSettlementReport(settlementParams) - if err != nil { - handleError(w, fmt.Sprintf("Settlement report generation failed: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleExport handles transaction export requests -func handleExport(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleExportWithParams(w, r, params) -} - -func handleExportWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - format := "json" - if f, ok := params["format"].(string); ok { - format = strings.ToLower(f) - } - - if format != "json" && format != "csv" && format != "xml" { - handleError(w, "Invalid format. Supported formats: json, csv, xml", http.StatusBadRequest, "VALIDATION_ERROR") - return - } - - // Build export filters - exportFilters := make(map[string]interface{}) - filterKeys := []string{"start_date", "end_date", "transaction_id", "payment_type", "status", "amount_min", "amount_max", "card_last_four"} - - for _, key := range filterKeys { - if val, ok := params[key].(string); ok { - if key == "start_date" || key == "end_date" { - if err := validateDateFormat(val); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - } - exportFilters[key] = val - } - } - - exportFilters = removeEmptyParams(exportFilters) - - result, err := reportingService.ExportTransactions(exportFilters, format) - if err != nil { - handleError(w, fmt.Sprintf("Export failed: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - // Handle CSV/XML export with appropriate headers - if format == "csv" { - w.Header().Set("Content-Type", "text/csv") - filename := "transactions.csv" - if fn, ok := result["filename"].(string); ok { - filename = fn - } - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) - if data, ok := result["data"].(string); ok { - w.Write([]byte(data)) - } - return - } else if format == "xml" { - w.Header().Set("Content-Type", "application/xml") - filename := "transactions.xml" - if fn, ok := result["filename"].(string); ok { - filename = fn - } - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) - if data, ok := result["data"].(string); ok { - w.Write([]byte(data)) - } - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleSummary handles summary statistics requests -func handleSummary(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleSummaryWithParams(w, r, params) -} - -func handleSummaryWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - summaryParams := make(map[string]interface{}) - - if startDate, ok := params["start_date"].(string); ok { - if err := validateDateFormat(startDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - summaryParams["start_date"] = startDate - } - - if endDate, ok := params["end_date"].(string); ok { - if err := validateDateFormat(endDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - summaryParams["end_date"] = endDate - } - - summaryParams = removeEmptyParams(summaryParams) - - result, err := reportingService.GetSummaryStats(summaryParams) - if err != nil { - handleError(w, fmt.Sprintf("Summary generation failed: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleDisputes handles dispute report requests -func handleDisputes(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleDisputesWithParams(w, r, params) -} - -func handleDisputesWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - disputeFilters := make(map[string]interface{}) - - // Pagination - if page, ok := params["page"].(string); ok { - if p, err := strconv.Atoi(page); err == nil { - disputeFilters["page"] = p - } - } - if pageSize, ok := params["page_size"].(string); ok { - if ps, err := strconv.Atoi(pageSize); err == nil && ps <= 100 { - disputeFilters["page_size"] = ps - } - } - - // Date filters - if startDate, ok := params["start_date"].(string); ok { - if err := validateDateFormat(startDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - disputeFilters["start_date"] = startDate - } - - if endDate, ok := params["end_date"].(string); ok { - if err := validateDateFormat(endDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - disputeFilters["end_date"] = endDate - } - - // Dispute-specific filters - if stage, ok := params["stage"]; ok { - disputeFilters["stage"] = stage - } - if status, ok := params["status"]; ok { - disputeFilters["status"] = status - } - - disputeFilters = removeEmptyParams(disputeFilters) - - result, err := reportingService.GetDisputeReport(disputeFilters) - if err != nil { - handleError(w, fmt.Sprintf("Dispute report generation failed: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleDisputeDetail handles dispute detail requests -func handleDisputeDetail(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - vars := mux.Vars(r) - disputeID := vars["id"] - - if disputeID == "" { - params, _ := getRequestParams(r) - handleDisputeDetailWithParams(w, r, params) - return - } - - result, err := reportingService.GetDisputeDetails(disputeID) - if err != nil { - handleError(w, fmt.Sprintf("Failed to retrieve dispute details: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -func handleDisputeDetailWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - if err := validateRequiredParams(params, []string{"dispute_id"}); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - - disputeID := params["dispute_id"].(string) - result, err := reportingService.GetDisputeDetails(disputeID) - if err != nil { - handleError(w, fmt.Sprintf("Failed to retrieve dispute details: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleDeposits handles deposit report requests -func handleDeposits(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleDepositsWithParams(w, r, params) -} - -func handleDepositsWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - depositFilters := make(map[string]interface{}) - - // Pagination - if page, ok := params["page"].(string); ok { - if p, err := strconv.Atoi(page); err == nil { - depositFilters["page"] = p - } - } - if pageSize, ok := params["page_size"].(string); ok { - if ps, err := strconv.Atoi(pageSize); err == nil && ps <= 100 { - depositFilters["page_size"] = ps - } - } - - // Date filters - if startDate, ok := params["start_date"].(string); ok { - if err := validateDateFormat(startDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - depositFilters["start_date"] = startDate - } - - if endDate, ok := params["end_date"].(string); ok { - if err := validateDateFormat(endDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - depositFilters["end_date"] = endDate - } - - // Deposit-specific filters - if depositID, ok := params["deposit_id"]; ok { - depositFilters["deposit_id"] = depositID - } - if status, ok := params["status"]; ok { - depositFilters["status"] = status - } - - depositFilters = removeEmptyParams(depositFilters) - - result, err := reportingService.GetDepositReport(depositFilters) - if err != nil { - handleError(w, fmt.Sprintf("Deposit report generation failed: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleDepositDetail handles deposit detail requests -func handleDepositDetail(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - vars := mux.Vars(r) - depositID := vars["id"] - - if depositID == "" { - params, _ := getRequestParams(r) - handleDepositDetailWithParams(w, r, params) - return - } - - result, err := reportingService.GetDepositDetails(depositID) - if err != nil { - handleError(w, fmt.Sprintf("Failed to retrieve deposit details: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -func handleDepositDetailWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - if err := validateRequiredParams(params, []string{"deposit_id"}); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - - depositID := params["deposit_id"].(string) - result, err := reportingService.GetDepositDetails(depositID) - if err != nil { - handleError(w, fmt.Sprintf("Failed to retrieve deposit details: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleBatches handles batch report requests -func handleBatches(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleBatchesWithParams(w, r, params) -} - -func handleBatchesWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - batchFilters := make(map[string]interface{}) - - if startDate, ok := params["start_date"].(string); ok { - if err := validateDateFormat(startDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - batchFilters["start_date"] = startDate - } - - if endDate, ok := params["end_date"].(string); ok { - if err := validateDateFormat(endDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - batchFilters["end_date"] = endDate - } - - batchFilters = removeEmptyParams(batchFilters) - - result, err := reportingService.GetBatchReport(batchFilters) - if err != nil { - handleError(w, fmt.Sprintf("Batch report generation failed: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleDeclines handles declined transactions report requests -func handleDeclines(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleDeclinesWithParams(w, r, params) -} - -func handleDeclinesWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - declineFilters := make(map[string]interface{}) - - // Pagination - if page, ok := params["page"].(string); ok { - if p, err := strconv.Atoi(page); err == nil { - declineFilters["page"] = p - } - } - if pageSize, ok := params["page_size"].(string); ok { - if ps, err := strconv.Atoi(pageSize); err == nil && ps <= 100 { - declineFilters["page_size"] = ps - } - } - - // Date filters - if startDate, ok := params["start_date"].(string); ok { - if err := validateDateFormat(startDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - declineFilters["start_date"] = startDate - } - - if endDate, ok := params["end_date"].(string); ok { - if err := validateDateFormat(endDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - declineFilters["end_date"] = endDate - } - - // Other filters - filterKeys := []string{"payment_type", "amount_min", "amount_max", "card_last_four"} - for _, key := range filterKeys { - if val, ok := params[key]; ok { - declineFilters[key] = val - } - } - - declineFilters = removeEmptyParams(declineFilters) - - result, err := reportingService.GetDeclinedTransactionsReport(declineFilters) - if err != nil { - handleError(w, fmt.Sprintf("Declined transactions report generation failed: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleDateRange handles comprehensive date range report requests -func handleDateRange(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - params, err := getRequestParams(r) - if err != nil { - handleError(w, "Failed to parse request parameters", http.StatusBadRequest, "PARSE_ERROR") - return - } - - handleDateRangeWithParams(w, r, params) -} - -func handleDateRangeWithParams(w http.ResponseWriter, r *http.Request, params map[string]interface{}) { - dateRangeParams := make(map[string]interface{}) - - if startDate, ok := params["start_date"].(string); ok { - if err := validateDateFormat(startDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - dateRangeParams["start_date"] = startDate - } - - if endDate, ok := params["end_date"].(string); ok { - if err := validateDateFormat(endDate); err != nil { - handleError(w, err.Error(), http.StatusBadRequest, "VALIDATION_ERROR") - return - } - dateRangeParams["end_date"] = endDate - } - - // Limit parameters - limitKeys := []string{"transaction_limit", "settlement_limit", "dispute_limit", "deposit_limit"} - maxLimits := map[string]int{"transaction_limit": 1000, "settlement_limit": 500, "dispute_limit": 100, "deposit_limit": 100} - - for _, key := range limitKeys { - if val, ok := params[key].(string); ok { - if limit, err := strconv.Atoi(val); err == nil { - if limit > maxLimits[key] { - limit = maxLimits[key] - } - dateRangeParams[key] = limit - } - } - } - - dateRangeParams = removeEmptyParams(dateRangeParams) - - result, err := reportingService.GetDateRangeReport(dateRangeParams) - if err != nil { - handleError(w, fmt.Sprintf("Date range report generation failed: %v", err), http.StatusInternalServerError, "API_ERROR") - return - } - - sendJSONResponse(w, result, http.StatusOK) -} - -// handleReportsConfig handles configuration status requests -func handleReportsConfig(w http.ResponseWriter, r *http.Request) { - if handlePreflight(w, r) { - return - } - - configStatus := GetSDKConfigStatus() - envValidation := ValidateEnvironmentConfig() - - response := APIResponse{ - Success: true, - Data: map[string]interface{}{ - "sdk_status": configStatus, - "environment_validation": envValidation, - "api_endpoints": map[string]string{ - "search": "/reports/search", - "detail": "/reports/detail?transaction_id={id}", - "settlement": "/reports/settlement", - "disputes": "/reports/disputes", - "dispute_detail": "/reports/dispute/{id}", - "deposits": "/reports/deposits", - "deposit_detail": "/reports/deposit/{id}", - "batches": "/reports/batches", - "declines": "/reports/declines", - "date_range": "/reports/date-range", - "export": "/reports/export?format={json|csv|xml}", - "summary": "/reports/summary", - "config": "/reports/config", - }, - }, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - } - - sendJSONResponse(w, response, http.StatusOK) -} - -// StartReportingServer starts a standalone reporting server -func StartReportingServer(port string) error { - router := mux.NewRouter() - - if err := InitializeReportingAPI(router); err != nil { - return fmt.Errorf("failed to initialize reporting API: %w", err) - } - - log.Printf("Reporting API server starting on http://localhost:%s", port) - log.Printf("API documentation available at http://localhost:%s/reports", port) - - return http.ListenAndServe("0.0.0.0:"+port, router) -} \ No newline at end of file diff --git a/go/run.sh b/go/run.sh deleted file mode 100755 index 555ddfc..0000000 --- a/go/run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# Exit on error -set -e - -# Install dependencies -go mod download - -# Start the server -go run main.go diff --git a/go/static/documentation.html b/go/static/documentation.html deleted file mode 100644 index 35a9a7f..0000000 --- a/go/static/documentation.html +++ /dev/null @@ -1,308 +0,0 @@ - - -
- - -Query and export transaction data from Global Payments
- - - -Add the following to your .env file:
GP_API_APP_ID=your_app_id_here
-GP_API_APP_KEY=your_app_key_here
- Download dependencies: go mod download
Start server: go run main_reporting.go
Base URL: http://localhost:8080/api/reports?action=
curl "http://localhost:8080/api/reports?action=search&start_date=2025-09-01&page_size=20"
- page - Page number (default: 1)page_size - Items per page (max: 100)start_date - YYYY-MM-DDend_date - YYYY-MM-DDstatus - CAPTURED, DECLINED, PENDING, REVERSEDtransaction_id - Specific transactionamount_min / amount_max - Amount rangecard_last_four - Last 4 card digitscurl "http://localhost:8080/api/reports?action=detail&transaction_id=TRN_xxx"
- CSV:
-curl "http://localhost:8080/api/reports?action=export&format=csv&start_date=2025-09-01" -o file.csv
-
- JSON:
-curl "http://localhost:8080/api/reports?action=export&format=json&start_date=2025-09-01" -o file.json
- curl "http://localhost:8080/api/reports?action=summary&start_date=2025-09-01&end_date=2025-09-30"
- Returns totals, averages, and breakdowns by status/payment type.
- -curl "http://localhost:8080/api/reports?action=declines&start_date=2025-09-01&page_size=20"
- Returns declined transactions with analysis (reasons, card types, trends).
- -curl "http://localhost:8080/api/reports?action=settlement&start_date=2025-09-01"
- curl "http://localhost:8080/api/reports?action=disputes&status=PENDING"
- curl "http://localhost:8080/api/reports?action=deposits&start_date=2025-09-01"
- curl "http://localhost:8080/api/reports?action=batches&start_date=2025-09-01"
- All responses follow this structure:
- -Success Response:
-{
- "success": true,
- "data": { ... },
- "timestamp": "2025-10-06 12:00:00"
-}
-
- Error Response:
-{
- "success": false,
- "error": {
- "code": "VALIDATION_ERROR",
- "message": "Invalid date format",
- "timestamp": "2025-10-06 12:00:00"
- }
-}
- All search endpoints support:
-start_date and end_datepage and page_sizeTRN_Comprehensive reporting service for Global Payments transactions, providing search, filtering, and export capabilities.
- - -- Secure payment processing using Global Payments tokenization with client-side form validation. -
-- Access comprehensive transaction reporting, search, and export functionality. -
-- View recent transaction details in a simple table format. -
-Query and export transaction data from Global Payments
- - - -Add the following to your .env file:
GP_API_APP_ID=your_app_id_here
-GP_API_APP_KEY=your_app_key_here
- Install dependencies: pip install -r requirements.txt
Start server: python server.py
Base URL: http://localhost:8000/api/reports?action=
curl "http://localhost:8000/api/reports?action=search&start_date=2025-09-01&page_size=20"
- page - Page number (default: 1)page_size - Items per page (max: 100)start_date - YYYY-MM-DDend_date - YYYY-MM-DDstatus - CAPTURED, DECLINED, PENDING, REVERSEDtransaction_id - Specific transactionamount_min / amount_max - Amount rangecard_last_four - Last 4 card digitscurl "http://localhost:8000/api/reports?action=detail&transaction_id=TRN_xxx"
- CSV:
-curl "http://localhost:8000/api/reports?action=export&format=csv&start_date=2025-09-01" -o file.csv
-
- JSON:
-curl "http://localhost:8000/api/reports?action=export&format=json&start_date=2025-09-01" -o file.json
- curl "http://localhost:8000/api/reports?action=summary&start_date=2025-09-01&end_date=2025-09-30"
- Returns totals, averages, and breakdowns by status/payment type.
- -curl "http://localhost:8000/api/reports?action=declines&start_date=2025-09-01&page_size=20"
- Returns declined transactions with analysis (reasons, card types, trends).
- -curl "http://localhost:8000/api/reports?action=settlement&start_date=2025-09-01"
- curl "http://localhost:8000/api/reports?action=disputes&status=PENDING"
- curl "http://localhost:8000/api/reports?action=deposits&start_date=2025-09-01"
- curl "http://localhost:8000/api/reports?action=batches&start_date=2025-09-01"
- All responses follow this structure:
- -Success Response:
-{
- "success": true,
- "data": { ... },
- "timestamp": "2025-10-06 12:00:00"
-}
-
- Error Response:
-{
- "success": false,
- "error": {
- "code": "VALIDATION_ERROR",
- "message": "Invalid date format",
- "timestamp": "2025-10-06 12:00:00"
- }
-}
- All search endpoints support:
-start_date and end_datepage and page_sizeTRN_Comprehensive reporting service for Global Payments transactions, providing search, filtering, and export capabilities.
- - -- Secure payment processing using Global Payments tokenization with client-side form validation. -
-- Access comprehensive transaction reporting, search, and export functionality. -
-- View recent transaction details in a simple table format. -
-