Moneybag

Python SDK

Official Python SDK for Moneybag Payment API

Python SDK

Integrate Moneybag payments into your Python applications with our comprehensive SDK. Works seamlessly with Django, Flask, FastAPI, and all modern Python frameworks.


Installation

Using pip

pip install requests
# SDK package coming soon: moneybag-python

Using poetry

poetry add requests
# SDK package coming soon: moneybag-python

Requirements

  • Python 3.7 or higher
  • requests library

Quick Start

Initialize the Client

import requests
from typing import Dict, Optional, Any
from dataclasses import dataclass
import hashlib
import hmac

@dataclass
class Customer:
    name: str
    email: str
    phone: str
    address: Optional[str] = None
    city: Optional[str] = None
    postcode: Optional[str] = None
    country: Optional[str] = "Bangladesh"

class MoneybagClient:
    def __init__(self, api_key: str, environment: str = 'sandbox'):
        self.api_key = api_key
        self.base_url = (
            'https://api.moneybag.com.bd/api/v2' 
            if environment == 'production' 
            else 'https://sandbox.api.moneybag.com.bd/api/v2'
        )
        self.session = requests.Session()
        self.session.headers.update({
            'X-Merchant-API-Key': self.api_key,
            'Content-Type': 'application/json'
        })
    
    def create_checkout(self, data: Dict[str, Any]) -> Dict[str, Any]:
        """Create a payment checkout session"""
        response = self.session.post(
            f'{self.base_url}/payments/checkout',
            json=data,
            timeout=30
        )
        response.raise_for_status()
        return response.json()
    
    def verify_payment(self, transaction_id: str) -> Dict[str, Any]:
        """Verify payment status"""
        response = self.session.get(
            f'{self.base_url}/payments/verify/{transaction_id}',
            timeout=30
        )
        response.raise_for_status()
        return response.json()
    
    def create_refund(self, transaction_id: str, amount: Optional[float] = None) -> Dict[str, Any]:
        """Process a refund"""
        data = {'transaction_id': transaction_id}
        if amount:
            data['refund_amount'] = amount
        
        response = self.session.post(
            f'{self.base_url}/payments/refund',
            json=data,
            timeout=30
        )
        response.raise_for_status()
        return response.json()

# Usage
client = MoneybagClient(api_key='your_api_key', environment='sandbox')

Examples

Create a Payment

from datetime import datetime
import os

# Initialize client
moneybag = MoneybagClient(
    api_key=os.getenv('MONEYBAG_API_KEY'),
    environment='sandbox'
)

# Create checkout
def create_payment(amount: float, customer: Customer) -> str:
    try:
        checkout_data = {
            'order_id': f'ORDER_{datetime.now().timestamp():.0f}',
            'order_amount': amount,
            'currency': 'BDT',
            'order_description': 'Purchase from Python Store',
            'success_url': 'https://yoursite.com/payment/success',
            'cancel_url': 'https://yoursite.com/payment/cancel',
            'fail_url': 'https://yoursite.com/payment/fail',
            'customer': {
                'name': customer.name,
                'email': customer.email,
                'phone': customer.phone,
                'address': customer.address,
                'city': customer.city,
                'postcode': customer.postcode,
                'country': customer.country
            },
            'metadata': {
                'source': 'python_sdk',
                'version': '1.0.0'
            }
        }
        
        response = moneybag.create_checkout(checkout_data)
        
        if response['success']:
            return response['data']['payment_url']
        else:
            raise Exception(f"Checkout failed: {response.get('message')}")
            
    except requests.exceptions.RequestException as e:
        print(f"API Error: {e}")
        raise

# Example usage
customer = Customer(
    name="John Doe",
    email="john@example.com",
    phone="+8801700000000",
    address="123 Main St",
    city="Dhaka",
    postcode="1000"
)

payment_url = create_payment(1000.00, customer)
print(f"Redirect to: {payment_url}")

Verify Payment

def verify_payment_status(transaction_id: str) -> bool:
    try:
        result = moneybag.verify_payment(transaction_id)
        
        if result['success'] and result['data']['status'] == 'SUCCESS':
            print(f"Payment verified: {result['data']['amount']} {result['data']['currency']}")
            return True
        else:
            print(f"Payment not verified: {result['data']['status']}")
            return False
            
    except requests.exceptions.HTTPError as e:
        if e.response.status_code == 404:
            print("Transaction not found")
        else:
            print(f"Verification failed: {e}")
        return False

