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.duration
nepalpay.khalti.payment.lookup.duration
nepalpay.khalti.payment.refund.duration
nepalpay.khalti.retry.attempts{operation=initiate|lookup|refund}
eSewa nepalpay.esewa.callback.verify.duration
nepalpay.esewa.status.check.duration
nepalpay.esewa.callback.signature.failed
nepalpay.esewa.retry.attempts
ConnectIPS nepalpay.connectips.validate.duration nepalpay.connectips.retry.attempts
Fonepay None โ€” no HTTP calls nepalpay.fonepay.redirect.built
nepalpay.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.