基于java实现的微信支付(H5举例)

之前也一直对接微信,支付宝,银联,第三方支付公司的支付流程,但是记录的还是比较少的。今天重新梳理支付中心,简单的记录下心得

怎么开通商户号和公众号我这就不说了。

1、只是微信支付必须要公众号appID,所以先在商户号上关联一个服务号。

2、设置秘钥,这个秘钥是自己设置的,我这里直接就是32位随机数。

3、开通产品和设置域名

 

ok,到这里基本前期的准备工作差不多了。微信作为支付体系的一个大部分,肯定是需要封装到支付中心的。项目结构

然后再解释一个东西,微信证书,这个是在某些敏感接口需要进行证书验证的,具体申请流程比较简单,这里就不详细描述。

开发开始:

1、SDK准备

去官网下载SDK文件:https://pay.weixin.qq.com/wiki/doc/api/download/WxPayAPI_JAVA.zip

当然,说是SDK嘛,其实很多东西就是一个开放接口,还是需要自己具体去实现。所以这里新建2个实现类

对请求域名进行处理,实现容灾 

public class IWXPayDomainImpl implements IWXPayDomain {
    //3 minutes
    private final int MIN_SWITCH_PRIMARY_MSEC = 3 * 60 * 1000;
    private long switchToAlternateDomainTime = 0;
    private Map<String, DomainStatics> domainData = new HashMap();

    private IWXPayDomainImpl(){

    }
    private static class WxPayDomainHolder{
        private static IWXPayDomain holder = new IWXPayDomainImpl();
    }
    public static IWXPayDomain instance(){
        return WxPayDomainHolder.holder;
    }

    @Override
    public void report(String domain, long elapsedTimeMillis, Exception ex) {
        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) {
        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;
        }
    }

}

配置实现:

@Slf4j
public class WxPayConfigImpl extends WXPayConfig {

    private WxProperties wxProperties;

    private static WxPayConfigImpl INSTANCE;

    private byte[] certData;

    private WxPayConfigImpl() {

    }

    @Override
    public String getAppID() {
        return wxProperties.getAppId();
    }

    @Override
    public String getMchID() {
        return wxProperties.getMchId();
    }

    @Override
    public String getKey() {
        return wxProperties.getKey();
    }

    @Override
    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    @Override
    public IWXPayDomain getWXPayDomain() {
        return IWXPayDomainImpl.instance();
    }

    /**
     * 静态内部类保证现成安全
     */
    private static class SingleWxPayConfig{
        /**
         * 静态对象初始化,由JVM保证线程安全
         */
        private static WxPayConfigImpl instance = new WxPayConfigImpl();
    }

    /**
     * 单例调用
     * @return
     */
    public static WxPayConfigImpl getInstance(){
        return SingleWxPayConfig.instance;
    }


    public void setWxProperties(WxProperties wxProperties) {
        this.wxProperties = wxProperties;
    }

    public void setCertData(byte[] certData) {
        this.certData = certData;
    }
}

增加相关配置实现,进行部分功能初始化,包含证书信息。

@Configuration
@EnableConfigurationProperties(WxProperties.class)
@Slf4j
public class MyWxPayConfig {

    @Autowired
    private WxProperties wxProperties;

    @Bean
    public WXPay wxPay(){
        byte[] certData = null;
        try {
            File file = new File(wxProperties.getCertPath());
            InputStream certStream = new FileInputStream(file);
            certData = new byte[(int) file.length()];
            certStream.read(certData);
            certStream.close();
        } catch (Exception e) {
            e.printStackTrace();
            log.error("读取证书出错!错误原因:{}",e.getMessage());
        }
        WxPayConfigImpl wxPayConfig = WxPayConfigImpl.getInstance();
        wxPayConfig.setWxProperties(wxProperties);
        wxPayConfig.setCertData(certData);
        WXPay wxpay = null;
        try {
            wxpay = new WXPay(wxPayConfig);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("微信初始化出错");
        }
        return wxpay;
    }
}

配置类实现:


@Data
@ConfigurationProperties(prefix = "XX.pay.wx")
public class WxProperties {

    private String appId;

    private String mchId;

    private String key;

    private String certPath;
}

增加YML文件配置:

XX:
  pay:
    wx:
      appId: XX
      mchId: XX
      key: XX
      certPath: /app_server/pay/apiclient_cert.p12

到这里SDK相关准备工作完成,相关配置内容就位

第二步:业务实现

下单,这其中的H5OrderDto 是业务方具体的传递参数。用到微信支付的地方可能不止一个地方,抽离公共请求参数,让支付脱离业务:

public String h5Pay(H5OrderDto orderDto) {
        log.info("当前请求参数:{}", JSONObject.toJSONString(orderDto));
        Map<String,String> reqData = new HashMap<>();
        //公众号
        reqData.put("appid", wxPay.getConfig().getAppID());
        //商户号
        reqData.put("mch_id",  wxPay.getConfig().getMchID());
        //设备信息
        reqData.put("device_info", "WEB");
        //随机字符串
        reqData.put("nonce_str", PayUtil.getRandom());
        //签名类型
        reqData.put("sign_type", "MD5");
        //商品描述
        reqData.put("body", orderDto.getBody());
        //订单号
        reqData.put("out_trade_no", orderDto.getOrderId());
        //价格
        reqData.put("total_fee",String.valueOf(orderDto.getPrice()));
        //终端IP
        reqData.put("spbill_create_ip", orderDto.getIp());
        log.info("id地址"+reqData.get("spbill_create_ip"));
        //回调地址
        reqData.put("notify_url", orderDto.getCallBackUrl());
        //交易类型
        reqData.put("trade_type", "MWEB");//h5支付
        //商品编号
        reqData.put("product_id", orderDto.getProductId());//h5支付
        JSONObject sceneInfo = new JSONObject();
        JSONObject sceneItem = new JSONObject();
        sceneItem.put("type", "Wap");
        sceneItem.put("wap_url", orderDto.getPageUrl());
        sceneItem.put("wap_name", orderDto.getPageDesc());
        sceneInfo.put("h5_info", sceneItem);
        reqData.put("scene_info", sceneInfo.toString());//h5支付
        //回传参数
        reqData.put("attach", orderDto.getTelephone());
        Map<String, String> result = null;
        try {
            result = wxPay.unifiedOrder(reqData);
        }catch (Exception e){
            e.printStackTrace();
            log.error("下单出错!");
            throw new PayException("下单失败");
        }
        if(result!=null&&WXPayConstants.SUCCESS.equals(result.get("return_code"))){
            return result.get("mweb_url");
        }
        throw new PayException("下单失败");
    }

