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.0.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.0-py3-none-any.whl (16.8 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for valoris_payment_gateway-0.1.0.tar.gz
Algorithm Hash digest
SHA256 6dd71ab42803e7c2cf3384db15d354cef6516f2cc95381ca8840b233c0af6c21
MD5 4bee3ab70da2da1a1e7a705563128dc8
BLAKE2b-256 eaf4555e75ee29c76be6ea098221761cc2ea93d38308943b35700861fd2ae63f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for valoris_payment_gateway-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1f2f2ecfaa1c61c0c2236fb7afada8482166dd1b919344cb60bb195fabb362a3
MD5 c8551f8ae2a96388a55362a82ec41d98
BLAKE2b-256 395e33d4de0f9dfbcdc5405972feb09d690330c1e778be8ef589eeac9c932efe

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