GP API-based card payment processing with multi-currency and multi-language support using Global Payments Node.js SDK.
- Client-Side Tokenization: Secure card data capture using GP API JavaScript SDK
- Server-Side Payment Processing: Token-based payment charging via GP API
- Multi-Currency Support: Process payments in USD, EUR, GBP, CAD, AUD, JPY
- Multi-Language Support: Interface available in English, Spanish, French, German, Portuguese
- Independent Selection: Currency and language can be chosen independently
- Automatic Locale Detection: Browser language detection with manual override
- Currency Formatting: Locale-aware number and currency formatting using Intl API
- Session Persistence: User preferences saved across sessions using express-session middleware
- Card Details Management: Automatic extraction and return of card metadata
- CORS Support: Cross-origin request handling for frontend integration
- Error Handling: Comprehensive exception handling with localized error messages
- ES6 Modules: Modern JavaScript module system with import/export syntax
- Node.js 14.x or later
- npm (Node Package Manager)
- Global Payments account and GP API credentials
- npm packages (auto-installed):
- globalpayments-api (^3.10.6)
- express (^4.18.2)
- express-session (^1.17.3)
- cors (^2.8.5)
- dotenv (^16.3.1)
server.js- Main Express application with API endpoints and SDK configurationindex.html- Frontend payment form with localization features.env.sample- Environment configuration templaterun.sh- Convenience script to run the applicationpackage.json- Dependencies and scripts
services/LocaleService.js- Locale detection, validation, and session managementservices/CurrencyConfig.js- Currency metadata and formatting rulesservices/TranslationService.js- Server-side translation handling
translations/en.json- English translationstranslations/es.json- Spanish translationstranslations/fr.json- French translationstranslations/de.json- German translationstranslations/pt.json- Portuguese translations
wwwroot/js/translations.js- Client-side i18n modulewwwroot/js/currency-formatter.js- Currency formatting utilities
-
Clone or navigate to this directory
-
Copy
.env.sampleto.env:cp .env.sample .env
-
Update
.envwith your GP API credentials:GP_API_APP_ID=your_gp_api_app_id_here GP_API_APP_KEY=your_gp_api_app_key_here GP_API_ENVIRONMENT=sandbox SESSION_SECRET=your_session_secret_key_here
Get your credentials from: https://developer.globalpayments.com/
-
Install dependencies:
npm install
-
Run the application:
./run.sh
Or manually:
npm start
-
Open in browser:
http://localhost:8000
Generates GP API access token with locale/currency configuration for client-side SDK initialization.
Headers Required:
- Accept-Language: (optional) Browser language for locale detection
Response:
{
"success": true,
"data": {
"accessToken": "PMT_...",
"locale": "en",
"currency": "USD",
"supportedLocales": {
"en": {"code": "en", "name": "English", "nativeName": "English", "defaultCurrency": "USD"},
"es": {"code": "es", "name": "Spanish", "nativeName": "Español", "defaultCurrency": "EUR"},
"fr": {"code": "fr", "name": "French", "nativeName": "Français", "defaultCurrency": "EUR"},
"de": {"code": "de", "name": "German", "nativeName": "Deutsch", "defaultCurrency": "EUR"},
"pt": {"code": "pt", "name": "Portuguese", "nativeName": "Português", "defaultCurrency": "EUR"}
},
"supportedCurrencies": {
"USD": {"code": "USD", "symbol": "$", "decimals": 2, "country": "US"},
"EUR": {"code": "EUR", "symbol": "€", "decimals": 2, "country": "GB"},
"GBP": {"code": "GBP", "symbol": "£", "decimals": 2, "country": "GB"},
"CAD": {"code": "CAD", "symbol": "C$", "decimals": 2, "country": "CA"},
"AUD": {"code": "AUD", "symbol": "A$", "decimals": 2, "country": "AU"},
"JPY": {"code": "JPY", "symbol": "¥", "decimals": 0, "country": "JP"}
}
},
"message": "Configuration retrieved successfully",
"timestamp": "2025-11-25T..."
}Retrieves current user's locale and currency settings with translations.
Response:
{
"success": true,
"data": {
"locale": "en",
"currency": "USD",
"translations": {
"form.amount": "Amount",
"button.process_payment": "Process Payment",
"message.success": "Payment Successful!"
},
"supportedLocales": {...},
"supportedCurrencies": {...}
},
"timestamp": "2025-11-25T..."
}Updates user locale and currency preferences.
Request:
{
"locale": "es",
"currency": "EUR"
}Response:
{
"success": true,
"data": {
"locale": "es",
"currency": "EUR",
"translations": {...}
},
"message": "Locale preferences updated",
"timestamp": "2025-11-25T..."
}Processes a payment with the provided token and localized preferences.
Request:
{
"payment_token": "PMT_xxx",
"amount": 25.00,
"currency": "EUR",
"locale": "es"
}Response (Success):
{
"success": true,
"data": {
"transactionId": "txn_xxx",
"amount": 25.00,
"currency": "EUR",
"status": "CAPTURED",
"reference": "ref_xxx",
"timestamp": "2025-11-25T..."
},
"message": "¡Pago Exitoso!",
"timestamp": "2025-11-25T..."
}Response (Error):
{
"success": false,
"message": "Pago fallido: Fondos insuficientes",
"error_code": "API_ERROR",
"timestamp": "2025-11-25T..."
}- en (English) - Default currency: USD
- es (Spanish/Español) - Default currency: EUR
- fr (French/Français) - Default currency: EUR
- de (German/Deutsch) - Default currency: EUR
- pt (Portuguese/Português) - Default currency: EUR
- USD (US Dollar) - Symbol: $, Decimals: 2, Country: US
- EUR (Euro) - Symbol: €, Decimals: 2, Country: GB
- GBP (British Pound) - Symbol: £, Decimals: 2, Country: GB
- CAD (Canadian Dollar) - Symbol: C$, Decimals: 2, Country: CA
- AUD (Australian Dollar) - Symbol: A$, Decimals: 2, Country: AU
- JPY (Japanese Yen) - Symbol: ¥, Decimals: 0, Country: JP
- Session storage (if user previously selected)
- Accept-Language HTTP header (browser preference)
- Default: English (en)
- Session storage (if user previously selected)
- Default currency for detected locale
- Default: USD
Browser → POST /config → Server generates GP API token
→ Detects locale from Accept-Language header
→ Returns token + locale/currency + translations
Frontend → Initializes GP API SDK with token
→ Sets UI language based on locale
→ Formats currency based on currency code
User selects language → POST /api/locale with new preferences
→ Server updates express-session
→ Returns new translations
Frontend → Updates UI with new language
User enters card → GP API SDK tokenizes (client-side)
→ Returns payment_token
Frontend → POST /process-payment with token + amount + currency + locale
Server → Reconfigures SDK with dynamic country code based on currency
→ Processes payment
→ Returns localized success/error message
This implementation uses express-session middleware to persist user preferences:
Configuration (in server.js):
app.use(session({
secret: process.env.SESSION_SECRET || 'gp-api-localization-secret-key',
resave: false,
saveUninitialized: true,
cookie: {
secure: process.env.NODE_ENV === 'production',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));Session Data Stored:
locale- User's selected language codecurrency- User's selected currency code
Persistence:
- 24-hour timeout
- Memory-based session storage (default)
- Secure cookies in production (HTTPS required)
- Cookie survives browser restart
Production Considerations:
- Use persistent session store (Redis, MongoDB) instead of memory store
- Enable
secure: truewith HTTPS - Consider
httpOnlyandsameSitecookie options
Node.js implementation uses the SDK's generateTransactionKey() method for secure token generation:
import { GpApiConfig, GpApiService, Channel, Environment } from 'globalpayments-api';
// Configure GP API for token generation
const config = new GpApiConfig();
config.appId = process.env.GP_API_APP_ID || '';
config.appKey = process.env.GP_API_APP_KEY || '';
config.environment = Environment.TEST;
config.channel = Channel.CardNotPresent;
config.country = 'US';
config.permissions = ['PMT_POST_Create_Single'];
config.secondsToExpire = 600;
// Generate access token using SDK
const accessTokenInfo = await GpApiService.generateTransactionKey(config);
const accessToken = accessTokenInfo.accessToken;Benefits of SDK Approach:
- Consistent behavior with other SDK implementations
- Proper error handling built into SDK
- Automatic nonce and secret generation
- Matches PHP, .NET, and Java implementations
- User opens page → Detects English browser → Shows English UI with USD currency
- Enters test card
4263 9826 4026 9299 - Clicks "Process Payment"
- Receives: "Payment Successful! Transaction ID: txn_xxx"
- User selects "Español" from language dropdown
- UI updates to Spanish
- User selects "EUR" from currency dropdown
- Amount shows as "25,00 €" (European formatting)
- Enters test card
- Clicks "Procesar Pago"
- Receives: "¡Pago Exitoso! ID de transacción: txn_xxx"
- User selects "English" language
- User selects "JPY" currency
- Amount shows as "¥2,500" (no decimals for JPY)
- Processes payment
- GP API routes to Japan (JP country code)
-
Create translation file:
cp translations/en.json translations/it.json
-
Translate all keys in
translations/it.json:{ "form.amount": "Importo", "button.process_payment": "Elabora Pagamento", "message.success": "Pagamento Riuscito!" } -
Update services/LocaleService.js - Add to supported locales:
const SUPPORTED_LOCALES = { // ... existing locales it: { code: 'it', name: 'Italian', nativeName: 'Italiano', defaultCurrency: 'EUR' } };
-
Update frontend
wwwroot/js/translations.js:const translations = { // ... existing it: { /* Italian translations */ } };
-
Update HTML language selector in
index.html:<option value="it">🇮🇹 Italiano</option>
-
Update services/CurrencyConfig.js:
const SUPPORTED_CURRENCIES = { // ... existing CHF: { code: 'CHF', symbol: 'CHF', decimals: 2, country: 'CH' } };
-
Update frontend
wwwroot/js/currency-formatter.js:const currencies = { // ... existing CHF: { code: 'CHF', symbol: 'CHF', decimals: 2, country: 'CH' } };
-
Update HTML currency selector:
<option value="CHF">🇨🇭 CHF - Swiss Franc (CHF)</option>
-
Verify GP API support for the country code
Use these test cards for different scenarios:
Visa - Successful:
- Card Number:
4263 9826 4026 9299 - Expiry: Any future date (e.g.,
12/25) - CVV: Any 3 digits (e.g.,
123)
Visa - Declined (Insufficient Funds):
- Card Number:
4000120000001154
Mastercard - Successful:
- Card Number:
5425 2334 2424 1200
- Change browser language to Spanish (es)
- Reload page
- Verify UI is in Spanish
- Verify default currency is EUR
- Select language: French, currency: CAD
- Reload page (F5)
- Verify French + CAD are still selected
- Close tab, reopen within 24 hours
- Verify preferences persisted
- USD:
$25.00(before, period decimal) - EUR:
25,00 €(after, comma decimal) - JPY:
¥2500(no decimals)
# Get config
curl -X POST http://localhost:8000/config \
-H "Accept-Language: es-ES,es;q=0.9"
# Process payment
curl -X POST http://localhost:8000/process-payment \
-H "Content-Type: application/json" \
-d '{
"payment_token": "PMT_xxx",
"amount": 25.00,
"currency": "EUR",
"locale": "es"
}'Cause: Invalid GP API credentials
Solution:
- Verify
.envfile exists and has correct variables:GP_API_APP_ID=your_actual_app_id GP_API_APP_KEY=your_actual_app_key
- Verify credentials are for GP API (not Portico/Heartland)
- Check credentials at https://developer.globalpayments.com/
Cause: Credentials not recognized by GP API
Solution:
- Ensure you're using GP API credentials (not Heartland/Portico)
- Verify APP_ID and APP_KEY are correct
- Check environment is set to
sandboxfor test credentials
Cause: Session configuration issue or memory store limitations
Solution:
- Verify express-session middleware is configured in
server.js - Check browser console for cookie errors
- Enable cookies in browser
- For production, use persistent session store (Redis/MongoDB):
import RedisStore from 'connect-redis'; import { createClient } from 'redis'; const redisClient = createClient(); redisClient.connect().catch(console.error); app.use(session({ store: new RedisStore({ client: redisClient }), secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false }));
Cause: Browser doesn't support Intl.NumberFormat or incorrect locale
Solution:
- Update browser to latest version
- Check browser console for Intl errors
- Verify locale code is correct (e.g.,
en, noten-US)
Cause: Translation file missing or malformed JSON
Solution:
- Verify translation file exists:
translations/{locale}.json - Validate JSON syntax (use JSON validator)
- Check browser console for JSON parsing errors
- Ensure translation keys match frontend expectations
Cause: Missing "type": "module" in package.json
Solution:
- Verify
package.jsoncontains:{ "type": "module" } - Use
.jsextension for all imports - Use
importinstead ofrequire()
This is a demonstration implementation. For production use, add:
- Input Validation: Validate all user inputs (amount, currency codes, locale codes)
- Rate Limiting: Prevent brute force attacks on payment endpoint
- CSRF Protection: Implement CSRF tokens for state-changing operations
- Security Headers: Add helmet middleware for security headers
import helmet from 'helmet'; app.use(helmet());
- Logging: Implement comprehensive request/response logging
- PCI Compliance: Ensure no card data touches your server (token-based only)
- HTTPS: Use TLS in production (required for payment processing)
- Environment Variables: Use secure secrets management (AWS Secrets Manager, Azure Key Vault)
- Session Store: Use Redis or database-backed sessions (not memory store)
- CORS: Configure CORS properly for production domains
- Global Payments Developer Portal
- GP API Documentation
- Node.js SDK Documentation
- GP API Reference
- Express.js Documentation
- express-session Documentation
MIT License - See LICENSE file for details