微信支付自推出 V3 版本接口以来,体验下来确实比 V2 有了很大的提升,但介于其官方没有提供 Python 版本的 SDK,故在此记录通过 Python3 来使用微信支付 V3 的主要过程。
这一步看似可以忽略,但也需要仔细,笔者之前就犯了一个错,看了不对应的文档。
微信支付的商家有两种身份模式:普通商户、服务商。
一般的大部分商户都是属于普通商户,只有品牌商等才为服务商,并且两者身份不能互换。
普通商户的API文档:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
服务商API文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/index.shtml
需要在商户端界面 申请API证书 和 配置APIv3密钥,次过程可以参考每一个步骤右侧的查看指引按钮。
V3 版本的接口调用需要对请求进行签名,参考https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml 根据官方的文档可以按此步骤生成请求签名。
class WeixinPayUtil(object):
@staticmethod
def signature(private_key_path, sign_str):
"""
生成签名值
private_key_path 私钥路径
sign_str 签名字符串
https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml
"""
with open(private_key_path) as file:
private_key = file.read()
try:
rsa_key = RSA.import_key(private_key)
signer = pkcs1_15.new(rsa_key)
digest = SHA256.new(sign_str.encode('utf-8'))
return b64encode(signer.sign(digest)).decode('utf-8')
except Exception:
raise WeixinPaySignIError
def _generate_request_sign(self, url_path, data, method='POST'):
"""
生成请求签名
"""
sign_list = [method, url_path, self.timestamp, self.nonce_str]
if data is not None:
sign_list.append(json.dumps(data))
else:
sign_list.append('')
sign_str = '\n'.join(sign_list) + '\n'
return WeixinPayUtil.signature(private_key_path=self.cert_dir + 'api_key.pem', sign_str=sign_str)
准备好了上面这一切就可以开始调用 V3 版本的接口了,以统一支付为例,笔者写了一个示例,其中部分传参的命名和官方的命名稍有些不一致,至此整个 V3 版本的微信支付对接就告一段落了。
class WeixinPay(object):
"""
微信支付
"""
base_url = 'https://api.mch.weixin.qq.com'
def __init__(self, mch_id, app_id, api_v3_key, mch_cert_no, cert_dir, notify_url):
self.mch_id = mch_id
self.app_id = app_id
self.api_v3_key = api_v3_key
self.mch_cert_no = mch_cert_no
self.cert_dir = cert_dir
self.notify_url = notify_url
self.timestamp = str(int(time.time()))
self.nonce_str = ''.join(random.sample(string.ascii_letters + string.digits, 16))
def _generate_request_sign(self, url_path, data, method='POST'):
"""
生成请求签名
"""
sign_list = [method, url_path, self.timestamp, self.nonce_str]
if data is not None:
sign_list.append(json.dumps(data))
else:
sign_list.append('')
sign_str = '\n'.join(sign_list) + '\n'
return WeixinPayUtil.signature(private_key_path=self.cert_dir + 'api_key.pem', sign_str=sign_str)
def _generate_pay_sign(self, app_id, package):
"""
生成支付签名
"""
sign_list = [app_id, self.timestamp, self.nonce_str, package]
sign_str = '\n'.join(sign_list) + '\n'
return WeixinPayUtil.signature(private_key_path=self.cert_dir + 'api_key.pem', sign_str=sign_str)
def _generate_auth_header(self, signature):
"""
生成授权请求头
"""
return f'WECHATPAY2-SHA256-RSA2048 mchid="{self.mch_id}",nonce_str="{self.nonce_str}",' \
f'signature="{signature}",timestamp="{self.timestamp}",serial_no="{self.mch_cert_no}"'
def unified_order(self, order_id, openid, amount, desc, mch_id=None, notify_url=None, profit_sharing=False,
expire_time=None, attach=None, goods_tag=None, detail=None, scene_info=None, currency='CNY'):
"""
统一下单
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
"""
url_path = '/v3/pay/transactions/jsapi'
url = self.base_url + url_path
data = {
'appid': self.app_id,
'mchid': mch_id if mch_id is not None else self.mch_id,
'description': desc,
'out_trade_no': order_id,
'notify_url': notify_url if notify_url is not None else self.notify_url,
'settle_info': {
'profit_sharing': profit_sharing
},
'amount': {
'total': amount,
'currency': currency
},
'payer': {
'openid': openid
}
}
if attach:
data.update({'attach': attach})
if expire_time:
data.update({'time_expire': expire_time})
if goods_tag:
data.update({'goods_tag': goods_tag})
if detail:
data.update({'detail': detail})
if scene_info:
data.update({'scene_info': scene_info})
signature = self._generate_request_sign(url_path=url_path, data=data)
headers = {'Authorization': self._generate_auth_header(signature)}
res = requests.post(url=url, json=data, headers=headers, timeout=TIMEOUT).json()
# 支付签名
pay_sign = self._generate_pay_sign(app_id=self.app_id, package='prepay_id=' + res['prepay_id'])
return {
'timestamp': self.timestamp,
'nonce_str': self.nonce_str,
'prepay_id': res['prepay_id'],
'sign_type': 'RSA',
'pay_sign': pay_sign
}