集成JPay的微信支付


前言
项目用到微信支付,参照了 JPay 的 Spring Boot Demo 程序。
项目地址:https://gitee.com/javen205/IJPay/tree/master/IJPay-Demo-SpringBoot
但最好还是理解下微信支付的流程。
可以看一下官方的扫码支付:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5&index=3
然后在看 Demo 程序中是如何实现扫码程序的。
我们将 Demo 程序中的部分代码移植到自己的程序中。
自己项目为非前后台,技术栈 Spring Boot + thymeleaf。
移植步骤
pom.xml 文件添加依赖
添加了 微信支付 和 支付宝支付 两种。
<properties>
<ijapy.version>2.7.3</ijapy.version>
</properties>
<dependencies>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-WxPay</artifactId>
<version>${ijapy.version}</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-AliPay</artifactId>
<version>${ijapy.version}</version>
</dependency>
</dependencies>
添加配置
在 application.yml 中添加支付相关的配置
pay:
weixin:
appId: 应用编号
appSecret: appSecret 是 appId 对应的接口密码,微信公众号授权获取用户 openId 时使用
mchId: 微信支付商户号
partnerKey: API 密钥
certPath: apiclient_cert.p1 证书路径,在微信商户后台下载
domain: 外网访问项目的域名,支付通知中会使用
ali:
appId: 应用编号
privateKey: 应用私钥
publicKey: 支付宝公钥,通过应用公钥上传到支付宝开放平台换取支付宝公钥(如果是证书模式,公钥与私钥在CSR目录)。
appCertPath: 应用公钥证书
aliPayCertPath: 支付宝公钥证书
aliPayRootCertPath: 支付宝根证书
serverUrl: 支付宝支付网关,沙箱环境时设置为 http://openapi.ev.com/gateway.do 使用正式环境时设置为 http://openapi.com/gateway.do
domain: 外网访问项目的域名,支付通知中会使用
在配置包下(我的是 config)新增一个包专门放置和支付相关的配置,比如包名为 pay
新增微信配置类和支付宝配置类。
@Configuration
@ConfigurationProperties(prefix = "pay.ali")
public class AliConfig {
private String appId;
private String privateKey;
private String publicKey;
private String appCertPath;
private String aliPayCertPath;
private String aliPayRootCertPath;
private String serverUrl;
private String domain;
//getter setter 略
}
@Configuration
@ConfigurationProperties(prefix = "pay.weixin")
public class WxConfig {
private String appId;
private String appSecret;
private String mchId;
private String partnerKey;
private String certPath;
private String domain;
//getter setter 略
}
添加接口
编写微信的控制器
此控制器包含一个【花钱购买角色】的业务逻辑。
前端调用 /wxPay/setRole 的接口,后台会创建一个订单记录(自己系统记录的订单),同时根据角色价格生成一个二维码,二维码会以 BASE64 的形式返回给前端。用户扫码支付后,微信后台就会调用回调接口(生成二维码的时候指定的),我们在回调接口中补充上自己的业务逻辑也就是赋值角色即可。
如果有新的逻辑,修改 setRole 方法,和 UpdateOrder 方法即可。
package com.sqber.personMgr.ui.controller.pay;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.enums.TradeType;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.IpKit;
import com.ijpay.core.kit.QrCodeKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.WxPayApiConfigKit;
import com.ijpay.wxpay.model.OrderQueryModel;
import com.ijpay.wxpay.model.UnifiedOrderModel;
import com.sqber.personMgr.base.BaseResponse;
import com.sqber.personMgr.base.SessionHelper;
import com.sqber.personMgr.bll.IPayOrderService;
import com.sqber.personMgr.entity.PayOrder;
import com.sqber.personMgr.ui.config.pay.WxConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import com.ijpay.wxpay.WxPayApiConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/wxPay")
public class WxController {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
WxConfig wxConfig;
@Autowired
IPayOrderService payOrderService;
public void getApiConfig() {
WxPayApiConfig apiConfig;
try {
apiConfig = WxPayApiConfigKit.getApiConfig(wxConfig.getAppId());
} catch (Exception e) {
apiConfig = WxPayApiConfig.builder()
.appId(wxConfig.getAppId())
.mchId(wxConfig.getMchId())
.partnerKey(wxConfig.getPartnerKey())
.certPath(wxConfig.getCertPath())
.domain(wxConfig.getDomain())
.build();
WxPayApiConfigKit.setThreadLocalWxPayApiConfig(apiConfig);
}
}
@GetMapping("/test")
public String test(){
return "test";
}
/**
* 扫码支付模式二
*/
@PostMapping("/scanCode2")
public BaseResponse scanCode2(HttpServletRequest request, HttpServletResponse response, String totalFee) {
if (StringUtils.isEmpty(totalFee)) {
return BaseResponse.fail("支付金额不能为空");
}
String ip = IpKit.getRealIp(request);
if (StringUtils.isEmpty(ip)) {
ip = "127.0.0.1";
}
return createCode(WxPayKit.generateStr(), ip,"","","", 1);
}
private BaseResponse createCode(String orderNo, String ip, String body, String detail, String attach, int totalFee){
getApiConfig();
WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
String notifyUrl = wxPayApiConfig.getDomain().concat("/wxPay/payNotify");
Map<String, String> params = UnifiedOrderModel
.builder()
.appid(wxPayApiConfig.getAppId())
.mch_id(wxPayApiConfig.getMchId())
.nonce_str(WxPayKit.generateStr())
.body(body)
.attach(attach)
.detail(detail)
.out_trade_no(orderNo)
.total_fee(String.valueOf(totalFee))
.spbill_create_ip(ip)
.notify_url(notifyUrl)
.trade_type(TradeType.NATIVE.getTradeType())
.build()
.createSign(wxPayApiConfig.getPartnerKey(), SignType.HMACSHA256);
log.info("parmas:" + params);
log.info("notifyUrl:" + notifyUrl);
String xmlResult = WxPayApi.pushOrder(false, params);
log.info("统一下单:" + xmlResult);
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
String returnCode = result.get("return_code");
String returnMsg = result.get("return_msg");
System.out.println(returnMsg);
if (!WxPayKit.codeIsOk(returnCode)) {
return BaseResponse.fail("error:" + returnMsg);
}
String resultCode = result.get("result_code");
if (!WxPayKit.codeIsOk(resultCode)) {
return BaseResponse.fail("error:" + returnMsg);
}
//生成预付订单success
String qrCodeUrl = result.get("code_url");
// 生成二维码,并返回 BASE64 格式的图片
try {
ByteArrayOutputStream output = new ByteArrayOutputStream();
QrCodeKit.encodeOutPutSteam(output, qrCodeUrl, BarcodeFormat.QR_CODE, 3, ErrorCorrectionLevel.H, "png", 200, 200);
byte[] byteArr = output.toByteArray();
String str = Base64.getEncoder().encodeToString(byteArr);
String imgStr = new StringBuilder().append("data:image/png;base64,").append(str).toString();
BaseResponse baseResponse = new BaseResponse();
baseResponse.setData(imgStr);
return baseResponse;
} catch (IOException e) {
log.error("error:{}", e);
e.printStackTrace();
return BaseResponse.fail("服务器错误");
}
}
@GetMapping("/setRole")
// 创建业务订单
private BaseResponse createOrder(HttpServletRequest request, HttpServletResponse response){
// 从数据库中获取角色的价格
PayOrder payOrder = new PayOrder();
payOrder.setOrderNo(WxPayKit.generateStr());
payOrder.setAmount(1); // 1分 1元 = 10角 = 100分
payOrder.setUserId(SessionHelper.GetLoginUserID());
payOrder.setOrderState(PayOrder.NOT_PAYED);
payOrder.setProduct("获取管理员角色");
payOrder.setContent("获取管理员角色");
payOrderService.insert(payOrder);
String ip = IpKit.getRealIp(request);
if (StringUtils.isEmpty(ip)) {
ip = "127.0.0.1";
}
return createCode(payOrder.getOrderNo(),ip, payOrder.getContent(), "详情", "附加", payOrder.getAmount());
}
/**
* 异步通知
*/
@RequestMapping(value = "/payNotify", method = {RequestMethod.POST, RequestMethod.GET})
@ResponseBody
public String payNotify(HttpServletRequest request) {
log.info("异步通知来了");
String xmlMsg = HttpKit.readData(request);
log.info("支付通知=" + xmlMsg);
Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
String returnCode = params.get("return_code");
String resultCode = params.get("result_code");
String transactionId = params.get("transaction_id"); // 微信订单号
String out_trade_no = params.get("out_trade_no"); // 商户订单号
String attach = params.get("attach"); // 附加数据
log.info("微信订单号(交易单号):" + transactionId);
log.info("订单号(商户单号):" + out_trade_no);
// 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
// 注意此处签名方式需与统一下单的签名类型一致
getApiConfig();
if (WxPayKit.verifyNotify(params, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey(), SignType.HMACSHA256)) {
if (WxPayKit.codeIsOk(returnCode) && WxPayKit.codeIsOk(resultCode)) {
// 更新订单信息
// 发送通知等
updateOrder(out_trade_no);
Map<String, String> xml = new HashMap<String, String>(2);
xml.put("return_code", "SUCCESS");
xml.put("return_msg", "OK");
return WxPayKit.toXml(xml);
}
}
return null;
}
// 更新订单 && 发送商品
private void updateOrder(String orderNo){
payOrderService.completeOrder(orderNo);
PayOrder payOrder = payOrderService.getByOrderNo(orderNo);
log.info("设置指定角色");
// 赋予用户特定角色
// userRole.updateRole(userId,payOrder.getProduct());
}
/**
* 查询订单
*
* @param transactionId
* @param outTradeNo
* @return
*/
@RequestMapping(value = "/queryOrder", method = {RequestMethod.POST, RequestMethod.GET})
@ResponseBody
public String queryOrder(@RequestParam(value = "transactionId", required = false) String transactionId, @RequestParam(value = "outTradeNo", required = false) String outTradeNo) {
try {
getApiConfig();
WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
Map<String, String> params = OrderQueryModel.builder()
.appid(wxPayApiConfig.getAppId())
.mch_id(wxPayApiConfig.getMchId())
.transaction_id(transactionId)
.out_trade_no(outTradeNo)
.nonce_str(WxPayKit.generateStr())
.build()
.createSign(wxPayApiConfig.getPartnerKey(), SignType.MD5);
log.info("请求参数:{}", WxPayKit.toXml(params));
String query = WxPayApi.orderQuery(params);
log.info("查询结果: {}", query);
// if(成功了){
// updateOrder();
// }
return query;
} catch (Exception e) {
e.printStackTrace();
return "系统错误";
}
}
}
支付宝接口控制器暂无。
业务订单表 SQL 语句
CREATE TABLE `payOrder` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`orderNo` varchar(100) NOT NULL DEFAULT '' COMMENT '订单号',
`amount` int NOT NULL DEFAULT 0 COMMENT '金额(单位:分)',
`userId` int NOT NULL DEFAULT 0 COMMENT '用户id',
`orderState` int NOT NULL DEFAULT 0 COMMENT '订单状态:0-待支付 1-已支付 2-支付失败',
`product` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '商品(比如按角色付费,则可以存角色id)',
`content` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '商品描述',
`createUser` varchar(64) NULL DEFAULT '' COMMENT '创建人',
`createTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`modifyUser` varchar(64) NULL DEFAULT '' COMMENT '更新人',
`modifyTime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
`status` tinyint DEFAULT '1' COMMENT '记录状态:0-删除 1-正常',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='支付订单表';
Demo地址
扫码分享
版权说明
作者:SQBER
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
{0}
{5}
{1}
{2}回复
{4}
*昵称:
*邮箱:
个人站点:
*想说的话: