Py学习  »  Python

自己写了一款支付神器!利用Python实现的!某云给60w年薪被拒!

Python学习交流 • 5 年前 • 374 次点击  

一、准备环境

###1、要有微信公众号,商户平台账号

https://pay.weixin.qq.com/wiki/doc/api/index.html

2、支持的支付方式有

3、备案域名

选择扫码支付,如果使用模式二则不需要域名,只需要可访问的ip地址就行。

4、建一个Django项目。

一、扫码支付

点击“扫码支付”按官方文档配置好回调url(具体如何配置看官网)

先从公众号上获取APP_ID,APP_SECRECT,从商户平台上获取MCH_ID,API_KEY

###1、使用模式一生成支付二维码


私信小编01 02 03 04 05即可获取数十套PDF以及小编精心整理的入门教程!

这个二维码是没有时间限制的。

####create_qrcode.html

创建二维码页面

生成扫码支付二维码

输入手机号:


{% if img_url %}

{% endif %}


{{ url }}

pay_settings.py

#微信支付配置

# ========支付相关配置信息===========

import random

import time

import hashlib

from random import Random

import qrcode

from bs4 import BeautifulSoup

APP_ID = "xxx" # 你公众账号上的appid

MCH_ID = "xxx" # 你的商户号

API_KEY = "xxx" # 微信商户平台(pay.weixin.qq.com) -->账户设置 -->API安全 -->密钥设置,设置完成后把密钥复制到这里

APP_SECRECT = "xxx"

UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder" # 该url是微信下单api

NOTIFY_URL = "http://xxx/" # 微信支付结果回调接口,需要改为你的服务器上处理结果回调的方法路径

CREATE_IP = 'xxx' # 你服务器的IP

def random_str(randomlength=8):

"""

生成随机字符串

:param randomlength: 字符串长度

:return:

"""

str = ''

chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'

length = len(chars) - 1

random = Random()

for i in range(randomlength):

str+=chars[random.randint(0, length)]

return str

def order_num(phone):

"""

生成扫码付款订单号

:param phone: 手机号

:return:

"""

local_time = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))

result = phone + 'T' + local_time + random_str(5)

return result

def get_sign(data_dict, key):

# 签名函数,参数为签名的数据和密钥

params_list = sorted(data_dict.items(), key=lambda e: e[0], reverse=False) # 参数字典倒排序为列表

params_str = "&".join(u"{}={}".format(k, v) for k, v in params_list) + '&key=' + key

# 组织参数字符串并在末尾添加商户交易密钥

md5 = hashlib.md5() # 使用MD5加密模式

md5.update(params_str.encode('utf-8')) # 将参数字符串传入

sign = md5.hexdigest().upper() # 完成加密并转为大写

return sign

def trans_dict_to_xml(data_dict): # 定义字典转XML的函数

data_xml = []

for k in sorted(data_dict.keys()): # 遍历字典排序后的key

v = data_dict.get(k) # 取出字典中key对应的value

