Skip to main content

Unified payment gateway library supporting eSewa, Khalti, ConnectIPS and more

Project description

Valoris Payment Gateway

A unified, reusable Python payment gateway library supporting eSewa, Khalti, ConnectIPS, and FonePay. Built for FastAPI apps but framework-agnostic.

Installation

# Base (eSewa, Khalti, FonePay)
pip install git+https://github.com/Techore/valoris-payment-gateway.git

# With Connect IPS support (requires cryptography for RSA signing)
pip install "valoris-payment-gateway[connectips] @ git+https://github.com/Techore/valoris-payment-gateway.git"

# All providers
pip install "valoris-payment-gateway[all] @ git+https://github.com/Techore/valoris-payment-gateway.git"

Supported Providers

Provider Status Signature Method
eSewa Ready HMAC-SHA256
Khalti Ready API Key Header
ConnectIPS Ready RSA (SHA256withRSA)
FonePay Ready HMAC-SHA512

Provider Configuration Reference

eSewa

Parameter Type Required Description
secret_key str Yes HMAC secret key from eSewa merchant dashboard
product_code str Yes Merchant product code (e.g. EPAYTEST for sandbox)
sandbox bool No Use sandbox environment (default: True)

Environment variables (recommended):

ESEWA_SECRET_KEY=8gBm/:&EnhH.1/q
ESEWA_PRODUCT_CODE=EPAYTEST

Sandbox test credentials:

  • eSewa IDs: 9806800001 - 9806800005
  • Password: Nepal@123
  • MPIN: 1122
  • OTP: 123456

Verify kwargs: total_amount (float, NPR)

provider = create_provider(
    "esewa",
    secret_key=os.getenv("ESEWA_SECRET_KEY"),
    product_code=os.getenv("ESEWA_PRODUCT_CODE"),
    sandbox=True,
)

Khalti

Parameter Type Required Description
secret_key str Yes Live secret key from Khalti admin dashboard
sandbox bool No Use sandbox environment (default: True)
website_url str No Your website URL (required by Khalti API)

Environment variables (recommended):

KHALTI_SECRET_KEY=your_secret_key_here
KHALTI_WEBSITE_URL=https://yourapp.com

Sandbox: Get test keys from test-admin.khalti.com Production: Get live keys from admin.khalti.com

Sandbox test credentials:

  • Khalti IDs: 9800000000 - 9800000005
  • Password: (not needed for ePayment)
  • MPIN: 1111
  • OTP: 987654

Note: Amount in PaymentRequest is in NPR. The library auto-converts to paisa (x100) for the Khalti API.

Verify kwargs: pidx (str, from Khalti initiation response)

provider = create_provider(
    "khalti",
    secret_key=os.getenv("KHALTI_SECRET_KEY"),
    website_url=os.getenv("KHALTI_WEBSITE_URL"),
    sandbox=True,
)

ConnectIPS

Parameter Type Required Description
merchant_id str Yes Merchant ID from NCHL
app_id str Yes Application ID from NCHL
app_name str Yes Application display name
app_password str Yes Password for validation API (Basic Auth)
pfx_path str Yes Path to CREDITOR.pfx certificate file
pfx_password str Yes Password for the .pfx file
sandbox bool No Use sandbox environment (default: True)

Environment variables (recommended):

CONNECTIPS_MERCHANT_ID=550
CONNECTIPS_APP_ID=MER-550-APP-1
CONNECTIPS_APP_NAME=YourAppName
CONNECTIPS_APP_PASSWORD=your_password
CONNECTIPS_PFX_PATH=/path/to/CREDITOR.pfx
CONNECTIPS_PFX_PASSWORD=your_pfx_password

Important: Requires cryptography package. Install with pip install valoris-payment-gateway[connectips].

Note:

  • CREDITOR.pfx is provided by NCHL for testing. You get your own certificate for production.
  • Success/failure URLs must be pre-configured with NCHL support (not passed in the API).
  • Amount is in NPR — auto-converted to paisa (x100) for the API.

Verify kwargs: amount (float, NPR)

provider = create_provider(
    "connectips",
    merchant_id=os.getenv("CONNECTIPS_MERCHANT_ID"),
    app_id=os.getenv("CONNECTIPS_APP_ID"),
    app_name=os.getenv("CONNECTIPS_APP_NAME"),
    app_password=os.getenv("CONNECTIPS_APP_PASSWORD"),
    pfx_path=os.getenv("CONNECTIPS_PFX_PATH"),
    pfx_password=os.getenv("CONNECTIPS_PFX_PASSWORD"),
    sandbox=True,
)

FonePay

Parameter Type Required Description
merchant_code str Yes Merchant ID/PID from FonePay (via your bank)
secret_key str Yes HMAC secret key from FonePay (via your bank)
sandbox bool No Use sandbox environment (default: True)

Environment variables (recommended):

FONEPAY_MERCHANT_CODE=your_merchant_code
FONEPAY_SECRET_KEY=your_secret_key

Note: Contact your bank to obtain merchant credentials. Separate keys for dev/production.

Sandbox test credentials:

  • Mobile: 9801111111
  • Password: 123
  • MPIN: 123

Verify kwargs: amount (float), bid (str, bank/transaction ID), uid (str, FonePay unique ID)

