DEV Community

Sujan Lamichhane
Sujan Lamichhane

Posted on

I Built an Open-Source Java Library for Khalti, eSewa, ConnectIPS & Fonepay — Spring Boot 3 and 4 Supported

After integrating Nepal payment gateways from scratch three different times, I finally decided to stop copy-pasting the same code into every project and build a proper open-source library.

That library became NepalPay Spring Boot Starter.


How It Started

I have integrated Khalti into Java backends three different times.

The first time, I spent two full days:

  • Reading documentation
  • Building HTTP clients from scratch
  • Manually constructing JSON payloads
  • Debugging signature mismatches
  • Wondering why payments showed Completed but orders were never actually charged

Eventually it worked.

Then I moved on.

The second time, I copied the code from the first project.

I went through exactly the same debugging cycle.

The same confusing eSewa HMAC signatures.

The same ConnectIPS RSA certificates.

The same amount conversion issues.

The third time, I stopped and thought:

Why am I solving the same problems over and over again?

That's when I decided to build NepalPay Spring Boot Starter.


What Is NepalPay?

NepalPay is an open-source Spring Boot starter that lets you integrate Nepal's major payment gateways with almost zero boilerplate.

No manual HTTP clients.

No JSON string formatting.

No signature generation code.

No copy-pasting from scattered blog posts.

You simply inject the clients:

@Service
@RequiredArgsConstructor
public class PaymentService {

    private final KhaltiClient khaltiClient;
    private final EsewaClient esewaClient;
    private final ConnectIpsClient connectIpsClient;
    private final FonepayClient fonepayClient;
}
Enter fullscreen mode Exit fullscreen mode

Spring Boot auto-configures everything when it detects your credentials in application.yml.

No @EnableNepalPay.

No @Bean.

No configuration class.


Supported Gateways

Gateway Flow Security
Khalti API-first Server-side lookup
eSewa Form POST HMAC-SHA256 verification
ConnectIPS Form POST RSA-SHA256 signing
Fonepay URL Redirect HMAC-SHA512 verification

💳 Khalti — API First

Khalti follows a standard API-first payment flow:

Your Backend
      ↓
POST /initiate
      ↓
{ pidx, payment_url }
      ↓
Redirect user
      ↓
Khalti redirects back
      ↓
POST /lookup
      ↓
{ status: "Completed" }
      ↓
✅ Safe to mark as paid
Enter fullscreen mode Exit fullscreen mode

With NepalPay:

KhaltiInitiateResponse response =
    khaltiClient.initiatePayment(
        KhaltiInitiateRequest.builder()
            .amount(10000L)
            .purchaseOrderId("ORD-001")
            .purchaseOrderName("Pro Plan")
            .build()
    );

KhaltiLookupResponse lookup =
    khaltiClient.lookupPayment(response.pidx());

if (lookup.isPaymentSuccessful()) {
    // mark order as paid
}
Enter fullscreen mode Exit fullscreen mode

⚠️ Never trust ?status=Completed in the redirect URL.

Always verify with lookupPayment().


💸 eSewa — Form Submission + HMAC Signatures

eSewa works completely differently.

Backend
   ↓
Generate HMAC signed form
   ↓
Frontend POSTs to eSewa
   ↓
User pays
   ↓
eSewa redirects back
   ↓
Decode Base64 callback
   ↓
Verify HMAC
   ↓
Call status API
   ↓
✅ Payment confirmed
Enter fullscreen mode Exit fullscreen mode

Building the payload:

String uuid =
    EsewaClient.generateTransactionUuid();

EsewaFormPayload payload =
    esewaClient.buildFormPayload(
        new BigDecimal("100.00"),
        uuid
    );
Enter fullscreen mode Exit fullscreen mode

Verification:

EsewaClient.EsewaVerificationResult result =
    esewaClient.verifyCallback(data);

if (result.isPaymentSuccessful()) {
    // mark order as paid
}
Enter fullscreen mode Exit fullscreen mode

🏦 ConnectIPS — RSA Signatures and Bank Transfers

ConnectIPS is the most complex gateway.

It requires:

  • Merchant registration
  • .pfx certificate
  • RSA-SHA256 signatures
  • Strict field ordering

NepalPay handles all of this:

ConnectIpsFormPayload payload =
    connectIpsClient.buildFormPayload(
        ConnectIpsPaymentRequest.builder()
            .txnId("TXN-001")
            .amountNPR(100L)
            .referenceId("ORD-001")
            .remarks("Order payment")
            .build()
    );
Enter fullscreen mode Exit fullscreen mode

Verification:

ConnectIpsValidateResponse response =
    connectIpsClient.validateTransaction(
        txnId,
        referenceId,
        txnAmtPaisa
    );

if (response.isPaymentSuccessful()) {
    // mark order as paid
}
Enter fullscreen mode Exit fullscreen mode

🔵 Fonepay — HMAC-SHA512 URL Redirect

Fonepay is now supported in v0.4.0.

Backend
   ↓
Generate signed redirect URL
   ↓
Frontend redirects user
   ↓
User pays
   ↓
Fonepay redirects back
   ↓
Verify HMAC-SHA512 signature
   ↓