if k == 'detail' and not v.startswith('

v = ''.format(v)

data_xml.append('{value}{key}>'.format(key=k, value=v))

return '{}'.format(''.join(data_xml)).encode('utf-8') # 返回XML,并转成utf-8,解决中文的问题

def trans_xml_to_dict(data_xml):

soup = BeautifulSoup(data_xml, features='xml')

xml = soup.find('xml') # 解析XML

if not xml:

return {}

data_dict = dict([(item.name, item.text) for item in xml.find_all()])

return data_dict

def wx_pay_unifiedorde(detail):

"""

访问微信支付统一下单接口

:param detail:

:return:

"""

detail['sign'] = get_sign(detail, API_KEY)

# print(detail)

xml = trans_dict_to_xml(detail) # 转换字典为XML

response = requests.request('post', UFDODER_URL, data=xml) # 以POST方式向微信公众平台服务器发起请求

# data_dict = trans_xml_to_dict(response.content) # 将请求返回的数据转为字典

return response.content

def pay_fail(err_msg):

"""

微信支付失败

:param err_msg: 失败原因

:return:

"""

data_dict = {'return_msg': err_msg, 'return_code': 'FAIL'}

return trans_dict_to_xml(data_dict)

def create_qrcode(phone,url):

"""

生成扫码支付二维码

:param phone: 手机号

:param url: 支付路由

:return:

"""

img = qrcode.make(url) # 创建支付二维码片

# 你存放二维码的地址

img_url = r'media/QRcode' + '/' + phone + '.png'

img.save(img_url)

return img_url

views.py

from django.shortcuts import render

from django.http import HttpResponse

from django.views import View

from django.views.decorators.csrf import csrf_exempt

from pay_settings.py import *

class Wxpay_QRccode(View):

"""

生成微信支付二维码

"""

def get(self, request, *args, **kwargs):

return render(request, 'create_qrcode.html')

def post(self, request, *args, **kwargs):

"""

# 生成可扫码支付的二维码

:param request:

:param args:

:param kwargs:

:return:

"""

phone = request.data.get('phone', None)

if not phone:

return HttpResponse('手机号获取失败')

paydict = {

'appid': APP_ID,

'mch_id': MCH_ID,

'nonce_str': random_str(phone),

'product_id': phone, # 商品id,可自定义

'time_stamp': int(time.time()),

}

paydict['sign'] = get_sign(paydict, API_KEY)

url = "weixin://wxpay/bizpayurl?appid=%s&mch_id=%s&nonce_str=%s&product_id=%s&time_stamp=%s&sign=%s"

% (paydict['appid'], paydict['mch_id'], paydict['nonce_str'], paydict['product_id'], paydict['time_stamp'], paydict['sign'])

# 可以直接在微信中点击该url,如果有错误,微信会弹出提示框,如果是扫码,如果失败,什么提示都没有,不利于调试

print(url)

# 创建二维码

img_url = create_qrcode(url)

return render(request, 'create_qrcode.html', context={'img_url': img_url})

@method_decorator(csrf_exempt, name='dispatch')

class Wxpay_ModelOne_pay(View):

"""

使用微信扫一扫扫描二维码,微信系统会自动回调此路由,Post请求

"""

def post(self, request, *args, **kwargs):

"""

扫描二维码后,微信系统回调的地址处理

微信传来的参数格式经trans_xml_to_dict()转成字典

{'openid': 'xxx',

'is_subscribe': 'Y',

'mch_id': 'xxx',

'nonce_str': 'xxx',

'sign': 'xxx',

'product_id': 'xxx',

'appid': 'xxx'}

:param request:

:param args:

:param kwargs:

:return:

"""

data_dict = trans_xml_to_dict(request.body) # 回调数据转字典

sign = data_dict.pop('sign') # 取出签名

key = API_KEY # 商户交易密钥

back_sign = get_sign(data_dict, key) # 计算签名

if sign == back_sign: # 验证签名是否与回调签名相同

total_fee = 1 # 付款金额,单位是分,必须是整数

params = {

'appid': APP_ID, # APPID

'mch_id': MCH_ID, # 商户号

'nonce_str': random_str(16), # 随机字符串

'out_trade_no': order_num(data_dict['product_id']), # 订单编号

'total_fee': total_fee, # 订单总金额

'spbill_create_ip': CREATE_IP, # 发送请求服务器的IP地址

'notify_url': NOTIFY_URL,

'body': 'xxx公司', # 商品描述

'detail': 'xxx商品', # 商品详情

'trade_type': 'NATIVE', # 扫码支付类型

}

# 调用微信统一下单支付接口url

notify_result = wx_pay_unifiedorde(params)

#print(trans_xml_to_dict(notify_result))

return HttpResponse(notify_result)

return HttpResponse(pay_fail('交易信息有误,请重新扫码'))

2、使用模式二生成支付二维码

这个二维码是有时间限制的。

模式二与模式一相比,流程更为简单,不依赖设置的回调支付URL。商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付。注意:code_url有效期为2小时,过期后扫码不能再发起支付。具体流程看微信公众号。

主体代码:

####pay_settings.py

#微信支付配置

# ========支付相关配置信息===========

import random

import time

import hashlib

from random import Random

from bs4 import BeautifulSoup

APP_ID = "xxx" # 你公众账号上的appid

MCH_ID = "xxx" # 你的商户号

API_KEY = "xxx" # 微信商户平台(pay.weixin.qq.com) -->账户设置 -->API安全 -->密钥设置,设置完成后把密钥复制到这里

APP_SECRECT = "xxx"

UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder" # 该url是微信下单api

NOTIFY_URL = "http://xxx/" # 微信支付结果回调接口,需要改为你的服务器上处理结果回调的方法路径

CREATE_IP = 'xxx' # 你服务器的IP

def get_sign(data_dict, key):

"""

签名函数

:param data_dict: 需要签名的参数,格式为字典

:param key: 密钥 ,即上面的API_KEY

:return: 字符串

"""

params_list = sorted(data_dict.items(), key=lambda e: e[0], reverse=False) # 参数字典倒排序为列表

params_str = "&".join(u"{}={}".format(k, v) for k, v in params_list) + '&key=' + key

# 组织参数字符串并在末尾添加商户交易密钥

md5 = hashlib.md5() # 使用MD5加密模式

md5.update(params_str.encode('utf-8')) # 将参数字符串传入

sign = md5.hexdigest().upper() # 完成加密并转为大写

return sign

def order_num(phone):

"""

生成扫码付款订单号

:param phone: 手机号

:return:

"""

local_time = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))

