在如今的云计算时代,SaaS(软件即服务)平台凭借其便捷、高效的特点,已成为众多企业数字化转型的首选。而支付功能作为 SaaS 平台商业化闭环的关键环节,其集成的稳定性与安全性至关重要。在 SaaS 平台集成第三方支付的过程中,多租户隔离与费率计算是两个核心难题。本文将结合 Stripe 支付接口,详细阐述如何实现这两项关键功能。
一、SaaS 平台支付集成的核心挑战
SaaS 平台通常服务于多个租户(即不同的企业或组织),每个租户都有其独特的业务需求和数据安全要求。在支付集成方面,主要面临以下挑战:
一方面是多租户数据隔离。不同租户的支付信息属于敏感数据,必须严格隔离,防止租户之间的数据泄露或混淆。这就要求平台在数据存储、访问控制等方面进行特殊设计。
另一方面是动态费率计算。不同租户可能与平台约定不同的支付费率,且费率形式多样,可能是固定费率,也可能是按交易金额的一定比例收取,甚至是两者的组合。平台需要能够根据租户的配置,准确、高效地计算每笔交易的费率。
二、基于拉卡拉开放平台的系统设计思路
针对上述挑战,我们基于拉卡拉开放平台支付接口设计了一套完整的解决方案,主要包括以下几个方面:
1: 多租户隔离设计
通过租户 ID 来区分不同客户的数据,在数据存储时,将每个租户的支付信息与租户 ID 关联,确保在数据访问时,只能获取到当前租户的相关数据。同时,为每个租户配置独立的支付参数,如拉卡拉商户号、API 密钥等,进一步增强数据隔离性。
2: 费率计算设计
支持按租户配置不同的费率,包括基础费率(百分比)和固定费用。在处理每笔交易时,根据租户的费率配置,结合交易金额动态计算出相应的费用和净额(交易金额减去费用)。
3: 支付流程设计
集成拉卡拉开放平台支付网关,实现支付创建、回调处理和退款等完整操作。具体流程如下:
- 平台接收租户用户的支付请求,创建支付订单;
- 前端引导用户使用拉卡拉提供的支付方式完成支付操作;
- 拉卡拉通过 webhook 将支付结果通知给平台;
- 平台处理回调信息,更新交易状态,并进行费率计算;
- 若有退款需求,平台处理退款请求并与拉卡拉开放平台交互。
三、代码实现详解
以下是基于上述设计思路的代码实现,主要包括 API 视图和相关服务类。
1: API 视图
API 视图主要用于处理前端发送的支付相关请求,如创建支付订单、获取交易详情、处理拉卡拉回调和处理退款等。
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from .services import LakalaPaymentService
from .models import PaymentTransaction
from django_tenant_schemas.utils import get_current_tenant
class PaymentOrderView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
"""创建拉卡拉支付订单"""
try:
amount = request.data.get('amount')
description = request.data.get('description')
out_trade_no = request.data.get('out_trade_no') # 商户订单号
if not all([amount, out_trade_no]):
return Response(
{'error': 'Amount and out_trade_no are required'},
status=status.HTTP_400_BAD_REQUEST
)
# 获取当前租户
tenant = get_current_tenant()
# 创建拉卡拉支付服务实例
payment_service = LakalaPaymentService(tenant)
# 创建支付订单
result = payment_service.create_payment_order(
amount=amount,
description=description,
out_trade_no=out_trade_no
)
return Response(result, status=status.HTTP_201_CREATED)
except Exception as e:
return Response(
{'error': str(e)},
status=status.HTTP_400_BAD_REQUEST
)
class TransactionDetailView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, transaction_id):
"""获取交易详情"""
try:
transaction = PaymentTransaction.objects.get(transaction_id=transaction_id)
return Response({
'transaction_id': transaction.transaction_id,
'amount': transaction.amount,
'fee': transaction.fee,
'net_amount': transaction.net_amount,
'status': transaction.status,
'description': transaction.description,
'created_at': transaction.created_at,
'updated_at': transaction.updated_at
})
except PaymentTransaction.DoesNotExist:
return Response(
{'error': 'Transaction not found'},
status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
return Response(
{'error': str(e)},
status=status.HTTP_400_BAD_REQUEST
)
class LakalaWebhookView(APIView):
"""拉卡拉支付回调处理"""
permission_classes = [] # 拉卡拉回调无需用户认证
def post(self, request):
# 拉卡拉回调参数通常通过表单或JSON传递
data = request.data
# 验证回调签名
tenant = get_tenant_by_merchant_no(data.get('merchant_no')) # 根据商户号获取租户
payment_service = LakalaPaymentService(tenant)
success, message = payment_service.handle_payment_webhook(data)
if success:
return Response({'respCode': '0000', 'respMsg': 'success'}) # 拉卡拉要求的成功响应格式
return Response({'respCode': '9999', 'respMsg': message})
class RefundView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, transaction_id):
"""处理退款请求"""
try:
amount = request.data.get('amount')
tenant = get_current_tenant()
payment_service = LakalaPaymentService(tenant)
result = payment_service.process_refund(
transaction_id=transaction_id,
amount=amount
)
return Response(result)
except Exception as e:
return Response(
{'error': str(e)},
status=status.HTTP_400_BAD_REQUEST
)
2: 支付服务类
支付服务类封装了与拉卡拉开放平台交互的核心逻辑,包括创建支付订单、处理支付回调、计算费率、处理退款等方法。
import requests
import hashlib
import time
import uuid
from .models import PaymentTransaction, TenantLakalaConfig
class LakalaPaymentService:
def __init__(self, tenant):
self.tenant = tenant
# 获取当前租户的拉卡拉配置
self.lakala_config = TenantLakalaConfig.objects.get(tenant=tenant)
self.base_url = "https://api.lakala.com/pay" # 拉卡拉开放平台接口基础地址
def create_payment_order(self, amount, description, out_trade_no):
"""创建拉卡拉支付订单"""
# 构造请求参数
params = {
'merchant_no': self.lakala_config.merchant_no,
'out_trade_no': out_trade_no,
'total_amount': amount,
'body': description,
'notify_url': self.lakala_config.notify_url,
'timestamp': self.get_current_timestamp(),
# 其他必要参数
}
# 生成签名
params['sign'] = self.generate_sign(params)
# 调用拉卡拉创建订单接口
response = requests.post(f"{self.base_url}/createorder", params=params)
result = response.json()
if result.get('respCode') == '0000':
# 存储交易记录
PaymentTransaction.objects.create(
tenant=self.tenant,
out_trade_no=out_trade_no,
lakala_trade_no=result.get('trade_no'),
amount=amount,
description=description,
status='PENDING'
)
return result
else:
raise Exception(f"创建订单失败:{result.get('respMsg')}")
def generate_sign(self, params):
"""根据拉卡拉签名规则生成签名"""
# 按拉卡拉要求的顺序拼接参数
sorted_params = sorted(params.items(), key=lambda x: x[0])
sign_str = '&'.join([f"{k}={v}" for k, v in sorted_params]) + self.lakala_config.api_secret
# 进行MD5加密等操作(根据拉卡拉具体要求)
sign = hashlib.md5(sign_str.encode()).hexdigest().upper()
return sign
def handle_payment_webhook(self, data):
"""处理拉卡拉支付回调"""
# 验证签名
if not self.verify_webhook_sign(data):
return False, "签名验证失败"
# 解析回调数据
out_trade_no = data.get('out_trade_no')
trade_status = data.get('trade_status')
# 根据商户号和外部订单号查询交易记录
try:
transaction = PaymentTransaction.objects.get(
tenant=self.tenant,
out_trade_no=out_trade_no
)
# 更新交易状态
if trade_status == 'SUCCESS':
transaction.status = 'SUCCESS'
# 计算费率
self.calculate_fee(transaction)
transaction.save()
return True, "回调处理成功"
else:
transaction.status = 'FAILED'
transaction.save()
return True, "支付失败"
except PaymentTransaction.DoesNotExist:
return False, "交易记录不存在"
def verify_webhook_sign(self, data):
"""验证回调签名"""
# 移除sign参数后重新生成签名并与传入的sign比对
sign = data.pop('sign', '')
sorted_params = sorted(data.items(), key=lambda x: x[0])
sign_str = '&'.join([f"{k}={v}" for k, v in sorted_params]) + self.lakala_config.api_secret
generated_sign = hashlib.md5(sign_str.encode()).hexdigest().upper()
return sign == generated_sign
def calculate_fee(self, transaction):
"""计算交易费用"""
# 获取当前租户的费率配置
rate_config = self.tenant.rate_config
# 按配置计算费用(示例:基础费率+固定费用)
fee = transaction.amount * rate_config.percent_rate + rate_config.fixed_fee
transaction.fee = fee
transaction.net_amount = transaction.amount - fee
# 若拉卡拉有其他费用扣除,在此处进行相应计算
def process_refund(self, transaction_id, refund_amount):
"""处理退款"""
# 查询交易记录
transaction = PaymentTransaction.objects.get(
tenant=self.tenant,
transaction_id=transaction_id
)
# 构造退款请求参数
params = {
'merchant_no': self.lakala_config.merchant_no,
'out_trade_no': transaction.out_trade_no,
'out_refund_no': self.generate_refund_no(),
'refund_amount': refund_amount,
'timestamp': self.get_current_timestamp()
}
params['sign'] = self.generate_sign(params)
# 调用拉卡拉退款接口
response = requests.post(f"{self.base_url}/refund", params=params)
result = response.json()
if result.get('respCode') == '0000':
# 更新交易的退款状态
transaction.refund_status = 'SUCCESS'
transaction.refund_amount = refund_amount
transaction.save()
return result
else:
raise Exception(f"退款失败:{result.get('respMsg')}")
# 其他辅助方法
def get_current_timestamp(self):
return int(time.time() * 1000)
def generate_refund_no(self):
return f"REFUND_{uuid.uuid4().hex[:16].upper()}"
四、关键实现要点说明
1: 多租户隔离实现
· 使用TenantAwareModel确保每个租户只能访问自己的支付数据,在模型定义时,通过租户 ID 进行过滤。
· 每个租户有独立的支付配置,如拉卡拉商户号、API 密钥、费率等,这些配置与租户信息关联存储。
· 交易记录在存储时与租户强关联,通过租户 ID 进行区分,保证了数据的隔离性。
· 签名与加密隔离:每个租户使用自身的 API 密钥进行签名和加密操作,确保签名的唯一性和安全性。
2: 费率计算逻辑
· 支持多种费率形式,包括基础费率(百分比)和固定费用的混合计费模式,满足不同租户的需求。
· 费率按租户进行配置,平台可以根据租户的实际情况灵活调整费率参数。
· 在每笔交易完成后,自动根据租户的费率配置计算出费用和净额,并存储到交易记录中,方便租户查询和核对。
· 退款费率处理:对于退款场景,根据拉卡拉的退款费率规则以及租户约定,准确计算退款费用。
3: 拉卡拉接口集成要点
· 签名机制适配:严格按照拉卡拉开放平台的签名规则生成和验证签名,包括参数排序、加密方式等。
· 回调处理规范:正确解析拉卡拉的回调数据格式,验证回调的真实性后,及时更新交易状态。 · 接口错误处理:针对拉卡拉接口返回的错误码和错误信息,设计完善的错误处理机制,便于问题排查。
五、使用方法
· 租户配置:平台管理员为每个租户配置拉卡拉开放平台参数(商户号、API 密钥等)及费率规则。
· 支付发起:租户用户在前端发起支付请求,调用创建支付订单 API,传入交易金额、订单号等信息。
· 完成支付:系统调用拉卡拉接口创建订单,前端引导用户通过拉卡拉支付渠道完成支付。
· 回调处理:支付完成后,拉卡拉通过预设地址通知平台,平台处理回调并更新交易状态和费率。
· 退款操作:租户发起退款请求,平台调用拉卡拉退款接口处理,并根据回调更新退款状态。
· 交易查询:租户通过 API 查询交易详情,包括金额、费用、状态等信息。
基于拉卡拉开放平台的 SaaS 平台支付集成方案,通过合理的多租户隔离设计和灵活的费率计算逻辑,能够为租户提供安全、稳定的支付服务。实际应用中,需根据拉卡拉接口更新和业务需求变化不断优化方案。