微信支付java服务端开发(APP)

我这边是针对微信商户支付功能开发。其他的未涉及到。

当你所有的准备工作准备好后:微信支付申请成功,api_key 配置好,等等一系列。

那么让我们进入java开发吧。

微信支付demo下载:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

下载JAVA版。这里面全是微信支付的工具类。

maven配置:

<!-- https://mvnrepository.com/artifact/com.github.wxpay/wxpay-sdk -->
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>

别去maven工厂搜索了。就3个版本0.01,0.02,0.03

微信支付和支付宝的支付区别在于。支付宝已经全部封装好了。而微信支付这B坑的一比吊扫。只有工具类,还要自己去写继承方法。

上面的java demo 下载之后。当如到自己当前的项目中。新建包名存放。

需要修改的地方:

新建WXPayConfig继承实现类:

public class WxPayUtilConfig extends WXPayConfig

如果该WxPayUtilConfig 和微信工具类的WXPayConfig 不在同一个包下,则WXPayConfig里面的方法需要public

/**
 * 微信支付配置文件
 * 继承微信工具配置文件抽象接口
 * @author think
 */
public class WxPayUtilConfig extends WXPayConfig{
    
    private byte[] certData;//将证书地址解析成byte
    //证书需要自己到微信商户端自己下来。一些了操作很简答,需要找找而已。如果支付放到服务器,则cert这个需要放到服务器,并且路径改成服务器路径,否则,app唤醒支付报500异常
    public WxPayUtilConfig() throws Exception {
        //从微信商户平台下载的安全证书存放的路径
        String certPath = "C:\\services\\wxpay\\cert\\apiclient_cert.p12";
        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 IWXPayDomain getWXPayDomain() {
        // TODO Auto-generated method stub
        return WXPayDomainSimpleImpl.instance();
    }
    
    @Override
    public String getAppID() {
        // TODO Auto-generated method stub
        return WXPayCommonPath.APP_ID;
    }

    @Override
    public String getMchID() {
        // TODO Auto-generated method stub
        return WXPayCommonPath.MCH_ID;
    }

    @Override
    public String getKey() {
        // TODO Auto-generated method stub
        return WXPayCommonPath.API_KEY;
    }

    @Override
    public InputStream getCertStream() {
        // TODO Auto-generated method stub
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    @Override
    public int getHttpConnectTimeoutMs() {
        // TODO Auto-generated method stub
        return 8000;
    }

    @Override
    public int getHttpReadTimeoutMs() {
        // TODO Auto-generated method stub
        return 10000;
    }
}

2:实现IWXPayDomain域名主备方法,该方法直接复制即可。无需修改。该方法与IWXPayDomain微信工具类一个包名下。

/**
 * 域名管理,实现主备域名自动切换
 * 该实现方法需要自己写一个。
 * @author think
 */
public class WXPayDomainSimpleImpl implements IWXPayDomain {
    
    private final int MIN_SWITCH_PRIMARY_MSEC = 3 * 60 * 1000;  //3 minutes
    private long switchToAlternateDomainTime = 0;
    private Map<String, DomainStatics> domainData = new HashMap<String, DomainStatics>();
    
    private WXPayDomainSimpleImpl(){}
    private static class WxpayDomainHolder{
        private static IWXPayDomain holder = new WXPayDomainSimpleImpl();
    }
    public static IWXPayDomain instance(){
        return WxpayDomainHolder.holder;
    }

    @Override
    public void report(String domain, long elapsedTimeMillis, Exception ex) {
        // TODO Auto-generated method stub
        DomainStatics info = domainData.get(domain);
        if(info == null){
            info = new DomainStatics(domain);
            domainData.put(domain, info);
        }

        if(ex == null){ //success
            if(info.succCount >= 2){    //continue succ, clear error count
                info.connectTimeoutCount = info.dnsErrorCount = info.otherErrorCount = 0;
            }else{
                ++info.succCount;
            }
        }else if(ex instanceof ConnectTimeoutException){
            info.succCount = info.dnsErrorCount = 0;
            ++info.connectTimeoutCount;
        }else if(ex instanceof UnknownHostException){
            info.succCount = 0;
            ++info.dnsErrorCount;
        }else{
            info.succCount = 0;
            ++info.otherErrorCount;
        }
    }

    @Override
    public DomainInfo getDomain(WXPayConfig config) {
        // TODO Auto-generated method stub
        DomainStatics primaryDomain = domainData.get(WXPayConstants.DOMAIN_API);
        if(primaryDomain == null ||
                primaryDomain.isGood()) {
            return new DomainInfo(WXPayConstants.DOMAIN_API, true);
        }

        long now = System.currentTimeMillis();
        if(switchToAlternateDomainTime == 0){   //first switch
            switchToAlternateDomainTime = now;
            return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
        }else if(now - switchToAlternateDomainTime < MIN_SWITCH_PRIMARY_MSEC){
            DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
            if(alternateDomain == null ||
                alternateDomain.isGood() ||
                alternateDomain.badCount() < primaryDomain.badCount()){
                return new DomainInfo(WXPayConstants.DOMAIN_API2, false);
            }else{
                return new DomainInfo(WXPayConstants.DOMAIN_API, true);
            }
        }else{  //force switch back
            switchToAlternateDomainTime = 0;
            primaryDomain.resetCount();
            DomainStatics alternateDomain = domainData.get(WXPayConstants.DOMAIN_API2);
            if(alternateDomain != null)
                alternateDomain.resetCount();
            return new DomainInfo(WXPayConstants.DOMAIN_API, true);
        }
    }

    static class DomainStatics {
        final String domain;
        int succCount = 0;
        int connectTimeoutCount = 0;
        int dnsErrorCount =0;
        int otherErrorCount = 0;

        DomainStatics(String domain) {
            this.domain = domain;
        }
        void resetCount(){
            succCount = connectTimeoutCount = dnsErrorCount = otherErrorCount = 0;
        }
        boolean isGood(){ return connectTimeoutCount <= 2 && dnsErrorCount <= 2; }
        int badCount(){
            return connectTimeoutCount + dnsErrorCount * 5 + otherErrorCount / 4;
        }
    }
}

3:新建WXPayCommonPath微信支付配置的常量类。自己创建

/**
 * 微信支付常量
 * @author think
 */
public class WXPayCommonPath {

    public static final String WXUNIFIEDORDER_HTTPURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";// 微信统一下单接口  
    public static final String APP_ID = "";// 微信appid  
    public static final String MCH_ID = "";// 商户号  
    public static final String API_KEY = "";// apikey应用私钥 api密钥  这个需要自己到商户平台去配置32位长度。
    public static final String NOTIFYURL = "";//回调方法
    public static final String TRADE_TYPE = "APP";//app支付
    public static final String SPBILL_CREATE_IP = "";//服务器ip地址
}

下面写微信支付调用方法:

1:新建WxPayController

:OperLog 该方法是aop日志,可以去掉。

/**
     * (微信支付) app请求预付支付订单
     * @param req
     * @param res
     * @return
     * @throws Exception
     */
    @OperLog(logDescription = "app请求预付支付订单(微信支付)")
    @RequestMapping(value = "getOrderInfo", method = { RequestMethod.POST, RequestMethod.GET })
    @ResponseBody
    public Map<String, Object> getOrderInfo(HttpServletRequest req, HttpServletResponse res) throws Exception {
        res.setCharacterEncoding("UTF-8");

        //该字段属于自定义字段,比如传入id用来service逻辑处理等,不要放入中文,中文会乱码.
        String attach = "";
        //创建订单编号  commonUtils是我自己的工具类,你可以自己写一个生成随机的订单编号
        String out_trade_no = CommonUtils.createNum();

        //创建预支付信息
        Map<String, String> resultMap = wxPayService.dounifiedOrder(attach,out_trade_no);
        
        Map<String, String> map = new HashMap<String, String>();
        map.put("appid", resultMap.get("appid"));
        map.put("partnerid", resultMap.get("mch_id"));
        map.put("prepayid", resultMap.get("prepay_id"));
        map.put("packagevalue", "Sign=WXPay");
        map.put("noncestr", resultMap.get("nonce_str"));
        map.put("timestamp", String.valueOf(WXPayUtil.getCurrentTimestamp()));// 单位为秒
        //这里使用生成带有sign的xml方法,如果使用签名,则前端app无法唤起微信支付界面
        String sign = WXPayUtil.generateSignedXml(map, WXPayCommonPath.API_KEY);
        map.put("sign",sign);
        map.put("extdata", "预留字段,可传入自定字段。");
        map.put("tradetype", resultMap.get("trade_type"));

        return JsonMethod.setJsonMethod(PathKeyEnum.SUCCESS.getKey(), PathKeyEnum.SUCCESS.getValue(), map);
    }

 

/**
     * 微信支付回调接口
     * 
     * @param request
     * @param response
     * @return
     */
    @OperLog(logDescription = "微信支付回调接口(微信支付)")
    @RequestMapping(value = "returnNotify", method = { RequestMethod.POST, RequestMethod.GET })
    @ResponseBody
    public String returnNotify(HttpServletRequest request, HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        
        String resXml = "";
        try {
            InputStream inputStream = request.getInputStream();
            //将InputStream转换成xmlString
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder sb = new StringBuilder();
            String line = null;
            try {
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n");
                }
            } catch (IOException e) {
                System.out.println(e.getMessage());
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            resXml = sb.toString();
            String result = wxPayService.returnXml(resXml);
            return result;
        } catch (Exception e) {
            System.out.println("微信手机支付失败:" + e.getMessage());
            String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
            return result;
        }
    }

 

2:创建 service  serviceImpl 方法  (spring mvc 框架)

WxPayService:

/**
 * 微信支付逻辑处理
 * @author think
 *
 */
public interface WxPayService {

    /**
     * 微信支付请求预付单
     * @return
     * @throws Exception
     */
    public Map<String, String> dounifiedOrder(String attach,String out_trade_no)throws Exception;
    
    /**
     * 接收微信回调的方法并解析xml数据
     * @param notifyData
     * @return
     */
    public String returnXml(String notifyData)throws Exception;
}

 

创建impl方法

/**
 * 微信支付逻辑实现
 * @author think
 */
@Service("WxPayService")
public class WxPayServiceImpl implements WxPayService {

    /**
     * 微信支付预付单生成
     */
    @Override
    public Map<String, String> dounifiedOrder(String attach,String out_trade_no) throws Exception {
        // TODO Auto-generated method stub
        Map<String, String> returnMap = new HashMap<String, String>();
        Map<String, String> param = new HashMap<String, String>();
        WxPayUtilConfig config = new WxPayUtilConfig();
        
        WXPay wxPay = new WXPay(config);
        param.put("appid", config.getAppID());
        param.put("attach", attach);
        //内容描述可通过前端app传递过来.
        param.put("body", "订单支付");
        param.put("mch_id", config.getMchID());
        param.put("nonce_str", WXPayUtil.generateNonceStr());
        param.put("notify_url", WXPayCommonPath.NOTIFYURL);
        param.put("out_trade_no", out_trade_no);
        //自己的服务器IP地址
        param.put("spbill_create_ip", WXPayCommonPath.SPBILL_CREATE_IP);
        param.put("total_fee", "1");//金额 以分为单位.我这边是测试数据.
        //交易类型
        param.put("trade_type", WXPayCommonPath.TRADE_TYPE);
        //该类型定义和不定义都无法更改微信回调的签名方式.所以我注释掉.写只是为了测试是否有用
        //param.put("sign_type","MD5");
        String sign = WXPayUtil.generateSignature(param,WXPayCommonPath.API_KEY);
        param.put("sign", sign);
        Map<String, String> response = wxPay.unifiedOrder(param);
        
        String returnCode = response.get("return_code");//获取返回码
        //若返回码为SUCCESS,则会返回一个result_code,再对该result_code进行判断
        if (returnCode.equals("SUCCESS")) {//主要返回以下5个参数
            String resultCode = response.get("result_code");
            returnMap.put("appid", response.get("appid"));
            returnMap.put("mch_id", response.get("mch_id"));
            returnMap.put("nonce_str", response.get("nonce_str"));
            returnMap.put("sign", response.get("sign"));
            if ("SUCCESS".equals(resultCode)) {
                //resultCode 为SUCCESS,才会返回prepay_id和trade_type
                //获取预支付交易回话标志
                returnMap.put("trade_type", response.get("trade_type"));
                returnMap.put("prepay_id", response.get("prepay_id"));
                return returnMap;
            } else {
                //此时返回没有预付订单的数据
                return returnMap;
            }
        }
        return returnMap;
    }

    /**
     * 解析回调后的xml数据
     */
    @Override
    public String returnXml(String notifyData) throws Exception{
        // TODO Auto-generated method stub
        String xmlBack = "";
        Map<String, String> notifyMap = null;
        try {
            notifyMap = WXPayUtil.xmlToMap(notifyData);//调用官方SDK转换成map类型数据
            /**
             * 微信回调返回的xml签名使用的是HMACSHA256
             * if判断的方法里需要加入HMACSHA256签名
             * 并且HMACSHA256该签名方法要导入sdk的包,别用工具类的包
             * 因为它底层判断的是用sdk的包里的枚举来判断的.
             */
            if (WXPayUtil.isSignatureValid(notifyMap, WXPayCommonPath.API_KEY,SignType.HMACSHA256)) {//验证签名是否有效,有效则进一步处理
                String return_code = notifyMap.get("return_code");//状态
                String out_trade_no = notifyMap.get("out_trade_no");//商户订单号
                if (return_code.equals("SUCCESS")) {
                    if (out_trade_no != null) {
                        //这里就需要去做你的逻辑判断,比如说订单的状态

                        System.err.println("支付成功");
                        xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                    } else {
                        LogComm.setLog("微信手机支付回调失败订单号:{}" + out_trade_no);
                        xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                    }
                }
                return xmlBack;
            } else {
                // 签名错误,如果数据里没有sign字段,也认为是签名错误
                //失败的数据要不要存储?
                LogComm.setLog("手机支付回调通知签名错误:");
                xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
                return xmlBack;
            }
        } catch (Exception e) {
            LogComm.setLog("手机支付回调通知失败:" + e.getMessage());
            xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        }
        return xmlBack;
    }
}

以上所有功能就是java微信支付服务端开发的功能

备注:

1:微信预付单需要签名,而返回给app的json里面的sign 调用工具类里面的 xml方法即可,不需要继续签名,否则app端无法唤起支付,上面有注释到。

2:微信回调已经给你处理过了签名使用的是HMACSHA256 签名方式。

在回调方法里如果用MD5签名去和微信回调的xml签名对比,永远都不会对,

所以回调之后需要用HMACSHA256 签名 并且与微信回调xml签名对比即可。

HMACSHA256:导入的是sdk包,别到工具类包,看工具类源码就可以读到。它底层判断sign就是用sdk的包来判断的。

好了。最后说一句。微信支付是真坑。开发花半天,回调 调试就浪费了半天,就因为签名对比不成功。坑。

注各位脱坑愉快。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值