概述
Java 实现企业级支付
- 一、简介
- 1、支付方式
- 2、微信支付
- 支付方式的支付场景
- 注册微信公众平台账号
- 安装微信开发者工具
- 3、支付宝支付
- 4、银联支付
- 5、开发工具与环境配置
- 二、微信支付
- 1、微信小程序支付
- (1)开发小程序必备
- (2)开通小程序支付业务
- (3)小程序支付接口原理
- (4)实现微信登陆小程序
- 1)、为什么先创建小程序项目
- 2)、设置小程序开发工具兼容 HBuilderX
- 3)、小程序获取临时的登陆凭证
- 4)、后端系统获取 openId
- (5)创建小程序订单及创建支付订单
- 1)、微信支付接口规则
- 2)、支付订单的 API 参数
- 3)、下载 SDK
- 4)、创建支付订单
- 5)、处理微信平台返回的结果
- (6)商户系统接收支付结果
- 1)、微信平台的支付回调
- 2)、支付回调的参数(当return_code为SUCCESS时)
- 3)、商户系统主动查询订单支付结果
- 小结
- 2、Native 支付
- (1)什么是 Native 支付?
- (2)开通 Native 支付
- (3)Native 支付原理
- (4)创建支付订单
- 1)、创建支付订单提交的参数
- 2)、创建支付订单
- (5)生成支付二维码
- (7)后端系统查询支付结果
- 3、付款码支付
- (1)什么是扫描支付
- (2)没有扫码器怎么实现扫码付款
- (3)扫码支付原理
- (4)提交扣款请求需要的参数
- (5)后端系统收款的方法
- 4、JSAPI 支付
- 5、H5 支付
- 6、APP 支付
- 7、刷脸支付
- 三、支付宝支付
- 1、支付宝小程序支付
- 2、Native 支付
- 3、H5 支付
- 四、银联支付
- 1、Native 支付
- 2、H5 支付
- 五、聚合支付
- 总结
一、简介
1、支付方式
目前主流的线上支付方式有:微信支付、支付宝支付、银联支付等。
2、微信支付
支付方式的支付场景
1)、小程序支付场景
用户在微信小程序上面下单支付,那么就调用微信小程序支付接口。
2)、Native 支付场景
用户在 PC 浏览器打开电商网站下单支付,此时电商网站调用微信的 Native 支付接口(PC 浏览器)。
3)、付款码支付场景
线下支付的时候,商家通过扫码器扫描你的付款码完成免密支付。
4)、H5 支付场景
用户在手机内置浏览器里打开电商网站下单支付,这个时候该网站调用的是微信的 H5 支付接口(手机浏览器)。
付款码与收款码的区别:付款码可以接入商户系统,而收款码直接接入微信平台。
5)、JASAPI 支付场景
用户在微信内置浏览器里面打开电商网站下单支付,此时需要调用微信的 JSAPI 接口(必须时在微信内置的浏览器里)。
6)、App 支付场景
用户在第三方 APP 上面下单支付,此时就需要调用微信的 App 支付接口。
7)、刷脸支付场景
用户使用刷脸方式下单支付,此时就需要调用微信的刷脸支付接口。
注册微信公众平台账号
注册步骤:
- 打开微信公众平台:https://mp.weixin.qq.com
- 点击该页面右上角“立即注册”
- 选择注册小程序
- 填写用户注册信息
- 其余注册部分后续补!!!
安装微信开发者工具
微信开发者工具下载地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
安装步骤:直接下一步!!!
3、支付宝支付
后续待补!!!
4、银联支付
后续待补!!!
5、开发工具与环境配置
IDEA 版本:2020.1
下载地址:https://www.jetbrains.com/idea/
JDK 版本:1.8
下载地址:https://www.oracle.com/java/technologies/javase-jdk16-downloads.html
Maven 版本:3.6.3
下载地址:https://maven.apache.org/download.cgi
MySQL 版本:8.0.23
下载地址:https://dev.mysql.com/downloads/mysql/
数据库管理工具:Navicat Premium 1.5
下载地址:http://www.navicat.com.cn/products/navicat-premium/
Node 版本:15.12.0
下载地址:https://nodejs.org/zh-cn/
HBuilderX 版本:3.1.4
下载(APP开发版)地址:https://www.dcloud.io/hbuilderx.html
Postman 版本:1.8
下载地址:https://www.postman.com/downloads/
二、微信支付
1、微信小程序支付
(1)开发小程序必备
- 企业类型小程序唯一的 AppID 和 AppSecret;AppID 和 AppSecret 只有在实现支付的时候才会在后台代码中使用到
- 开发者账户与小程序一一对应,每个开发者账户对应唯一的小程序,若要重新开发新的小程序就需要重新注册开发者账号
- 除了AppID 和 AppSecret 之外,还需要从微信商户平台得到商户号、数字证书、密钥
(2)开通小程序支付业务
后续待补!!!
(3)小程序支付接口原理
小程序支付交互图:
小程序支付业务流程:
- ---------创建支付订单:---------
- 用户在小程序上点击微信支付按钮。
- 小程序通过发起 Ajax 请求 告诉商户系统向微信平台申请创建支付订单。
- 商户系统检查小程序提交的数据,例如:openId 在数据库里是否存在,是否是用户用微信登陆小程序产生的 ID 值;还有就是小程序提交的订单编号是否有效。
- 商户系统验证通过后,向微信平台发送生成支付订单的请求,并发送跟支付相关的各种信息,例如:订单金额?人民币还是其它币种?收款的商户ID?付款用户的 openId?
- 微信平台接收到信息后核实是否有误,若是无误,则微信平台就会生成微信支付订单,并且将订单信息返回给商户系统。
- 商户系统得到订单信息后,对订单信息生成 MD5 数字签名。
- 然后商户系统把支付订单的参数返回给小程序。
- -----------用户付款:-----------
- 小程序将从商户系统得到的支付订单参数提交给微信平台进行验证,验证商户系统返回的参数是否正确(毕竟订单是商户系统提交给微信平台的,防止商户系统提交的信息与小程序给商户系统的不一致)。
- 微信平台确认小程序的得到的支付参数没问题,于是就把商户创建的订单信息返回给小程序让用户确认;此时小程序就会弹出支付的金额和收款的商户信息。
- 用户对弹出的信息没有异议后,于是输入支付密码。
- 然后小程序向微信平台发送用户确认支付的请求。
- 微信平台验证用户支付密码正确之后就可以执行支付订单了(微信平台从微信用户账户中扣款)。
- 最后微信平台无论是扣款成功还是失败,都会把支付的结果分别发送给商户系统和小程序。
商户系统如何验证用户登陆:
商户系统在申请创建支付订单之前,必须判断提交请求的小程序用户是否已经登陆,会不会是 Postman 或者 HttpClient 这类的软件模拟小程序发起的请求。
商户系统验证用户是否登陆的方法:验证 openId 和 Token 字符串。用户在手机上用微信登陆小程序的时候,会产生一个唯一的 openId 值,商户系统会记录这个 openId 值,如果商户系统收到的请求里没有这个 openId 值或者这个 openId 值与数据库中的对应不上,就说明这不是一个合法用户;仅仅 openId 能核对还不行,还需要看发起请求的用户是否已经登陆小程序,只有用户登陆小程序之后,才可以证明是本人下单支付。成功登陆小程序的用户后端系统会返回一个 Token 字符串,小程序每次发器请求都会带上这个 Token 字符串,告诉后端系统已经登陆,后端系统也会验证 Token 字符串是否有效和是否过期。
(4)实现微信登陆小程序
1)、为什么先创建小程序项目
因为后端项目需要拿到小程序提交的临时登陆凭证 code 字符串到微信平台验证,并且获得 open_id 字符串。
用户在小程序点击登陆按钮;小程序获得临时登陆凭证和用户账号基本信息;小程序将得到的信息提交到商户系统;商户系统不信任小程序提交的信息,于是商户系统拿着登陆凭证及用户信息到微信平台去验证;如果微信平台核实登陆有效,就会向商户系统返回一个唯一的 openId 字符串;商户系统拿着 openId 字符串到本地数据库去查询,如果客户表里有 openId 的记录,则证明用户不是第一次登陆系统,然后执行正常登陆即可;如果客户表中查询不到 openId 字符串,则说明用户第一次登陆系统,于是后端系统将这些信息保存到数据表中,然后再执行登陆。登录成功商户系统给小程序返回一个登陆令牌字符串,以后小程序再发起请求都要带着这个令牌字符串,后端系统验证令牌字符串后,就知道小程序是登陆,如果小程序发起的请求没带令牌字符串或者令牌字符串不正确,后端系统则判定用户未登陆小程序。
2)、设置小程序开发工具兼容 HBuilderX
- 微信扫描微信开发者工具二维码
- 创建项目后,选择“设置”里的“安全设置”
- 点击“安全”,开启“服务端口”
- 打开 HBuilderX 工具,点击“工具”、“外部命令”、“运行设置”进入 HBuilderX 功能设置界面
- 在“运行配置”里设置“微信开发者工具路径”
- 然后新建一个 uni-app 项目
为什么使用 uni-app 框架?
uni-app 是一个跨平台的移动端框架,它采用 Hybrid 方式开发,可以把代码编译成各种小程序、iOS App、Android App等;
uni-app 可以使用 Vue 语法开发移动端程序;
微信开发者工具语法提示不如 HBuilderX 智能。
- 在 manifest.json 中的“微信小程序配置”设置小程序的 AppId
3)、小程序获取临时的登陆凭证
- 用户使用微信账号登陆小程序,先要在微信 APP 上面获得临时登陆凭证
- 在小程序端调用 uni.login() 即可获取登陆凭证字符串 code (code 的有效期为 5 分钟)
- 调用 uni.getUserInfo() 方法获得微信用户基本信息(登陆按钮必须要有 open-type = “getUserInfo” 属性)
<view>
<button open-type="getUserInfo" @tap="login">登陆</button>
</view>
methods: {
login:function(){
let that = this
uni.login({
success:function(res){
// console.log(res)
let code = res.code
uni.getUserInfo({
success:function(res){
// console.log(res)
let avatarUrl = res.userInfo.avatarUrl
let nickName = res.userInfo.nickName
// console.log(avatarUrl)
// console.log(nickName)
// 发起 Ajax 请求后端系统
uni.request({
url:that.url.wxLogin,
method:"POST",
data:{
"code":code,
"nickname":nickName,
"photo":avatarUrl
},
// 回调函数:后端处理完 Ajax请求返回的结果
success:function(res){
// console.log(res)
let token = res.data.token
let expire = res.data.expire
// 因为小程序每次请求都需要带 token 字符串,
// 于是将 token 保存到小程序本地StorageSync(同步)
uni.setStorageSync("token",token)
uni.setStorageSync("expire",expire)
// 登陆成功后跳转页面
uni.switchTab({
url:"../index/index"
})
}
})
}
})
}
})
}
}
4)、后端系统获取 openId
商户向微信平台提交的请求参数如下:
参数名 | 含义 | 备注 |
---|---|---|
appid | 小程序ID | 无 |
secret | 小程序密钥 | 无 |
js_code | 临时登陆凭证 | 无 |
grant_type | 授权码模式 | 固定值为:authorization_code |
@RestController
@RequestMapping("/app/wx")
public class WxController {
@Value("${application.app-id}")
private String appId;
@Value("${application.app-secret}")
private String appSecret;
@Value("${application.key}")
private String key;
@Value("${application.mch-id}")
private String mchId;
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
@Autowired
private JwtUtils jwtUtils;
@Autowired
private MyWxPayConfig myWxPayConfig;
/**
* 登录
*/
@PostMapping("/login")
@ApiOperation("登录")
public R login(@RequestBody WxLoginForm form){
//表单校验
ValidatorUtils.validateEntity(form);
/**
* 1、接收信息
* 这部分是接收小程序端 Ajax请求传过来的用户信息
*/
Map map = new HashMap();
map.put("appid",appId); //
map.put("secret",appSecret); //
map.put("js_code",form.getCode());
map.put("grant_type","authorization_code");
/**
* 2、验证信息
* 由于商户系统不信任小程序,于是将得到的信息拿到微信平台去验证
* 如果微信平台验证通过会返回一个 openid 字符串
*/
// 微信平台 URL
String url = "https://api.weixin.qq.com/sns/jscode2session";
String response = HttpUtil.post(url,map);
JSONObject jsonObject = JSONUtil.parseObj(response);
String openId = jsonObject.getStr("openid");
/**
* 3、查询数据库
* 商户系统拿着 openId 字符串到本地数据库去查询,
* 如果客户表里有 openId 的记录,则证明用户不是第一次登陆系统,然后执行正常登陆即可;
* 如果客户表中查询不到 openId 字符串,则说明用户第一次登陆系统,于是后端系统将这些信息保存到数据表中,然后再执行登陆。
*/
// 注册
if (openId == null || openId.length() == 0){
return R.error("临时登陆凭证不正确");
}
UserEntity userEntity = new UserEntity();
userEntity.setOpenId(openId);
QueryWrapper queryWrapper = new QueryWrapper(userEntity);
int count = userService.count(queryWrapper);
if (count == 0){
userEntity.setNickname(form.getNickname());
userEntity.setPhoto(form.getPhoto());
userEntity.setType(2);
//1:普通用户登陆 2:微信用户登陆
userEntity.setCreateTime(new Date());
userService.save(userEntity);
}
// 登陆
UserEntity user = new UserEntity();
user.setOpenId(openId);
QueryWrapper query = new QueryWrapper(user);
UserEntity one = userService.getOne(query);
Long userId = one.getUserId();
/**
* 4、返回登陆令牌
* 登录成功商户系统给小程序返回一个登陆令牌字符串,
* 以后小程序再发起请求都要带着这个令牌字符串,
* 后端系统验证令牌字符串后,就知道小程序是登陆,
* 如果小程序发起的请求没带令牌字符串或者令牌字符串不正确,后端系统则判定用户未登陆小程序。
*/
//生成token
String token = jwtUtils.generateToken(userId);
//
System.out.println(token);
Map<String, Object> result = new HashMap<>();
result.put("token", token);
result.put("expire", jwtUtils.getExpire());
return R.ok(result);
}
/**
* 接收小程序已经支付成功的请求,修改订单状态码
*/
@Login
@PostMapping("/updateOrderStatus")
public R updateOrderStatus(UpdateOrderStatusForm form, @RequestHeader HashMap header){
// 表单验证
ValidatorUtils.validateEntity(form);
String token = header.get("token").toString();
long userId = Long.parseLong(jwtUtils.getClaimByToken(token).getSubject());
int orderId = form.getOrderId();
// 查询这个订单是否属于该用户
OrderEntity orderEntity = new OrderEntity();
orderEntity.setUserId((int) userId);
orderEntity.setId(orderId);
QueryWrapper wrapper = new QueryWrapper(orderEntity);
int count = orderService.count(wrapper);
// 验证逻辑的有效性
if (count == 0){
return R.error("用户与订单不匹配!");
}
/**
* 如果匹配,商户系统需要发出网络请求到微信平台查询订单对应的结果
*/
// 根据小程序提交的商品订单ID查询商品订单的流水号
orderEntity = orderService.getOne(wrapper);
// 提取订单的流水号
String code = orderEntity.getCode();
Map map = new HashMap();
map.put("appid",appId);
map.put("mch_id",mchId);
map.put("out_trade_no",code);
map.put("nonce_str",WXPayUtil.generateNonceStr());
try {
// 生成数字签名
String sign = WXPayUtil.generateSignature(map,key);
map.put("sign",sign);
WXPay wxPay = new WXPay(myWxPayConfig);
// 到微信平台查询订单对应结果
Map<String,String> result = wxPay.orderQuery(map);
// 获取通信状态码和业务状态码
String returnCode = result.get("return_code"); // 通信
String resultCode = result.get("result_code"); // 业务
// 这两个状态码都是SUCCESS表示小程序支付成功并且商户系统收到的小程序的支付成功通知不是由postman发出的
if ("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)){
//交易状态 trade_state
// SUCCESS--支付成功
//REFUND--转入退款
//NOTPAY--未支付
//CLOSED--已关闭
//REVOKED--已撤销(刷卡支付)
//USERPAYING--用户支付中
//PAYERROR--支付失败(其他原因,如银行返回失败)
//ACCEPT--已接收,等待扣款
// 获得微信平台的交易状态
String tradeState = result.get("trade_state");
// 微信平台的交易状态为:SUCCESS 表示小程序的确支付成功,可以更改商户系统的状态码
if ("SUCCESS".equals(tradeState)){
UpdateWrapper updateWrapper = new UpdateWrapper();
// 通过流水号(商户订单号)查询订单信息
updateWrapper.eq("code",code);
// 更改该条订单的支付状态码改为已付款
// 状态:1未付款,2已付款,3已发货,4已签收
updateWrapper.set("status",2);
orderService.update(updateWrapper);
return R.ok("订单状态已修改!");
}else {
return R.ok("订单状态未修改!");
}
}
return R.ok("订单状态未修改!");
} catch (Exception e) {
e.printStackTrace();
return R.error("查询支付订单失败!");
}
}
}
(5)创建小程序订单及创建支付订单
1)、微信支付接口规则
- 为了保证交易的安全,支付必须使用 HTTPS 协议
- 必须使用 POST 方式提交支付数据
- 提交数据和返回结果都为 XML 格式
- 所有数据采用 UTF-8 编码
- 交易支付金额单位为 分,不能有小数点,并且最低金额为 1 元(1元=100)
- 小程序的交易类型(trade_type)是 JSAPI
- 境内的商户只能用 人民币交易
2)、支付订单的 API 参数
支付订单 API 参数文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
必须上传的参数:
参数名称 | 参数名 | 是否必填 | 参数类型 | 示例 | 说明 |
---|---|---|---|---|---|
公众账号ID | appid | 是 | String(32) | wxd678efh567hg6787 | 微信支付分配的公众账号ID(企业号corpid即为此appId) |
商户号 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商户号 |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,长度要求在32位以内。推荐随机数生成算法 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 通过签名算法计算得出的签名值 |
商品描述 | body | 是 | String(128) | 会员充值 | 商品简单描述,该字段请按照规范传递,具体请见参数规定 |
商户订单号 | out_trade_no | 是 | String(32) | 20150806125346 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-* 且在同一个商户号下唯一 |
标价金额 | total_fee | 是 | Int | 150 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(64) | 123.12.12.123 | 支持IPV4和IPV6两种格式的IP地址。用户的客户端IP |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 |
交易类型 | trade_type | 是 | String(16) | JSAPI | JSAPI -JSAPI支付、NATIVE -Native支付、APP -APP支付 |
用户标识 | openid | 是 | String(128) | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o | trade_type=JSAPI时(即JSAPI支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" |
签名类型 | sign_type | 否 | String(32) | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 |
商品详情 | detail | 否 | String(6000) | 商品详细描述,对于使用单品优惠的商户,该字段必须按照规范上传 | |
附加数据 | attach | 否 | String(127) | 深圳分店 | 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 |
标价币种 | fee_type | 否 | String(16) | CNY | 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型 |
交易起始时间 | time_start | 否 | String(14) | 20091225091010 | 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010 |
交易结束时间 | time_expire | 否 | String(14) | 20091227091010 | 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。建议:最短失效时间间隔大于1分钟 |
订单优惠标记 | goods_tag | 否 | String(32) | WXG | 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠 |
商品ID | product_id | 否 | String(32) | 12235413214070356458058 | trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。 |
指定支付方式 | limit_pay | 否 | String(32) | no_credit | 上传此参数no_credit–可限制用户不能使用信用卡支付 |
电子发票入口开放标识 | receipt | 否 | String(8) | Y | Y,传入Y时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效 |
是否需要分账 | profit_sharing | 否 | String(16) | Y | Y-是,需要分账N-否,不分账字母要求大写,不传默认不分账 |
场景信息 | scene_info | 否 | String(256) | 该字段常用于线下活动时的场景信息上报,支持上报实际门店信息,商户也可以按需求自己上报相关信息。该字段为JSON对象数据 |
3)、下载 SDK
下载微信 SDK 地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
下载好SDK 后,将这个包解压,进入 src/main/java 目录下,然后将 “com”包复制到自己项目的 src/main/java 目录下,再然后修改“WXPay”中第 47 行的“SignType.HMACSHA256”改为“SignType.MD5”。
如果不去创建 WXPayConfig 的子类,就没有办法创建 WXPay 的对象,而WXPay 里有创建微信支付订单的方法,所以必须把 WXPayConfig 这个抽象类的子类创建出来。
package com.github.wxpay.sdk;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.*;
@Component
public class MyWxPayConfig extends WXPayConfig {
@Value("${application.app-id}")
private String appId;
@Value("${application.mch-id}")
private String mchId;
@Value("${application.key}")
private String key;
@Value("${application.cert-path}")
private String certPath;
// 用于保存数字证书的内容
private byte[] certData;
/**
* 用于读书磁盘上的数字证书
* 方法名可以随便取
*
@PostConstruct 作用:当 Spring 创建完该对象并且完成各种依赖注入、值注入之后就要执行该方法
*/
@PostConstruct
public void init() throws Exception{
File file = new File(certPath);
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
this.certData = new byte[(int) file.length()];
bis.read(this.certData);
bis.close();
fis.close();
}
@Override
String getAppID() {
return appId;
}
@Override
String getMchID() {
return mchId;
}
@Override
String getKey() {
return key;
}
@Override
InputStream getCertStream() {
ByteArrayInputStream bais = new ByteArrayInputStream(this.certData);
return bais;
}
@Override
IWXPayDomain getWXPayDomain() {
return new IWXPayDomain() {
@Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
@Override
public DomainInfo getDomain(WXPayConfig config) {
return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API,true);
}
};
}
@Override
public int getHttpConnectTimeoutMs() {
return super.getHttpConnectTimeoutMs();
}
@Override
public int getHttpReadTimeoutMs() {
return super.getHttpReadTimeoutMs();
}
@Override
public boolean shouldAutoReport() {
return super.shouldAutoReport();
}
@Override
public int getReportWorkerNum() {
return super.getReportWorkerNum();
}
@Override
public int getReportQueueMaxSize() {
return super.getReportQueueMaxSize();
}
@Override
public int getReportBatchSize() {
return super.getReportBatchSize();
}
}
4)、创建支付订单
<template>
<view class="page">
<view class="order" v-for="list in lists" :key = "list">
<view class="line-1">
<text>订单号:{{list.code}}</text>
<text>{{list.status}}</text>
</view>
<view class="line-2">
<text>订单的简要信息!!!</text>
</view>
<view class="line-3">
<text>金额:{{list.amount}} 元</text>
<button class="pay-btn" type="primary" v-if="list.status == '未付款' " @tap="pay(list.id)">付款</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
lists:[]
}
},
methods: {
pay:function(id){
let that = this
uni.request({
url:that.url.microAppPayOrder,
method:"POST",
header:{
"token":uni.getStorageSync("token")
},
data:{
"orderId": id
},
success:function(res){
// console.log(res)
let timeStamp = res.data.timeStamp
let nonceStr = res.data.nonceStr
let pack = res.data.package
let paySign = res.data.paySign
uni.requestPayment({
"timeStamp":timeStamp,
"nonceStr":nonceStr,
"package":pack,
"signType":"MD5",
"paySign":paySign,
success() {
uni.showToast({
title:"支付成功"
})
// 小程序发出请求让后端系统主动查询支付结果
},
fail() {
uni.showToast({
title:"支付失败"
})
}
})
}
})
}
},
onShow() {
let that = this
uni.request({
url:that.url.searchUserOrderList,
method:"POST",
header:{
"token":uni.getStorageSync("token")
},
data:{
"page": 1,
"length": 20
},
success(res) {
console.log(res)
let list = res.data.list
for(let one of list){
if(one.status == 1){
one.status = "未付款"
}else if(one.status == 2){
one.status == "已付款"
}else if(one.status == 3){
one.status == "已发货"
}else if(one.status == 4){
one.status == "已签收"
}
}
that.lists = list
}
})
}
}
</script>
<style>
.page{
padding: 10px;
}
.order{
padding: 10px;
border-bottom: solid 1px #e0e0e0;
}
.line-1{
display: flex;
justify-content: space-between;
padding-bottom: 5px;
}
.line-2{
padding-bottom: 5px;
}
.line-3{
display: flex;
justify-content: space-between;
align-items: baseline;
}
.pay-btn{
margin: 0;
font-size: 14px;
line-height: 2;
}
</style>
5)、处理微信平台返回的结果
微信平台响应的参数:
参数名称 | 参数名 | 说明 |
---|---|---|
状态码 | return_code | 状态码有两种值:success、fail 该状态码只代表商户的请求是否成功送达微信平台,至于微信平台能不能成功创建支付订单,状态码反映不出来 |
小程序ID | app_id | 返回商户上传的 app_id |
商户ID | mch_id | 返回商户上传的 mch_id |
随机字符串 | nonce_str | 微信平台返回的一个随机字符串 |
数字签名 | sign | 微信平台将返回的数据进行数字签名,防止发送数据时丢包,商户收到数据之后进行MD5运算,然后跟数字签名比较,如果相同则数据没有丢包 |
业务结果 | result_code | success:表示支付订单创建成功,fail:表示支付订单创建失败 |
交易类型 | trade_type | 原封不动返回提交的交易类型 |
支付订单ID | prepay_id | 微信平台产生的订单ID |
后台商户系统端:
/**
* 小程序付款
*/
@Login
@PostMapping("/microAppPayOrder")
public R microAppPayOrder(PayOrderForm form, @RequestHeader HashMap header){
/**
* 1、小程序申请创建支付订单
* 商户系统校验小程序提供的信息是否无误
*/
// 校验
ValidatorUtils.validateEntity(form);
String token = header.get("token").toString();
long userId = Long.parseLong(jwtUtils.getClaimByToken(token).getSubject());
int orderId = form.getOrderId();
UserEntity user = new UserEntity();
user.setUserId(userId);
QueryWrapper wrapper = new QueryWrapper(user);
long count = userService.count(wrapper);
if (count == 0){
return R.error("用户不存在!");
}
// 定义一个变量用于保存用户的 openId
String openId = userService.getOne(wrapper).getOpenId();
OrderEntity order = new OrderEntity();
order.setUserId((int) userId);
order.setId(orderId);
order.setStatus(1);
wrapper = new QueryWrapper(order);
count = orderService.count(wrapper);
if (count == 0){
return R.error("不是有效订单!");
}
// 验证购物券是否有效
// 验证团购是否有效
order = new OrderEntity();
order.setId(orderId);
wrapper = new QueryWrapper(order);
order = orderService.getOne(wrapper);
/**
* 2、商户平台校验信息无误后,利用 SDK程序包 向微信平台发出创建支付订单请求
*/
// 获取订单的总金额 乘100(单位转为分) 取整(有小数点后两位)
String amount = order.getAmount().multiply(new BigDecimal("100")).intValue() + "";
// 获取用户 openid
try {
WXPay wxPay = new WXPay(myWxPayConfig);
Map map = new HashMap();
// 随机字符串,可自己编写代码生成,此处直接使用SDK产生
map.put("nonce_str", WXPayUtil.generateNonceStr());
// 商品描述
map.put("body", "订单备注");
// 商户订单号(编号为字符串)
map.put("out_trade_no", order.getCode());
// 标价金额
map.put("total_fee", amount);
// 终端IP
map.put("spbill_create_ip", "127.0.0.1");
// 支付结果通知地址,地址可以随便编写,即使商户系统接收不到付款通知结果,也可以让商户平台主动向微信平台发起请求去得到支付结果
map.put("notify_url", "https://127.0.0.1/test");
// 交易类型,固定为:JSAPI
map.put("trade_type", "JSAPI");
// 用户标识
map.put("openid", openId);
// 上面是必填,下面是根据场景填
/*// 设备号
map.put("device_info", );
// 签名类型
map.put("sign_type", );
// 商品详情
map.put("detail", );
// 剩余参数根据具体情况补充,详情参考API文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
*/
/**
* 此方法可以帮我们发出请求让微信平台生成支付订单
*/
Map<String,String> result = wxPay.unifiedOrder(map);
// 微信支付订单的ID
String prepayId = result.get("prepay_id");
// 将支付订单ID保存到数据表
order.setPrepayId(prepayId);
if (prepayId != null){
UpdateWrapper updateWrapper = new UpdateWrapper();
updateWrapper.eq("id",order.getId());
orderService.update(order,updateWrapper);
/**
* 将订单数据返回给小程序
*/
// 生成数字签名:由于小程序端没有微信提供的sdk程序包,如果自己写需要写很多代码,
// 所以不如就让后台程序生成数字签名,返回给小程序
map.clear();
map.put("appId",appId);
// 时间戳
String timeStamp = new Date().getTime() + "";
map.put("timeStamp",timeStamp);
// 随机字符串
String nonceStr = WXPayUtil.generateNonceStr();
map.put("nonceStr",nonceStr);
map.put("package","prepay_id =" + prepayId);
map.put("signType","MD5");
// 生成数字签名
String paySign = WXPayUtil.generateSignature(map,key);
/**
* 3、商户系统将从微信平台得到的信息MD5签名后,将数据返回给小程序
*/
return R.ok().put("package","prepay_id =" + prepayId)
.put("timeStamp",timeStamp)
.put("nonceStr",nonceStr)
.put("paySign",paySign);
}else {
return R.error("支付订单创建失败!");
}
} catch (Exception e) {
e.printStackTrace();
return R.error("微信支付模块故障");
}
}
/**
* 商户系统web接收支付结果通知
* @param request
* @param response
* @throws Exception
*/
@RequestMapping("/recieveMessage")
public void recieveMessage(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 设置字符集
request.setCharacterEncoding("utf-8");
// 创建字符流读取请求里的数据
Reader reader = request.getReader();
BufferedReader br = new BufferedReader(reader);
String line = br.readLine();
StringBuffer temp = new StringBuffer();
while (line != null){
temp.append(line);
line = br.readLine();
}
br.close();
reader.close();
/**
*
*/
Map<String, String> map = WXPayUtil.xmlToMap(temp.toString());
String resultCode = map.get("result_code");
String returnCode = map.get("return_code");
if ("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)){
// 如果通信状态码和业务状态码都是:SUCCESS
// 取出商品订单流水号
String outTradeNo = map.get("out_trade_no");
UpdateWrapper updateWrapper = new UpdateWrapper();
// 查询条件
updateWrapper.eq("code",outTradeNo);
// 设置修改哪个字段的值
updateWrapper.set("status",2);
orderService.update(updateWrapper);
// 设置响应的字符集
response.setCharacterEncoding("utf-8");
// 告诉微信平台响应的内容是什么
response.setContentType("application/xml");
Writer writer = response.getWriter();
BufferedWriter bufferedWriter = new BufferedWriter(writer);
bufferedWriter.write("<xml>n" +
"
<return_code><![CDATA[SUCCESS]]></return_code>n" +
"
<return_msg><![CDATA[OK]]></return_msg>n" +
"</xml>");
bufferedWriter.close();
writer.close();
}
}
(6)商户系统接收支付结果
1)、微信平台的支付回调
支付结果通知文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
- 微信平台会把用户支付的结果,发送到 URL 回调地址(即:notify_url),格式为 XML
- 如果商户系统没有响应,微信平台会每隔一段时间再发送一次支付结果;多次未响应之后就不再发送
2)、支付回调的参数(当return_code为SUCCESS时)
参数名称 | 参数名 | 是否必填 | 参数类型 | 示例 | 说明 |
---|---|---|---|---|---|
通信状态码 | return_code | String | SUCCESS/FAIL | 通信成功不代表支付成功,例如:微信平台从银行卡扣款,由于余额不足,扣款不成功,但小程序发送给微信平台的扣款请求微信平台收到了,所以通信状态码为SUCCESS | |
业务状态码 | result_code | 是 | String(16) | SUCCESS | SUCCESS表示支付成功 |
微信支付订单号 | transaction_id | 是 | String(32) | 1217752501201407033233368018 | 微信支付订单号 |
商户订单号 | out_trade_no | 是 | String(32) | 1212321211201407033568112322 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-*@ ,且在同一个商户号下唯一 |
订单金额 | total_fee | 是 | Int | 100 | 订单总金额,单位为分 |
小程序ID | appid | 是 | String(32) | wx8888888888888888 | 微信分配的小程序ID |
用户标识 | openid | 是 | String(128) | wxd930ea5d5a258f4f | 用户在商户appid下的唯一标识 |
商户号 | mch_id | 是 | String(32) | 1900000109 | 微信支付分配的商户号 |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 签名,详见签名算法 |
交易类型 | trade_type | 是 | String(16) | JSAPI | JSAPI、NATIVE、APP |
现金支付金额 | cash_fee | 是 | Int | 100 | 现金支付金额订单现金支付金额,详见支付金额 |
付款银行 | bank_type | 是 | String(32) | CMC | 银行类型,采用字符串类型的银行标识,银行类型见银行列表 |
是否关注公众账号 | is_subscribe | 是 | String(1) | Y | 用户是否关注公众账号,Y-关注,N-未关注 |
支付完成时间 | time_end | 是 | String(14) | 20141030133525 | 支付完成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 微信支付分配的终端设备号 |
签名类型 | sign_type | 否 | String(32) | HMAC-SHA256 | 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5 |
错误代码 | err_code | 否 | String(32) | SYSTEMERROR | 错误返回的信息描述 |
错误代码描述 | err_code_des | 否 | String(128) | 系统错误 | 错误返回的信息描述 |
应结订单金额 | settlement_total_fee | 否 | Int | 100 | 应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 |
货币种类 | fee_type | 否 | String(8) | CNY | 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 |
现金支付货币类型 | cash_fee_type | 否 | String(16) | CNY | 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 |
总代金券金额 | coupon_fee | 否 | Int | 10 | 代金券金额<=订单金额,订单金额-代金券金额=现金支付金额,详见支付金额 |
代金券使用数量 | coupon_count | 否 | Int | 1 | 代金券使用数量 |
代金券类型 | coupon_type_$n | 否 | String | CASH | CASH–充值代金券 NO_CASH—非充值代金券并且订单使用了免充值券后有返回(取值:CASH、NO_CASH)。$n为下标,该笔订单使用多张代金券时,从0开始编号,举例:coupon_type_0、coupon_type_1注意:只有下单时订单使用了优惠,回调通知才会返回券信息。下列情况可能导致订单不可以享受优惠:可能情况。 |
代金券ID | coupon_id_$n | 否 | String(20) | 10000 | 代金券ID,$n为下标,该笔订单使用多张代金券时,从0开始编号,举例:coupon_id_0、coupon_id_1注意:只有下单时订单使用了优惠,回调通知才会返回券信息。下列情况可能导致订单不可以享受优惠:可能情况。 |
单个代金券支付金额 | coupon_fee_$n | 否 | Int | 100 | 单个代金券支付金额,$n为下标,从0开始编号 |
商家数据包 | attach | 否 | String(128) | 123456 | 商家数据包,原样返回 |
3)、商户系统主动查询订单支付结果
为什么需要商户系统主动查询支付结果?
- 即使在外网环境下,也不能保证商户系统一定能接收到微信支付结果的通知消息
- 如果微信平台出现故障,有可能支付成功后,没有发送支付通知给商户系统
- 无论是哪一端出现网络故障,都能导致商户系统接收不到支付结果的通知
商户系统什么时候主动查询支付结果?
- 当小程序上面提示支付成功后,小程序应当立即向商户系统发送Ajax请求,告诉商户系统小程序端已经收到付款成功的通知,商户系统也应该去微信平台查询订单的支付结果。
商户系统向微信平台发送网络请求需要的参数:
查询订单文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
参数名称 | 参数名 | 是否必填 | 参数类型 | 示例 | 说明 |
---|---|---|---|---|---|
小程序ID | appid | 是 | String(32) | wx8888888888888888 | 微信分配的小程序ID |
商户号 | mch_id | 是 | String(32) | 1900000109 | 微信支付分配的商户号 |
微信支付订单号(与商户订单号二选一) | transaction_id | 是 | String(32) | 1217752501201407033233368018 | 微信支付订单号 |
商户订单号(与支付订单号二选一) | out_trade_no | 是 | String(32) | 1212321211201407033568112322 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-*@ ,且在同一个商户号下唯一 |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 签名,详见签名算法 |
小程序端:
这段代码为“创建支付订单”小程序端的补充。
// 小程序发出请求让后端系统主动查询支付结果
uni.request({
url:that.url.updateOrderStatus,
method:"POST",
header:{
"token":uni.getStorageSync("token")
},
data:{
"orderId": id
},
success() {
// 这是数组
let pages = getCurrentPages()
// 从当前数组中取出最后一个元素
let page = pages[pages.length - 1]
page.onShow()
},
fail() {
// 选择控制台输出而不是showToast方式
// 原因是用户付款成功了,就不必给用户提供异常信息了
console.log("更新订单状态失败!")
}
})
商户系统端:
/**
* 接收小程序已经支付成功的请求,修改订单状态码
*/
@Login
@PostMapping("/updateOrderStatus")
public R updateOrderStatus(UpdateOrderStatusForm form, @RequestHeader HashMap header){
// 表单验证
ValidatorUtils.validateEntity(form);
String token = header.get("token").toString();
long userId = Long.parseLong(jwtUtils.getClaimByToken(token).getSubject());
int orderId = form.getOrderId();
// 查询这个订单是否属于该用户
OrderEntity orderEntity = new OrderEntity();
orderEntity.setUserId((int) userId);
orderEntity.setId(orderId);
QueryWrapper wrapper = new QueryWrapper(orderEntity);
int count = orderService.count(wrapper);
// 验证逻辑的有效性
if (count == 0){
return R.error("用户与订单不匹配!");
}
/**
* 如果匹配,商户系统需要发出网络请求到微信平台查询订单对应的结果
*/
// 根据小程序提交的商品订单ID查询商品订单的流水号
orderEntity = orderService.getOne(wrapper);
// 提取订单的流水号
String code = orderEntity.getCode();
Map map = new HashMap();
map.put("appid",appId);
map.put("mch_id",mchId);
map.put("out_trade_no",code);
map.put("nonce_str",WXPayUtil.generateNonceStr());
try {
// 生成数字签名
String sign = WXPayUtil.generateSignature(map,key);
map.put("sign",sign);
WXPay wxPay = new WXPay(myWxPayConfig);
// 到微信平台查询订单对应结果
Map<String,String> result = wxPay.orderQuery(map);
// 获取通信状态码和业务状态码
String returnCode = result.get("return_code"); // 通信
String resultCode = result.get("result_code"); // 业务
// 这两个状态码都是SUCCESS表示小程序支付成功并且商户系统收到的小程序的支付成功通知不是由postman发出的
if ("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)){
//交易状态 trade_state
// SUCCESS--支付成功
//REFUND--转入退款
//NOTPAY--未支付
//CLOSED--已关闭
//REVOKED--已撤销(刷卡支付)
//USERPAYING--用户支付中
//PAYERROR--支付失败(其他原因,如银行返回失败)
//ACCEPT--已接收,等待扣款
// 获得微信平台的交易状态
String tradeState = result.get("trade_state");
// 微信平台的交易状态为:SUCCESS 表示小程序的确支付成功,可以更改商户系统的状态码
if ("SUCCESS".equals(tradeState)){
UpdateWrapper updateWrapper = new UpdateWrapper();
// 通过流水号(商户订单号)查询订单信息
updateWrapper.eq("code",code);
// 更改该条订单的支付状态码改为已付款
// 状态:1未付款,2已付款,3已发货,4已签收
updateWrapper.set("status",2);
orderService.update(updateWrapper);
return R.ok("订单状态已修改!");
}else {
return R.ok("订单状态未修改!");
}
}
return R.ok("订单状态未修改!");
} catch (Exception e) {
e.printStackTrace();
return R.error("查询支付订单失败!");
}
}
小结
2、Native 支付
(1)什么是 Native 支付?
用户用手机微信扫描 PC 浏览器打开电商网站支付二维码支付,此时电商网站调用微信的 Native 支付接口(PC 浏览器)。
(2)开通 Native 支付
后续待补!!!
(3)Native 支付原理
微信Native 支付开发文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_2.shtml
支付时序图:
后续待补!!!
支付业务流程:
- ---------创建支付订单:---------
- 用户在网页上面点击支付按钮。
- 网页向后端系统发送 Ajax请求,并且提交商品订单号、用户的 Token 令牌字符串。
- 后端系统验证网页端的数据之后,让微信平台创建支付订单,并且提交商户号、appId、商户订单号、随机字符串、数字签名等信息。
- 微信平台验证之后,创建支付订单,并将支付订单的相关信息返回给商户系统。
- 商户系统从接收到的信息里提取支付链接,并把支付链接生成二维码图片。
- 然后商户系统把这个二维码图片的URL地址返回给网页,网页端处理后就可以展现支付二维码。
- -----------用户付款:-----------
- 用户用微信扫描二维码,与此同时,用户微信app会向微信平台发出请求查询商户创建的支付订单。
- 微信平台弹出对话框显示哪个商户创建的支付订单和订单金额。
- 用户确认商户和金额没问题之后,就在手机上输入密码,于是这个付款请求就会被微信app发送给微信平台。
- 微信平台验证通过后,就执行扣款。
- 扣款成功后将支付结果分别发送给商户系统和用户微信app。
(4)创建支付订单
1)、创建支付订单提交的参数
微信 Native 支付支付订单参数文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
参数名称 | 参数名 | 是否必填 | 参数类型 | 示例 | 说明 |
---|---|---|---|---|---|
应用ID | appid | string[1,32] | 是 | wxd678efh567hg6787 | body 直连商户申请的公众号或移动应用appid。 |
商户号 | mchid | string[1,32] | 是 | 1230000109 | body 直连商户的商户号,由微信支付生成并下发。 |
随机字符串 | nonce_str | String | 否 | 随机字符串 | |
数字签名 | sign | String | |||
商品描述 | body | String | qq会员 | ||
商户订单号 | out_trade_no | string[6,32] | 是 | 1217752501201407033233368018 | body 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一 |
订单金额 | total_fee | int | 是 | 100 | 订单金额,单位为分。 |
用户终端IP | apbill_create_ip | string[1,45] | 是 | 14.23.150.211 | 用户的客户端IP,支持IPv4和IPv6两种格式的IP地址。 |
通知地址 | notify_url | string[1,256] | 是 | https://www.weixin.qq.com/wxpay/pay.php | body 通知URL必须为直接可访问的URL,不允许携带查询串。 格式:URL |
交易类型 | trade_type | string | NATIVE |
2)、创建支付订单
/**
* Native 支付
*/
@Login
@PostMapping("/nativePayOrder")
@ApiOperation("Native 付款")
public R nativePayOrder(PayOrderForm form, @RequestHeader HashMap header){
/**
* 1、用户点击支付,网页发送Ajax请求向商户系统申请创建支付订单
* 商户系统校验网页提供的信息是否无误
*/
// 校验
ValidatorUtils.validateEntity(form);
String token = header.get("token").toString();
long userId = Long.parseLong(jwtUtils.getClaimByToken(token).getSubject());
int orderId = form.getOrderId();
UserEntity user = new UserEntity();
user.setUserId(userId);
QueryWrapper wrapper = new QueryWrapper(user);
long count = userService.count(wrapper);
if (count == 0){
return R.error("用户不存在!");
}
OrderEntity order = new OrderEntity();
order.setUserId((int) userId);
order.setId(orderId);
order.setStatus(1);
wrapper = new QueryWrapper(order);
count = orderService.count(wrapper);
if (count == 0){
return R.error("不是有效订单!");
}
// 验证购物券是否有效
// 验证团购是否有效
order = new OrderEntity();
order.setId(orderId);
wrapper = new QueryWrapper(order);
order = orderService.getOne(wrapper);
/**
* 2、商户平台校验信息无误后,利用 SDK程序包 向微信平台发出创建支付订单请求
*/
// 获取订单的总金额 乘100(单位转为分) 取整(有小数点后两位)
String amount = order.getAmount().multiply(new BigDecimal("100")).intValue() + "";
// 获取用户 openid
try {
WXPay wxPay = new WXPay(myWxPayConfig);
Map map = new HashMap();
// 随机字符串,可自己编写代码生成,此处直接使用SDK产生
map.put("nonce_str", WXPayUtil.generateNonceStr());
// 商品描述
map.put("body", "订单备注");
// 商户订单号(编号为字符串)
map.put("out_trade_no", order.getCode());
// 标价金额
map.put("total_fee", amount);
// 终端IP
map.put("spbill_create_ip", "127.0.0.1");
// 支付结果通知地址,地址可以随便编写,即使商户系统接收不到付款通知结果,也可以让商户平台主动向微信平台发起请求去得到支付结果
map.put("notify_url", "https://127.0.0.1/test");
// 交易类型,固定为:NATIVE
map.put("trade_type", "NATIVE");
// WXPay 对象会帮我们生成数字签名
// 我们也可以自己生成数字签名传到微信平台
String sign = WXPayUtil.generateSignature(map,key);
map.put("sign",sign);
/**
* 3、微信平台校验信息无误后生成支付订单
*
* 此方法可以帮我们发出请求让微信平台生成支付订单
*/
Map<String,String> result = wxPay.unifiedOrder(map);
// 获取微信支付订单的ID
String prepayId = result.get("prepay_id");
// 获取支付链接的URL
String codeUrl = result.get("code_url");
if (prepayId != null){
// 将支付订单ID保存到数据表
order.setPrepayId(prepayId);
UpdateWrapper updateWrapper = new UpdateWrapper();
updateWrapper.eq("id",order.getId());
orderService.update(order,updateWrapper);
/**
* 4、商户系统将订单数据返回给网页
* 网页端通过将支付链接转换为二维码图片展示给用户
*/
return R.ok().put("codeUrl", codeUrl);
}else {
return R.error("支付订单创建失败!");
}
} catch (Exception e) {
e.printStackTrace();
return R.error("微信支付模块故障");
}
}
(5)生成支付二维码
hutool 工具包最新版内置了生成二维码的工具类,只需引入依赖,同时还需引入下面一个依赖,因为 hutool 在生成二维码图片时依赖下面那个包:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.4.1</version>
</dependency>
后端生成二维码图片:
/**
* 将支付链接转化为二维码图片
* @param request
* @param response
*/
@GetMapping("/qrcode")
public void qrcode(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 从请求中获取支付链接
String codeUrl = request.getParameter("codeUrl");
if (codeUrl != null && codeUrl.length() > 0){
// 设置图片的大小
QrConfig qrConfig = new QrConfig();
qrConfig.setWidth(250); // 设置图片的宽
qrConfig.setHeight(250);// 设置图片的高
qrConfig.setMargin(2); // 设置二维码图片边块的宽度
// 通过IO流的方式将图片写到响应里面
OutputStream outputStream = response.getOutputStream();
/**
* 生成二维码图片
* codeUrl:支付链接
* qrConfig:图片配置信息
* jpg:生成图片的格式(也可以为其它格式)
* outputStream:二维码图片的位置,将二维码图片写到响应里
*/
QrCodeUtil.generate(codeUrl,qrConfig,"jpg",outputStream);
outputStream.close();
}
}
(7)后端系统查询支付结果
用户在微信上付款成功后可以收到付款结果通知,但由于网页端和微信app是不同的平台,网页端无法收到付款成功的通知,因此网页端可以用定时器的方式发送Ajax请求让后端系统主动查询支付结果,然后返回给网页端(例如:5秒发一次,发10次)。
/**
* 查询支付订单状态并修改商品订单状态
* @return
*/
@Login
@PostMapping("/searchOrderStatus")
@ApiOperation("查询支付订单状态")
public R searchOrderStatus(SearchOrderStatusForm form, @RequestHeader HashMap header){
// 表单验证
ValidatorUtils.validateEntity(form);
String token = header.get("token").toString();
long userId = Long.parseLong(jwtUtils.getClaimByToken(token).getSubject());
int orderId = form.getOrderId();
// 查询这个订单是否属于该用户
OrderEntity orderEntity = new OrderEntity();
orderEntity.setUserId((int) userId);
orderEntity.setId(orderId);
QueryWrapper wrapper = new QueryWrapper(orderEntity);
int count = orderService.count(wrapper);
// 验证逻辑的有效性
if (count == 0){
return R.error("用户与订单不匹配!");
}
/**
* 如果匹配,商户系统需要发出网络请求到微信平台查询订单对应的结果
*/
// 根据小程序提交的商品订单ID查询商品订单的流水号
orderEntity = orderService.getOne(wrapper);
// 提取订单的流水号
String code = orderEntity.getCode();
Map map = new HashMap();
map.put("appid",appId);
map.put("mch_id",mchId);
map.put("out_trade_no",code);
map.put("nonce_str",WXPayUtil.generateNonceStr());
try {
// 生成数字签名
String sign = WXPayUtil.generateSignature(map,key);
map.put("sign",sign);
WXPay wxPay = new WXPay(myWxPayConfig);
// 到微信平台 查询订单对应结果
Map<String,String> result = wxPay.orderQuery(map);
// 获取通信状态码和业务状态码
String returnCode = result.get("return_code"); // 通信
String resultCode = result.get("result_code"); // 业务
// 这两个状态码都是SUCCESS表示小程序支付成功并且商户系统收到的小程序的支付成功通知不是由postman发出的
if ("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)){
//交易状态 trade_state
// SUCCESS--支付成功
//REFUND--转入退款
//NOTPAY--未支付
//CLOSED--已关闭
//REVOKED--已撤销(刷卡支付)
//USERPAYING--用户支付中
//PAYERROR--支付失败(其他原因,如银行返回失败)
//ACCEPT--已接收,等待扣款
// 获得微信平台的交易状态
String tradeState = result.get("trade_state");
// 微信平台的交易状态为:SUCCESS 表示小程序的确支付成功,可以更改商户系统的状态码
if ("SUCCESS".equals(tradeState)){
UpdateWrapper updateWrapper = new UpdateWrapper();
// 通过流水号(商户订单号)查询订单信息
updateWrapper.eq("code",code);
// 更改该条订单的支付状态码改为已付款
// 状态:1未付款,2已付款,3已发货,4已签收
updateWrapper.set("status",2);
// 设置支付方式:1微信,2支付宝,3银联
updateWrapper.set("payment_type",1);
// 更新商品订单状态
orderService.update(updateWrapper);
return R.ok("订单状态已修改!");
}else {
return R.ok("订单状态未修改!");
}
}
return R.ok("订单状态未修改!");
} catch (Exception e) {
e.printStackTrace();
return R.error("查询支付订单失败!");
}
}
3、付款码支付
(1)什么是扫描支付
线下支付的时候,商家通过扫码器扫描你的付款码完成免密支付,这个免密支付并不是真正意义上的免密支付,在一段时间内使用一次以上的付款码支付,第二次还是需要输入密码。
(2)没有扫码器怎么实现扫码付款
扫码器的作用相当于键盘,它能识别一维码或二维码的内容,然后输入到电脑。在测试开发时,可以对结账页面添加键盘监听器模拟扫码输入。
(3)扫码支付原理
微信扫码支付文档:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=5_4
扫码支付时序图:
扫码支付业务流程:
- 收银员扫描商品并在商户收银系统生成支付订单,然后向用户展示支付金额。
- 用户打开微信app出示付款码。
- 收银员使用扫码设备读取用户付款码,并将读取的信息上传收银系统。
- 收银系统得到扫码数据后,向后端系统发起支付请求。
- 后端系统得到信息后,将其和其它数据一起发送到微信平台。
- 微信平台验证过数据后,生成支付订单并扣款。
- 然后微信平台将支付结果返回给后端系统和用户微信app。
- 后端系统收到支付结果后,如果支付成功,后端系统更改订单状态,然后将结果进行签名验证,返回给收银系统。
- 收银员看到收银系统的成功支付信息后给用户商品。
(4)提交扣款请求需要的参数
微信付款码支付订单参数文档:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
参数名称 | 参数名 | 是否必填 | 参数类型 | 示例 | 说明 |
---|---|---|---|---|---|
公众账号ID | appid | 是 | String(32) | wx8888888888888888 | 微信分配的公众账号ID(企业号corpid即为此appId) |
商户号 | mch_id | 是 | String(32) | 1900000109 | 微信支付分配的商户号 |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位。推荐随机数生成算法 |
签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 签名,详见签名生成算法 |
商品描述 | body | 是 | String(128) | QQ公仔 | 商品简单描述,该字段须严格按照规范传递,具体请见参数规定 |
商户订单号 | out_trade_no | 是 | String(32) | 1217752501201407033233368018 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_- |
订单金额 | total_fee | 是 | Int | 100 | 订单总金额,单位为分,只能为整数,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(64) | 127.0.0.1 | 支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP |
付款码 | auth_code | 是 | String(128) | 120061098828009406 | 扫码支付付款码,设备读取用户微信中的条码或者二维码信息(注:用户付款码条形码规则:18位纯数字,以10、11、12、13、14、15开头) |
签名类型 | sign_type | 否 | String(32) | HMAC-SHA256 | 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 终端设备号(商户自定义,如门店编号) |
商品详情 | detail | 否 | String(6000) | 单品优惠功能字段,需要接入详见单品优惠详细说明 | |
附加数据 | attach | 否 | String(127) | 说明 附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据 | |
货币类型 | fee_type | 否 | String(16) | CNY | 符合ISO4217标准的三位字母代码,默认人民币:CNY,详见货币类型 |
订单优惠标记 | goods_tag | 否 | String(32) | 1234 | 订单优惠标记,代金券或立减优惠功能的参数,详见代金券或立减优惠 |
指定支付方式 | limit_pay | 否 | String(32) | no_credit | no_credit–指定不能使用信用卡支付 |
交易起始时间 | time_start | 否 | String(14) | 20091225091010 | 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010 |
交易结束时间 | time_expire | 否 | String(14) | 20091227091010 | 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。注意:最短失效时间间隔需大于1分钟 |
电子发票入口开放标识 | receipt | 否 | String(8) | Y | Y,传入Y时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效 |
(5)后端系统收款的方法
4、JSAPI 支付
5、H5 支付
6、APP 支付
后续待补!!!
7、刷脸支付
后续待补!!!
三、支付宝支付
1、支付宝小程序支付
后续待补!!!
2、Native 支付
后续待补!!!
3、H5 支付
后续待补!!!
四、银联支付
1、Native 支付
后续待补!!!
2、H5 支付
后续待补!!!
五、聚合支付
后续待补!!!
总结
该项目文档:
最后
以上就是孤独玫瑰为你收集整理的Java 实现企业级支付一、简介二、微信支付三、支付宝支付四、银联支付五、聚合支付总结的全部内容,希望文章能够帮你解决Java 实现企业级支付一、简介二、微信支付三、支付宝支付四、银联支付五、聚合支付总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复