前段时间公司交给了我一个微信支付相关的业务功能改造任务,那么为什么要改造呢?
主要原因有两个:
其一. 代码不规范、不够严谨,支付漏洞多,导致系统被黑客攻击,公司亏钱;
其二. 业务需求变动,之前的需求是会员支付完成后的结算都是走的虚拟账户余额,
现在的需求是在会员支付完成后对会员进行实时部分退款(消费返现)并且公司向会员支付的
商家实时打款(转账,相当于给商家的货款,因为商家都在公司平台上入驻的);
那么改动最大得部分无非就是回调通知函数。
特别提醒:
1、商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏 导致出现“假通知”,造成资金损失。
2、当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果 处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据 混乱。
贴出部分精华代码:
package com.sam.project.mvc.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.sam.project.mvc.common.log.RequestLog;
import com.sam.project.mvc.common.manager.RyscSecurityManager;
import com.sam.project.mvc.model.*;
import com.sam.project.mvc.service.LocalLifeAccountService;
import com.sam.project.mvc.service.LoclifeAccountDetailService;
import com.sam.project.mvc.service.MemberService;
import com.sam.project.mvc.service.NearbyService;
import com.sam.project.mvc.service.OrderService;
import com.sam.project.mvc.service.RyMiddleSettlementService;
import com.sam.project.mvc.service.RyscBusinessProfitService;
import com.sam.project.mvc.util.HttpRequest;
import com.sam.project.mvc.util.JavaAndPhp;
import com.sam.project.mvc.util.RSASecurityCoder;
import com.sam.project.mvc.util.SecurityUtil;
import com.sam.project.mvc.util.sdk.WXPayUtil;
import com.sam.project.mvc.util.wexin.WeXinUtil;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Controller
@RequestMapping("/businessProfit")
public class RyscBusinessProfitController extends BaseController {
private static final Logger LOG = LoggerFactory.getLogger(RequestLog.class);
private static final String mchId = "xxxxxxxxxxxx";
private static ExecutorService executorService;
@Autowired
private RyscBusinessProfitService ryscBusinessProfitService;
@Autowired
private NearbyService nearbyService;
@Autowired
private OrderService orderService;
@Autowired
private RyMiddleSettlementService ryMiddleSettlementService;
@Autowired
private LocalLifeAccountService localLifeAccountService;
@Autowired
private LoclifeAccountDetailService loclifeAccountDetailService;
@Autowired
private MemberService memberService;
private Lock accountLock = new ReentrantLock();
private FutureTask<Integer> asaFutureTask2 = null;
private FutureTask<Integer> asaFutureTask = null;
private FutureTask<Integer> asaFutureTask4 = null;
private FutureTask<Integer> asaFutureTask6 = null;
private FutureTask<String> asaFutureTask8 = null;
private FutureTask<Integer> refundExceptionTask = null;
private FutureTask<Integer> transfersExceptionTask = null;
private FutureTask<Integer> inviteCarveUpExceptionTask = null;
@SuppressWarnings("unused")
@RequestMapping(value = "payBackBusinessProfit", method = RequestMethod.POST)
@ResponseBody
public String payBackBusinessProfit(@RequestBody String payBack) {
String resXml = "";
// 加锁处理本地生活虚拟账户中的金额
accountLock.lock();
try {
executorService = Executors.newCachedThreadPool();// 创建线程池
refundExceptionTask = null;
transfersExceptionTask = null;
inviteCarveUpExceptionTask = null;
Map<Object, Object> map;
map = WXPayUtil.xmlToMap2(payBack);
String out_trade_no = map.get("out_trade_no").toString();
float userSendMoney = Float.parseFloat(map.get("total_fee").toString()) / 100;// 回调返回的金额
if (map != null) {
LOG.info("======================================");
logger.info("请求回调后的参数:{}", map);
System.out.println(map.get("sign"));
Boolean flag = verifyWeixinNotify(map);
LOG.info("map:{}" + map);
LOG.info("======================================");
if (flag) {
// 验证签名成功
RySettementAction action = new RySettementAction();
RyBussinessOrder rbo = nearbyService.findByWxOrderId(out_trade_no);
Integer maxMidId = null;
if (rbo != null) {
maxMidId = ryMiddleSettlementService.getMidIdByOrderId(rbo.getId());
}
String orderDesc = "";
if (rbo == null) {
System.out.println("查询数据库订单失败,没有这个订单。");
orderDesc = "查询数据库订单失败,没有这个订单。";
return resXml;
}
// 查询商家信息
RyNearbyBusiness rnb = nearbyService.findRyNearbyBusinessById(rbo.getBussinessId());
// 异步写入结算流水日志
addSettementActionTask(action, maxMidId, rnb.getLeaderId());
if (rbo.getIsPay() == 1 && rbo.getIsNotice() == 1) {
// 已经支付并通知成功
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml>";
return resXml;
}
String oId = rbo.getOrderId();
int orderId = rbo.getId();
// 加密字符串对比并验证金额
String tempKeyMoney = "";
String tempKey = oId + "," + rbo.getOrderMoney();
System.out.println("对比、加密前字符串:" + tempKey);
RyParameter ryParameter = nearbyService.getRyParameter(4);// 请求参数秘钥
String keyWord = JavaAndPhp.encrypt(tempKey, ryParameter.getParameterValue());// 现在加密的字符串
String attach = map.get("attach").toString(); // 商家数据包,原样返回(预支付加密的字符串)
String orderPassword = rbo.getOrderPassword(); // 数据库保存加密字符串
String code = "";
RyscBusinessProfit rbp = new RyscBusinessProfit();
rbp.setMemberId(rbo.getCreateMemberId());
rbp.setBusinessPhone(rnb.getBusinessPhone());
float businessProfit = Float.parseFloat(rbo.getOrderMoney()) * rnb.getBusinessDiscount();
DecimalFormat fnum = new DecimalFormat("##0.00");
String temp = fnum.format(businessProfit);
rbp.setBusinessProfit(Float.parseFloat(temp));
float memberReward = Float.parseFloat(rbo.getOrderMoney()) * rnb.getBusinessMoneyrewardDiscount();
String temp2 = fnum.format(memberReward);
rbp.setMemberReward(Float.parseFloat(temp2));
rbp.setOId(rbo.getOrderId());
rbp.setOrderId(rbo.getId());
float orderMoney = Float.parseFloat(rbo.getOrderMoney());
// 查询订单
org.json.JSONObject jsonObject = orderService.checkWxOrderResult(rbo.getCreateMemberId(),
rbo.getOrderId(), null);
if (jsonObject == null) {
System.out.println("查询微信订单失败,没有这个订单。");
orderDesc = "查询微信订单失败,没有这个订单。";
} else {
System.out.println(jsonObject.toString());
code = jsonObject.getString("trade_state");
}
boolean orderStatus = false;
int isPay = 0;
float inviteMoney = queryInviteMoney(rbo, orderMoney); // 会员上/上上级、商家上级分成
switch (code) {
case "SUCCESS":
orderDesc = "查询微信订单,支付成功!金额还未比对";
// 1、比对金额
String keyWordDecrypt = JavaAndPhp.decrypt(keyWord, ryParameter.getParameterValue()); // 解密现在加密的字符串
String attachDecrypt = JavaAndPhp.decrypt(attach, ryParameter.getParameterValue()); // 解密预支付加密的字符串
String orderPasswordDecrypt = JavaAndPhp.decrypt(orderPassword,
ryParameter.getParameterValue());// 解密数据库加密的字符串
float keyWordMoney = Float.parseFloat(keyWordDecrypt.split(",")[1]);
float attachMoney = Float.parseFloat(attachDecrypt.split(",")[1]);
float orderPasswordMoney = Float.parseFloat(orderPasswordDecrypt.split(",")[1]);
float wxMoney = Float.parseFloat(jsonObject.getString("total_fee")); // 查询订单返回金额
wxMoney = wxMoney / 100;
String OrderEndTiem = jsonObject.getString("time_end");
/**
* 1判断订单查询出来的金额,微信订单金额,乘以商家的百分比,跟前端传过来的商家打款金额对比。
* 2、该订单的支付