Check PS=success
   ↓
✅ Payment confirmed
Enter fullscreen mode Exit fullscreen mode

Building the redirect URL:

FonepayRedirectParams params =
    fonepayClient.buildRedirectParams(
        FonepayPaymentRequest.builder()
            .prn("FP-001")
            .amount(100.0)
            .remarks1("Pro Plan")
            .build()
    );
Enter fullscreen mode Exit fullscreen mode

Verification:

FonepayClient.FonepayVerificationResult result =
    fonepayClient.verifyCallback(callback);

if (result.isPaymentSuccessful()) {
    // mark order as paid
}
Enter fullscreen mode Exit fullscreen mode

The Amount Confusion Problem

Every gateway uses different amount units.

Gateway Unit Java Type
Khalti Paisa long
eSewa NPR BigDecimal
ConnectIPS Paisa amountNPR() auto-converts
Fonepay NPR double

NepalPay makes these differences explicit to prevent silent bugs.


Supporting Spring Boot 3 and Spring Boot 4

One challenge was supporting both Spring Boot versions.

Spring Boot 3 uses:

import com.fasterxml.jackson.databind.ObjectMapper;
Enter fullscreen mode Exit fullscreen mode

Spring Boot 4 uses:

import tools.jackson.databind.json.JsonMapper;
Enter fullscreen mode Exit fullscreen mode

Because of this, NepalPay uses a multi-module architecture:

nepal-pay-core/
├── models
├── exceptions
└── enums

nepal-pay-spring-boot-3-starter/
└── Jackson 2

nepal-pay-spring-boot-4-starter/
└── Jackson 3
Enter fullscreen mode Exit fullscreen mode

This allows the same APIs to work seamlessly across both Spring Boot generations.


Installation

Spring Boot 3.2+

Maven

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependency>
    <groupId>com.github.sujankim.nepal-pay-spring-boot-starter</groupId>
    <artifactId>nepal-pay-spring-boot-3-starter</artifactId>
    <version>v0.4.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Spring Boot 4.x

Maven

<dependency>
    <groupId>com.github.sujankim.nepal-pay-spring-boot-starter</groupId>
    <artifactId>nepal-pay-spring-boot-4-starter</artifactId>
    <version>v0.4.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Configuration

nepalpay:
  khalti:
    secret-key: ${KHALTI_SECRET_KEY}
    return-url: ${KHALTI_RETURN_URL}
    website-url: ${YOUR_WEBSITE_URL}
    sandbox: true

  esewa:
    secret-key: ${ESEWA_SECRET_KEY}
    product-code: ${ESEWA_PRODUCT_CODE}
    success-url: ${ESEWA_SUCCESS_URL}
    failure-url: ${ESEWA_FAILURE_URL}
    sandbox: true

  fonepay:
    merchant-code: ${FONEPAY_MERCHANT_CODE}
    secret-key: ${FONEPAY_SECRET_KEY}
    return-url: ${FONEPAY_RETURN_URL}
    sandbox: true
Enter fullscreen mode Exit fullscreen mode

That's it.

Spring Boot auto-configures all client beans.


Tech Stack

  • Java 17+
  • Spring Boot 3.2+
  • Spring Boot 4.x
  • Jackson 2 & 3
  • SLF4J
  • MockWebServer
  • Java Records
  • HexFormat

The library currently provides:

  • KhaltiClient
  • EsewaClient
  • ConnectIpsClient
  • FonepayClient
  • 80+ tests
  • Documentation website
  • Consumer demo application

What I Learned Building This

1. Security Matters

Redirect URLs should never be trusted.

Server-side verification and signature validation are essential.

2. Every Gateway Is Different

  • Khalti → API key
  • eSewa → HMAC-SHA256
  • ConnectIPS → RSA-SHA256
  • Fonepay → HMAC-SHA512

Abstraction removes this complexity from application developers.

3. Multi-Module Maven Was Worth It

Supporting Spring Boot 3 and 4 in one JAR became unnecessarily difficult.

Separate starters made dependencies cleaner and testing easier.

4. Nepal Needs More Open Source Tooling

There are excellent libraries for Stripe and PayPal.

There was little in the Java ecosystem for Nepal's payment gateways.

That gap felt worth filling.


What's Next

Feature Status
Khalti ✅ v0.1.0
eSewa ✅ v0.1.0
ConnectIPS ✅ v0.2.0
Spring Boot 3 Support ✅ v0.3.0
Fonepay ✅ v0.4.0
Khalti Refund API 🔲 Planned
Retry with Backoff 🔲 Planned
Maven Central 🔲 Planned

Try NepalPay

📖 Documentation

https://sujankim.github.io/nepal-pay-spring-boot-starter/

💻 GitHub

https://github.com/sujankim/nepal-pay-spring-boot-starter

🎯 JitPack

https://jitpack.io/#sujankim/nepal-pay-spring-boot-starter

If NepalPay saves you time, a ⭐ on GitHub goes a long way.

Questions? Open a Discussion.

Found a bug? Open an Issue.

Want to contribute? Open a PR.

Built with ❤️ for Nepal's developer community 🇳🇵

Top comments (0)