# Usage
is_verified = verify_payment_status("TXN_123456789")

Framework Integration

Django Integration

# moneybag_integration/views.py
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
import json

class MoneybagPaymentView:
    def __init__(self):
        self.client = MoneybagClient(
            api_key=settings.MONEYBAG_API_KEY,
            environment=settings.MONEYBAG_ENVIRONMENT
        )
    
    def create_checkout(self, request):
        """Create payment checkout"""
        if request.method == 'POST':
            data = json.loads(request.body)
            
            try:
                checkout = self.client.create_checkout({
                    'order_id': f'ORDER_{request.user.id}_{datetime.now().timestamp():.0f}',
                    'order_amount': data['amount'],
                    'currency': 'BDT',
                    'order_description': data.get('description', 'Purchase'),
                    'success_url': request.build_absolute_uri('/payment/success/'),
                    'cancel_url': request.build_absolute_uri('/payment/cancel/'),
                    'fail_url': request.build_absolute_uri('/payment/fail/'),
                    'customer': {
                        'name': request.user.get_full_name(),
                        'email': request.user.email,
                        'phone': data['phone']
                    }
                })
                
                return JsonResponse({
                    'success': True,
                    'payment_url': checkout['data']['payment_url']
                })
                
            except Exception as e:
                return JsonResponse({
                    'success': False,
                    'error': str(e)
                }, status=400)
        
        return JsonResponse({'error': 'Method not allowed'}, status=405)
    
    def payment_success(self, request):
        """Handle successful payment"""
        transaction_id = request.GET.get('transaction_id')
        
        if transaction_id:
            verification = self.client.verify_payment(transaction_id)
            
            if verification['success']:
                # Update order status in database
                # Send confirmation email
                return render(request, 'payment_success.html', {
                    'transaction': verification['data']
                })
        
        return render(request, 'payment_error.html')
    
    @csrf_exempt
    def webhook(self, request):
        """Handle webhook notifications"""
        signature = request.headers.get('X-Webhook-Signature')
        payload = request.body.decode('utf-8')
        
        # Verify signature
        if not self.verify_signature(payload, signature):
            return JsonResponse({'error': 'Invalid signature'}, status=401)
        
        event = json.loads(payload)
        
        # Process event
        if event['event'] == 'payment.success':
            # Handle successful payment
            pass
        elif event['event'] == 'payment.failed':
            # Handle failed payment
            pass
        
        return JsonResponse({'status': 'ok'})
    
    def verify_signature(self, payload: str, signature: str) -> bool:
        """Verify webhook signature"""
        secret = settings.MONEYBAG_WEBHOOK_SECRET
        expected = hmac.new(
            secret.encode(),
            payload.encode(),
            hashlib.sha256
        ).hexdigest()
        return f"sha256={expected}" == signature

# urls.py
from django.urls import path
from .views import MoneybagPaymentView

payment_view = MoneybagPaymentView()

urlpatterns = [
    path('checkout/', payment_view.create_checkout, name='checkout'),
    path('payment/success/', payment_view.payment_success, name='payment_success'),
    path('webhook/moneybag/', payment_view.webhook, name='moneybag_webhook'),
]

# settings.py
MONEYBAG_API_KEY = os.getenv('MONEYBAG_API_KEY')
MONEYBAG_WEBHOOK_SECRET = os.getenv('MONEYBAG_WEBHOOK_SECRET')
MONEYBAG_ENVIRONMENT = 'sandbox' if DEBUG else 'production'

Flask Integration

# app.py
from flask import Flask, request, jsonify, redirect, render_template
import os

app = Flask(__name__)
moneybag = MoneybagClient(
    api_key=os.getenv('MONEYBAG_API_KEY'),
    environment='sandbox'
)

@app.route('/create-payment', methods=['POST'])
def create_payment():
    data = request.json
    
    try:
        checkout = moneybag.create_checkout({
            'order_id': f'ORDER_{datetime.now().timestamp():.0f}',
            'order_amount': data['amount'],
            'currency': 'BDT',
            'order_description': 'Flask Store Purchase',
            'success_url': f"{request.host_url}payment/success",
            'cancel_url': f"{request.host_url}payment/cancel",
            'fail_url': f"{request.host_url}payment/fail",
            'customer': data['customer']
        })
        
        return jsonify({
            'success': True,
            'payment_url': checkout['data']['payment_url']
        })
        
    except Exception as e:
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

