我们项目中的支付交互流程:用户点击购买时后台首先创建订单,然后调用微信支付统一下单API返回给前台相应的参数,如果为NATIVE支付方式,前台将参数中的支付连接生成二维码展示给用户,如果为JSAPI支付方式,前台(微信客户端)调用JSAPI支付拉起柜台进行支付。
在接入微信支付之前阅读对应的官方文档(
开发步骤、业务流程、API列表
)非常有必要。不过微信文档写的写很坑,坑在哪?在API列表与加解密相关的写的比较坑。不过开发步骤和业务流程写的还是很不错的。 微信支付官方文档
话不多说,直接上码(这是我们项目阉割版的支付主体代码,但是调起支付是没问题的,如果有小伙伴在使用过程中遇到什么问题,可以私信我。)
# 项目中加入微信支付的SDK
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
# application.properties配置文件中加入如下配置
#微信相关配置
wxconfig.appId = appId
# 微信公众平台中开发设置中的AppSecret
wxconfig.secret = secret
# 微信商户平台商户号
wxconfig.mchId = mchId
# 微信商户平台支付API秘钥
wxconfig.key = key
wxconfig.pay.notify.url = 支付成功回调地址
wxconfig.refund.notify.url = 退款成功回调地址
wxconfig.certPath = 证书apiclient_cert.p12路径
/**
* 微信支付接口
*
* @create: 2019-10-10 15:25
* @author: Sun
*/
public interface WxPayService {
/**
* 统一下单(JSAPI-NATIVE支付方式该接口都支持)
*
* @param uid 用户id
* @param openid 用户openid 公众号/小程序 JSAPI支付必传参数
* @param desc 商品描述
* @param orderNo 订单号
* @param totalFee 订单总金额,单位为分
* @param spbillCreateIp 下单终端ip
* @param startTime 交易起始时间
* @param expireTime 交易结束时间
* @param tradeType 交易类型
* @param productId 商品id。trade_type=NATIVE时,此参数必传。
* @return return_code 和result_code都为SUCCESS的时候,将响应结果,存放到map中返回,否则throw WxPayException
*/
Map<String, String> unifiedOrder(String uid, String openid, String desc, String orderNo, int totalFee,
String spbillCreateIp, String startTime, String expireTime, String tradeType,String productId) throws WxPayException;
/**
* JSAPI支付方式-生成支付参数和支付签名
*
* @param prepayId 统一下单返回的预支付单id
* @return
*/
Map<String, String> generatePaySign(String prepayId);
}
/**
* 微信支付接口实现
*
* @create: 2019-10-10 15:40
* @author: Sun
*/
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {
@Autowired
private MyWxPayConfig myWxPayConfig;
/**
* 支付、退款 异步通知地址
*/
@Value("${wxconfig.pay.notify.url}")
private String payAsyncNotifyUrl;
@Value("${wxconfig.refund.notify.url}")
private String refundAsyncNotifyUrl;
/**
* 微信服务器响应字段
*/
private static final String RETURN_CODE = "return_code";
private static final String RESULT_CODE = "result_code";
private static final String TRADE_STATE = "trade_state";
private static final String RETURN_MSG = "return_msg";
private static final String ERROR_CODE = "err_code";
private static final String ERROR_CODE_DES = "err_code_des";
private static final String PREPAY_ID = "prepay_id";
@Override
public Map<String, String> unifiedOrder(String uid, String openid, String desc,
String orderNo, int totalFee, String spbillCreateIp,
String startTime, String expireTime, String tradeType, String productId) {
log.info("[unifiedOrder] [入参] [uid:{}] [openid:{}] [desc:{}] [orderNo:{}] [totalFee:{}] [spbillCreateIp:{}] " +
"[startTime:{}] [expireTime:{}] [tradeType:{}] [productId:{}]", uid, openid, desc, orderNo, totalFee, spbillCreateIp, startTime, expireTime, tradeType, productId);
WXPay wxPay = new WXPay(myWxPayConfig);
Map<String, String> paramMap = new HashMap<>();
// 如果传递openid为JSAPI支付方式,否则视为NATIVE支付方式
if (!StringUtils.isBlank(openid)) {
// JSAPI支付方式时 openid为必传参数
paramMap.put("openid", openid);
} else {
// NATIVE支付方式时 product_id为必传参数
paramMap.put("product_id", productId);
}
paramMap.put("body", desc);
paramMap.put("out_trade_no", orderNo);
paramMap.put("total_fee", totalFee + "");
paramMap.put("spbill_create_ip", spbillCreateIp);
paramMap.put("notify_url", payAsyncNotifyUrl);
paramMap.put("time_start", startTime);
paramMap.put("time_expire", expireTime);
paramMap.put("trade_type", tradeType);
// 调用微信统一下单API返回参数存放集合
Map<String, String> resultMap = null;
try {
resultMap = wxPay.unifiedOrder(paramMap);
log.info("[unifiedOrder] [微信统一下单API响应参数] [uid:{}] [out_trade_no:{}] [resultMap:{}]", uid, orderNo, resultMap);
// NATIVE 支付方式返回参数
if (StringUtils.isBlank(openid)) {
return resultMap;
}
// JSAPI 支付方式返回参数
return generatePaySign(resultMap.get(PREPAY_ID));
} catch (Exception e) {
log.error("[unifiedOrder] [uid:{}] [out_trade_no:{}] [调用微信SDK统一下单API过程中出现异常:{}]", uid, orderNo, ExceptionUtils.getStackTrace(e));
throw new WxPayException(4504, "调用统一下单API异常,请重新尝试!");
}
}
@Override
public Map<String, String> generatePaySign(String prepayId) {
Map<String, String> payMap = new HashMap<>();
try {
payMap.put("appId", myWxPayConfig.getAppID());
payMap.put("timeStamp", getCurrentTimestamp() + "");
payMap.put("nonceStr", WXPayUtil.generateNonceStr());
payMap.put("signType", WXPayConstants.MD5);
payMap.put("package", "prepay_id=" + prepayId);
// 生成paySign参数,paySign参数需要重新进行签名计算,参与签名的参数为:appId、timeStamp、nonceStr、package、signType,参数区分大小写。
String paySign = WXPayUtil.generateSignature(payMap, myWxPayConfig.getKey());
payMap.put("paySign", paySign);
} catch (Exception e) {
log.error("[generatePaySign] [生成返回前台JSAPI支付请求参数时异常:{}]", ExceptionUtils.getStackTrace(e));
throw new WxPayException(4504, "调用统一下单API异常,请重新尝试!");
}
log.info("[generatePaySign] [success] [payMap:{}]", payMap);
return payMap;
}
/**
* 获取当前时间戳,单位秒
*
* @return
*/
private long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
}
/**
* 微信支付相关配置
*
* @author: Sun
* @create: 2019-10-10 15:59
*/
@Component
public class MyWxPayConfig implements WXPayConfig {
@Value("${wxconfig.appId}")
private String appId;
@Value("${wxconfig.mchId}")
private String mchId;
@Value("${wxconfig.key}")
private String key;
private byte[] certData;
public MyWxPayConfig(@Value("${wxconfig.certPath}") String certPath) throws Exception {
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
@Override
public String getAppID() {
return appId;
}
@Override
public String getMchID() {
return mchId;
}
@Override
public String getKey() {
return key;
}
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public int getHttpConnectTimeoutMs() {
return 10000;
}
@Override
public int getHttpReadTimeoutMs() {
return 10000;
}
}
/**
* WxPayException class.
*
* @create: 2019-10-10 15:12
* @author: Sun
*/
public class WxPayException extends RuntimeException {
/**
* 状态码
*/
private int code;
public WxPayException(int code, String message) {
super(code, message);
}
public WxPayException(int code, String message, Throwable cause) {
super(code, message, cause);
}
public int getCode() {
return code;
}
}
大家使用自己的Controller接收请求调用该Service接口就可测试是否可以调起微信支付了。建议使用NATIVE支付方式进行测试,因为不需要获取openid,并且在调用统一下单API之后拿到微信服务器返回的code_url然后随便找一个二维码生成网站生成二维码,就可以使用微信进行扫描支付了。