Requirements
โ ๏ธ Merchant registration required.
ConnectIPS requires registration with NCHL before use.
You cannot test or go live without NCHL credentials.
You will need from NCHL:
- Merchant ID (integer)
- Application ID and Application Name
- Application Password
- CREDITOR.pfx certificate file
- Password for the .pfx file
To register, contact your bank or NCHL directly: connectips@nchl.com.np
How ConnectIPS Works
โน๏ธ ConnectIPS uses a form-submission model similar
to eSewa, but uses RSA-SHA256 digital signatures
instead of HMAC-SHA256.
Backend: builds ConnectIpsFormPayload
(RSA-SHA256 signed with your .pfx private key)
โ return to frontend
Frontend: POSTs form to ConnectIPS gateway URL
โ
User completes bank transfer on ConnectIPS
โ
ConnectIPS redirects to your callback URL
โ
โ ๏ธ DO NOT trust this redirect alone!
โ
Backend: calls validateTransaction() with Basic Auth
(uses appId:appPassword for HTTP Basic Auth)
โ
โ
mark order as paid when status = "SUCCESS"
Key Difference from Khalti and eSewa
| Feature | Khalti | eSewa | ConnectIPS |
|---|---|---|---|
| Flow type | API-first | Form submit | Form submit |
| Signature | None (API key) | HMAC-SHA256 | RSA-SHA256 (.pfx) |
| Amount unit | Paisa | NPR | Paisa |
| Verify API auth | Bearer key | No auth needed | HTTP Basic Auth |
| Registration | Self-service | Self-service | Via NCHL/bank |
Configure
nepalpay:
connectips:
merchant-id: ${CONNECTIPS_MERCHANT_ID} # e.g. 550
app-id: ${CONNECTIPS_APP_ID} # e.g. "MER-550-APP-1"
app-name: ${CONNECTIPS_APP_NAME} # e.g. "MyStore"
app-password: ${CONNECTIPS_APP_PASSWORD}
pfx-path: ${CONNECTIPS_PFX_PATH} # e.g. file:/app/CREDITOR.pfx
pfx-password: ${CONNECTIPS_PFX_PASSWORD}
sandbox: true # false for production
โ ๏ธ Never commit your .pfx file to Git.
Add it to
.gitignore and load it via environment variable or
a secrets manager.
buildFormPayload()
@Service
@RequiredArgsConstructor
public class PaymentService {
private final ConnectIpsClient connectIpsClient;
public ConnectIpsFormPayload startPayment(String orderId, long amountNPR) {
String txnId = "TXN-" + orderId + "-" + System.currentTimeMillis();
// ConnectIPS uses PAISA โ multiply by 100
long amountPaisa = amountNPR * 100L;
// Store txnId in your database before returning!
ConnectIpsFormPayload payload = connectIpsClient.buildFormPayload(
txnId,
amountPaisa,
orderId, // referenceId โ your order ID
"Order payment", // remarks
"NepalPay" // particulars
);
return payload;
// Return to frontend โ frontend POSTs form to payload.formActionUrl()
}
}
โ ๏ธ Amount is in Paisa.
ConnectIPS uses the same unit as Khalti.
NPR 100 โ send 10000.
Frontend Form Submission
ConnectIPS uses the same form-submission pattern as eSewa.
POST all payload fields to form_action_url.
// Angular/TypeScript:
initiateConnectIps(payload: ConnectIpsFormPayload): void {
const form = document.createElement('form');
form.method = 'POST';
form.action = payload.form_action_url;
const fields = ['MERCHANTID', 'APPID', 'APPNAME', 'TXNID',
'TXNDATE', 'TXNCRNCY', 'TXNAMT', 'REFERENCEID',
'REMARKS', 'PARTICULARS', 'TOKEN'];
fields.forEach(field => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = field;
input.value = (payload as any)[field.toLowerCase()] ?? '';
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
validateTransaction()
// ConnectIPS redirects to your callback URL after payment
// Always verify server-side โ never trust redirect alone!
@GetMapping("/connectips/callback")
public ResponseEntity<String> callback(
@RequestParam String txnId,
@RequestParam String referenceId,
@RequestParam long txnAmt) {
ConnectIpsValidateResponse response =
connectIpsClient.validateTransaction(txnId, referenceId, txnAmt);
if (!response.isPaymentSuccessful()) {
return ResponseEntity.badRequest()
.body("Payment not confirmed: " + response.statusDesc());
}
// โ
Safe to mark order as paid
orderRepo.markPaid(referenceId);
return ResponseEntity.ok("Payment successful");
}
Security Notes
๐ด Never commit your CREDITOR.pfx file to Git.
It contains your private key. Add it to
.gitignore.
๐ด Never trust the ConnectIPS redirect alone.
Always call
validateTransaction() server-side.
โ
Store
txnId in your database before
returning the form payload to the frontend.
โ
The RSA signature generation uses your .pfx private key
and happens entirely on your server โ it never reaches the frontend.