Khalti Properties
KhaltiClient (and
KhaltiReactiveClient) is created when
nepalpay.khalti.secret-key is present.
| Property | Required | Default | Description |
|---|---|---|---|
secret-key |
โ | โ | Khalti secret key. Creates bean when present. |
return-url |
โ | โ | URL Khalti redirects to after payment. |
website-url |
โ | โ | Your merchant website URL. |
sandbox |
โ | true |
true = dev.khalti.com ยท false = khalti.com |
timeout-seconds |
โ | 10 |
HTTP connect and read timeout in seconds. |
retry.* |
โ | disabled | See Retry Config โ |
eSewa Properties
EsewaClient (and
EsewaReactiveClient) is created when
nepalpay.esewa.secret-key is present.
| Property | Required | Default | Description |
|---|---|---|---|
secret-key |
โ | โ | HMAC-SHA256 secret key. Sandbox: 8gBm/:&EnhH.1/q |
product-code |
โ | โ | Sandbox: EPAYTEST ยท Production: your merchant code |
success-url |
โ | โ | Redirect on successful payment with ?data=BASE64 |
failure-url |
โ | โ | Redirect on failed or canceled payment |
sandbox |
โ | true |
true = rc-epay.esewa.com.np ยท false = epay.esewa.com.np |
timeout-seconds |
โ | 10 |
HTTP timeout for status API calls in seconds |
retry.* |
โ | disabled | Applies to checkStatus() inside verifyCallback() |
ConnectIPS Properties
Requires NCHL merchant registration before use. Contact connectips@nchl.com.np .
| Property | Required | Default | Description |
|---|---|---|---|
merchant-id |
โ | โ | NCHL integer merchant ID. Creates bean when present. |
app-id |
โ | โ | Application ID from NCHL. |
app-name |
โ | โ | Application name from NCHL. |
app-password |
โ | โ | Used for HTTP Basic Auth on the validate API. |
pfx-path |
โ | โ | Spring Resource path. file:/app/CREDITOR.pfx or classpath:CREDITOR.pfx |
pfx-password |
โ | โ | Password for the CREDITOR.pfx file. |
sandbox |
โ | true |
true = uat.connectips.com ยท false = connectips.com |
timeout-seconds |
โ | 30 |
HTTP timeout in seconds. Default 30s โ intentionally longer because ConnectIPS validates via NCHL to bank systems. Increase for slower banks. |
retry.* |
โ | disabled | Applies to validateTransaction() |
Fonepay Properties
No retry or timeout for Fonepay. Fonepay uses URL redirect and makes zero server-to-server HTTP calls.
| Property | Required | Default | Description |
|---|---|---|---|
merchant-code |
โ | โ | Your Fonepay PID (merchant code). |
secret-key |
โ | โ | HMAC-SHA512 secret key. Creates bean when present. |
return-url |
โ | โ | Fonepay redirects here with all callback params after payment. |
sandbox |
โ | true |
true = dev.fonepay.com ยท false = fonepay.com |
Retry Configuration
Retry is disabled by default.
You must explicitly set enabled: true
per gateway. The blocking starter uses
Thread.sleep(). The reactive starter
uses Reactor's Retry.backoff() โ
fully non-blocking.
nepalpay:
khalti:
retry:
enabled: true # opt-in โ false by default
max-attempts: 3 # retries after first failure (4 total calls)
initial-delay-ms: 500 # wait 500ms before first retry
multiplier: 2.0 # 500 โ 1000 โ 2000ms
max-delay-ms: 5000 # never wait more than 5 seconds
What gets retried
| Scenario | Retried? | Reason |
|---|---|---|
| 5xx server errors | โ Yes | Gateway temporarily down โ may recover |
| Network timeout / connection reset | โ Yes | Transient network issue |
| 400 Bad Request | โ No | Invalid request โ retrying won't help |
| 401 Unauthorized | โ No | Wrong key โ retrying won't help |
| Signature failure | โ No | Security alert โ must never retry |
Retry Properties Reference
| Property | Default | Description |
|---|---|---|
retry.enabled |
false |
Must be true to activate. |
retry.max-attempts |
3 |
Retries after first failure. Total calls = max-attempts + 1. |
retry.initial-delay-ms |
500 |
Milliseconds before first retry. |
retry.multiplier |
2.0 |
Delay multiplier per retry. Use 1.0 for constant delay. Must be >= 1.0. |
retry.max-delay-ms |
5000 |
Maximum wait between retries in milliseconds. |
Exponential backoff with jitter
# initial-delay-ms=500, multiplier=2.0, max-delay-ms=5000, max-attempts=3
First call โ fails 5xx โ wait ~500ms (ยฑ10% jitter)
Retry 1 โ fails 5xx โ wait ~1000ms (500 ร 2.0)
Retry 2 โ fails 5xx โ wait ~2000ms (1000 ร 2.0)
Retry 3 โ fails 5xx โ throw exception โ no more retries
# Jitter: ยฑ10% random offset prevents thundering herd
# All clients retry on 4xx โ never
Micrometer Metrics (v1.2.0)
Add spring-boot-starter-actuator โ metrics
register automatically. Opt-out with:
nepalpay:
metrics:
enabled: false # opt-out โ true by default when Actuator present
| Gateway | Timers | Counters |
|---|---|---|
| Khalti | nepalpay.khalti.payment.initiate.durationnepalpay.khalti.payment.lookup.durationnepalpay.khalti.payment.refund.duration |
nepalpay.khalti.retry.attempts{operation=initiate|lookup|refund} |
| eSewa | nepalpay.esewa.callback.verify.durationnepalpay.esewa.status.check.duration |
nepalpay.esewa.callback.signature.failednepalpay.esewa.retry.attempts |
| ConnectIPS | nepalpay.connectips.validate.duration |
nepalpay.connectips.retry.attempts |
| Fonepay | None โ no HTTP calls | nepalpay.fonepay.redirect.builtnepalpay.fonepay.callback.verified{status=success|failed}nepalpay.fonepay.callback.signature.failed |
Example Grafana queries
# Khalti P99 initiation latency
histogram_quantile(0.99,
rate(nepalpay_khalti_payment_initiate_duration_seconds_bucket[5m])
)
# eSewa fraud alert โ HMAC mismatch spike
rate(nepalpay_esewa_callback_signature_failed_total[5m]) > 5
# ConnectIPS retry rate
rate(nepalpay_connectips_retry_attempts_total[5m])
Actuator Health Indicators (v1.2.0)
Each configured gateway exposes its own health component. Opt-out with:
nepalpay:
health:
enabled: false # opt-out โ true by default when Actuator present
Example response
GET /actuator/health
{
"status": "UP",
"components": {
"nepalpayKhalti": {
"status": "UP",
"details": { "gateway": "Khalti", "mode": "SANDBOX", "configured": true }
},
"nepalpayEsewa": {
"status": "UP",
"details": { "gateway": "eSewa", "mode": "SANDBOX", "configured": true }
},
"nepalpayConnectIps": {
"status": "UP",
"details": { "pfxLoaded": true, "configured": true }
},
"nepalpayFonepay": {
"status": "UP",
"details": { "note": "URL redirect โ no HTTP calls", "configured": true }
}
}
}
Health indicators are config-only
โ no outbound HTTP ping to the gateway APIs.
This avoids false DOWN states caused
by sandbox rate limits during startup health checks.
Real payment failures are tracked via Micrometer
counters.
Sandbox Configuration
nepalpay:
khalti:
secret-key: ${KHALTI_SECRET_KEY:test_key_placeholder}
return-url: http://localhost:8080/api/khalti/callback
website-url: http://localhost:8080
sandbox: true
esewa:
# Sandbox: ID=9806800001 Password=Nepal@123 MPIN=1122 Token=123456
secret-key: ${ESEWA_SECRET_KEY:8gBm/:&EnhH.1/q}
product-code: EPAYTEST
success-url: http://localhost:8080/api/esewa/callback
failure-url: http://localhost:8080/api/esewa/failed
sandbox: true
fonepay:
merchant-code: ${FONEPAY_MERCHANT_CODE}
secret-key: ${FONEPAY_SECRET_KEY}
return-url: http://localhost:8080/api/fonepay/callback
sandbox: true
# ConnectIPS โ uncomment when you have NCHL UAT credentials
# connectips:
# merchant-id: ${CONNECTIPS_MERCHANT_ID}
# app-id: ${CONNECTIPS_APP_ID}
# app-name: ${CONNECTIPS_APP_NAME}
# app-password: ${CONNECTIPS_APP_PASSWORD}
# pfx-path: ${CONNECTIPS_PFX_PATH}
# pfx-password: ${CONNECTIPS_PFX_PASSWORD}
# timeout-seconds: 30
# sandbox: true
Production Configuration
Never hardcode production keys. Always use environment variables. Never commit real credentials to Git.
nepalpay:
khalti:
secret-key: ${KHALTI_SECRET_KEY}
return-url: https://yourapp.com/api/khalti/callback
website-url: https://yourapp.com
sandbox: false # โ production
esewa:
secret-key: ${ESEWA_SECRET_KEY}
product-code: ${ESEWA_PRODUCT_CODE} # โ your real merchant code
success-url: https://yourapp.com/api/esewa/callback
failure-url: https://yourapp.com/payment/failed
sandbox: false # โ production
connectips:
merchant-id: ${CONNECTIPS_MERCHANT_ID}
app-id: ${CONNECTIPS_APP_ID}
app-name: ${CONNECTIPS_APP_NAME}
app-password: ${CONNECTIPS_APP_PASSWORD}
pfx-path: ${CONNECTIPS_PFX_PATH}
pfx-password: ${CONNECTIPS_PFX_PASSWORD}
timeout-seconds: 30
sandbox: false # โ production
fonepay:
merchant-code: ${FONEPAY_MERCHANT_CODE}
secret-key: ${FONEPAY_SECRET_KEY}
return-url: https://yourapp.com/api/fonepay/callback
sandbox: false # โ production
Environment Variables
# Khalti
KHALTI_SECRET_KEY=live_secret_key_here
KHALTI_RETURN_URL=https://yourapp.com/api/khalti/callback
YOUR_WEBSITE_URL=https://yourapp.com
# eSewa
ESEWA_SECRET_KEY=your_esewa_secret_key
ESEWA_PRODUCT_CODE=your_merchant_code
ESEWA_SUCCESS_URL=https://yourapp.com/api/esewa/callback
ESEWA_FAILURE_URL=https://yourapp.com/payment/failed
# ConnectIPS
CONNECTIPS_MERCHANT_ID=550
CONNECTIPS_APP_ID=MER-550-APP-1
CONNECTIPS_APP_NAME=YourApp
CONNECTIPS_APP_PASSWORD=your_app_password
CONNECTIPS_PFX_PATH=file:/app/CREDITOR.pfx
CONNECTIPS_PFX_PASSWORD=your_pfx_password
# Fonepay
FONEPAY_MERCHANT_CODE=your_merchant_pid
FONEPAY_SECRET_KEY=your_fonepay_key
FONEPAY_RETURN_URL=https://yourapp.com/api/fonepay/callback
| Platform | How to set |
|---|---|
| Render | Dashboard โ Service โ Environment โ Add variables |
| Railway | Project โ Variables โ Add variable |
| Heroku | heroku config:set KHALTI_SECRET_KEY=xxx |
| Docker | docker run -e KHALTI_SECRET_KEY=xxx yourimage |
| Kubernetes | Secret + envFrom in deployment YAML |
| Local dev | .env file โ add to .gitignore |
Override Beans
Every NepalPay bean uses
@ConditionalOnMissingBean. Define your
own bean and NepalPay steps aside automatically.
@Configuration
public class MyPaymentConfig {
// Override KhaltiClient โ NepalPay's auto-configured bean is skipped
@Bean
public KhaltiClient customKhaltiClient(
NepalPayProperties props,
RestClient.Builder builder) {
return new KhaltiClient(props.khalti(), builder);
}
}
Works for all four clients:
KhaltiClient,
EsewaClient,
ConnectIpsClient,
FonepayClient
โ and their reactive equivalents.