如何在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
相关产品推荐
相关产品推荐