result = phone + 'T' + local_time + random_str(5)

return result

def random_str(randomlength=8):

"""

生成随机字符串

:param randomlength: 字符串长度

:return:

"""

strs = ''

chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'

length = len(chars) - 1

random = Random()

for i in range(randomlength):

strs += chars[random.randint(0, length)]

return strs

def trans_dict_to_xml(data_dict):

"""

定义字典转XML的函数

:param data_dict:

:return:

"""

data_xml = []

for k in sorted(data_dict.keys()): # 遍历字典排序后的key

v = data_dict.get(k) # 取出字典中key对应的value

if k == 'detail' and not v.startswith('

v = ''.format(v)

data_xml.append('{value}{key}>'.format(key=k, value=v))

return '{}'.format(''.join(data_xml)) # 返回XML

def trans_xml_to_dict(data_xml):

"""

定义XML转字典的函数

:param data_xml:

:return:

"""

soup = BeautifulSoup(data_xml, features='xml')

xml = soup.find('xml') # 解析XML

if not xml:

return {}

data_dict = dict([(item.name, item.text) for item in xml.find_all()])

return data_dict

####views.py

import qrcode

from django.shortcuts import render

from pay_settings import *

from django.http import HttpResponse

from django.views import View

class WXPayView(View):

def get(self,request):

# 在info.html有一个按钮,点击后跳转到这个类的post()中,具体urls.py设置不详述

return render(request, 'info.html')

def post(self,request):

phone = request.POST.get('phone','')

if not phone:

return render(request, 'info.html', {'err_msg': '获取手机号失败'})

data_dict = wxpay(phone)

if data_dict.get('return_code') == 'SUCCESS': # 如果请求成功

qrcode_name = phone + '.png' # 二维码图片名称

img = qrcode.make(data_dict.get('code_url')) # 创建支付二维码片

img.save(r'static' + '/' + qrcode_name)

return render(request, 'qrcode.html', {'qrcode_img': qrcode_name})

return render(request, 'info.html', {'err_msg': '获取微信的code_url失败'})

def wxpay(phone):

nonce_str = random_str() # 拼接出随机的字符串即可,我这里是用 时间+随机数字+5个随机字母

total_fee = 1 # 付款金额,单位是分,必须是整数

params = {

'appid': APP_ID, # APPID

'mch_id': MCH_ID, # 商户号

'nonce_str': nonce_str, # 随机字符串

'out_trade_no': order_num(phone), # 订单编号,可自定义

'total_fee': total_fee, # 订单总金额

'spbill_create_ip': CREATE_IP, # 自己服务器的IP地址

'notify_url': NOTIFY_URL, # 回调地址,微信支付成功后会回调这个url,告知商户支付结果

'body': 'xxx公司', # 商品描述

'detail': 'xxx商品', # 商品描述

'trade_type': 'NATIVE', # 扫码支付类型

}

sign = get_sign(params,API_KEY) # 获取签名

params['sign'] = sign # 添加签名到参数字典

# print(params)

xml = trans_dict_to_xml(params) # 转换字典为XML

response = requests.request('post', settings._UFDODER_URL, data=xml) # 以POST方式向微信公众平台服务器发起请求

data_dict = trans_xml_to_dict(response.content) # 将请求返回的数据转为字典

return data_dict

class Wxpay_Result(View):

"""

微信支付结果回调通知路由

"""

def get(self, request, *args, **kwargs):

machine_code = request.GET.get('machine_code', '获取机器编号失败')

# 返回支付成功页面,可自定义

return render(request, 'zfcg.html', {'machine': {'machine_code': machine_code}})

def post(self, request, *args, **kwargs):

"""

微信支付成功后会自动回调

返回参数为:

{'mch_id': '',

'time_end': '',

'nonce_str': '',

'out_trade_no': '',

'trade_type': '',

'openid': '',

'return_code': '',

'sign': '',

'bank_type': '',

'appid': '',

'transaction_id': '',

'cash_fee': '',

'total_fee': '',

'fee_type': '', '

is_subscribe': '',

'result_code': 'SUCCESS'}

:param request:

:param args:

:param kwargs:

:return:

"""

data_dict = trans_xml_to_dict(request.body) # 回调数据转字典

# print('支付回调结果', data_dict)

sign = data_dict.pop('sign') # 取出签名

back_sign = get_sign(data_dict, API_KEY) # 计算签名

# 验证签名是否与回调签名相同

if sign == back_sign and data_dict['return_code'] == 'SUCCESS':

'''

检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。

'''

print('微信支付成功会回调!')

# 处理支付成功逻辑

# 返回接收结果给微信,否则微信会每隔8分钟发送post请求

return HttpResponse(trans_dict_to_xml({'return_code': 'SUCCESS', 'return_msg': 'OK'}))

return HttpResponse(trans_dict_to_xml({'return_code': 'FAIL', 'return_msg': 'SIGNERROR'}))

##二、使用JsAPI发起微信支付

在微信公众号中左下角 设置->公众号设置->功能设置,把业务域名,js接口安全域名,网页授权域名设置好。

用户在微信中点击一个路由Url(可把这个url封装成二维码).在后台中的views.py中的WxJsAPIPay类中使用get()函数处理用户的请求,先获取用户的openid,然后调用微信统一下单接口,获取prepay_id,具体看官网。

####wx_js_pay.html

{# #}

{# #}

{# #}

订单详情

//调用微信JS api 支付

function onBridgeReady() {

WeixinJSBridge.invoke(

'getBrandWCPayRequest',

{

appId: "{{ params.appid }}", //公众号名称,由商户传入

timeStamp: "{{ params.timeStamp }}", //时间戳,自1970年以来的秒数

nonceStr: "{{ params.nonceStr }}", //随机串

package: "prepay_id={{ params.prepay_id }}", //预支付id

signType: "MD5", //微信签名方式

paySign: "{{ params.sign }}" //微信签名

},

function (res) {

//支付成功后返回 get_brand_wcpay_request:ok

if (res.err_msg == "get_brand_wcpay_request:ok") {

// 跳转到支付成功的页面

window.location.href = '#';

{#alert('支付成功');#}

} else if (res.err_msg == "get_brand_wcpay_request:cancel") {

alert("您已取消支付!");

{#alert({{ params.machine_code }});#}

{#window.location.href = '';#}

} else if (res.err_msg == "get_brand_wcpay_request:fail") {

$.each(res, function(key,value){

alert(value);

});

alert("支付失败!");

}

}

);

}

function callpay() {

if (typeof WeixinJSBridge == "undefined") {

if (document.addEventListener) {

document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);

} else if (document.attachEvent) {

document.attachEvent('WeixinJSBridgeReady', onBridgeReady);

document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);

}

} else {

onBridgeReady();

}

}

pay_settings.py

# encoding: utf-8

import hashlib

import time

import requests

from collections import OrderedDict

from random import Random

from bs4 import BeautifulSoup

APP_ID = "xxx" # 公众账号appid

MCH_ID = "xxx" # 商户号

API_KEY = "xxx" # 微信商户平台(pay.weixin.qq.com) -->账户设置 -->API安全 -->密钥设置,设置完成后把密钥复制到这里

APP_SECRECT = "xxx"

UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder" # url是微信下单api

NOTIFY_URL = "http://xxx/wxpay/pay_result/" # 微信支付结果回调接口,需要你自定义

CREATE_IP = 'xxx' # 你服务器上的ip

# 生成随机字符串

def random_str(randomlength=8):

"""

生成随机字符串

:param randomlength: 字符串长度

:return:

"""

str = ''

chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'

length = len(chars) - 1

random = Random()

for i in range(randomlength):

str+=chars[random.randint(0, length)]

return str

def order_num(phone):

"""

生成扫码付款订单,

:param phone:

:return:

"""

local_time = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))

result = phone + 'T' + local_time + random_str(5)

return result

def get_sign(data_dict, key):

# 签名函数,参数为签名的数据和密钥

params_list = sorted(data_dict.items(), key=lambda e: e[0], reverse=False) # 参数字典倒排序为列表

params_str = "&".join(u"{}={}".format(k, v) for k, v in params_list) + '&key=' + key

# 组织参数字符串并在末尾添加商户交易密钥

md5 = hashlib.md5() # 使用MD5加密模式

md5.update(params_str.encode('utf-8')) # 将参数字符串传入

sign = md5.hexdigest().upper() # 完成加密并转为大写

return sign

def trans_dict_to_xml(data_dict): # 定义字典转XML的函数

data_xml = []

for k in sorted(data_dict.keys()): # 遍历字典排序后的key

v = data_dict.get(k) # 取出字典中key对应的value

if k == 'detail' and not v.startswith('

v = ''.format(v)

data_xml.append('{value}{key}>'.format(key=k, value=v))

return '{}'.format(''.join(data_xml)).encode('utf-8') # 返回XML,并转成utf-8,解决中文的问题

def trans_xml_to_dict(data_xml):

soup = BeautifulSoup(data_xml, features='xml')

xml = soup.find('xml') # 解析XML

if not xml:

return {}

data_dict = dict([(item.name, item.text) for item in xml.find_all()])

return data_dict

def wx_pay_unifiedorde(detail):

"""

访问温馨支付统一下单接口

:param detail:

:return:

"""

detail['sign'] = get_sign(detail, API_KEY)

# print(detail)

xml = trans_dict_to_xml(detail) # 转换字典为XML

response = requests.request('post', UFDODER_URL, data=xml) # 以POST方式向微信公众平台服务器发起请求

# data_dict = trans_xml_to_dict(response.content) # 将请求返回的数据转为字典

return response.content

def get_redirect_url():

"""

获取微信返回的重定向的url

:return: url,其中携带code

"""

WeChatcode = 'https://open.weixin.qq.com/connect/oauth2/authorize'

urlinfo = OrderedDict()

urlinfo['appid'] = APP_ID

urlinfo['redirect_uri'] = 'http://xxx/wxjsapipay/?getInfo=yes' # 设置重定向路由

urlinfo['response_type'] = 'code'

urlinfo['scope'] = 'snsapi_base' # 只获取基本信息

urlinfo['state'] = 'mywxpay' # 自定义的状态码

info = requests.get(url=WeChatcode, params=urlinfo)

return info.url

def get_openid(code,state):

"""

获取微信的openid

:param code:

:param state:

:return:

"""

if code and state and state == 'mywxpay':

WeChatcode = 'https://api.weixin.qq.com/sns/oauth2/access_token'

urlinfo = OrderedDict()

urlinfo['appid'] = APP_ID

urlinfo['secret'] = APP_SECRECT

urlinfo['code'] = code

urlinfo['grant_type'] = 'authorization_code'

info = requests.get(url=WeChatcode, params=urlinfo)

info_dict = eval(info.content.decode('utf-8'))

return info_dict['openid']

return None

def get_jsapi_params(openid):

"""

获取微信的Jsapi支付需要的参数

:param openid: 用户的openid

:return:

"""

total_fee = 1 # 付款金额,单位是分,必须是整数

params = {

'appid': APP_ID, # APPID

'mch_id': MCH_ID, # 商户号

'nonce_str': random_str(16), # 随机字符串

'out_trade_no': order_num('123'), # 订单编号,可自定义

'total_fee': total_fee, # 订单总金额

'spbill_create_ip': CREATE_IP, # 发送请求服务器的IP地址

'openid': openid,

'notify_url': NOTIFY_URL, # 支付成功后微信回调路由

'body': 'xxx公司', # 商品描述

'trade_type': 'JSAPI', # 公众号支付类型

}

# print(params)

# 调用微信统一下单支付接口url

notify_result = wx_pay_unifiedorde(params)

params['prepay_id'] = trans_xml_to_dict(notify_result)['prepay_id']

params['timeStamp'] = int(time.time())

params['nonceStr'] = random_str(16)

params['sign'] = get_sign({'appId': APP_ID,

"timeStamp": params['timeStamp'],

'nonceStr': params['nonceStr'],

'package': 'prepay_id=' + params['prepay_id'],

'signType': 'MD5',

},

API_KEY)

return params

views.py

from django.http import HttpResponseRedirect, HttpResponse

from django.shortcuts import render

from django.views import View

from pay_settings import *

class WxJsAPIPay(View):

def get(self, request, *args, **kwargs):

"""

用户点击一个路由或者扫码进入这个views.py中的函数,首先获取用户的openid,

使用jsapi方式支付需要此参数

:param self:

:param request:

:param args:

:param kwargs:

:return:

"""

getInfo = request.GET.get('getInfo', None)

openid = request.COOKIES.get('openid', '')

if not openid:

if getInfo != 'yes':

# 构造一个url,携带一个重定向的路由参数,

# 然后访问微信的一个url,微信会回调你设置的重定向路由,并携带code参数

return HttpResponseRedirect(get_redirect_url())

elif getInfo == 'yes':

# 我设置的重定向路由还是回到这个函数中,其中设置了一个getInfo=yes的参数

# 获取用户的openid

openid = get_openid(request.GET.get('code'), request.GET.get('state', ''))

if not openid:

return HttpResponse('获取用户openid失败')

response = render_to_response('wx_js_pay.html', context={'params': get_jsapi_params(openid)})

response.set_cookie('openid', openid, expires=60 * 60 * 24 *30)

return response

else:

return HttpResponse('获取机器编码失败')

else:

return render(request, 'wx_js_pay.html', context={'params': get_jsapi_params(openid)})



今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/s3GGWJizNN
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/26294
 
374 次点击