注:本实验使用springboot框架
2.创建支付订单所需参数
大家可以用浏览器访问微信支付的API手册( https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1), 查阅具体的调用规范。下面是本次项目所使用到的参数
参数 | 含义 | 类型 | 例子 |
---|---|---|---|
appid | 公众号ID | String | wxd678efh567hg6787 |
mch_id | 商户号ID | String | 1230000109 |
nonce_str | 随机字符串 | String | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS |
sign | 签名 | String | C380BEC2BFD727A4B6845133519F3AD6 |
body | 商品订单描述 | String | 腾讯充值中心-QQ会员充值 |
out_trade_no | 商品订单号 | String | 20150806125346 |
total_fee | 订单金额,单位为分 | int | 150 |
spbill_create_ip | 终端IP(用户的客户端IP ) | String | 123.12.12.123 |
notify_url | 通知地址 | String | http://140.143.132.225:8000/project-1/doc-110/ |
trade_type | 交易类型 | String | NATIVE |
微信支付平台给我们返回的响应中包含了下面这些参数。
参数 | 含义 | 类型 | 例子 |
---|---|---|---|
return_code | 通信状态码 | String | SUCCESS |
result_code | 业务状态码 | String | SUCCESS |
app_id | 微信公众账号APPID | String | wxd678efh567hg6787 |
mch_id | 商户ID | String | 1230000109 |
nonce_str | 随机字符串 | String | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS |
sign | 数字签名 | String | C380BEC2BFD727A4B6845133519F3AD6 |
trade_type | 交易类型 | String | NATIVE |
prepay_id | 支付订单ID | String | wx201410272009395522657a690389285100 |
wx:
app-id: 企业身份APPID
app-secret: 企业身份密钥
mch-id: 企业身份商户号ID
key: 企业身份微信支付的密钥
cert-path: 企业身份微信支付的数字证书路径
1.searchAmectByCondition:根据id,用户id,(或状态码)查询罚款单的uuid(做商品编号),amount(罚款金额),prepay_id(支付订单),status(是否支付成功)
2.updatePrepayId:更新支付订单(用于向微信服务器请求生成订单成功后保存订单编号,方便之后主动查询支付状态)
<select id="searchAmectByCondition" parameterType="HashMap" resultType="HashMap">
SELECT uuid,
amount,
prepay_id AS prepayId,
`status`
FROM tb_amect
WHERE id = #{amectId}
AND user_id = #{userId}
<if test="status!=null">
AND `status` = #{status}
if>
select>
<update id="updatePrepayId" parameterType="HashMap">
UPDATE tb_amect
SET prepay_id = #{prepayId}
WHERE id = #{amectId} AND status = 1
update>
public interface TbAmectDao {
……
public HashMap searchAmectByCondition(HashMap param);
public int updatePrepayId(HashMap param);
}
public interface AmectService {
……
public String createNativeAmectPayOrder(HashMap param);
}
public class AmectServiceImpl implements AmectService {
@Autowired
private MyWXPayConfig myWXPayConfig;
……
@Override
public String createNativeAmectPayOrder(HashMap param) {
int userId = MapUtil.getInt(param, "userId");
int amectId = MapUtil.getInt(param, "amectId");
//根据罚款单ID和用户ID查询罚款单记录
HashMap map = amectDao.searchAmectByCondition(param);
if (map != null && map.size() > 0) {
//将元转为分
String amount = new BigDecimal(MapUtil.getStr(map, "amount")).multiply(new BigDecimal("100")).intValue() + "";
try {
WXPay wxPay = new WXPay(myWXPayConfig);
param.clear();
param.put("nonce_str", WXPayUtil.generateNonceStr()); //随机字符串
param.put("body", "缴纳罚款");
param.put("out_trade_no", MapUtil.getStr(map, "uuid"));
param.put("total_fee", amount);
param.put("spbill_create_ip", "127.0.0.1");
param.put("notify_url", "http://s1.nsloop.com:35750/emos-api/amect/recieveMessage");
param.put("trade_type", "NATIVE");
String sign = WXPayUtil.generateSignature(param, myWXPayConfig.getKey());
param.put("sign", sign);
Map<String, String> result = wxPay.unifiedOrder(param); //创建支付订单
String prepayId = result.get("prepay_id"); //微信订单ID
String codeUrl = result.get("code_url"); //支付连接,需要生成二维码让手机扫码
if (prepayId != null) {
param.clear();
param.put("prepayId", prepayId);
param.put("amectId", amectId);
int rows = amectDao.updatePrepayId(param);
if (rows != 1) {
throw new EmosException("更新罚款单的支付订单ID失败");
}
//把支付订单的URL生成二维码
QrConfig qrConfig = new QrConfig();
qrConfig.setWidth(255);
qrConfig.setHeight(255);
qrConfig.setMargin(2);
String qrCodeBase64 = QrCodeUtil.generateAsBase64(codeUrl, qrConfig, "jpg");
return qrCodeBase64;
} else {
log.error("创建支付订单失败", result);
throw new EmosException("创建支付订单失败");
}
} catch (Exception e) {
log.error("创建支付订单失败", e);
throw new EmosException("创建支付订单失败");
}
} else {
throw new EmosException("没有找到罚款单");
}
}
}
@Data
@Schema(description = "创建Native支付罚款订单表单")
public class CreateNativeAmectPayOrderForm {
@NotNull(message = "amectId不能为空")
@Min(value = 1, message = "amectId不能小于1")
@Schema(description = "罚款单ID")
private Integer amectId;
}
public class AmectController {
……
@PostMapping("/createNativeAmectPayOrder")
@Operation(summary = "创建Native支付罚款订单")
@SaCheckLogin
public R createNativeAmectPayOrder(@Valid @RequestBody CreateNativeAmectPayOrderForm form) {
int userId = StpUtil.getLoginIdAsInt();
int amectId = form.getAmectId();
HashMap param = new HashMap() {{
put("amectId", amectId);
put("userId", userId);
}};
String qrCodeBase64 = amectService.createNativeAmectPayOrder(param);
//R是一个统一的返回类,你也可以用hashmap返回
return R.ok().put("qrCodeBase64", qrCodeBase64);
}
}
会有相应的罚款列表,点击缴纳罚款,发送ajax请求,提交罚款订单id,调用createNativeAmectPayOrder接口,返回的二维码使用img标签显示,记为支付码,使用微信扫码支付即可
修改罚款状态
<update id="updateStatus" parameterType="HashMap">
UPDATE tb_amect
SET status = #{status}
WHERE uuid = #{uuid}
</update>
public interface TbAmectDao {
……
public int updateStatus(HashMap param);
}
public interface AmectService {
……
public int updateStatus(HashMap param);
}
public class AmectServiceImpl implements AmectService {
……
@Override
public int updateStatus(HashMap param) {
int rows = amectDao.updateStatus(param);
return rows;
}
}
微信平台发送付款结果通知的Request里面是XML格式的数据,所以我们要从中提取出我们需要的关键数据,这部分的文档规范,大家可以查阅微信支付的官方资料( https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8)。 而且这个请求并不是只发送一次,如果商户系统没有接收到请求,微信平台会每隔一小段时间再发送一次付款结果。
参数 | 含义 | 类型 | 示例 |
---|---|---|---|
return_code | 通信状态码 | String | SUCCESS |
result_code | 业务状态吗 | String | SUCCESS |
sign | 数字签名 | String | C380BEC2BFD727A4B6845133519F3AD6 |
out_trade_no | 商品订单ID | String | 1212321211201407033568112322 |
transaction_id | 支付订单ID | String | 1217752501201407033233368018 |
total_fee | 订单金额 | int | 150 |
time_end | 支付的时间 | String | 20141030133525 |
<xml>
<appid>appid>
<attach>attach>
<bank_type>bank_type>
<fee_type>fee_type>
<is_subscribe>is_subscribe>
<mch_id>mch_id>
<nonce_str>nonce_str>
<openid>openid>
<out_trade_no>out_trade_no>
<result_code>result_code>
<return_code>return_code>
<sign>sign>
<time_end>time_end>
<total_fee>1total_fee>
<coupon_fee>coupon_fee>
<coupon_count>coupon_count>
<coupon_type>coupon_type>
<coupon_id>coupon_id>
<trade_type>trade_type>
<transaction_id>transaction_id>
xml>
返回给微信平台的响应内容也必须是XML格式的,否则微信平台就会认为你没收到付款结果通知。
<xml>
<return_code>return_code>
<return_msg>return_msg>
xml>
修改一处代码
修改 WXPayUtil.isSignatureValid(xml, key) -》改为WXPayUtil.isSignatureValid(data,key, WXPayConstants.SignType.HMACSHA256)
添加Map data = WXPayUtil.xmlToMap(xml);
原因是WXPayUtil.isSignatureValid(xml, key) 的签名验证使用MD5加密,源码如下,
而我们真实的微信支付使用的是HMACSHA256,所以签名验证怎么都不会对,
所以要改一下签名验证的方法
之前我没考虑到这个,以为是只能使用MD5,所以我直接修改了微信支付工具类WXPay,
直接改为用MD5,后来觉得使用MD5不需要密钥很不安全,重新debug后发现原来
是这个原因。现在改回用HMACSHA256,需要修改三处代码,这是其中一处
另外两处:
实现微信支付(Native支付),使用WebSocket进行推送——2.微信支付工具类:中的二、官网给的工具类 的 2.1 WXPay类 核心类
以及:实现微信支付(Native支付),使用WebSocket进行推送 ——4.配置SpringBoot支持WebSocket,推送结果:中的五、主动查询付款结果 的 5.2 业务层代码
//源码
public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
Map<String, String> data = xmlToMap(xmlStr);
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key).equals(sign);
}
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, SignType.MD5);
}
之前创建订单时提交给微信服务端的回调url,notify_url,支付成功后微信服务器会调用该接口,发送回调通知
public class AmectController {
@Value("${wx.key}")
private String key;
……
@Operation(summary = "接收消息通知")
@RequestMapping("/receiveMessage")
public void receiveMessage(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setCharacterEncoding("utf-8");
Reader reader = request.getReader();
BufferedReader buffer = new BufferedReader(reader);
String line = buffer.readLine();
StringBuffer temp = new StringBuffer();
while (line!= null){
temp.append(line);
line = buffer.readLine();
}
buffer.close();
reader.close();
String xml = temp.toString();
//利用数字证书验证收到的响应内容,避免有人伪造付款结果发送给Web方法。
//修改该处代码 WXPayUtil.isSignatureValid(xml, key) -》改为WXPayUtil.isSignatureValid(data,key, WXPayConstants.SignType.HMACSHA256)
//添加Map data = WXPayUtil.xmlToMap(xml);
Map<String, String> data = WXPayUtil.xmlToMap(xml);
if(WXPayUtil.isSignatureValid(data,key, WXPayConstants.SignType.HMACSHA256)){
Map<String,String> map = WXPayUtil.xmlToMap(xml);
String resultCode = map.get("result_code");
String returnCode = map.get("return_code");
if ("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)) {
String outTradeNo = map.get("out_trade_no"); //罚款单UUID
//更新订单状态
HashMap param = new HashMap() {{
put("status", 2);
put("uuid", outTradeNo);
}};
int rows = amectService.updateStatus(param);
if (rows == 1) {
//向前端页面推送付款结果
//根据罚款单ID查询用户ID
int userId = amectService.searchUserIdByUUID(outTradeNo);
//向用户推送结果
WebSocketService.sendInfo("收款成功", userId + "");
//给微信平台返回响应
response.setCharacterEncoding("utf-8");
response.setContentType("application/xml");
Writer writer = response.getWriter();
BufferedWriter bufferedWriter = new BufferedWriter(writer);
bufferedWriter.write(" ");
bufferedWriter.close();
writer.close();
} else {
log.error("更新订单状态失败");
response.sendError(500, "更新订单状态失败");
}
}
}else {
log.error("数字签名异常");
response.sendError(500, "数字签名异常");
}
}
}
微信支付系列其他部分