A complete recurring payment implementation using the Global Payments GP API. Developers can set up subscription billing by capturing an initial payment with customer data, tokenizing the card server-side with StoredCredential, and scheduling future charges against the stored payment method — without handling raw card numbers at any point. All implementations use the official Global Payments SDK (GpApiConfig).
Available in four languages: PHP, Node.js, .NET, and Java.
| Language | Framework | SDK Version |
|---|---|---|
| PHP | Built-in Server | globalpayments/php-sdk ^13.1 |
| Node.js | Express.js | globalpayments-api ^3.10.6 |
| .NET | ASP.NET Core | GlobalPayments.Api 9.0.16 |
| Java | Jakarta Servlet | globalpayments-sdk 14.2.20 |
Preview links (runs in browser via CodeSandbox):
This project demonstrates the full recurring payment lifecycle: an initial charge captures the customer's card and stores it as a reusable payment method. All future charges use the stored method without prompting the customer for card details again.
Browser
│
├─ GET /config ──────────────────► Server
│ └─ GP API: generate scoped access token
│ ◄── { accessToken } ──────────────┘
│
├─ Hosted fields tokenize card (client-side, PCI-compliant)
│
├─ POST /process-payment ────────► Server
│ { ├─ is_recurring: false → charge().execute()
│ payment_token, └─ is_recurring: true → charge()
│ amount, .withStoredCredential()
│ is_recurring, .withCustomer()
│ frequency, .withAddress()
│ start_date, .execute()
│ first_name, last_name,
│ email, phone, address Returns transaction_id + payment_method_id
│ }
│ ◄── { transactionId, paymentMethodId, customerId, schedule } ──┘
│
└─ Future charges use paymentMethodId (no card re-entry)
The initial charge uses StoredCredentialType.INSTALLMENT and StoredCredentialSequence.FIRST. All subsequent charges reference the same paymentMethodId with StoredCredentialSequence.SUBSEQUENT. This meets card network requirements for recurring billing and avoids false fraud declines.
- Global Payments developer account — Sign up at developer.globalpayments.com
- GP API credentials:
APP_IDandAPP_KEY(sandbox available after sign-up) - A local runtime for your chosen language:
- PHP 8.0+ with Composer
- Node.js 18+ with npm
- .NET 8.0 SDK
- Java 17+ with Maven
git clone https://github.com/globalpayments-samples/online-recurring-payments.git
cd online-recurring-paymentscd php # or nodejs, dotnet, java
cp .env.sample .envEdit .env:
APP_ID=your_gp_api_app_id_here
APP_KEY=your_gp_api_app_key_here
GP_API_ENVIRONMENT=sandboxPHP:
composer install
php -S localhost:8000Open: http://localhost:8000
Node.js:
npm install
npm startOpen: http://localhost:8000
.NET:
dotnet restore
dotnet runOpen: http://localhost:8000
Java:
mvn clean package
mvn cargo:runOpen: http://localhost:8000
- Open the app in your browser
- Enter amount (e.g.
25.00) and select Recurring - Fill in customer details (name, email, address)
- Choose frequency (e.g. Monthly) and a start date
- Use a test card from Test Cards below
- Click Submit — note the
paymentMethodIdin the response - That ID can be used for all future recurring charges
Returns a scoped GP API access token for client-side hosted field initialization. Token has PMT_POST_Create_Single permission — it can only tokenize cards, not process transactions.
Response:
{
"success": true,
"data": {
"accessToken": "uua7...."
},
"message": "Configuration retrieved successfully",
"timestamp": "2025-01-15T10:00:00.000Z"
}Processes either a one-time payment or an initial recurring charge with customer data.
Request body:
{
"payment_token": "PMT_abc123...",
"amount": 25.00,
"currency": "USD",
"is_recurring": false
}Success response:
{
"success": true,
"data": {
"transaction_id": "TRN_abc123xyz",
"amount": 25.00,
"currency": "USD",
"status": "captured",
"message": "Payment processed successfully"
}
}Request body:
{
"payment_token": "PMT_abc123...",
"amount": 25.00,
"currency": "USD",
"is_recurring": true,
"frequency": "Monthly",
"start_date": "2025-02-01",
"first_name": "Jane",
"last_name": "Smith",
"email": "jane.smith@example.com",
"phone": "555-0100",
"street_address": "123 Main St",
"city": "Atlanta",
"state": "GA",
"billing_zip": "30301"
}Success response:
{
"success": true,
"data": {
"transaction_id": "TRN_abc123xyz",
"payment_method_id": "PMT_stored456",
"customer_id": "CUS_789xyz",
"amount": 25.00,
"currency": "USD",
"frequency": "Monthly",
"start_date": "2025-02-01",
"status": "active",
"message": "Initial payment successful. Recurring payment method stored."
},
"message": "Recurring payment schedule created successfully"
}Supported frequency values: Weekly, Bi-Weekly, Monthly, Quarterly, Annually
Error response (422):
{
"success": false,
"error": "Payment declined: Insufficient funds",
"timestamp": "2025-01-15T10:00:00.000Z"
}Use these in sandbox (GP_API_ENVIRONMENT=sandbox). CVV: 123. Expiry: any future date.
| Brand | Card Number | Expected Result |
|---|---|---|
| Visa | 4263 9826 4026 9299 | Approved |
| Mastercard | 5425 2334 2424 1200 | Approved |
| Discover | 6011 0000 0000 0012 | Approved |
| Declined | 4000 1200 0000 1154 | Declined |
Sandbox transactions do not move real money.
online-recurring-payments/
├── index.html # Shared frontend (served by all backends)
├── LICENSE
├── README.md
│
├── php/ # Port 8000
│ ├── .env.sample
│ ├── composer.json
│ ├── Dockerfile
│ ├── PaymentUtils.php # SDK config + shared helpers
│ ├── config.php # GET /config endpoint
│ ├── process-payment.php # POST /process-payment endpoint
│ └── run.sh
│
├── nodejs/ # Port 8000
│ ├── .env.sample
│ ├── package.json
│ ├── Dockerfile
│ ├── server.js # Express app: /config, /process-payment
│ ├── paymentUtils.js # SDK config + recurring helpers
│ └── run.sh
│
├── dotnet/ # Port 8000
│ ├── .env.sample
│ ├── *.csproj
│ ├── Program.cs # ASP.NET Core app: all endpoints
│ ├── Dockerfile
│ └── wwwroot/ # Static frontend files
│
└── java/ # Port 8000
├── .env.sample
├── pom.xml
├── Dockerfile
└── src/
└── main/java/com/globalpayments/example/
├── ConfigServlet.java
└── ProcessPaymentServlet.java
| Variable | Description | Example |
|---|---|---|
APP_ID |
Your GP API application ID | UJqPrAhrDkGzzNoFInpzKqoI8vfZtGRV |
APP_KEY |
Your GP API application key | zCFrbrn0NKly9sB4 |
GP_API_ENVIRONMENT |
sandbox for testing, production for live |
sandbox |
Credentials are available in the GP Developer Portal after creating an account.
401 Unauthorized on /config
Credentials are invalid or for the wrong environment. Verify APP_ID and APP_KEY in .env. Confirm GP_API_ENVIRONMENT matches the credential set (sandbox vs production).
422 — "Payment declined" on initial charge
The test card was declined. Try a different card from the Test Cards table. Confirm GP_API_ENVIRONMENT=sandbox when using test cards.
Recurring charge returns "Invalid payment method"
The payment_method_id from the initial charge must be stored and reused in subsequent charges. The stored credential expires if the sandbox session ends — run a new initial charge to get a fresh ID.
Missing customer fields validation error
Recurring payments require first_name, last_name, and email. phone, street_address, city, state, and billing_zip are optional but improve auth rates.
Port 8000 already in use
Another process is binding the port. Check with lsof -i :8000 and stop the conflicting process, or update the port in run.sh and the server file.
Node.js — Cannot find module 'globalpayments-api'
Run npm install before npm start. Confirm Node.js 18+ is installed: node -v.
Java build fails
Requires Java 17+ and Maven 3.8+. Verify with java -version and mvn -version. Run mvn clean package before mvn cargo:run.
- Process one-time payments using GP API hosted fields
- Process recurring payments with
StoredCredential— no card re-entry after the initial charge - 4-language implementations (PHP, Node.js, .NET, Java)
- Docker support with per-language containers
- Store credentials in
.envfiles, never commit to source control - API keys are never exposed to the frontend
- All payment processing happens server-side
- Use HTTPS in production
- Stored credentials must comply with card network rules
- 🌐 Developer Portal — developer.globalpayments.com
- 💬 Discord — Join the community
- 📋 GitHub Discussions — github.com/orgs/globalpayments/discussions
- 📧 Newsletter — Subscribe
- 💼 LinkedIn — Global Payments for Developers
Have a question or found a bug? Open an issue or reach out at communityexperience@globalpay.com.
MIT — see LICENSE.