v1.2.1 — Now on Maven Central

Nepal's Payment Gateways.
One Library. Zero Boilerplate.

The first production-grade Java starter for Khalti, eSewa, ConnectIPS, and Fonepay. Blocking, reactive, retry, metrics, and security — trusted by Nepal's developer community.

◆ ◆ ◆
4
Gateways
400+
Tests
3
Starters
0
Config classes
Quick Install — no <repositories> block needed
Spring Boot 3.2+ (Java 17+)
<dependency>
    <groupId>io.github.sujankim</groupId>
    <artifactId>nepal-pay-spring-boot-3-starter</artifactId>
    <version>1.2.0</version>
</dependency>
Spring Boot 4.x (Java 21+)
<dependency>
    <groupId>io.github.sujankim</groupId>
    <artifactId>nepal-pay-spring-boot-4-starter</artifactId>
    <version>1.2.0</version>
</dependency>
Spring WebFlux — Reactive (Boot 3.2+)
<dependency>
    <groupId>io.github.sujankim</groupId>
    <artifactId>nepal-pay-spring-boot-reactive-starter</artifactId>
    <version>1.2.0</version>
</dependency>
Spring Boot 3.2+
implementation 'io.github.sujankim:nepal-pay-spring-boot-3-starter:1.2.1'
Spring Boot 4.x
implementation 'io.github.sujankim:nepal-pay-spring-boot-4-starter:1.2.1'
Spring WebFlux — Reactive
implementation 'io.github.sujankim:nepal-pay-spring-boot-reactive-starter:1.2.1'
Spring Boot 3.2+ — Blocking
implementation("io.github.sujankim:nepal-pay-spring-boot-3-starter:1.2.1")
Spring Boot 4.x — Blocking
implementation("io.github.sujankim:nepal-pay-spring-boot-4-starter:1.2.1")
Spring WebFlux — Reactive
implementation("io.github.sujankim:nepal-pay-spring-boot-reactive-starter:1.2.1")

All starters share the same nepalpay.* YAML — same config, switch artifact to migrate.  ·  Full install guide →

Built for Nepal's developers.
Ready for production.

After integrating Nepal payment gateways from scratch three different times — the same HMAC debugging, the same RSA certificate handling — NepalPay was built to solve this once and for all.

5-Minute Integration

Add dependency → configure keys → inject and use. No @Bean, no config class. Auto-configured by Spring Boot.

🔄

Blocking + Reactive

Spring MVC or WebFlux — same YAML, same API pattern. Reactive returns Mono<T> via WebClient.

🔐

Secure by Design

HMAC-SHA256, HMAC-SHA512, RSA-SHA256, constant-time comparison, server-side verification — all built in.

🔁

Smart Retry

Exponential backoff with jitter. Never retries 4xx or signature failures. Opt-in, configurable per gateway.

📊

Micrometer Metrics

Operation timers, per-operation retry counters, HMAC failure alerts. Auto-registered when Actuator is on the classpath.

🩺

Actuator Health

Every configured gateway under /actuator/health. Config-only health check — no outbound pings.

All four Nepal payment gateways

One library. Consistent API across Khalti, eSewa, ConnectIPS, and Fonepay — blocking and reactive.

Three lines. Not three hundred.

Khalti payment initiation — the honest comparison.

❌ Without NepalPay — 90+ lines per gateway
HttpClient client = HttpClient.newHttpClient();

String json = """
    {
      "return_url": "%s",
      "website_url": "%s",
      "amount": %d,
      "purchase_order_id": "%s",
      "purchase_order_name": "%s"
    }
    """.formatted(returnUrl, url, amount, id, name);

HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create(
        "https://dev.khalti.com/api/v2/epayment/initiate/"))
    .header("Authorization", "Key " + secretKey)
    .header("Content-Type", "application/json")
    .POST(BodyPublishers.ofString(json))
    .build();

// parse manually, handle errors manually,
// write HMAC for eSewa manually,
// repeat for every new project...
✅ With NepalPay — auto-configured
// Zero @Bean. Zero @EnableNepalPay.
@Service
@RequiredArgsConstructor
public class PaymentService {

