You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django项目中集成MTN MoMo API实现收付款及支付数据入库

Django集成MTN MoMo API 实现收付款与数据库记录

1. 基础配置

将MTN沙箱提供的密钥和ID配置到Django的settings.py中,建议用环境变量避免硬编码:

# settings.py
import os

MTN_MOMO_API_KEY = os.environ.get('MTN_MOMO_API_KEY')
MTN_MOMO_USER_ID = os.environ.get('MTN_MOMO_USER_ID')
MTN_MOMO_BASE_URL = "https://sandbox.momodeveloper.mtn.com"
MTN_MOMO_COLLECTION_SUBKEY = os.environ.get('MTN_MOMO_COLLECTION_SUBKEY')

2. 获取API访问令牌

编写工具函数获取MTN API的访问令牌,所有后续请求都需要这个令牌:

# utils.py
import base64
import requests
from django.conf import settings

def get_momo_token():
    url = f"{settings.MTN_MOMO_BASE_URL}/collection/token/"
    auth_str = f"{settings.MTN_MOMO_USER_ID}:{settings.MTN_MOMO_API_KEY}"
    encoded_auth = base64.b64encode(auth_str.encode()).decode()
    
    headers = {
        "Authorization": f"Basic {encoded_auth}",
        "Ocp-Apim-Subscription-Key": settings.MTN_MOMO_COLLECTION_SUBKEY
    }
    
    response = requests.post(url, headers=headers)
    if response.status_code == 200:
        return response.json()['access_token']
    raise Exception(f"令牌获取失败: {response.text}")

3. 数据库模型设计

创建模型记录支付详情,包含交易ID、用户关联、金额、手机号、状态等核心字段:

# models.py
from django.db import models
from django.contrib.auth.models import User

class MomoPayment(models.Model):
    PAYMENT_STATUS_CHOICES = [
        ('PENDING', '待处理'),
        ('SUCCESS', '成功'),
        ('FAILED', '失败'),
    ]
    
    transaction_id = models.CharField(max_length=100, unique=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='momo_payments')
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    phone_number = models.CharField(max_length=20)
    status = models.CharField(max_length=10, choices=PAYMENT_STATUS_CHOICES, default='PENDING')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.transaction_id} | {self.amount} | {self.status}"

执行迁移命令创建表:

python manage.py makemigrations
python manage.py migrate

4. 发起收款请求

编写视图处理用户的支付请求,调用MTN API并将支付记录保存到数据库:

# views.py
import uuid
import requests
from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from .models import MomoPayment
from .utils import get_momo_token

@login_required
@csrf_exempt
def initiate_momo_payment(request):
    if request.method != 'POST':
        return JsonResponse({'error': '仅支持POST请求'}, status=400)
    
    data = request.POST
    amount = data.get('amount')
    phone_number = data.get('phone_number')
    
    if not amount or not phone_number:
        return JsonResponse({'error': '金额和手机号为必填项'}, status=400)
    
    # 生成唯一交易ID
    transaction_id = str(uuid.uuid4())
    try:
        token = get_momo_token()
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)
    
    # 构造收款请求参数
    url = f"{settings.MTN_MOMO_BASE_URL}/collection/v1_0/requesttopay"
    headers = {
        "Authorization": f"Bearer {token}",
        "Ocp-Apim-Subscription-Key": settings.MTN_MOMO_COLLECTION_SUBKEY,
        "X-Reference-Id": transaction_id,
        "Content-Type": "application/json"
    }
    
    payload = {
        "amount": amount,
        "currency": "EUR",  # 沙箱环境固定用EUR,生产环境需匹配MTN要求
        "externalId": transaction_id,
        "payer": {
            "partyIdType": "MSISDN",
            "partyId": phone_number
        },
        "payerMessage": "订单支付",
        "payeeNote": "感谢您的支付"
    }
    
    response = requests.post(url, json=payload, headers=headers)
    
    # 保存支付记录到数据库
    payment_status = 'PENDING' if response.status_code == 202 else 'FAILED'
    MomoPayment.objects.create(
        transaction_id=transaction_id,
        user=request.user,
        amount=amount,
        phone_number=phone_number,
        status=payment_status
    )
    
    if response.status_code == 202:
        return JsonResponse({
            'message': '支付请求已发起,请完成手机端验证',
            'transaction_id': transaction_id,
            'status': payment_status
        })
    else:
        return JsonResponse({
            'error': '支付请求提交失败',
            'details': response.text,
            'transaction_id': transaction_id
        }, status=response.status_code)

5. 处理MTN支付回调

MTN会在支付状态变更时回调你的服务器,编写视图接收回调并更新数据库状态:

# views.py 追加
@csrf_exempt
def momo_payment_callback(request):
    if request.method != 'POST':
        return JsonResponse({'status': 'fail'}, status=400)
    
    try:
        data = request.json()
    except ValueError:
        return JsonResponse({'error': '无效的JSON数据'}, status=400)
    
    transaction_id = data.get('referenceId')
    payment_status = data.get('status')
    
    try:
        payment = MomoPayment.objects.get(transaction_id=transaction_id)
        if payment_status == 'SUCCESSFUL':
            payment.status = 'SUCCESS'
        elif payment_status == 'FAILED':
            payment.status = 'FAILED'
        payment.save()
        return JsonResponse({'status': 'success'})
    except MomoPayment.DoesNotExist:
        return JsonResponse({'error': '交易记录不存在'}, status=404)

在urls.py中配置回调路由:

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

urlpatterns = [
    path('initiate-payment/', views.initiate_momo_payment, name='initiate_momo_payment'),
    path('payment-callback/', views.momo_payment_callback, name='momo_payment_callback'),
    # 可选:添加状态查询路由
    path('check-status/<str:transaction_id>/', views.check_momo_payment_status, name='check_momo_status'),
]

6. 主动查询支付状态(可选)

如果回调未及时触发,可编写视图主动查询支付状态并更新数据库:

# views.py 追加
@login_required
def check_momo_payment_status(request, transaction_id):
    try:
        payment = MomoPayment.objects.get(transaction_id=transaction_id)
    except MomoPayment.DoesNotExist:
        return JsonResponse({'error': '交易记录不存在'}, status=404)
    
    try:
        token = get_momo_token()
    except Exception as e:
        return JsonResponse({'error': str(e)}, status=500)
    
    url = f"{settings.MTN_MOMO_BASE_URL}/collection/v1_0/requesttopay/{transaction_id}"
    headers = {
        "Authorization": f"Bearer {token}",
        "Ocp-Apim-Subscription-Key": settings.MTN_MOMO_COLLECTION_SUBKEY
    }
    
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        api_status = response.json()['status']
        if api_status == 'SUCCESSFUL':
            payment.status = 'SUCCESS'
        elif api_status == 'FAILED':
            payment.status = 'FAILED'
        payment.save()
        return JsonResponse({
            'transaction_id': transaction_id,
            'status': payment.status,
            'amount': str(payment.amount),
            'created_at': payment.created_at.strftime('%Y-%m-%d %H:%M:%S')
        })
    else:
        return JsonResponse({'error': '状态查询失败'}, status=response.status_code)

沙箱测试注意事项

  • 沙箱环境仅支持指定测试手机号,需在MTN开发者后台查看可用测试号码
  • 回调地址必须是公网可访问的URL,本地测试可使用ngrok进行端口转发
  • 确保所有配置项(API密钥、用户ID、订阅密钥)与沙箱后台完全一致

内容的提问来源于stack exchange,提问作者francis Murungi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:28:10