开篇介绍:
本文介绍微信支付中的Native支付方式,版本是APIv3,其中Native和JSAPI的区别如下
Native支付:商家在系统中按微信支付协议生成支付二维码,用户扫码拉起微信收银台,确认并完成付款
JSAPI支付:商家张贴收款码物料,用户打开扫一扫,扫码后输入金额,完成付款
以下内容增加了支付接口调和策略模式的使用,支付的VO类,大家可以参照自己公司的VO字段,需要改动支付的部分业务参数即可。
注意:微信支付个人无法对接操作,需要有公司商户账号,一般开发过程中是产品经理或者相关负责人提供。
我们来看下支付成功和退款成功的截图,看完本博文后,你将学会在实际项目中完成微信支付。
首先我们将先复习下策略模式,前面4步骤都是策略模式,后面几步骤是代码实操,不要急,一步步来。
一、策略模式(Strategy Pattern)的概念
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
比如说淘宝天猫双十一,正在搞活动有打折的、有满减的、有返利的等等,这些算法只是一种策略,并且是随时都可能互相替换的, 我们就可以定义一组算法,将每个算法都封装起来,并且使它们之间可以互换
二、应用场景举例
。老王计划外出旅游,选择骑自行车、坐汽车、飞机等,每一种旅行方式都是一个策略
。 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么可以使用策略模式
。 不希望暴露复杂的、与算法有关的数据结构,那么可以使用策略模式来封装算法
。对接第三方支付里面,微信支付、支付宝支付等都可以是一种策略
三、角色
Context上下文:屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化(相当于中介)
Strategy策略角色:抽象策略角色,是对策略、算法家族的抽象,定义每个策略或算法必须具有的方法和属性
ConcreteStrategy具体策略角色:用于实现抽象策略中的操作,即实现具体的算法
四、服务端OR客户端生成支付等二维码优缺点分析
服务端生成二维码
优点:
引入二维码jar包统一生成
图片生成兼容性好,传输相对安全不易被篡改
缺点:
占据服务端CPU内存/资源、消耗传输带宽,性能较差
没法一并返回其他参数,定制化相对弱
客户端生成二维码(建议使用)
优点
在客户端生成二维码会降低服务器的运算与存储压力
选择框架要考虑兼容性,客户端根据js框架生成二维码
后端返回 二维码文本值,还可以携带其他参数(比如订单号,可以轮训支付状态)
缺点
客户端需要引入二维码生成js插件
需要考虑浏览器兼容性
五、 多渠道支付对接-策略模式+简单工厂模式编码实操
策略模式代码骨架分为以下几种:
策略接口开发 PayStrategy
策略上下文 PayStrategyContext开发
具体支付策略开发 AlipayStrategy、WechatPayStrategy
简单工厂类开发;
首先第一步我们从impl调用层开始倒推:
//这个是在实际service实现类中的调用,下面这段代码被controller层调用,
//然后返回给前端,前端根据返回的code_url展示二维码信息
@Autowired
private PayFactory payFactory;
//调用支付信息
String codeUrl = payFactory.pay(payInfoVO);
if(StringUtils.isNotBlank(codeUrl)){
Map<String,String> resultMap = new HashMap<>(2);
resultMap.put("code_url",codeUrl);
resultMap.put("out_trade_no",payInfoVO.getOutTradeNo());
return JsonData.buildSuccess(resultMap);
}
第二步再来看下PayFactory支付工厂类,里面的支付 做了什么:
package net.wnn.component;
import lombok.extern.slf4j.Slf4j;
import net.wnn.enums.ProductOrderPayTypeEnum;
import net.wnn.vo.PayInfoVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class PayFactory {
@Autowired
private AliPayStrategy aliPayStrategy;
@Autowired
private WechatPayStrategy wechatPayStrategy;
/**
* 创建支付,简单工厂模式
* @param payInfoVO
* @return
*/
public String pay(PayInfoVO payInfoVO){
String payType = payInfoVO.getPayType();
if (ProductOrderPayTypeEnum.ALI_PAY.name().equals(payType)) {
//支付宝支付
PayStrategyContext payStrategyContext = new PayStrategyContext(aliPayStrategy);
return payStrategyContext.executeUnifiedOrder(payInfoVO);
} else if(ProductOrderPayTypeEnum.WECHAT_PAY.name().equals(payType)){
//微信支付
PayStrategyContext payStrategyContext = new PayStrategyContext(wechatPayStrategy);
return payStrategyContext.executeUnifiedOrder(payInfoVO);
}
return "";
}
/**
* 关闭订单
* @param payInfoVO
* @return
*/
public String closeOrder(PayInfoVO payInfoVO){
String payType = payInfoVO.getPayType();
if (ProductOrderPayTypeEnum.ALI_PAY.name().equals(payType)) {
//支付宝支付
PayStrategyContext payStrategyContext = new PayStrategyContext(aliPayStrategy);
return payStrategyContext.executeCloseOrder(payInfoVO);
} else if(ProductOrderPayTypeEnum.WECHAT_PAY.name().equals(payType)){
//微信支付
PayStrategyContext payStrategyContext = new PayStrategyContext(wechatPayStrategy);
return payStrategyContext.executeCloseOrder(payInfoVO);
}
return "";
}
/**
* 查询支付状态
* @param payInfoVO
* @return
*/
public String queryPayStatus(PayInfoVO payInfoVO){
String payType = payInfoVO.getPayType();
if (ProductOrderPayTypeEnum.ALI_PAY.name().equals(payType)) {
//支付宝支付
PayStrategyContext payStrategyContext = new PayStrategyContext(aliPayStrategy);
return payStrategyContext.executeQueryPayStatus(payInfoVO);
} else if(ProductOrderPayTypeEnum.WECHAT_PAY.name().equals(payType)){
//微信支付
PayStrategyContext payStrategyContext = new PayStrategyContext(wechatPayStrategy);
return payStrategyContext.executeQueryPayStatus(payInfoVO);
}
return "";
}
/**
* 退款接口
* @param payInfoVO
* @return
*/
public String refund(PayInfoVO payInfoVO){
String payType = payInfoVO.getPayType();
if (ProductOrderPayTypeEnum.ALI_PAY.name().equals(payType)) {
//支付宝支付
PayStrategyContext payStrategyContext = new PayStrategyContext(aliPayStrategy);
return payStrategyContext.executeRefund(payInfoVO);
} else if(ProductOrderPayTypeEnum.WECHAT_PAY.name().equals(payType)){
//微信支付
PayStrategyContext payStrategyContext = new PayStrategyContext(wechatPayStrategy);
return payStrategyContext.executeRefund(payInfoVO);
}
return "";
}
}
由上面代码可见,支付的工厂是用来根据支付方式调用具体的实现方法 。
第三步,这一步我们来看一下PayStrategyContext支付上下文是什么东西:
package net.wnn.component;
import net.wnn.vo.PayInfoVO;
public class PayStrategyContext {
private PayStrategy payStrategy;
public PayStrategyContext(PayStrategy payStrategy){
this.payStrategy = payStrategy;
}
/**
* 根据策略对象,执行不同的下单接口
* @return
*/
public String executeUnifiedOrder(PayInfoVO payInfoVO){
return payStrategy.unifiedOrder(payInfoVO);
}
/**
* 根据策略对象,执行不同的退款接口
* @return
*/
public String executeRefund(PayInfoVO payInfoVO){
return payStrategy.refund(payInfoVO);
}
/**
* 根据策略对象,执行不同的关闭接口
* @return
*/
public String executeCloseOrder(PayInfoVO payInfoVO){
return payStrategy.closeOrder(payInfoVO);
}
/**
* 根据策略对象,执行不同的查询订单状态接口
* @return
*/
public String executeQueryPayStatus(PayInfoVO payInfoVO){
return payStrategy.queryPayStatus(payInfoVO);
}
}
标准对Context上下文的解释就是:屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。通俗点说这就是个中介,根据传入的参数去调相应的实现接口,自己就干个调用
第四步、再看看PayStrategy
package net.wnn.component;
import net.wnn.vo.PayInfoVO;
public interface PayStrategy {
/**
* 统一下单接口
* @param payInfoVO
* @return
*/
String unifiedOrder(PayInfoVO payInfoVO);
/**
* 退款接口
* @param payInfoVO
* @return
*/
default String refund(PayInfoVO payInfoVO){ return ""; }
/**
* 查询支付状态
* @param payInfoVO
* @return
*/
default String queryPayStatus(PayInfoVO payInfoVO){ return ""; }
/**
* 关闭订单
* @param payInfoVO
* @return
*/
default String closeOrder(PayInfoVO payInfoVO){ return ""; }
}
Strategy策略角色:抽象策略角色,是对策略、算法家族的抽象,定义每个策略或算法必须具有的方法和属性
第五步、再看看AliPayStrategy,这只是代码骨架,表明策略模式的实现方式,本文重点说的是微信支付,在第六步有
package net.wnn.component;
import lombok.extern.slf4j.Slf4j;
import net.wnn.vo.PayInfoVO;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class AliPayStrategy implements PayStrategy{
@Override
public String unifiedOrder(PayInfoVO payInfoVO) {
return null;
}
@Override
public String refund(PayInfoVO payInfoVO) {
return null;
}
@Override
public String queryPayStatus(PayInfoVO payInfoVO) {
return null;
}
@Override
public String closeOrder(PayInfoVO payInfoVO) {
return null;
}
}
ConcreteStrategy就是策略模式中的具体策略角色:用于实现抽象策略中的操作,即实现具体的算法
第六步、本文用的是微信支付,那就再看下WechatPayStrategy 下单的代码逻辑
package net.wnn.component;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import net.wnn.config.WechatPayApi;
import net.wnn.config.WechatPayConfig;
import net.wnn.vo.PayInfoVO;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
@Service
@Slf4j
public class WechatPayStrategy implements PayStrategy{
@Autowired
private WechatPayConfig payConfig;
@Autowired
private CloseableHttpClient wechatPayClient;
/**
* 统一下单接口
* @param payInfoVO
* @return
*/
@Override
public String unifiedOrder(PayInfoVO payInfoVO) {
//过期时间 RFC 3339格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
//支付订单过期时间
String timeExpire = sdf.format(new Date(System.currentTimeMillis() + payInfoVO.getOrderPayTimeoutMills()));
JSONObject amountObj = new JSONObject();
//数据库存储是double比如,100.99元,微信支付需要以分为单位
int amount = payInfoVO.getPayFee().multiply(BigDecimal.valueOf(60)).intValue();
amountObj.put("total", amount);
amountObj.put("currency", "CNY");
JSONObject payObj = new JSONObject();
payObj.put("mchid", payConfig.getMchId());
payObj.put("out_trade_no", payInfoVO.getOutTradeNo());
payObj.put("appid", payConfig.getWxPayAppid());
payObj.put("description", "王姑娘之微信支付增加策略模式后的下单验证");
payObj.put("notify_url", payConfig.getCallbackUrl());
payObj.put("time_expire", timeExpire);
payObj.put("amount", amountObj);
//回调携带
payObj.put("attach", "{\"accountNo\":" + payInfoVO.getAccountNo() + "}");
// 处理请求body参数
String body = payObj.toJSONString();
log.debug("请求参数:{}",body);
StringEntity entity = new StringEntity(body,"utf-8");
entity.setContentType("application/json");
HttpPost httpPost = new HttpPost(WechatPayApi.NATIVE_ORDER);
httpPost.setHeader("Accept","application/json");
httpPost.setEntity(entity);
String result = "";
try(CloseableHttpResponse response = wechatPayClient.execute(httpPost)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.debug("下单响应码:{},响应体:{}",statusCode,responseStr);
if(statusCode == HttpStatus.OK.value()){
JSONObject jsonObject = JSONObject.parseObject(responseStr);
if(jsonObject.containsKey("code_url")){
result = jsonObject.getString("code_url");
}
}else {
log.error("下单响应失败:{},响应体:{}",statusCode,responseStr);
}
}catch (Exception e){
log.error("微信支付响应异常:{}",e);
}
return result;
}
}
第七步、下单验证截图
postman请求:
根据code_url生成的二维码,用微信客户端进行扫码支付:
下单支付接口调用的一些参数:
请求参数:{"time_expire":"2022-03-02T21:29:14+08:00","amount":{"total":60,"currency":"CNY"},"mchid":"1601xxxx","out_trade_no":"yb6gMTsnzyxxxxl4","appid":"wx5beac1xxxxc","description":"王姑娘之微信支付增加策略模式后的下单验证","attach":"{\"accountNo\":693232xxxx}","notify_url":"http://api.xxxx/shop-server/api/callback/order/v1/wechat"}
下单响应码:200,响应体:{"code_url":"weixin://wxpay/bizpayurl?pr=8L2yqCSzz"}
支付截图:
支付成功截图:
看到这你肯定有疑惑了,支付的那些参数等都是从哪来,怎么加载的?由于文章篇幅有限,基础的微信支付信息,写在了下面这篇博客中,可以直接点击查看,里面详细的介绍了微信支付的准备参数:
一文带你学会微信V3版本下单支付、退款、关单流程代码实操_8年开发工作经验的老王,积极分享工作中遇到的问题~-CSDN博客_wechatpay-apache-httpclient
六、查询支付订单状态
第一步实现类的调用:
@Override
public String queryProductOrderState(String outTradeNo) {
PayInfoVO payInfoVO = PayInfoVO.builder().outTradeNo(outTradeNo).payType("WECHAT_PAY").build();
String result = payFactory.queryPayStatus(payInfoVO);
return result;
}
第二步:
PayFactory的queryPayStatus方法,在上面下单的工厂类都有,这里不再赘述 PayStrategyContext的executeQueryPayStatus方法,上面也有,这里不再赘述
第三步:支付订单查询的具体操作代码
package net.wnn.component;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import net.wnn.config.WechatPayApi;
import net.wnn.config.WechatPayConfig;
import net.wnn.vo.PayInfoVO;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class WechatPayStrategy implements PayStrategy{
@Autowired
private WechatPayConfig payConfig;
@Autowired
private CloseableHttpClient wechatPayClient;
/**
* 微信支付查询订单状态
* @param payInfoVO
* @return
*/
@Override
public String queryPayStatus(PayInfoVO payInfoVO) {
String outTradeNo = payInfoVO.getOutTradeNo();
String url = String.format(WechatPayApi.NATIVE_QUERY,outTradeNo,payConfig.getMchId());
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept","application/json");
String result = "";
try(CloseableHttpResponse response = wechatPayClient.execute(httpGet)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.info("查询响应码:{},响应体:{}",statusCode,responseStr);
if(statusCode == HttpStatus.OK.value()){
JSONObject jsonObject = JSONObject.parseObject(responseStr);
if(jsonObject.containsKey("trade_state")){
result = jsonObject.getString("trade_state");
}
}else {
log.error("查询支付状态响应失败:{},响应体:{}",statusCode,responseStr);
}
}catch (Exception e){
log.error("微信支付响应异常:{}",e);
}
return result;
}
}
第四步 postman请求查询接口接口 传参 支付订单ID
查询返回结果: 订单支付成功
查询响应码:200,响应体:{"amount":{"currency":"CNY","payer_currency":"CNY","payer_total":60,"total":60},"appid":"wx5xxxc","attach":"{\"accountNo\":693232391484866560}","bank_type":"OTHERS","mchid":"16016xxxx",
"out_trade_no":"yb6gMTsnzyNrjb5iDa4Oxxx","payer":{"openid":"oiNKG04xxx"},"promotion_detail":[],"success_time":"2022-03-02T20:59:36+08:00","trade_state":"SUCCESS","trade_state_desc":"支付成功","trade_type":"NATIVE","transaction_id":"4200001363202203029043441628"}
七、再看下订单退款实操
第一步实现类的调用:
@Override
public String executeRefund(String outTradeNo) {
PayInfoVO payInfoVO = PayInfoVO.builder().outTradeNo(outTradeNo).payType("WECHAT_PAY").build();
String result = payFactory.refund(payInfoVO);
return result;
}
第二步:
PayFactory的refund方法,在上面下单的工厂类都有,这里不再赘述
PayStrategyContext的executeRefund方法,上面也有,这里不再赘述
第三步:支付订单退款的具体操作代码
package net.wnn.component;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import net.wnn.config.WechatPayApi;
import net.wnn.config.WechatPayConfig;
import net.wnn.util.CommonUtil;
import net.wnn.vo.PayInfoVO;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
@Service
@Slf4j
public class WechatPayStrategy implements PayStrategy{
@Autowired
private WechatPayConfig payConfig;
@Autowired
private CloseableHttpClient wechatPayClient;
@Override
public String refund(PayInfoVO payInfoVO) {
String outTradeNo = "yb6gMTsnzyNrjb5iDa4OYFwiRc5nh7l4";
String refundNo = CommonUtil.getStringNumRandom(32);
// 请求body参数
JSONObject refundObj = new JSONObject();
//订单号
refundObj.put("out_trade_no", outTradeNo);
//退款单编号,商户系统内部的退款单号,商户系统内部唯一,
// 只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔
refundObj.put("out_refund_no", refundNo);
refundObj.put("reason","商品已售完需要退款");
refundObj.put("notify_url", payConfig.getCallbackUrl());
JSONObject amountObj = new JSONObject();
//退款金额
amountObj.put("refund", 60);
//实际支付的总金额
amountObj.put("total", 60);
amountObj.put("currency", "CNY");
refundObj.put("amount", amountObj);
String body = refundObj.toJSONString();
log.info("请求参数:{}",body);
StringEntity entity = new StringEntity(body,"utf-8");
entity.setContentType("application/json");
HttpPost httpPost = new HttpPost(WechatPayApi.NATIVE_REFUND_ORDER);
httpPost.setHeader("Accept","application/json");
httpPost.setEntity(entity);
try(CloseableHttpResponse response = wechatPayClient.execute(httpPost)){
//响应码
int statusCode = response.getStatusLine().getStatusCode();
//响应体
String responseStr = EntityUtils.toString(response.getEntity());
log.info("申请订单退款响应码:{},响应体:{}",statusCode,responseStr);
}catch (Exception e){
e.printStackTrace();
}
return "退款成功";
}
}
退款成功图片:
退款日志消息:
请求参数:{"reason":"商品已售完需要退款","amount":{"total":60,"currency":"CNY","refund":60},"out_trade_no":"yb6gMTsnzyNrjb5iDa4xxx","out_refund_no":"RbpQWZntjoxxxx","notify_url":"http://api.oxxxx/shop-server//wechat"}
申请订单退款响应码:200,响应体:{"amount":{"currency":"CNY","discount_refund":0,"from":[],"payer_refund":60,"payer_total":60,"refund":60,"settlement_refund":60,"settlement_total":60,"total":60},"channel":"ORIGINAL","create_time":"2022-03-02T21:25:00+08:00","funds_account":"AVAILABLE","out_refund_no":"RbpQWZntjoxxxxq","out_trade_no":"yb6gMTsnzyNrjb5iDa4xxxx","promotion_detail":[],"refund_id":"50300400982022xxxx","status":"PROCESSING","transaction_id":"420000136320xxx","user_received_account":"支付用户零钱"}
到这微信Native支付方式的下单、查询、退款操作就整合进实际开发项目中啦,大家引用到自己项里面的时候,只需要改动支付的部分业务参数即可。
基础参数不知道从哪来,证书不知道怎么加载的,看下下面这篇博客,跟本博客代码内容是上下两集:
微信支付回调验签流程