@app.route('/payment/success')
def payment_success():
    transaction_id = request.args.get('transaction_id')
    
    if transaction_id:
        verification = moneybag.verify_payment(transaction_id)
        if verification['success']:
            return render_template('success.html', transaction=verification['data'])
    
    return render_template('error.html')

@app.route('/webhook', methods=['POST'])
def webhook():
    signature = request.headers.get('X-Webhook-Signature')
    payload = request.get_data(as_text=True)
    
    # Verify signature
    secret = os.getenv('MONEYBAG_WEBHOOK_SECRET')
    expected = hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    if f"sha256={expected}" != signature:
        return jsonify({'error': 'Invalid signature'}), 401
    
    event = request.json
    
    # Process webhook event
    if event['event'] == 'payment.success':
        # Handle successful payment
        print(f"Payment successful: {event['data']['transaction_id']}")
    
    return jsonify({'status': 'ok'}), 200

if __name__ == '__main__':
    app.run(debug=True)

FastAPI Integration

# main.py
from fastapi import FastAPI, HTTPException, Request, BackgroundTasks
from fastapi.responses import RedirectResponse
from pydantic import BaseModel
import os
import uvicorn

app = FastAPI()

# Initialize Moneybag client
moneybag = MoneybagClient(
    api_key=os.getenv('MONEYBAG_API_KEY'),
    environment='sandbox'
)

class PaymentRequest(BaseModel):
    amount: float
    customer_name: str
    customer_email: str
    customer_phone: str
    description: str = "Purchase"

class WebhookEvent(BaseModel):
    event: str
    data: dict

@app.post("/api/checkout")
async def create_checkout(payment: PaymentRequest):
    """Create payment checkout"""
    try:
        checkout = moneybag.create_checkout({
            'order_id': f'ORDER_{datetime.now().timestamp():.0f}',
            'order_amount': payment.amount,
            'currency': 'BDT',
            'order_description': payment.description,
            'success_url': 'https://yoursite.com/payment/success',
            'cancel_url': 'https://yoursite.com/payment/cancel',
            'fail_url': 'https://yoursite.com/payment/fail',
            'customer': {
                'name': payment.customer_name,
                'email': payment.customer_email,
                'phone': payment.customer_phone
            }
        })
        
        return {
            'success': True,
            'payment_url': checkout['data']['payment_url']
        }
        
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.get("/payment/verify/{transaction_id}")
async def verify_payment(transaction_id: str):
    """Verify payment status"""
    try:
        result = moneybag.verify_payment(transaction_id)
        return result
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.post("/webhook/moneybag")
async def handle_webhook(
    request: Request,
    background_tasks: BackgroundTasks
):
    """Handle Moneybag webhooks"""
    signature = request.headers.get('x-webhook-signature')
    body = await request.body()
    
    # Verify signature
    if not verify_webhook_signature(body.decode(), signature):
        raise HTTPException(status_code=401, detail="Invalid signature")
    
    event = await request.json()
    
    # Process webhook in background
    background_tasks.add_task(process_webhook_event, event)
    
    return {"status": "ok"}

def process_webhook_event(event: dict):
    """Process webhook event asynchronously"""
    if event['event'] == 'payment.success':
        # Update database
        # Send confirmation email
        print(f"Payment successful: {event['data']['transaction_id']}")
    elif event['event'] == 'refund.completed':
        # Handle refund
        print(f"Refund completed: {event['data']['refund_id']}")

def verify_webhook_signature(payload: str, signature: str) -> bool:
    """Verify webhook signature"""
    secret = os.getenv('MONEYBAG_WEBHOOK_SECRET')
    expected = hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()
    return f"sha256={expected}" == signature

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Async Support

import asyncio
import aiohttp
from typing import Dict, Any

