ForhoppPay Connect — Hosted Checkout
Feature Reference: FR-9 | Service:
MerchantCheckoutService,SandboxPaymentSimulator| Controller:ConnectCheckoutController
1. Overview
ForhoppPay Connect provides hosted checkout pages at pay.forhopp.com/checkout/{charge_id}. These are publicly accessible pages where end customers complete payments. The checkout service handles page data retrieval, payment method selection, sandbox simulation, and redirect logic.
2. Checkout URL
https://pay.forhopp.com/checkout/{charge_id}
Generated automatically when a charge is created. The URL is included in the charge response as checkout_url.
3. Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/checkout/{chargeId} |
None (public) | Get checkout page data |
POST |
/checkout/{chargeId}/complete |
None (public) | Complete payment |
POST |
/checkout/{chargeId}/cancel |
None (public) | Cancel checkout |
These endpoints are publicly accessible — they are used by end customers, not merchants.
4. Checkout Page Data
4.1 Request
GET /checkout/ch_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
4.2 Response
{
"chargeId": "ch_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"amountCents": 5000,
"currency": "USD",
"formattedAmount": "$50.00",
"description": "Order #12345",
"merchantName": "Acme Corp",
"merchantLogoUrl": "https://acme.com/logo.png",
"merchantBrandColor": "#4F46E5",
"merchantSupportEmail": "support@acme.com",
"returnUrl": "https://acme.com/success",
"cancelUrl": "https://acme.com/cancel",
"availablePaymentMethods": ["card", "paypal"],
"status": "pending",
"valid": true,
"expiresAt": 1711900800,
"testMode": false,
"metadata": { "order_id": "12345" }
}
4.3 Invalid States
The checkout page returns valid: false with an error message for:
| Condition | Error Message |
|---|---|
| Invalid charge ID format | "Invalid charge ID format" |
| Charge not found | "Charge not found" |
| Session expired (24h) | "This checkout session has expired" |
| Already authorized/captured | "This payment has already been processed" |
| Failed | "This payment has failed" |
| Voided | "This payment has been cancelled" |
| Refunded | "This payment has been refunded" |
| Disputed | "This payment is under dispute" |
5. Checkout Completion
5.1 Request
POST /checkout/ch_xxx/complete
Content-Type: application/json
{
"paymentMethod": "card",
"paymentMethodDetails": {
"card_number": "4242424242424242",
"exp_month": 12,
"exp_year": 2030,
"cvc": "123"
}
}
5.2 Success Response
{
"success": true,
"chargeId": "ch_xxx",
"status": "authorized",
"redirectUrl": "https://merchant.com/success",
"testMode": false
}
5.3 Failure Response
{
"success": false,
"chargeId": "ch_xxx",
"status": "failed",
"redirectUrl": "https://merchant.com/cancel",
"errorCode": "generic_decline",
"errorMessage": "Your card was declined.",
"testMode": true
}
5.4 Processing Flow
1. Validate charge ID format
2. Find charge in database
3. Check not expired (24h)
4. Check status is PENDING
5. Validate payment method ∈ {card, paypal}
6. Route by environment:
├─ TEST → SandboxPaymentSimulator
└─ LIVE → Real payment processing
7. Update charge with payment details
8. Dispatch webhook (charge.completed or charge.failed)
9. Return redirect URL
6. Sandbox Payment Simulation
When the charge environment is TEST, the SandboxPaymentSimulator handles payment processing. No real payments are ever made for test keys.
6.1 Test Card Numbers
| Card Number | Result | Error Code |
|---|---|---|
4242424242424242 |
Success | — |
4000000000000002 |
Declined | generic_decline |
4000000000009995 |
Declined | insufficient_funds |
4000000000000069 |
Declined | expired_card |
4000000000000127 |
Declined | incorrect_cvc |
4000000000000119 |
Error | processing_error |
4000000000003220 |
Requires 3DS | authentication_required |
Any other card starting with 4 and length ≥ 13 simulates success.
6.2 Test PayPal Accounts
| Result | |
|---|---|
test-success@sandbox.paypal.com |
Success |
test-decline@sandbox.paypal.com |
Declined |
test-pending@sandbox.paypal.com |
Pending |
Any other valid email simulates success in sandbox.
6.3 Sandbox Response Details
Successful sandbox payments include simulated payment method details:
{
"type": "card",
"brand": "visa",
"last4": "4242",
"exp_month": 12,
"exp_year": 2030,
"funding": "credit",
"country": "US"
}
Transaction IDs are prefixed with sandbox_txn_.
7. Checkout Cancellation
POST /checkout/ch_xxx/cancel
Returns the cancel_url for redirect. The charge status is not changed (remains PENDING and will eventually expire).
8. Redirect Behavior
| Outcome | Redirect To |
|---|---|
| Payment succeeds | return_url from charge |
| Payment fails | cancel_url from charge |
| Customer cancels | cancel_url from charge |
| Session expired | cancel_url from charge |
9. Merchant Branding
The checkout page displays merchant branding from merchant_profiles:
| Field | Usage |
|---|---|
business_name |
Displayed as merchant name |
logo_url |
Merchant logo on checkout page |
brand_color |
Primary color for buttons/accents |
support_email |
Contact link on checkout page |
If no merchant profile exists, defaults to "Merchant" with no logo.
10. Currency Formatting
| Currency | Format Example |
|---|---|
| USD | $50.00 |
| EUR | €50.00 |
| GBP | £50.00 |
| CAD | CA$50.00 |
| AUD | A$50.00 |
| JPY | ¥5000 (no decimals) |
| CHF | CHF 50.00 |
11. Webhook Dispatch
After checkout completion, webhooks are dispatched asynchronously:
- Success →
charge.completedevent - Failure →
charge.failedevent
Webhook dispatch errors are caught and logged but do not affect the checkout response.
12. Correctness Properties
| # | Property | Validates |
|---|---|---|
| 17 | Success redirects to return_url; cancel redirects to cancel_url | FR-9.3, FR-9.4 |
| 18 | No real payments for test keys; environment=test marking | FR-10.1 |
| 19 | Test card 4242... succeeds; 4000...0002 declines; 4000...9995 insufficient funds | FR-10.2 |
Last Updated: March 2026