    private final KhaltiClient khaltiClient;

    public String initiate(
            String orderId, long nprAmount) {

        var res = khaltiClient.initiatePayment(
            KhaltiInitiateRequest.builder()
                .amount(nprAmount * 100L)
                .purchaseOrderId(orderId)
                .purchaseOrderName("Pro Plan")
                .build()
        );

        // Save pidx to DB, then redirect:
        return res.paymentUrl();
    }
}

Three starters — one API

All share the same nepalpay.* YAML. Switch the artifact name to migrate. Only the HTTP client and return type differ.

Spring Boot 3

Blocking — Boot 3

RestClient-based. Use with Spring MVC. Jackson 2 internally. Java 17+.

nepal-pay-spring-boot-3-starter Spring Boot 3.2+ · Java 17+
Spring Boot 4

Blocking — Boot 4

Same API as Boot 3. Jackson 3 internally. Required for Spring Boot 4.x.

nepal-pay-spring-boot-4-starter Spring Boot 4.x · Java 21+
WebFlux

Reactive — WebFlux

WebClient-based. Returns Mono<T>. Use with Spring WebFlux.

nepal-pay-spring-boot-reactive-starter Spring Boot 3.2+ · Java 17+

Observability — metrics + health

Add spring-boot-starter-actuator — NepalPay automatically instruments every gateway and registers a health indicator per configured gateway.

Operation Timers

Every HTTP call timed and tagged by gateway, operation, status. P99 in Grafana.

🔢

Per-Operation Retry Counters

Initiate, lookup, and refund each get their own retry counter — not a shared one.

🛡

Signature Failure Alert

nepalpay.esewa.callback
.signature.failed
— spike means tampering.

🩺

Health per Gateway

Each gateway under /actuator/health. Config-only — no outbound pings.

Reactive-Safe Timing

Timer.Sample + doOnSuccess/doOnError. Fully non-blocking throughout.

🔘

Opt-Out Anytime

nepalpay.metrics.enabled=false or nepalpay.health.enabled=false. No Actuator = nothing registers.

# 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

Exponential backoff — opt-in per gateway

Disabled by default. Enable for any HTTP-backed gateway with four YAML lines.

nepalpay:
  khalti:
    retry:
      enabled: true
      max-attempts: 3
      initial-delay-ms: 500
      multiplier: 2.0
      max-delay-ms: 5000
Gateway Retry Applies To Retry?
Khalti initiatePayment() lookupPayment() refundPayment() ✅ Yes
eSewa checkStatus() inside verifyCallback() ✅ Yes
ConnectIPS validateTransaction() ✅ Yes
Fonepay URL redirect — no HTTP calls ❌ N/A

Retries: 5xx + network timeouts  ·  Never: 4xx + signature failures  ·  Refund safety: verify with lookupPayment() before retrying to avoid double-refunds.

Different amount unit per gateway

The most common source of integration bugs. Each gateway expects a different Java type and amount unit.

Khalti
Paisa
NPR 100 → 10000L
eSewa
NPR
NPR 100 → BigDecimal("100.00")
Fonepay
NPR
NPR 100 → 100.0
ConnectIPS
Paisa
NPR 100 → 10000L

Never trust redirect parameters alone

Gateway redirect URL parameters can be faked by anyone. Always verify server-side.

🔐

Server-side Only

Secret keys never leave your backend. lookupPayment() and verifyCallback() enforce server-side confirmation.

🔏

HMAC Verification

eSewa and Fonepay signatures verified before marking payment successful. Constant-time comparison — timing-safe.

💰

Amount Validation

isAmountValid(expectedPaisa) on Khalti lookup detects tampered amounts. Always call after success.

Never trust redirect URL parameters alone — ?status=Completed, ?PS=success, ?data=BASE64 can all be faked.

Never put secret keys in frontend code or hardcode them in source. Use environment variables for all credentials.

Never commit CREDITOR.pfx to Git. Add *.pfx to .gitignore before your first commit.

Read the full security guide →