class AsyncMoneybagClient:
    def __init__(self, api_key: str, environment: str = 'sandbox'):
        self.api_key = api_key
        self.base_url = (
            'https://api.moneybag.com.bd/api/v2' 
            if environment == 'production' 
            else 'https://sandbox.api.moneybag.com.bd/api/v2'
        )
        self.headers = {
            'X-Merchant-API-Key': self.api_key,
            'Content-Type': 'application/json'
        }
    
    async def create_checkout(self, data: Dict[str, Any]) -> Dict[str, Any]:
        async with aiohttp.ClientSession() as session:
            async with session.post(
                f'{self.base_url}/payments/checkout',
                json=data,
                headers=self.headers,
                timeout=aiohttp.ClientTimeout(total=30)
            ) as response:
                response.raise_for_status()
                return await response.json()
    
    async def verify_payment(self, transaction_id: str) -> Dict[str, Any]:
        async with aiohttp.ClientSession() as session:
            async with session.get(
                f'{self.base_url}/payments/verify/{transaction_id}',
                headers=self.headers,
                timeout=aiohttp.ClientTimeout(total=30)
            ) as response:
                response.raise_for_status()
                return await response.json()

# Usage
async def main():
    client = AsyncMoneybagClient(api_key='your_api_key')
    
    # Create multiple checkouts concurrently
    tasks = [
        client.create_checkout(checkout_data1),
        client.create_checkout(checkout_data2),
        client.create_checkout(checkout_data3)
    ]
    
    results = await asyncio.gather(*tasks)
    print(f"Created {len(results)} checkouts")

asyncio.run(main())

Error Handling

class MoneybagError(Exception):
    """Base exception for Moneybag SDK"""
    pass

class AuthenticationError(MoneybagError):
    """API key or authentication error"""
    pass

class ValidationError(MoneybagError):
    """Request validation error"""
    pass

class PaymentError(MoneybagError):
    """Payment processing error"""
    pass

def handle_api_error(response):
    """Handle API error responses"""
    if response.status_code == 401:
        raise AuthenticationError("Invalid API key")
    elif response.status_code == 422:
        errors = response.json().get('errors', {})
        raise ValidationError(f"Validation failed: {errors}")
    elif response.status_code == 400:
        message = response.json().get('message', 'Bad request')
        raise PaymentError(message)
    elif response.status_code >= 500:
        raise MoneybagError("Server error, please try again")
    else:
        response.raise_for_status()

# Usage with retry
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=10)
)
def create_payment_with_retry(client, data):
    try:
        return client.create_checkout(data)
    except MoneybagError as e:
        print(f"Payment failed: {e}")
        raise

Testing

# test_moneybag.py
import unittest
from unittest.mock import Mock, patch
import os

class TestMoneybagClient(unittest.TestCase):
    def setUp(self):
        self.client = MoneybagClient(
            api_key='test_api_key',
            environment='sandbox'
        )
    
    @patch('requests.Session.post')
    def test_create_checkout(self, mock_post):
        # Mock response
        mock_response = Mock()
        mock_response.json.return_value = {
            'success': True,
            'data': {
                'payment_url': 'https://pay.moneybag.com/abc123',
                'transaction_id': 'TXN_123'
            }
        }
        mock_post.return_value = mock_response
        
        # Test checkout creation
        result = self.client.create_checkout({
            'order_id': 'TEST_001',
            'order_amount': 100,
            'currency': 'BDT'
        })
        
        self.assertTrue(result['success'])
        self.assertIn('payment_url', result['data'])
    
    def test_verify_signature(self):
        payload = '{"event":"payment.success"}'
        secret = 'test_secret'
        
        # Generate valid signature
        import hmac
        import hashlib
        signature = 'sha256=' + hmac.new(
            secret.encode(),
            payload.encode(),
            hashlib.sha256
        ).hexdigest()
        
        # Test signature verification
        self.assertTrue(
            verify_webhook_signature(payload, signature, secret)
        )

if __name__ == '__main__':
    unittest.main()

Configuration

# config.py
import os
from dataclasses import dataclass

@dataclass
class MoneybagConfig:
    api_key: str
    webhook_secret: str
    environment: str = 'sandbox'
    timeout: int = 30
    max_retries: int = 3
    
    @classmethod
    def from_env(cls):
        return cls(
            api_key=os.getenv('MONEYBAG_API_KEY'),
            webhook_secret=os.getenv('MONEYBAG_WEBHOOK_SECRET'),
            environment=os.getenv('MONEYBAG_ENV', 'sandbox'),
            timeout=int(os.getenv('MONEYBAG_TIMEOUT', '30')),
            max_retries=int(os.getenv('MONEYBAG_MAX_RETRIES', '3'))
        )

# Usage
config = MoneybagConfig.from_env()
client = MoneybagClient(config.api_key, config.environment)

Resources

Support