第三方支付接入(微信,支付宝)
QY
最近工作中安排了三方支付接入(一般就是微信,支付宝,银联),目前接入的是微信和支付宝;
阶段接近尾声并且测试很OK,现在记录一下开发过程.
目前对接了只有,支付宝的PC,H5,以及微信的PC,H5,JSAPI这几种, APP的支付暂时没有需求,所以没做考虑
大家在对接过程中如果遇到问题,欢迎留言,我看到会及时回复的.发私信也可以.希望对大家有帮助;
对接前提
你需要在支付宝或微信平台创建自己的应用,得到我们后续需要用到的各种id和secret
支付宝是有SDK的,记得引入依赖
至于怎么在微信和支付宝的管理平台创建应用,大家自己百度一下吧.o(╥﹏╥)o
思考
对接外部接口,其实很简单,只需要不停尝试就好了,哈哈. 当前网上有很多资源都是相当可取的;
需要注意的是,支付宝所需要的公钥和私钥 生成方式,此处需要特别注意下,开发者私钥,即我们通过支付宝的生成工具生成的应用私钥,
而公钥,如下图
是我们通过上面生成的应用公钥,进行填写,保存之后,支付宝生成的,切记!!! 不然调不通接口的哦.
支付宝生成秘钥
其次
说白了,不管支付宝还是微信,支付逻辑无在乎,一去一回,这两下, 先做统一下单的操作,然后通过返回数据,做具体的逻辑处理;
要注意的是,支付宝的PC和H5支付返回给我们的是完整的form表单,我们只需要将其响应给前端,让前端做submit即可进行掉起真正的支付动作;
而微信则削削不同, PC支付返回给我们的是一个二维码url,需要前端将其生成二维码展示; H5返回给我们的是一个web的url,由前端对其进行访问;
开发的时候本地可以,但是调试的时候,如果你没有调通接口,那么建议你线上进行调试;
类梳理
我业务中的
controller
和service
就不罗列了
-
PayPlatformService
姑且称之为收银台接口 -
AliPayServiceImpl
支付宝服务,实现了上面的收银台接口 -
WeChatPayServiceImpl
微信服务,实现了上面的收银台接口 -
PayCommons
支付用到的通用属性 -
PayProperties
支付相关的配置参数
具体实现
PayConstant
public interface PayConstant {
/**
* 由谁支付 0 企业 1 个人
*/
int PAY_SIDE_CORP = 0;
int PAY_SIDE_PERSONAL = 1;
/**
* 支付方式
*/
String PAY_TYPE_WX = "wcpay";
String PAY_TYPE_ALI = "alipay";
/**
* 微信支付类型
*/
String TRADE_TYPE_WX_JSAPI = "JSAPI";
String TRADE_TYPE_WX_NATIVE = "NATIVE";
String TRADE_TYPE_WX_APP = "APP";
String TRADE_TYPE_WX_MWEB = "MWEB";
}
PayCommons
@Data
@Accessors(chain = true)
public class PayCommons {
public static final String TRADE_TYPE_PC = "pc";
public static final String TRADE_TYPE_H5 = "h5";
/**
* sec_account_receivable的id
*/
private Integer sarId;
/**
* 订单标题
*/
private String subject;
/**
* 第三方(对于支付宝,微信来说)的订单号
*/
private String securityOrderNo;
/**
* 订单总金额
*/
private Integer totalAmount;
private BigDecimal fromTotalAmount;
/**
* 支付方式,PC还是WAP等等
*/
private String tradeType;
/**
* 交易该笔订单的设备IP
*/
private String clientIp;
/**
* 支付方,企业:0 还是个人:1
*/
private Integer paySide;
/**
* alipay:支付宝 wcpay:微信
*/
private String payType;
/**
* 业务类型 充值:RECHARGE
*/
private String bizType;
/**
* 业务单号
*/
private String bizNum;
/**
* 附件
*/
private String attach;
/**
* 微信获取token的code
*/
private String wxCode;
/**
* 微信的openId
*/
private String wxOpenId;
}
PayProperties
这个类可以放到配置文件中,后续我们会将其中一些配置移至Apollo配置中心
public class PayProperties {
/**
============================支付宝==========================================
*/
/**
* URL
*/
public static String ALI_PAY_BASE_URL = "https://openapi.alipay.com/gateway.do";
/**
* 对接支付宝时创建的应用
*/
public static String ALI_PAY_APPID = "";
/**
* 支付宝分配的商户号(账户中心,主账户ID)
*/
public static String ALI_SELLER_ID = "";
/**
* 开发者私钥,由开发者自己生成
*/
public static String ALI_PAY_APP_PRIVATE_KEY = "";
/**
* 开发者公钥,由支付宝生成
*/
public static String ALI_PAY_APP_PUBLIC_KEY = "";
/**
* 销售产品码,商家和支付宝签约的产品码
* 1、app支付product_code:QUICK_MSECURITY_PAY;
* 2、手机网站支付product_code:QUICK_WAP_WAY;
* 3、电脑网站支付product_code:FAST_INSTANT_TRADE_PAY;
* 4、统一收单交易支付接口product_code:FACE_TO_FACE_PAYMENT;
* 5、周期扣款签约product_code:CYCLE_PAY_AUTH;
*/
public static String ALI_WAP_PAY_PRODUCT_CODE = "QUICK_WAP_WAY";
public static String ALI_PAGE_PAY_PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY";
/**
* 参数返回格式
*/
public static String ALI_PAY_FORMAT = "json";
/**
* 编码集
*/
public static String ALI_PAY_CHARSET = "UTF-8";
/**
* 签名方式
*/
public static String ALI_PAY_SIGN_TYPE = "RSA2";
/**异步
* 回调接口
*/
public static String ALI_PAY_NOTIFY_URL = "";
/**
* 用户付款中途退出,返回商户网站的地址
*/
public static String ALI_PAY_QUIT_URL = "";
/**同步
* 用户支付成功返回的地址
*/
public static String ALI_PAY_PAGE_RETURN_URL = "";
public static String ALI_PAY_WAP_RETURN_URL = "";
/**
* 支付接口
*/
public static String ALI_PAY_WAP_PAY = "alipay.trade.wap.pay";
/**
** ============================微信=======================================
*/
/**
* 统一下单
*/
public static String WX_PAY_PAY_UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 订单查询
*/
public static String WX_PAY_PAY_ORDERQUERY_URL = "https://api.mch.weixin.qq.com/pay/orderquery";
/**
* 微信支付appid
*/
public static String WX_PAY_APPID = "";
/**
* 微信支付商户ID
*/
public static String WX_PAY_MCHID = "";
/**
* 商户秘钥
* API秘钥
*/
public static String WX_PAY_SIGN_SECRET_KEY = "";
/**
* 微信支付AppSecret
*/
public static String WX_PAY_APP_SECRET = "";
/**
* 微信支付回调
*/
public static String WX_PAY_NOTIFY_URL = "";
/**
* 微信H5支付成功之后返回的页面
*/
public static String WX_WAP_PAY_RETURN_URL = "";
}
AliPayClientFactory
当你阅读过支付宝文档之后你会发现,文档中明确说明,当
AlipayClient
创建完成之后,可以重复使用,因此我们在这里做一个单例的操作,算是代码的一个优化吧, 微信也会有类似的处理;
public class AliPayClientFactory {
private volatile static AlipayClient aliPayClient = null;
/**
* 私有化构造器
*/
private AliPayClientFactory(){}
/**
* 获取对象
*/
public static AlipayClient getInstance(){
if (aliPayClient == null){
synchronized (AliPayClientFactory.class){
if (aliPayClient == null){
aliPayClient = new DefaultAlipayClient(PayProperties.ALI_PAY_BASE_URL,PayProperties.ALI_PAY_APPID,PayProperties.ALI_PAY_APP_PRIVATE_KEY
,PayProperties.ALI_PAY_FORMAT,PayProperties.ALI_PAY_CHARSET,PayProperties.ALI_PAY_APP_PUBLIC_KEY,PayProperties.ALI_PAY_SIGN_TYPE);
}
}
}
return aliPayClient;
}
}
通用结果类Result
@Data
public class Result<T> {
private String code;
private String mesg;
private T data;
private Result() {
this.code = "000000";
this.mesg = "success";
this.data = null;
}
private Result(T data) {
this.code = "000000";
this.mesg = "success";
this.data = data;
}
private Result(TaurusErrorCodeEnum tec) {
if (tec==null){
return;
}
this.code = tec.getCode();
this.mesg = tec.getDescription();
}
/**
* 成功时调用
* @param <T>
* @return
*/
public static <T> Result<T> success(){
return new Result();
}
/**
* 成功时调用
* @param data
* @param <T>
* @return
*/
public static <T> Result<T> success(T data){
return new Result<T>(data);
}
/**
* 失败时调用
* @param
* @param <T>
* @return
*/
public static <T> Result<String> fail(){
Result<String> result = new Result<>();
result.setCode("2000");
result.setMesg("系统异常");
return result;
}
/**
* 失败时调用
* @param
* @param <T>
* @return
*/
public static <T> Result<String> fail(String code, String msg){
Result<String> result = new Result<String>();
result.setCode(code);
result.setMesg(msg);
return result;
}
/**
* 失败时调用
* @param tec
* @param <T>
* @return
*/
public static <T> Result<T> fail(TaurusErrorCodeEnum tec){
return new Result<T>(tec);
}
}
PayPlatformService
public interface SecurityPayPlatformService {
/**
* 支付
* @param pc
* @return
*/
Result securityPay(PayCommons pc);
/**
* 处理回调
* @param request
* @return
*/
Result handleNotify(HttpServletRequest request);
/**
* 查询订单状态
* @param payCommons
* @return
*/
Result selectOrderInfo(PayCommons payCommons);
}
AliPayServiceImpl
@Service
public class AliPayServiceImpl implements PayPlatformService{
private static final Logger log = LoggerFactory.getLogger(AliPayServiceImpl.class);
@Autowired
private SecAccountReceivableService secAccountReceivableService;
/**
* 支付宝支付充值
* @param payParam
* @return
*/
@Override
public Result securityPay(PayCommons payParam) {
if (null == payParam) {
return Result.fail().setMesg("参数未传");
}
AlipayClient alipayClient = AliPayClientFactory.getInstance();
//转义携带参数
JSONObject attach = (JSONObject)JSON.parse(payParam.getAttach());
StringBuilder attachSB = new StringBuilder();
attachSB.append("bizType=").append(attach.get("bizType")).append("&paySide=")
.append(attach.get("paySide"));
payParam.setAttach(attachSB.toString());
//0企业支付page 1 个人支付wap
if (payParam.getTradeType().equals(PayCommons.TRADE_TYPE_PC)){
return this.doPagePayRequest(alipayClient,payParam);
}else {
return this.doWapPayRequest(alipayClient,payParam);
}
}
/**
* 处理回调
* @param request
* @return
*/
@Override
public Result handleNotify(HttpServletRequest request) {
try {
Enumeration<String> names = request.getParameterNames();
HashMap<String, String> resData = new HashMap<>();
while (names.hasMoreElements()){
String name = names.nextElement();
resData.put(name,request.getParameter(name));
}
log.info("======支付宝支付的异步回调通知参数:{}",resData.toString());
//1.验签
boolean flag = AlipaySignature.rsaCheckV1(resData, PayProperties.ALI_PAY_APP_PUBLIC_KEY, "UTF-8", "RSA2");
if (!flag){
return Result.fail().setMesg("验签失败");
}
//2.必要参数非空验证
String tradeStatus = resData.get("trade_status");
//己方单号
String secTradeNo = resData.get("out_trade_no");
//商户号
String sellerId = resData.get("seller_id");
String totalAmount = resData.get("total_amount");
String appId = resData.get("app_id");
if (StringUtils.isAnyBlank(tradeStatus, secTradeNo, sellerId, totalAmount, appId)){
return Result.fail().setMesg("解析非空参数trade_status,out_trade_no,seller_id,total_amount,app_id部分为空");
}
//数据匹配验证
List<SecAccountReceivable> list = secAccountReceivableService.getList(new SecAccountReceivableQuery().setAccountNum(secTradeNo));
if (CollectionUtils.isEmpty(list)){
return Result.fail().setMesg("secTradeNo no found:"+secTradeNo);
}
SecAccountReceivable sar = list.get(0);
if (!(sellerId.equals(PayProperties.ALI_SELLER_ID) && secTradeNo.equals(sar.getAccountNum())
&& appId.equals(PayProperties.ALI_PAY_APPID) && totalAmount.equals(sar.getAccountAmount().toString()))){
return Result.fail().setMesg("数据匹配失败,当前回调数据与查询数据不一致");
}
//sar_id 是我们应收账单的id,此处可以忽略,删除
resData.put("sar_id",String.valueOf(sar.getId()));
return Result.success(resData);
}catch (Exception e){
e.printStackTrace();
log.error("支付宝支付的异步回调处理出现错误:{}",e.getStackTrace());
return Result.fail().setMesg("支付宝支付的异步回调处理出现错误");
}
}
/**
* 查詢支付宝订单信息
* @param payCommons
* @return
*/
@Override
public Result selectOrderInfo(PayCommons payCommons) {
AlipayClient client = AliPayClientFactory.getInstance();
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no",payCommons.getSecurityOrderNo());
request.setBizContent(bizContent.toJSONString());
AlipayTradeQueryResponse response;
try {
response = client.execute(request);
log.info("支付宝订单【{}】---查詢結果:{}",payCommons.getSecurityOrderNo(),response.getBody());
}catch (Exception e){
e.printStackTrace();
log.error("调用支付宝查询接口异常");
return Result.fail().setMesg("调用支付宝查询接口异常");
}
String body = response.getBody();
if (StringUtils.isBlank(body)){
return Result.fail();
}
JSONObject bodyObj = JSON.parseObject(body);
JSONObject bodybody = bodyObj.getJSONObject("alipay_trade_query_response");
//sar_id 是我们应收账单的id,此处可以忽略,删除
bodybody.put("sar_id",payCommons.getSarId());
return Result.success(bodybody.toString());
}
//=====================private method===========================
/**
* 处理PC支付
* @param alipayClient
* @param payParam
* @return
*/
private Result doPagePayRequest(AlipayClient alipayClient,PayCommons payParam) {
AlipayTradePagePayRequest pagePayRequest = new AlipayTradePagePayRequest();
JSONObject bizContent = new JSONObject();
Result result = null;
//回调接口,PC支付方式,returnUrl如果没有必要可以不必配置
//pagePayRequest.setReturnUrl(PayProperties.ALI_PAY_PAGE_RETURN_URL);
//异步通知调用接口
pagePayRequest.setNotifyUrl(PayProperties.ALI_PAY_NOTIFY_URL);
/**
* 以下必传项
*/
bizContent.put("subject",payParam.getSubject());
bizContent.put("out_trade_no",payParam.getSecurityOrderNo());
bizContent.put("total_amount",payParam.getFromTotalAmount());
bizContent.put("product_code",PayProperties.ALI_PAGE_PAY_PRODUCT_CODE);
/**
* 以下选传项
*/
//公共回传参数,如果请求时传递了该参数,支付宝只会在同步返回和异步通知时将该参数原样返回
try {
String encodeAttach = URLEncoder.encode(payParam.getAttach(), PayProperties.ALI_PAY_CHARSET);
bizContent.put("passback_params",encodeAttach);
}catch (Exception e){
log.error("ali pay passBackParams encode exception:{}",e.getStackTrace());
}
pagePayRequest.setBizContent(bizContent.toJSONString());
String form = "";
try {
form = alipayClient.pageExecute(pagePayRequest,"get").getBody();
result = Result.success(form);
}catch (Exception e){
e.printStackTrace();
//调用异常
log.error("调用支付宝PC支付异常,信息:{}",e.getMessage());
result = Result.fail().setMesg("支付宝PC支付出现异常");
}
return result;
}
/**
* 处理H5支付
* @param alipayClient
* @param payParam
* @return
*/
private Result doWapPayRequest(AlipayClient alipayClient, PayCommons payParam) {
AlipayTradeWapPayRequest wapPayRequest = new AlipayTradeWapPayRequest();
Result result = null;
//支付成功访问接口
wapPayRequest.setReturnUrl(PayProperties.ALI_PAY_WAP_RETURN_URL);
//异步通知调用接口
wapPayRequest.setNotifyUrl(PayProperties.ALI_PAY_NOTIFY_URL);
/**
* 以下必传项
*/
SortedMap<String, Object> bizContent = new TreeMap<>();
bizContent.put("subject",payParam.getSubject());
bizContent.put("out_trade_no",payParam.getSecurityOrderNo());
BigDecimal amount = payParam.getFromTotalAmount().setScale(2,BigDecimal.ROUND_HALF_UP);
bizContent.put("total_amount",amount);
bizContent.put("product_code",PayProperties.ALI_WAP_PAY_PRODUCT_CODE);
/**
* 以下选传项
*/
//公共回传参数,如果请求时传递了该参数,支付宝只会在同步返回和异步通知时将该参数原样返回
try {
String encodeAttach = URLEncoder.encode(payParam.getAttach(), PayProperties.ALI_PAY_CHARSET);
bizContent.put("passback_params",encodeAttach);
}catch (Exception e){
log.error("ali pay passBackParams encode exception:{}",e.getStackTrace());
}
wapPayRequest.setBizContent(JSON.toJSONString(bizContent));
try {
String form = alipayClient.pageExecute(wapPayRequest).getBody();
result = Result.success().setData(form);
}catch (AlipayApiException e){
e.printStackTrace();
//调用异常
log.error("调用支付宝WAP支付异常,信息:{}",e.getMessage());
result = Result.fail().setMesg("支付宝WAP支付出现异常");
}
return result;
}
}
微信支付,需要一个特殊工具类,在此贴出来WXPayUtil
WXPayUtil
public class WXPayUtil {
public static Logger log = LoggerFactory.getLogger(WXPayUtil.class);
/**
* 获取随机串
*/
public static String createNonceStr() {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String res = "";
for (int i = 0; i < 16; i++) {
Random rd = new Random();
res += chars.charAt(rd.nextInt(chars.length() - 1));
}
return res;
}
/**
* 获取client_ip
*
* @param request
* @return
*/
public static String getRemoteHost(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
String[] ips = ip.split(",");
return ips[0].equals("0:0:0:0:0:0:0:1") ? "127.0.0.1" : ips[0];
}
/**
* @param key
* @param characterEncoding
* @param parameters
* @return
*/
public static String createSign(String key, String characterEncoding, SortedMap<String, Object> parameters) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + key);
String sign = MD5Util.encode(sb.toString()).toUpperCase();
return sign;
}
/**
* @param characterEncoding 编码格式
* @param parameters 请求参数
* @return
* @Description:创建sign签名
*/
public static String createSign(String characterEncoding, SortedMap<String, Object> parameters) {
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if ("attach".equalsIgnoreCase(k)) {
sb.append(k + "=" + v + "&");
} else if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + PayProperties.WX_PAY_SIGN_SECRET_KEY);
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* 封装xml request
*/
public static String getRequestXml(SortedMap<String, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if ("sign".equalsIgnoreCase(k)) {
} else if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("<" + "sign" + ">" + "<![CDATA[" + parameters.get("sign") + "]]></" + "sign" + ">");
sb.append("</xml>");
return sb.toString();
}
/**
* 封装xml 通知返回
*/
public static String getnotifyRespXml(String isSuccess, String reason) {
SortedMap<String, Object> parameters = new TreeMap<>();
parameters.put("return_code", isSuccess);
parameters.put("return_msg", reason);
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set<Map.Entry<String, Object>> es = parameters.entrySet();
Iterator<Map.Entry<String, Object>> it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if ("sign".equalsIgnoreCase(k)) {
} else if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "return_code".equalsIgnoreCase(k) || "return_msg".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
public static SortedMap<String, Object> startWXPay(String result) throws Exception {
SecurityWXPayConfig wxPayConfig = SecurityWXPayConfig.getInstance();
Map<String, String> map = doXMLParse(result);
String prepayId = map.get("prepay_id");
SortedMap<String, Object> parameterMap = new TreeMap<>();
parameterMap.put("appId", wxPayConfig.getWxAppId());
parameterMap.put("timeStamp", String.valueOf(System.currentTimeMillis()));
parameterMap.put("nonceStr", map.get("nonce_str"));
parameterMap.put("package", "prepay_id=" + prepayId);
parameterMap.put("signType","MD5");
String sign = createSign("UTF-8", parameterMap);
parameterMap.put("paySign", sign);
parameterMap.putAll(map);
return parameterMap;
}
/**
* xml 解析
*
* @param strxml
* @return
*/
public static Map doXMLParse(String strxml) throws Exception {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (StringUtils.isBlank(strxml)) {
return null;
}
Map map = new HashMap();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder saxBuilder = new SAXBuilder();
Document doc = saxBuilder.build(in);
Element rootEle = doc.getRootElement();
List childrenList = rootEle.getChildren();
Iterator it = childrenList.iterator();
while (it.hasNext()) {
Element element = (Element) it.next();
String k = element.getName();
String v = "";
List children = element.getChildren();
if (children.isEmpty()) {
v = element.getTextNormalize();
} else {
v = getChildrenText(children);
}
map.put(k, v);
}
//关闭流
in.close();
return map;
}
private static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if (!children.isEmpty()) {
Iterator it = children.iterator();
while (it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if (!list.isEmpty()) {
sb.append(getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
/**
* 接收微信的异步通知,取出参数
*
* @param request
* @return
*/
public static String reciverWx(HttpServletRequest request) throws IOException {
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
in.close();
inputStream.close();
return sb.toString();
}
/**
* 是否签名正确
* 规则:按参数名称a-z排序,遇到空值的参数不参与签名
*
* @param characterEncoding
* @param packageParams
* @return
*/
public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams, SecurityWXPayConfig payConfig) {
StringBuffer sb = new StringBuffer();
Set<Map.Entry<Object, Object>> es = packageParams.entrySet();
Iterator<Map.Entry<Object, Object>> it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if (!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + payConfig.getWxSecretKey());
//算出摘要
String mySign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();
String sign = ((String) packageParams.get("sign")).toLowerCase();
return sign.equals(mySign);
}
/**
* 测试main方法
*
* @param args
*/
public static void main(String[] args) {
String 测试 = "<xml><return_code><![CDATA[FAIL]]></return_code>\n" +
"<return_msg><![CDATA[签名错误]]></return_msg>\n" +
"</xml>";
try {
Map sortedMap = WXPayUtil.doXMLParse(测试);
String string = JSON.toJSONString(sortedMap);
System.out.println("jsonString:" + string);
} catch (Exception e) {
e.printStackTrace();
}
}
}
SecurityWXPayConfig该类是微信的公共参数生成类
public class SecurityWXPayConfig{
/**
* 微信支付appid
*/
private String wxAppId;
/**
* 微信支付商户ID
*/
private String wxMchId;
/**
* 微信支付回调
*/
private String wxNotifyUrl;
/**
* 微信支付AppSecret
*/
private String wxAppSecret;
/**
* 微信支付秘钥
*/
private String wxSecretKey;
/**
* 微信包名
*/
private String wxPkg;
private String sceneInfo;
private static volatile SecurityWXPayConfig securityWXPayConfig = null;
private SecurityWXPayConfig() {
this.wxAppId = PayProperties.WX_PAY_APPID;
this.wxNotifyUrl = PayProperties.WX_PAY_NOTIFY_URL;
this.wxAppSecret = PayProperties.WX_PAY_APP_SECRET;
this.wxMchId = PayProperties.WX_PAY_MCHID;
this.wxSecretKey = PayProperties.WX_PAY_SIGN_SECRET_KEY;
Map<String,Object> map = new HashMap<>();
Map<String,String> infoMap = new HashMap<>();
infoMap.put("type","Wap");
infoMap.put("wap_url","https://www.xxx.com/safe/");
infoMap.put("wap_name","xx");
map.put("h5_info",infoMap);
this.sceneInfo = JSON.toJSONString(map);
}
public static SecurityWXPayConfig getInstance(){
if (securityWXPayConfig == null){
synchronized (SecurityWXPayConfig.class){
if (securityWXPayConfig == null){
securityWXPayConfig = new SecurityWXPayConfig();
}
}
}
return securityWXPayConfig;
}
public String getWxAppId() {
return wxAppId;
}
public String getWxMchId() {
return wxMchId;
}
public String getWxNotifyUrl() {
return wxNotifyUrl;
}
public String getWxAppSecret() {
return wxAppSecret;
}
public String getWxSecretKey() {
return wxSecretKey;
}
public String getWxPkg() {
return wxPkg;
}
public String getSceneInfo() {
return sceneInfo;
}
}
WeChatPayServiceImpl
@Service
public class WeChatPayServiceImpl implements SecurityPayPlatformService {
private static final Logger log = LoggerFactory.getLogger(AliPayServiceImpl.class);
//业务类,应收账单,用来检验异步通知的数据准确性
@Autowired
private SecAccountReceivableService secAccountReceivableService;
/**
* 支付充值
*
* @param payParam
* @return
*/
@Override
public Result securityPay(PayCommons payParam) {
if (null == payParam) {
return Result.fail().setMesg("参数必传");
}
//获取微信公共配置
SecurityWXPayConfig wxPayConfig = SecurityWXPayConfig.getInstance();
SortedMap<String, Object> parameterMap = new TreeMap<>();
//获取随机串
String nonceStr = WXPayUtil.createNonceStr();
parameterMap.put("appid", wxPayConfig.getWxAppId());
parameterMap.put("mch_id", wxPayConfig.getWxMchId());
parameterMap.put("notify_url", wxPayConfig.getWxNotifyUrl());
//随机字符串
parameterMap.put("nonce_str", nonceStr);
//商品描述
parameterMap.put("body", payParam.getSubject());
parameterMap.put("out_trade_no", payParam.getSecurityOrderNo());
//单位:分
//payCommons.setTotalAmount();
int totalAmount = payParam.getFromTotalAmount().multiply(new BigDecimal("100")).intValue();
parameterMap.put("total_fee", totalAmount);
parameterMap.put("spbill_create_ip", payParam.getClientIp());
parameterMap.put("attach", payParam.getAttach());
String tradeType = payParam.getTradeType();
if (tradeType.equals(PayCommons.TRADE_TYPE_PC)){
tradeType = PayConstant.TRADE_TYPE_WX_NATIVE;
}else if (tradeType.equals(PayCommons.TRADE_TYPE_H5)){
tradeType = PayConstant.TRADE_TYPE_WX_MWEB;
}else if (tradeType.equals(PayConstant.TRADE_TYPE_WX_JSAPI)){
parameterMap.put("openid",payParam.getWxOpenId());
}
payParam.setTradeType(tradeType);
parameterMap.put("trade_type", tradeType);
//{"h5_info": {"type":"Wap","wap_url": "https://pay.qq.com","wap_name": "腾讯充值"}}
//wap_url 是用工卫士的url wap_name 是用工卫士的name
parameterMap.put("scene_info", wxPayConfig.getSceneInfo());
//生成签名
String sign = WXPayUtil.createSign("UTF-8", parameterMap);
//签名
parameterMap.put("sign", sign);
//map转xml
String requestXml = WXPayUtil.getRequestXml(parameterMap);
log.info("请求微信支付xml参数requestXml:{}", requestXml);
//http post 请求
String result = HttpUtils.post(PayProperties.WX_PAY_PAY_UNIFIEDORDER_URL, requestXml);
log.info("微信支付请求结果:{}", result);
if (StringUtils.isBlank(result)) {
log.error("调用微信支付接口失败:返回结果为空");
return Result.fail().setMesg("微信支付接口调用失败");
}
SortedMap<String, Object> map = null;
try {
map = WXPayUtil.startWXPay(result);
} catch (Exception e) {
e.printStackTrace();
log.error("pay error WeChatPayServiceImpl:{}", e.getMessage());
return Result.fail().setMesg("请求结果解析有误");
}
//对result转成的map进行解析
if (map.get("return_code").equals("FAIL")) {
log.error("调用微信支付接口失败:{}", map.get("return_msg").toString());
return Result.fail().setMesg("微信支付接口调用失败");
}
if (map.get("return_code").equals("SUCCESS")
&& map.get("result_code").equals("FAIL")) {
log.error("微信支付出现错误,错误代码:{},错误描述:{}", map.get("err_code"), map.get("err_code_des"));
return Result.fail().setMesg("微信支付出现错误");
}
map.put("accountNumber", payParam.getSecurityOrderNo());
//此处只有H5支付有的需求
if (payParam.getTradeType().equals(PayConstant.TRADE_TYPE_WX_MWEB)){
String mwebUrl = (String)map.get("mweb_url");
String encodeReturnUrl = "";
try {
encodeReturnUrl = URLEncoder.encode(PayProperties.WX_WAP_PAY_RETURN_URL+"?out_trade_no="+payParam.getSecurityOrderNo(), "UTF-8");
}catch (Exception e){
e.printStackTrace();
}
if (StringUtils.isBlank(encodeReturnUrl)){
encodeReturnUrl = PayProperties.WX_WAP_PAY_RETURN_URL+"?out_trade_no="+payParam.getSecurityOrderNo();
}
map.put("mweb_url",mwebUrl+"&redirect_url="+encodeReturnUrl);
}
log.info("微信支付请求数据处理,返回前端结果:{}", map);
return Result.success(map);
}
/**
* 处理回调
*
* @param request
* @return
*/
@Override
public Result handleNotify(HttpServletRequest request) {
Result result = Result.fail();
String notifyXml = "";
try {
notifyXml = WXPayUtil.reciverWx(request);
} catch (Exception e) {
e.printStackTrace();
return result.setMesg("参数格式校验错误,reciverWx()方法出现异常");
}
//参数为空
if (StringUtils.isBlank(notifyXml)) {
//这里返回一个参数错误的xml字符串
return result.setMesg("参数格式校验错误" + "解析的请求参数为空");
}
try {
Map map = WXPayUtil.doXMLParse(notifyXml);
log.info("wx handleNotify doXMLParse result:{}", map);
SecurityWXPayConfig payConfig = SecurityWXPayConfig.getInstance();
//过滤空 设置 treeMap
SortedMap<Object, Object> packageParams = new TreeMap<>();
Iterator it = map.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterVal = (String) map.get(parameter);
String v = "";
if (StringUtils.isNotBlank(parameterVal)) {
v = parameterVal.trim();
}
packageParams.put(parameter, v);
}
//判断签名是否正确 isTenpaySign
if (!WXPayUtil.isTenpaySign("UTF-8", packageParams, payConfig)) {
//返回参数格式校验错误
return result.setMesg("签名失败");
}
//如果通讯异常,即return_code为fail,返回参数格式校验错误
if (StringUtils.isBlank((String) packageParams.get("return_code")) ||
"FAIL".equals(packageParams.get("return_code"))) {
//返回参数格式校验错误
return result.setMesg("参数格式校验错误" + "return_code" + "为空或返回为FAIL");
}
String mchId = (String) packageParams.get("mch_id");
String securityTradeNo = (String) packageParams.get("out_trade_no");
String totalFee = (String) packageParams.get("total_fee");
//查询应收账单,用来对通知中的单号,金额等做验证,此操作可以放到外面,使支付更加通用
List<SecAccountReceivable> list = secAccountReceivableService.getList(new SecAccountReceivableQuery().setAccountNum(securityTradeNo));
if (CollectionUtils.isEmpty(list)) {
return result.setMesg("未查询到相应out_trade_no的订单");
}
SecAccountReceivable sar = list.get(0);
//验证商户ID和价格,以防止篡改金额
BigDecimal accountAmount = sar.getAccountAmount().multiply(new BigDecimal(100));
if (StringUtils.isAnyBlank(mchId, totalFee) || !payConfig.getWxMchId().equals(mchId)
|| (accountAmount.compareTo(new BigDecimal(totalFee)) != 0)) {
//这里返回一个参数错误的xml字符串
return result.setMesg("参数格式校验错误,mchId,totalFee为空,或者其中一个与我方所持资源不匹配");
}
map.put("sar_id", sar.getId());
return Result.success().setData(map);
} catch (Exception e) {
e.printStackTrace();
//这里返回一个参数错误
return result.setMesg("参数格式校验错误,出现异常");
}
}
/**
* 查询订单状态
* @param payCommons
* @return
*/
@Override
public Result selectOrderInfo(PayCommons payCommons) {
SortedMap<String,Object> paramMap = new TreeMap<>();
paramMap.put("appid",PayProperties.WX_PAY_APPID);
paramMap.put("mch_id",PayProperties.WX_PAY_MCHID);
paramMap.put("out_trade_no",payCommons.getSecurityOrderNo());
paramMap.put("nonce_str",WXPayUtil.createNonceStr());
//获取签名
String sign = WXPayUtil.createSign("UTF-8", paramMap);
paramMap.put("sign",sign);
String requestXml = WXPayUtil.getRequestXml(paramMap);
String result = HttpUtils.post(PayProperties.WX_PAY_PAY_ORDERQUERY_URL, requestXml);
log.info("查询单号【{}】結果:{}",payCommons.getSecurityOrderNo(),result);
if (StringUtils.isBlank(result)){
return Result.fail().setMesg("单号:"+payCommons.getSecurityOrderNo()+"查询结果为空");
}
SortedMap<String, Object> map = null;
try {
map = WXPayUtil.startWXPay(result);
} catch (Exception e) {
e.printStackTrace();
log.error("微信支付订单号:{},查询结果解析异常",payCommons.getSecurityOrderNo());
return Result.fail().setMesg("请求结果解析有误");
}
//对result转成的map进行解析
if (map.get("return_code").equals("FAIL")) {
log.error("微信订单查询接口调用失败,原因:{}", map.get("return_msg").toString());
return Result.fail().setMesg("微信支付接口调用失败");
}
if (map.get("return_code").equals("SUCCESS")
&& map.get("result_code").equals("FAIL")) {
log.error("微信订单查询错误,错误代码:{},错误描述:{}", map.get("err_code"), map.get("err_code_des"));
return Result.fail().setMesg("微信支付出现错误");
}
map.put("sar_id",payCommons.getSarId());
return Result.success(map);
}
}
FAQ
上一篇: php中的观察者模式简单实例,php观察者实例_PHP教程
下一篇: 富文本框问题