支付成功回调处理,未了摆脱业务验证,这里抽离其业务验证实现

@Override
    public String backDeal(String requestXML,String className) {
        log.info("请求回调参数====:{}",requestXML);
        if(requestXML==null){
            return getBackXml(WXPayConstants.FAIL, "缺失请求参数");
        }
        try {
            Map<String, String> doc = WXPayUtil.xmlToMap(requestXML);
            log.info("转化后的参数:{}",doc);
            String returnCode = doc.get("return_code"); // 获取返回状态
            String returnMsg = doc.get("return_msg"); // 获取返回信息
            if(!WXPayConstants.SUCCESS.equals(returnCode)){
                return getBackXml(WXPayConstants.FAIL, returnMsg);
            }
            String resultCode = doc.get("result_code"); // 获取业务结果
            String errCodeDes = doc.get("err_code_des"); // 获取业务结果
            if(!WXPayConstants.SUCCESS.equals(resultCode)){
                return getBackXml(WXPayConstants.FAIL, errCodeDes);
            }
            String appid = doc.get("appid"); // 公众账号ID
            log.info("appid:{}",appid);
            if(!wxPay.getConfig().getAppID().equals(appid)){
                return getBackXml(WXPayConstants.FAIL, "公众号不匹配");
            }
            String mch_id = doc.get("mch_id"); // 商户号
            if(!wxPay.getConfig().getMchID().equals(mch_id)){
                return getBackXml(WXPayConstants.FAIL, "商户号不匹配");
            }
            log.info("基础参数判断完毕,{}",wxPay.getConfig().getKey());
            if(!WXPayUtil.isSignatureValid(requestXML, wxPay.getConfig().getKey())&&!WXPayUtil.isSignatureValid(WXPayUtil.xmlToMap(requestXML), wxPay.getConfig().getKey(), WXPayConstants.SignType.HMACSHA256)){
                log.error("签名验证失败");
                return getBackXml(WXPayConstants.FAIL, "签名不匹配");
            }
            String out_trade_no = doc.get("out_trade_no"); // 商户订单号
            log.error("签名验证成功,订单号:{}",out_trade_no);
            String attachEle =doc.get("attach"); // 回传参数,回传参数在此处未用到,可以用来处理优惠相关业务
            String total_fee = doc.get("total_fee"); // 订单金额
            //进入业务流程判断,判断逻辑
            //1、查询订单是否存在
            //2、判断订单状态,如果是已支付,返回SUCCESS,不做处理
            //3、如果订单未支付,判断返回金额与订单金额是否匹配
            OrderService orderService = null;
            if(className.equals("zjcOrderService")){
                orderService = zjcOrderService;
            }
            if(orderService.dealOrder(out_trade_no,total_fee)){
                return  getBackXml(WXPayConstants.SUCCESS, "OK");
            }else{
                return getBackXml(WXPayConstants.FAIL, "跟业务逻辑冲突");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return getBackXml(WXPayConstants.FAIL, "未知错误");
    }
    /**
     * 获取返回的XML
     * @param result
     * @param message
     * @return
     */
    private String getBackXml(String result,String message){
        return "<xml><return_code><![CDATA["+result+"]]></return_code><return_msg><![CDATA["+message+"]]></return_msg></xml>";
    }

抽象父类接口:

public interface OrderService {
    /**
     * 处理订单
     * @param orderId
     * @param total
     * @return
     */
    Boolean  dealOrder(String orderId,String total);
}

具体业务判断调用:

@FeignClient("uc")
public interface ZjcOrderService extends OrderService {
    /**
     * 处理订单业务
     * @param orderId
     * @return
     */
    @GetMapping("/zjc/{orderId}/{total}")
    @Override
    Boolean dealOrder(@PathVariable("orderId") String orderId,@PathVariable("total") String total);
}

当然这里处理的还是比较粗超的,大家可以想想怎么处理更细腻和完美一些。我这里的具体支付成功业务比较简单

 @Override
    public Boolean dealOrder(String orderId, String total) {
        log.info("开始验证订单:{},金额:{}",orderId,total);
        ZjcOrder zjcOrder = zjcOrderMapper.getByOrderId(orderId);
        log.info("订单信息:{}",JSONObject.toJSONString(zjcOrder));
        if(zjcOrder==null){
            return false;
        }
        if(zjcOrder.getStatus()==1){
            return true;
        }
        if(!total.equals(String.valueOf(zjcOrder.getPrice()))){
            return false;
        }
        zjcOrderMapper.updateStatus(orderId);
        return true;
    }

到这里后端流程基本OK。

前端下单就比较简单,返回url即可。然后前端进行跳转即可

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值