{"id":50946085,"url":"https://github.com/globalpayments-samples/basic-refund-tool","last_synced_at":"2026-06-17T20:07:31.886Z","repository":{"id":332794692,"uuid":"1062795932","full_name":"globalpayments-samples/basic-refund-tool","owner":"globalpayments-samples","description":"Global Payments refund processing tool with multi-language implementations. Process refunds with standardized Global Payments SDK integration and environment-based configuration.","archived":false,"fork":false,"pushed_at":"2026-05-22T13:23:52.000Z","size":158,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-22T17:27:48.078Z","etag":null,"topics":["global-payments","lang-dotnet","lang-java","lang-nodejs","lang-php","payments","refund","sdk"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/globalpayments-samples.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-23T18:33:31.000Z","updated_at":"2026-05-22T13:23:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/globalpayments-samples/basic-refund-tool","commit_stats":null,"previous_names":["globalpayments-samples/basic-refund-tool"],"tags_count":0,"template":false,"template_full_name":"globalpayments-samples/starter-template","purl":"pkg:github/globalpayments-samples/basic-refund-tool","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globalpayments-samples%2Fbasic-refund-tool","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globalpayments-samples%2Fbasic-refund-tool/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globalpayments-samples%2Fbasic-refund-tool/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globalpayments-samples%2Fbasic-refund-tool/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/globalpayments-samples","download_url":"https://codeload.github.com/globalpayments-samples/basic-refund-tool/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globalpayments-samples%2Fbasic-refund-tool/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34463586,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["global-payments","lang-dotnet","lang-java","lang-nodejs","lang-php","payments","refund","sdk"],"created_at":"2026-06-17T20:07:31.129Z","updated_at":"2026-06-17T20:07:31.875Z","avatar_url":"https://github.com/globalpayments-samples.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Basic Refund Tool\n\nA complete charge-and-refund implementation using the Global Payments GP API. Developers can charge a card using hosted tokenization, then issue a full or partial refund against any transaction ID — all without handling raw card data. All implementations use the official Global Payments SDK (`GpApiConfig`).\n\nAvailable in four languages: PHP, Node.js, .NET, and Java.\n\n---\n\n## Available Implementations\n\n| Language | Framework | SDK Version |\n|----------|-----------|-------------|\n| [**PHP**](./php/) | Built-in Server | globalpayments/php-sdk ^13.1 |\n| [**Node.js**](./nodejs/) | Express.js | globalpayments-api ^3.10.6 |\n| [**.NET**](./dotnet/) | ASP.NET Core | GlobalPayments.Api 9.0.16 |\n| [**Java**](./java/) | Jakarta Servlet | globalpayments-sdk 14.2.20 |\n\nPreview links (runs in browser via CodeSandbox):\n- [PHP Preview](https://githubbox.com/globalpayments-samples/basic-refund-tool/tree/main/php)\n- [Node.js Preview](https://githubbox.com/globalpayments-samples/basic-refund-tool/tree/main/nodejs)\n- [.NET Preview](https://githubbox.com/globalpayments-samples/basic-refund-tool/tree/main/dotnet)\n- [Java Preview](https://githubbox.com/globalpayments-samples/basic-refund-tool/tree/main/java)\n\n---\n\n## How It Works\n\nThis tool demonstrates a two-step payment lifecycle:\n\n1. **Charge** — The frontend loads a hosted payment form from GP API. The customer enters card details, which are tokenized client-side. The token is sent to `POST /charge`, which calls the GP API SDK to capture the payment and returns a `transactionId`.\n2. **Refund** — The `transactionId` from the charge is passed to `POST /refund` along with the refund amount. The backend calls `Transaction.fromId()` on the SDK to issue the refund without needing the original card data.\n\n```\nBrowser\n  │\n  ├─ GET /config ──────────────────► Server\n  │                                    └─ GP API: generate access token\n  │  ◄── { accessToken, environment } ──┘\n  │\n  ├─ Hosted fields tokenize card (client-side, PCI-compliant)\n  │\n  ├─ POST /charge ─────────────────► Server\n  │   { payment_token, amount }        └─ SDK: CreditCardData.charge().execute()\n  │  ◄── { transactionId, status } ───┘\n  │\n  └─ POST /refund ─────────────────► Server\n      { transactionId, amount }        └─ SDK: Transaction.fromId().refund().execute()\n     ◄── { refundId, status } ────────┘\n```\n\n---\n\n## Prerequisites\n\n- Global Payments developer account — [Sign up at developer.globalpayments.com](https://developer.globalpayments.com)\n- GP API credentials: `APP_ID` and `APP_KEY` (sandbox credentials available after sign-up)\n- Docker (for multi-service setup), or a local runtime for your chosen language:\n  - PHP 8.0+ with Composer\n  - Node.js 18+ with npm\n  - .NET 8.0 SDK\n  - Java 17+ with Maven\n\n---\n\n## Quick Start\n\n### 1. Clone the repository\n\n```bash\ngit clone https://github.com/globalpayments-samples/basic-refund-tool.git\ncd basic-refund-tool\n```\n\n### 2. Choose a language and configure credentials\n\n```bash\ncd php       # or nodejs, dotnet, java\ncp .env.sample .env\n```\n\nEdit `.env`:\n\n```env\nGP_API_APP_ID=your_app_id_here\nGP_API_APP_KEY=your_app_key_here\nGP_API_ENVIRONMENT=test\n```\n\n### 3. Install and run\n\n**PHP:**\n```bash\ncomposer install\nphp -S localhost:8003\n```\nOpen: http://localhost:8003\n\n**Node.js:**\n```bash\nnpm install\nnpm start\n```\nOpen: http://localhost:8001\n\n**.NET:**\n```bash\ndotnet restore\ndotnet run\n```\nOpen: http://localhost:8006\n\n**Java:**\n```bash\nmvn clean package\nmvn cargo:run\n```\nOpen: http://localhost:8004\n\n### 4. Run a charge and refund\n\n1. Open the app in your browser\n2. Enter an amount (e.g. `19.99`)\n3. Use a test card (see [Test Cards](#test-cards) below)\n4. Click **Charge** — note the `transactionId` in the response\n5. Enter that `transactionId` and an amount to refund\n6. Click **Refund** — confirm the refund response\n\n---\n\n## Docker Setup\n\nRun all four language implementations simultaneously:\n\n```bash\n# Copy root-level env (used by all services)\ncp .env.sample .env\n# Edit .env with your credentials, then:\ndocker-compose up\n```\n\nIndividual services:\n\n```bash\ndocker-compose up nodejs    # http://localhost:8001\ndocker-compose up php       # http://localhost:8003\ndocker-compose up dotnet    # http://localhost:8006\ndocker-compose up java      # http://localhost:8004\n```\n\nRun integration tests (requires all services healthy):\n\n```bash\ndocker-compose --profile testing up\n```\n\nTest results written to `./test-results/` and `./playwright-report/`.\n\n---\n\n## API Endpoints\n\n### `GET /config`\n\nReturns a GP API access token for client-side hosted field initialization. The token has restricted permissions (`PMT_POST_Create_Single`) — it can only tokenize cards, not process transactions.\n\n**Response:**\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"accessToken\": \"uua7....\",\n    \"environment\": \"test\",\n    \"supportedCurrencies\": [\"USD\", \"EUR\", \"GBP\", \"CAD\"],\n    \"defaultCurrency\": \"USD\",\n    \"refund\": {\n      \"maxPercentage\": 115,\n      \"timeWindowDays\": 180\n    }\n  }\n}\n```\n\n---\n\n### `POST /charge`\n\nProcesses a card payment using a tokenized payment reference.\n\n**Request body:**\n```json\n{\n  \"payment_token\": \"PMT_abc123...\",\n  \"amount\": 19.99,\n  \"currency\": \"USD\",\n  \"billing_zip\": \"10001\",\n  \"cardDetails\": {\n    \"cardType\": \"visa\",\n    \"cardLast4\": \"9299\"\n  }\n}\n```\n\n**Success response (`200`):**\n```json\n{\n  \"success\": true,\n  \"message\": \"Payment processed successfully\",\n  \"data\": {\n    \"transactionId\": \"TRN_abc123xyz\",\n    \"amount\": 19.99,\n    \"currency\": \"USD\",\n    \"status\": \"captured\",\n    \"responseCode\": \"SUCCESS\",\n    \"responseMessage\": \"Approved\",\n    \"authorizationCode\": \"12345\",\n    \"referenceNumber\": \"REF_001\",\n    \"timestamp\": \"2025-01-15T10:30:00.000Z\",\n    \"paymentMethod\": {\n      \"type\": \"card\",\n      \"brand\": \"Visa\",\n      \"last4\": \"9299\"\n    }\n  }\n}\n```\n\n**Error response (`422`):**\n```json\n{\n  \"success\": false,\n  \"message\": \"Payment failed: Declined\",\n  \"error_code\": \"PAYMENT_DECLINED\",\n  \"timestamp\": \"2025-01-15T10:30:00.000Z\"\n}\n```\n\n---\n\n### `POST /refund`\n\nIssues a refund against an existing transaction ID. Supports partial refunds (amount less than original) and full refunds.\n\n**Request body:**\n```json\n{\n  \"transactionId\": \"TRN_abc123xyz\",\n  \"amount\": 19.99,\n  \"currency\": \"USD\",\n  \"reason\": \"Customer requested refund\"\n}\n```\n\n**Success response (`200`):**\n```json\n{\n  \"success\": true,\n  \"message\": \"Refund processed successfully\",\n  \"data\": {\n    \"refundId\": \"TRN_ref456xyz\",\n    \"transactionId\": \"TRN_abc123xyz\",\n    \"amount\": 19.99,\n    \"currency\": \"USD\",\n    \"status\": \"captured\",\n    \"responseCode\": \"SUCCESS\",\n    \"responseMessage\": \"Refunded\",\n    \"authorizationCode\": \"67890\",\n    \"referenceNumber\": \"REF_002\",\n    \"reason\": \"Customer requested refund\",\n    \"timestamp\": \"2025-01-15T10:35:00.000Z\"\n  }\n}\n```\n\n**Error response (`422`):**\n```json\n{\n  \"success\": false,\n  \"message\": \"Refund failed: Transaction not found\",\n  \"error_code\": \"REFUND_DECLINED\",\n  \"timestamp\": \"2025-01-15T10:35:00.000Z\"\n}\n```\n\n---\n\n## Test Cards\n\nUse these in sandbox (`GP_API_ENVIRONMENT=test`). CVV: `123`. Expiry: any future date.\n\n| Brand | Card Number | Expected Result |\n|-------|-------------|-----------------|\n| Visa | 4263 9826 4026 9299 | Approved |\n| Visa | 4263 9700 0000 5262 | Approved |\n| Mastercard | 5425 2334 2424 1200 | Approved |\n| Discover | 6011 0000 0000 0012 | Approved |\n| Amex | 3714 496353 98431 | Approved |\n\n\u003e Sandbox transactions do not move real money. Use test credentials only.\n\n---\n\n## Project Structure\n\n```\nbasic-refund-tool/\n├── index.html                  # Shared frontend (served by all backends)\n├── docker-compose.yml          # Multi-service Docker config\n├── Dockerfile.tests            # Playwright test runner\n├── LICENSE\n├── README.md\n│\n├── php/                        # Port 8003\n│   ├── .env.sample\n│   ├── composer.json\n│   ├── Dockerfile\n│   ├── PaymentUtils.php        # SDK config + shared helpers\n│   ├── config.php              # GET /config endpoint\n│   ├── charge.php              # POST /charge endpoint\n│   ├── refund.php              # POST /refund endpoint\n│   └── index.html              # Serves frontend\n│\n├── nodejs/                     # Port 8001\n│   ├── .env.sample\n│   ├── package.json\n│   ├── Dockerfile\n│   └── server.js               # Express app: /config, /charge, /refund\n│\n├── dotnet/                     # Port 8006\n│   ├── .env.sample\n│   ├── dotnet.csproj\n│   ├── Program.cs              # ASP.NET Core app: all endpoints\n│   ├── Dockerfile\n│   └── wwwroot/                # Static frontend files\n│\n└── java/                       # Port 8004\n    ├── .env.sample\n    ├── pom.xml\n    ├── Dockerfile\n    └── src/\n        └── main/java/com/globalpayments/example/\n            ├── ConfigServlet.java\n            ├── ProcessPaymentServlet.java\n            └── RefundServlet.java\n```\n\n---\n\n## Environment Variables\n\nAll language implementations use the same three variables:\n\n| Variable | Description | Example |\n|----------|-------------|---------|\n| `GP_API_APP_ID` | Your GP API application ID | `UJqPrAhrDkGzzNoFInpzKqoI8vfZtGRV` |\n| `GP_API_APP_KEY` | Your GP API application key | `zCFrbrn0NKly9sB4` |\n| `GP_API_ENVIRONMENT` | `test` for sandbox, `production` for live | `test` |\n\nCredentials are available in the [GP Developer Portal](https://developer.globalpayments.com) after creating an account.\n\n---\n\n## Troubleshooting\n\n**`401 Unauthorized` on `/config`**\nCredentials are invalid or for the wrong environment. Verify `GP_API_APP_ID` and `GP_API_APP_KEY` in `.env` match the environment setting (`test` vs `production`).\n\n**`422` on `/charge` — \"Payment failed\"**\nThe test card may have been declined. Try a different card from the [Test Cards](#test-cards) table. Confirm `GP_API_ENVIRONMENT=test` when using test cards.\n\n**`422` on `/refund` — \"Transaction not found\"**\nThe `transactionId` does not exist in sandbox or has expired. Use the `transactionId` returned by a `/charge` call made in the same session with the same credentials.\n\n**Port already in use**\nAnother process is using the port. Either stop it (`lsof -i :8003`) or change the port mapping in `docker-compose.yml`.\n\n**Java build fails — `mvn cargo:run`**\nRequires Java 17+ and Maven 3.8+. Verify with `java -version` and `mvn -version`.\n\n**.NET — `dotenv.net` missing**\nRun `dotnet restore` before `dotnet run` to install NuGet dependencies.\n\n## Community\n\n- 🌐 **Developer Portal** — [developer.globalpayments.com](https://developer.globalpayments.com)\n- 💬 **Discord** — [Join the community](https://discord.gg/myER9G9qkc)\n- 📋 **GitHub Discussions** — [github.com/orgs/globalpayments/discussions](https://github.com/orgs/globalpayments/discussions)\n- 📧 **Newsletter** — [Subscribe](https://www.globalpayments.com/en-gb/modals/newsletter)\n- 💼 **LinkedIn** — [Global Payments for Developers](https://www.linkedin.com/showcase/global-payments-for-developers/posts/?feedView=all)\n\nHave a question or found a bug? [Open an issue](https://github.com/globalpayments-samples/basic-refund-tool/issues) or reach out at [communityexperience@globalpay.com](mailto:communityexperience@globalpay.com).\n\n---\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglobalpayments-samples%2Fbasic-refund-tool","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglobalpayments-samples%2Fbasic-refund-tool","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglobalpayments-samples%2Fbasic-refund-tool/lists"}