如何将 Odoo 与 Mobitel 和 Orange Money 支付网关集成?
结论
Odoo 通常没有适用于 Mobitel 或 Orange Money 的通用支付接口,也不存在一套覆盖所有国家和运营商的公开 REST API。要完成集成,需要先向当地的 Mobitel、Orange Money 运营主体或其授权支付聚合商申请商户接入资料,再按照实际 API 开发 Odoo 支付模块。
有几个问题需要先弄清楚:
- “Mobitel”可能指运营商支付、mCash,也可能指当地合作银行提供的账单支付服务。这三类服务使用的接口并不相同。
- Orange Money 的 API、认证方式、币种和回调规则通常因国家或地区而异。
- 部分接口不会公开展示。商户完成签约、KYC 和技术审核后,运营商才会提供文档和 Sandbox 凭据。
- 即使找到了第三方 Odoo module,也需要确认它是否适用于你的国家、Odoo 版本和商户协议。
所以,第一步不是寻找某个固定的 REST URL,而是确认具体国家、业务模式和签约主体。
先确认支付业务模式
向运营商申请接口前,应先明确需要接入哪些能力:
-
Merchant Payment
客户通过手机钱包向商户付款。Odoo 创建支付订单并接收处理结果。
-
Bill Payment / Biller Integration
客户输入账单号,网关查询应付金额,完成缴费后通知 Odoo 销账。
-
Payment Request / Push Payment
Odoo 根据客户手机号发起扣款请求,由客户在手机上确认。
-
Refund
Odoo 对已经成功的交易发起退款。
-
Payment Status Query
如果异步通知丢失,Odoo 可以主动查询交易状态。
问题中提到的是账单支付。如果客户需要在钱包端输入账单号并查询欠款,通常要接入 Biller API,而不是普通的收款 API。运营商还可能要求商户单独签署账单机构接入协议。
向 Mobitel 和 Orange Money 索取的资料
联系当地企业业务或开发者支持团队时,至少应索取以下资料:
- Merchant/Biller onboarding 流程
- Sandbox 和 Production 环境地址
- OAuth、API Key、证书或其他认证方式的说明
- 创建支付请求的接口
- 账单查询接口
- 支付状态查询接口
- 取消和退款接口
- Webhook/Callback 数据格式
- 签名生成和验签规则
- IP 白名单及 TLS 证书要求
- 错误码、超时和重试规则
- 测试手机号、测试钱包和测试用例
- 上线验收流程
- 对账文件或交易查询接口
接入 Orange Money 时,需要先确定具体国家,例如塞内加尔、科特迪瓦或喀麦隆。不同市场可能由不同实体运营,不能假定它们使用相同的域名、请求字段或认证流程。
Odoo 侧的实现方式
取得接口资料后,可以开发一个自定义支付提供商模块,例如:
payment_mobile_money/
├── __init__.py
├── __manifest__.py
├── models/
│ ├── __init__.py
│ ├── payment_provider.py
│ └── payment_transaction.py
├── controllers/
│ ├── __init__.py
│ └── main.py
├── data/
│ └── payment_method_data.xml
└── views/
└── payment_provider_views.xml
具体需要继承哪些模型、调用哪些方法,取决于 Odoo 版本。旧版 Odoo 通常使用 payment.acquirer,较新的版本则使用 payment.provider。开发前应查看目标版本自带的支付模块,例如 Stripe、PayPal 或其他支持异步通知的 provider,并按照同一版本的结构实现。
配置模型可以用来保存商户凭据和环境信息:
from odoo import fields, models
class PaymentProvider(models.Model):
_inherit = "payment.provider"
code = fields.Selection(
selection_add=[("mobile_money", "Mobile Money")],
ondelete={"mobile_money": "set default"},
)
mobile_money_api_base_url = fields.Char(
string="API Base URL",
required_if_provider="mobile_money",
)
mobile_money_client_id = fields.Char(
string="Client ID",
groups="base.group_system",
)
mobile_money_client_secret = fields.Char(
string="Client Secret",
groups="base.group_system",
)
mobile_money_callback_secret = fields.Char(
string="Callback Secret",
groups="base.group_system",
)
这些字段只用于展示模块设计方式,并不是 Mobitel 或 Orange Money 的官方参数。实际字段必须以签约后取得的接口文档为准。
创建支付请求
支付请求通常应由服务端发起,不能把 client_secret、证书私钥等凭据暴露给浏览器。下面使用占位字段展示基本结构:
import requests
from odoo import _
from odoo.exceptions import ValidationError
def create_payment(provider, transaction):
payload = {
"merchant_reference": transaction.reference,
"amount": str(transaction.amount),
"currency": transaction.currency_id.name,
"customer_msisdn": transaction.partner_phone,
"callback_url": provider.get_base_url()
+ "/payment/mobile-money/webhook",
}
headers = {
"Authorization": f"Bearer {provider._get_access_token()}",
"Content-Type": "application/json",
"Idempotency-Key": transaction.reference,
}
try:
response = requests.post(
f"{provider.mobile_money_api_base_url}/PAYMENT_ENDPOINT",
json=payload,
headers=headers,
timeout=30,
)
response.raise_for_status()
except requests.RequestException as exc:
raise ValidationError(
_("Unable to create the mobile money payment request.")
) from exc
result = response.json()
if not result.get("transaction_id"):
raise ValidationError(
_("The gateway response does not contain a transaction identifier.")
)
return result
/PAYMENT_ENDPOINT、请求字段和返回字段都是占位符,不能直接用于生产环境。接入时必须将它们替换为运营商文档规定的实际内容。
金额格式也要遵守网关要求。有些接口接收小数金额,有些则要求传入以最小货币单位表示的整数。例如,10.50 可能需要传为 1050,具体格式只能根据接口文档确定。
处理异步回调
移动钱包支付通常采用异步处理。创建请求成功只说明网关已经受理,并不表示客户完成了付款。Odoo 应通过 Webhook 或主动查询来确认最终状态。
import hmac
import hashlib
from odoo import http
from odoo.http import request
class MobileMoneyController(http.Controller):
@http.route(
"/payment/mobile-money/webhook",
type="http",
auth="public",
methods=["POST"],
csrf=False,
save_session=False,
)
def mobile_money_webhook(self, **kwargs):
raw_body = request.httprequest.get_data()
received_signature = request.httprequest.headers.get(
"X-Signature", ""
)
provider = request.env["payment.provider"].sudo().search(
[("code", "=", "mobile_money")],
limit=1,
)
expected_signature = hmac.new(
provider.mobile_money_callback_secret.encode(),
raw_body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(
received_signature,
expected_signature,
):
return request.make_response("Invalid signature", status=401)
data = request.httprequest.get_json()
transaction = request.env["payment.transaction"].sudo().search(
[("reference", "=", data.get("merchant_reference"))],
limit=1,
)
if not transaction:
return request.make_response(
"Transaction not found",
status=404,
)
gateway_status = data.get("status")
if gateway_status == "SUCCESS":
transaction._set_done()
elif gateway_status == "FAILED":
transaction._set_error(
data.get("message") or "Payment failed"
)
return request.make_response("OK", status=200)
这段代码也只是结构示例。X-Signature、HMAC-SHA256 和 SUCCESS 等名称不代表任何特定网关的真实协议。如果官方要求使用 RSA 签名、mTLS、JWT 或指定的响应正文,就必须严格按照官方规范实现。
账单支付还需要处理销账
普通订单支付只需关联 payment.transaction。账单支付还需要把交易与 Odoo 发票对应起来,建议使用唯一且稳定的业务编号,例如:
- Odoo 发票号
- 单独生成的 bill reference
- 由客户号和账期组成的标识
收到支付成功通知后,应按顺序完成以下处理:
- 验证回调签名和商户身份。
- 根据 gateway transaction ID 检查这笔交易是否已经处理。
- 核对金额、币种和账单号。
- 将
payment.transaction标记为成功。 - 创建或确认对应的
account.payment。 - 将付款与客户发票核销。
- 保存网关流水号,供退款和对账使用。
不能只凭回调中的账单号就把发票标记为已付。金额、币种和交易状态必须一并验证。
安全与可靠性注意事项
生产环境至少需要处理以下问题:
- 签名验证:回调 URL 对外公开,并不意味着收到的内容可信。
- 幂等处理:同一个回调可能发送多次,系统不能重复创建付款或重复核销。
- 服务端状态查询:对于重要交易,最好在收到回调后再次调用查询接口确认状态。
- 超时状态:网络超时不等于付款失败,不能立即要求客户重复支付。
- 敏感数据保护:不要把 API Secret、证书私钥和 Access Token 写入日志。
- 手机号格式:统一使用网关要求的国家代码格式。
- 金额校验:使用精确的小数或最小货币单位,避免浮点数误差。
- 权限隔离:只有系统管理员可以查看或修改生产凭据。
- 日志脱敏:日志记录 reference、transaction ID 和状态即可,不应保存完整认证信息。
- 环境隔离:Sandbox 与 Production 的地址和密钥应分别配置。
- 对账机制:定期使用状态查询接口或对账文件检查漏单、重复单和金额差异。
关于现成 Odoo module
可以在 Odoo Apps、GitHub 或当地支付服务商处查找现成模块,但使用前需要核对以下事项:
- 支持的 Odoo 主版本
- 对应的国家和运营主体
- 使用的是官方接口还是网页模拟
- 是否支持异步回调和主动查询
- 是否支持退款、对账和账单查询
- 模块是否仍在维护
- 商户凭据是否得到安全保存
- 是否能正确创建并核销会计付款
模块名称中包含 “Orange Money” 或 “Mobitel”,不代表它可以直接使用。移动支付接口具有很强的地区差异,即使使用同一品牌,后端协议也可能完全不同。
如果运营商不提供直接接入,可以考虑当地授权的支付聚合商。聚合商可能通过一套 API 同时接入 Orange Money、Mobitel 和其他钱包,但仍需单独确认手续费、结算周期、退款能力和合规责任。
备注:内容仅供参考。
