Jakarta EE/Servlet implementation of embedded payments processing with automatic fee splitting using the Global Payments GP-API. Card data is submitted directly to the server — no hosted fields or client-side tokenization required.
- Java 17+
- Maven 3.8+
- Global Payments developer account with GP-API credentials
java/
├── src/
│ └── main/
│ ├── java/com/globalpayments/example/
│ │ ├── ProcessPaymentServlet.java # POST /process-embedded-payments-payment
│ │ ├── services/
│ │ │ ├── SellerManager.java # Loads and validates sellers
│ │ │ └── SplitCalculator.java # Fee split logic
│ │ └── models/
│ │ ├── Seller.java # Seller entity
│ │ └── SplitDetails.java # Fee split result model
│ └── webapp/
│ ├── index.html # Payment form frontend
│ └── WEB-INF/web.xml # Servlet configuration
├── data/
│ └── sellers.json # Mock seller registry
├── pom.xml # com.globalpayments:java-sdk dependency
├── .env.sample
├── Dockerfile
├── run.sh
├── .devcontainer/
└── .codesandbox/
1. Build the project
mvn clean package2. Configure credentials
cp .env.sample .envEdit .env:
GP_APP_ID=your_app_id_here
GP_APP_KEY=your_app_key_here
GP_API_ENVIRONMENT=TEST
PLATFORM_FEE_RATE=103. Start the server
mvn jetty:run
# Open http://localhost:8000Or use the convenience script:
./run.sh| Variable | Description | Required | Example |
|---|---|---|---|
GP_APP_ID |
GP-API application ID | yes | P21Reaz4vIdxKiGB9sRY1lzuM8aK |
GP_APP_KEY |
GP-API application key | yes | oCGX7NwcNREGTjsq |
GP_API_ENVIRONMENT |
TEST or PRODUCTION |
no | TEST |
PLATFORM_FEE_RATE |
Default platform fee % (5–25) | no | 10 |
Export credentials before running:
export GP_APP_ID=your_app_id
export GP_APP_KEY=your_app_key
mvn jetty:runConfigured at servlet initialization in ProcessPaymentServlet.java:
import com.global.api.ServicesContainer;
import com.global.api.serviceConfigs.GpApiConfig;
import com.global.api.entities.enums.Environment;
import com.global.api.entities.enums.Channel;
GpApiConfig config = new GpApiConfig();
config.setAppId(System.getenv("GP_APP_ID"));
config.setAppKey(System.getenv("GP_APP_KEY"));
config.setEnvironment(Environment.TEST);
config.setChannel(Channel.CardNotPresent);
config.setCountry("US");
ServicesContainer.configureService(config);Processes a charge and returns a transaction ID with fee split breakdown.
Request fields (JSON body):
| Field | Type | Required | Description |
|---|---|---|---|
card_name |
string | yes | Cardholder name |
card_number |
string | yes | Card number (spaces stripped) |
card_expiry |
string | yes | Expiry in MM/YY format |
card_cvv |
string | yes | CVV / security code |
billing_zip |
string | yes | Billing postal code |
amount |
string | yes | Amount (minimum "0.50") |
seller_id |
string | yes | Seller ID from data/sellers.json |
platform_fee_rate |
double | no | Platform fee % 5–25 (default: env value or 10) |
Example request:
{
"card_name": "Jane Doe",
"card_number": "4263970000005262",
"card_expiry": "12/26",
"card_cvv": "123",
"billing_zip": "12345",
"amount": "100.00",
"seller_id": "seller_001",
"platform_fee_rate": 10
}Success response (200):
{
"success": true,
"message": "Payment successful! Transaction ID: TXN_ABC123",
"data": {
"transactionId": "TXN_ABC123",
"amount": 100.00,
"currency": "USD",
"splitDetails": {
"amount": 100.00,
"processingFee": 3.20,
"platformFee": 10.00,
"sellerPayout": 86.80,
"sellerId": "seller_001",
"sellerName": "Tech Gadgets Store"
}
}
}Error response (400):
{
"success": false,
"message": "Payment processing failed",
"error": {
"code": "API_ERROR",
"details": "Error message"
}
}// SplitCalculator logic
BigDecimal processingFee = amount.multiply(new BigDecimal("0.029"))
.add(new BigDecimal("0.30"));
BigDecimal platformFee = amount.multiply(platformFeeRate.divide(new BigDecimal("100")));
BigDecimal sellerPayout = amount.subtract(processingFee).subtract(platformFee);| Component | Formula | Example ($100.00, 10%) |
|---|---|---|
| Processing Fee | (amount × 2.9%) + $0.30 |
$3.20 |
| Platform Fee | amount × platformFeeRate |
$10.00 |
| Seller Payout | amount − processingFee − platformFee |
$86.80 |
// 1. Validate fields and seller
if (!sellerManager.isValidSeller(sellerId)) { /* 400 */ }
Seller seller = sellerManager.getSellerById(sellerId);
// 2. Calculate fee split
SplitCalculator calculator = new SplitCalculator(platformFeeRate);
SplitDetails splitDetails = calculator.calculateSplit(amount);
// 3. Parse expiry and build CreditCardData
String[] expiryParts = cardExpiry.split("/");
CreditCardData card = new CreditCardData();
card.setCardHolderName(cardName);
card.setNumber(cardNumber.replaceAll("\\s", ""));
card.setExpMonth(Integer.parseInt(expiryParts[0]));
card.setExpYear(Integer.parseInt("20" + expiryParts[1]));
card.setCvn(cardCvv);
// 4. Process charge
Address address = new Address();
address.setPostalCode(sanitizePostalCode(billingZip));
Transaction response = card.charge(amount)
.withAllowDuplicates(true)
.withCurrency("USD")
.withAddress(address)
.execute();| Brand | Card Number | CVV | Expiry |
|---|---|---|---|
| Visa | 4263970000005262 | 123 | Any future date |
| Mastercard | 5425230000004415 | 123 | Any future date |
| Discover | 6011000000000087 | 123 | Any future date |
| Amex | 374101000000608 | 1234 | Any future date |
docker build -t embedded-fee-splitting-java .
docker run -p 8004:8000 \
-e GP_APP_ID=your_app_id \
-e GP_APP_KEY=your_app_key \
-e GP_API_ENVIRONMENT=TEST \
embedded-fee-splitting-java
# Open http://localhost:8004Or via docker-compose from the project root:
docker-compose up java"Invalid seller selected" (400)
seller_id must match an entry in data/sellers.json. The form populates the seller dropdown automatically — direct API callers must use exact seller IDs from the file.
"Missing required fields" (400)
All of card_name, card_number, card_expiry, card_cvv, billing_zip, amount, and seller_id are required. Confirm all fields are present in the JSON request body.
"Payment processing failed" — GP-API error
Confirm GP_APP_ID and GP_APP_KEY are exported as shell environment variables before running mvn jetty:run. System environment variables take precedence over .env values in Java. Use test card numbers — the TEST environment does not process real cards.
Maven build fails
Requires Java 17+ and Maven 3.8+. Confirm with java -version and mvn -v. If the com.globalpayments:java-sdk dependency fails to resolve, run mvn clean package -U to force a fresh dependency download.
Port conflict on startup
The project configures Jetty to use port 8000. If that port is occupied, stop the conflicting process with lsof -i :8000 or update the port configuration in pom.xml under the Jetty plugin settings.
Expiry date rejected
card_expiry must be MM/YY — two digits, slash, two digits. The backend splits on / and prepends 20 to the year. Formats like 12/2026 or 1226 will fail parsing.