provider = create_provider(
    "fonepay",
    merchant_code=os.getenv("FONEPAY_MERCHANT_CODE"),
    secret_key=os.getenv("FONEPAY_SECRET_KEY"),
    sandbox=True,
)

Usage

Common Flow (all providers)

import os
from valoris_payment import create_provider, PaymentRequest

# 1. Create provider
provider = create_provider("esewa", secret_key="...", product_code="...", sandbox=True)

# 2. Initiate payment
request = PaymentRequest(
    amount=1000.0,                                        # NPR
    transaction_id="TXN-001",                             # your unique ID
    product_name="Premium Plan",
    success_url="https://yourapp.com/payment/success",
    failure_url="https://yourapp.com/payment/failure",
)
result = await provider.initiate(request)
# result.payment_url  -> redirect user here
# result.raw          -> form data (for providers that need POST form)

# 3. Handle callback (on redirect back)
callback = await provider.handle_callback(query_params)
# callback.status, callback.transaction_id, callback.provider_ref

# 4. Verify payment (server-side confirmation)
verification = await provider.verify("TXN-001", total_amount=1000.0)
# verification.status -> PaymentStatus.COMPLETED / FAILED / etc.

FastAPI Integration Example

from fastapi import FastAPI, Query, Request
from fastapi.responses import RedirectResponse
from valoris_payment import create_provider, PaymentRequest, PaymentStatus
import os

app = FastAPI()

esewa = create_provider(
    "esewa",
    secret_key=os.getenv("ESEWA_SECRET_KEY"),
    product_code=os.getenv("ESEWA_PRODUCT_CODE"),
    sandbox=True,
)

khalti = create_provider(
    "khalti",
    secret_key=os.getenv("KHALTI_SECRET_KEY"),
    website_url=os.getenv("KHALTI_WEBSITE_URL", "https://yourapp.com"),
    sandbox=True,
)

@app.post("/payments/initiate")
async def initiate_payment(amount: float, transaction_id: str, provider_name: str = "esewa"):
    providers = {"esewa": esewa, "khalti": khalti}
    provider = providers[provider_name]

    request = PaymentRequest(
        amount=amount,
        transaction_id=transaction_id,
        product_name="Order Payment",
        success_url="https://yourapp.com/payments/success",
        failure_url="https://yourapp.com/payments/failure",
    )
    result = await provider.initiate(request)
    return {"payment_url": result.payment_url, "provider_ref": result.provider_ref}

@app.get("/payments/success")
async def payment_success(request: Request):
    params = dict(request.query_params)
    # Detect provider from callback params and verify
    if "data" in params:  # eSewa
        callback = await esewa.handle_callback(params)
    elif "pidx" in params:  # Khalti
        callback = await khalti.handle_callback(params)
    return {"status": callback.status, "transaction_id": callback.transaction_id}

PaymentRequest Extra Fields

Provider-specific fields can be passed via the extra dict:

eSewa

PaymentRequest(
    ...,
    extra={
        "tax_amount": "50",
        "product_service_charge": "10",
        "product_delivery_charge": "20",
    }
)

Khalti

PaymentRequest(
    ...,
    extra={
        "website_url": "https://yourapp.com",
        "customer_info": {"name": "John", "email": "john@example.com", "phone": "9800000000"},
    }
)

ConnectIPS

PaymentRequest(
    ...,
    extra={
        "currency": "NPR",
        "reference_id": "REF-001",
        "remarks": "Order payment",
        "particulars": "Product purchase",
    }
)

FonePay

PaymentRequest(
    ...,
    extra={
        "currency": "NPR",
        "r1": "Additional info",
        "r2": "More info",
    }
)

Adding a Custom Provider

from valoris_payment import PaymentProvider, register_provider

class MyProvider(PaymentProvider):
    name = "myprovider"

    async def initiate(self, request):
        ...

    async def verify(self, transaction_id, **kwargs):
        ...

    async def handle_callback(self, payload):
        ...

register_provider("myprovider", MyProvider)

Development

git clone git@github.com:Techore/valoris-payment-gateway.git
cd valoris-payment-gateway
pip install -e ".[dev]"
pytest

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

valoris_payment_gateway-0.1.2.tar.gz (14.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

valoris_payment_gateway-0.1.2-py3-none-any.whl (16.8 kB view details)

Uploaded Python 3

File details

Details for the file valoris_payment_gateway-0.1.2.tar.gz.

File metadata

  • Download URL: valoris_payment_gateway-0.1.2.tar.gz
  • Upload date:
  • Size: 14.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for valoris_payment_gateway-0.1.2.tar.gz
Algorithm Hash digest
SHA256 9f2c73f0ccdb70478ba16a545c41e99ab793e72183223e8175b3ebd532b6347d
MD5 5e8797f00c0ce361e330d4816548f475
BLAKE2b-256 b175c67efe096f5d98648c58515bf44b034c81e60c67d76b126034de1ead923e

See more details on using hashes here.

File details

Details for the file valoris_payment_gateway-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for valoris_payment_gateway-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 9c60dc2a4bb4ab9af5fb3b55fff7f3cdb854c4cc22754fb9bd15b606f1c8746f
MD5 d4deabbaef76852ebe0a0ac16dff1189
BLAKE2b-256 81ca9ec7327b9dad55e6bba17320924174e87e4cba5a4297c36b91981101d6d3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page