java支付模块之支付宝

最近在公司做了关于网上连接支付的功能模块,包含了微信支付和支付宝支付,写下该笔记总结遇到的问题和解决方法,并记录下代码传到GitHub.com,并写到CSDN中,以后遇到类似的需求可以套代码。采用的框架是SpringBoot+Mybatis,此篇主要记录了支付宝支付(包含H5,App)。

以下为实际业务处理代码,以后有不同业务还需要修改参数

控制层

/**
 * @author 
 * @version 创建时间:2018年7月15日 下午5:16:45
 */
@RestController
@RequestMapping("/alipay")
public class ALiPayController {

    private static Logger LOG = Logger.getLogger(ALiPayController.class);
    @Autowired
    private ALiPayService aLiPayService;
    @Autowired
    //订单核心业务
    private ShortRentOrderService ShortRentOrderService;
    @Autowired
    private PayService payService;

    /**
     * createOrder app统一下单
     * @param billNo String 订单编号
     * @return
     */
    @RequestMapping("/app/createOrder")
    public @ResponseBody Result createAppOrder(String tradeNo, HttpServletResponse response) {
        // 获取订单详情
        ShortRentOrderDto dto = ShortRentOrderService.getShortRentOrderDetail(tradeNo);
        // 生成带时间戳的订单号,否则无法重复调起支付。
        String newbillNo = payService.createNewOrderNo(tradeNo);
        // 保存请求参数,回调时根据带时间戳的订单号获取到相应的真实订单号
        payService.savePayRequest(newbillNo, dto, PayTypeEnums.ALIPAY.getIndex(), SourceTypeEnums.ANDROID.getIndex());
        // 向支付宝发送请求
        Result result = aLiPayService.createAppOrder(newbillNo, dto.getModelName(), dto.getModelName(),
                dto.getOrderTotalPrice());
        if (result.getCode() == Constants.FAILURE_CODE) {
            return result;
        }
        return new Result(Constants.SUCCESS_CODE, "", result.getData());
    }

    /**
     * createOrder h5统一下单
     * @param billNo String 订单编号
     * @return
     */
    @RequestMapping("/h5/createOrder")
    public @ResponseBody Result createH5Order(String tradeNo, HttpServletResponse response) {
        // 获取订单详情
        ShortRentOrderDto dto = ShortRentOrderService.getShortRentOrderDetail(tradeNo);
        // 生成带时间戳的订单号,否则无法重复调起支付
        String newbillNo = payService.createNewOrderNo(tradeNo);
        // 保存请求参数
        payService.savePayRequest(newbillNo, dto, PayTypeEnums.ALIPAY.getIndex(), SourceTypeEnums.H5.getIndex());
        // 向支付宝发送请求
        Result result = aLiPayService.createH5Order(newbillNo, dto.getModelName(), dto.getModelName(),
                dto.getOrderTotalPrice());
        if (result.getCode() == Constants.FAILURE_CODE) {
            return result;
        }
        return new Result(Constants.SUCCESS_CODE, "", result.getData());
    }

    @RequestMapping("/orderQuery")
    public Result orderQuery(String erpOrderId, String aliOrderId) {
        // 向支付宝发送查询请求
        return aLiPayService.orderQuery(erpOrderId, aliOrderId);
    }

    @RequestMapping(value = "/notify-url", method = { RequestMethod.POST })
    public @ResponseBody String notifyUrl(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> params = HttpUtils.getRequestParamMap(request);
        // 获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表(以下仅供参考)
        return aLiPayService.payNotify(params);
    }
}

业务层

/**
 * @author 
 * @version 创建时间:2018年7月17日 上午11:02:21
 */
@Service
public class ALiPayServiceImpl implements ALiPayService {
    private static Logger LOG = Logger.getLogger(ALiPayServiceImpl.class);
    // 实例化客户端
    private AlipayClient alipayClient = null;
    @Autowired
    private ALiPayProperties aliProperties;
    @Autowired
    private PayService payService;

    @Override
    public Result createAppOrder(String billNo, String subject, String body, BigDecimal totalAmount) {
        // 实例化客户端
        initClient();

        // 实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        //
        // SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setBody(body);
        model.setSubject(subject);
        model.setOutTradeNo(billNo);
        model.setTimeoutExpress(aliProperties.getOverTime());
        model.setTotalAmount(String.valueOf(totalAmount.setScale(2, RoundingMode.HALF_EVEN)));
        model.setProductCode("QUICK_MSECURITY_PAY");
        request.setBizModel(model);
        request.setNotifyUrl(aliProperties.getNotifyUrl());
        try {
            // 这里和普通的接口调用不同,使用的是sdkExecute
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
            return new Result(Constants.SUCCESS_CODE, "", response.getBody());
            // 就是orderString 可以直接给客户端请求,无需再做处理。
        } catch (AlipayApiException e) {
            LOG.info(e.toString());
            return new Result(Constants.FAILURE_CODE, ErrorMsg.UNKNOWN_EXCEPTION);
        }
    }

    @Override
    public Result orderQuery(String erpOrderId, String aliOrderId) {
         initClient();
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();// 创建API对应的request类
        request.setBizContent(
                "{" + "\"out_trade_no\":\"" + payService.getNewOrderNoByOrderNo(erpOrderId) + "\"" + "  }");// 设置业务参数
        try {
            // 通过alipayClient调用API,获得对应的response类
            AlipayTradeQueryResponse response = alipayClient.execute(request);
            if (response.isSuccess()) {
                return new Result(Constants.SUCCESS_CODE, "",
                        AliPayOrderStatus.findNameByIndex(response.getTradeStatus()));
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return new Result(Constants.FAILURE_CODE, ErrorMsg.UNKNOWN_EXCEPTION);
    }

    @Override
    public Result createH5Order(String billNo, String subject, String body, BigDecimal totalAmount) {
        initClient();
        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();// 创建API对应的request
        alipayRequest.setReturnUrl("http://returnUrl.com/");
        alipayRequest.setNotifyUrl(aliProperties.getNotifyUrl());// 在公共参数中设置回跳和通知地址
        alipayRequest.setBizContent("{" + "    \"out_trade_no\":\"" + billNo + "\"," + "    \"total_amount\":"
                + totalAmount.setScale(2, RoundingMode.HALF_EVEN) + "," + "    \"subject\":\"" + subject + "\","
                + "    \"seller_id\":\"" + aliProperties.getPartner() + "\"," + "    \"product_code\":\"QUICK_WAP_WAY\""
                + "  }");// 填充业务参数
        try {
            // 调用SDK生成表单
            AlipayTradeWapPayResponse response = alipayClient.pageExecute(alipayRequest);
            String form = response.getBody();
            return new Result(Constants.SUCCESS_CODE, "", form);
        } catch (AlipayApiException e) {
            LOG.info(e.toString());
            return new Result(Constants.FAILURE_CODE, ErrorMsg.UNKNOWN_EXCEPTION);
        }

    }

    private void initClient() {
        if (alipayClient == null) {
            alipayClient = new DefaultAlipayClient(aliProperties.getaLiUrl(), aliProperties.getAppId(),
                    aliProperties.getPrivateKey(), "json", "utf-8", aliProperties.getAliPublicKey(),
                    aliProperties.getEncryptionMethod());
        }
    }

    @Override
    public String payNotify(Map<String, String> params) {
        boolean isValidate = validatePayNotify(params);
        if (!isValidate) {
            return aliProperties.FAIL_CODE;
        }
        // 商户订单号
        String out_trade_no = params.get("out_trade_no");
        // 异步通知ID
        BigDecimal fee = new BigDecimal(params.get("total_amount"));
        try {
            // 验证支付结果状态
            if (params.get("trade_status").equals("TRADE_FINISHED")
                    || params.get("trade_status").equals("TRADE_SUCCESS")) {

                /* 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
                 * 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
                 * 3、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方(
                 * 有的时候,一个商户可能有多个seller_id/seller_email), 4、验证app_id是否为该商户本身。 */
                // 实际业务处理
                boolean isUpdate = payService.handlePayMsg(out_trade_no, fee, params.get("trade_no"),
                        DateUtils.stringToDate(params.get("gmt_payment")), 7, 0);
                if (isUpdate) {
                    return aliProperties.SUCCESS_CODE;
                }
            }
        } catch (Exception e) {
            LOG.info("支付宝回调发生错误:" + e.toString());
        }
        return aliProperties.FAIL_CODE;
    }

    private boolean validatePayNotify(Map<String, String> params) {
        // 使用支付宝公钥验签
        try {
            boolean isSignTrue = AlipaySignature.rsaCheckV1(params, aliProperties.getAliPublicKey(), "UTF-8", "RSA2");
            if (!isSignTrue) {
                return false;
            }
        } catch (AlipayApiException e1) {
            LOG.info("支付宝公钥验签发生错误:" + e1.toString());
            e1.printStackTrace();
            return false;
        }

        String notify_id = params.get("notify_id");
        // 验证
        if (StringUtils.isEmpty(notify_id)) {
            return false;
        }
        // 判断成功之后使用getResponse方法判断是否是支付宝发来的异步通知。
        if (!verifyResponse(notify_id).equals("true")) {
            // 验证是否来自支付宝的通知失败
            return false;
        }
        if (!params.get("app_id").equals(aliProperties.getAppId())) {
            return false;
        }
        return true;
    }

    /**
     * 获取远程服务器ATN结果,验证返回URL
     * @param notify_id 通知校验ID
     * @return 服务器ATN结果 验证结果集: invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 true
     *         返回正确信息 false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     */
    public String verifyResponse(String notify_id) {
        // 获取远程服务器ATN结果,验证是否是支付宝服务器发来的请求

        String partner = aliProperties.getPartner();
        String veryfy_url = aliProperties.HTTPS_VERIFY_URL + "partner=" + partner + "&notify_id=" + notify_id;

        return checkUrl(veryfy_url);
    }

    /**
     * 获取远程服务器ATN结果
     * @param urlvalue 指定URL路径地址
     * @return 服务器ATN结果 验证结果集: invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 true
     *         返回正确信息 false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟
     */
    public static String checkUrl(String urlvalue) {
        String inputLine = "";

        try {
            URL url = new URL(urlvalue);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
            inputLine = in.readLine().toString();
        } catch (Exception e) {
            LOG.info("检查通知时发生错误:" + e.toString());
            inputLine = "";
        }

        return inputLine;
    }
}

配置类

/**
 * @author 
 * @version 创建时间:2017年7月19日 下午3:49:56
 */
@Configuration
@PropertySource("classpath:pay.properties")
public class ALiPayProperties {
    /**
     * 合作身份者ID,签约账号,以2088开头由16位纯数字组成的字符串,查看地址:https://b.alipay.com/order/
     * pidAndKey.htm
     */
    @Value("${ALiPay.partner}")
    private String partner;
    // 回调地址
    @Value("${ALiPay.notifyUrl}")
    private String notifyUrl;
    // 支付宝公钥
    @Value("${ALiPay.publicKey}")
    private String publicKey;
    // 支付宝私钥
    @Value("${ALiPay.privateKey}")
    private String privateKey;
    @Value("${AliPay.aliPublicKey}")
    private String aliPublicKey;
    // 支付宝appId
    @Value("${ALiPay.appId}")
    private String appId;
    // 加密方式
    private String encryptionMethod = "RSA2";
    // 超时时间
    private String overTime = "30m";
    // 支付宝网关
    private String aLiUrl = "https://openapi.alipay.com/gateway.do";
    // private String aLiUrl = " https://openapi.alipaydev.com/gateway.do";

    /**
     * 支付宝消息验证地址
     */
    public final String HTTPS_VERIFY_URL = "https://mapi.alipay.com/gateway.do?service=notify_verify&";

    /**
     * 失败返回
     */
    public final String FAIL_CODE = "fail";

    /**
     * 成功返回
     */
    public final String SUCCESS_CODE = "success";

    /**
     * @return the notifyUrl
     */
    public String getNotifyUrl() {
        return notifyUrl;
    }

    public String getAliPublicKey() {
        return aliPublicKey;
    }

    public void setAliPublicKey(String aliPublicKey) {
        this.aliPublicKey = aliPublicKey;
    }

    /**
     * @param notifyUrl the notifyUrl to set
     */
    public void setNotifyUrl(String notifyUrl) {
        this.notifyUrl = notifyUrl;
    }

    /**
     * @return the publicKey
     */
    public String getPublicKey() {
        return publicKey;
    }

    /**
     * @param publicKey the publicKey to set
     */
    public void setPublicKey(String publicKey) {
        this.publicKey = publicKey;
    }

    public String getPartner() {
        return partner;
    }

    public void setPartner(String partner) {
        this.partner = partner;
    }

    /**
     * @return the privateKey
     */
    public String getPrivateKey() {
        return privateKey;
    }

    /**
     * @param privateKey the privateKey to set
     */
    public void setPrivateKey(String privateKey) {
        this.privateKey = privateKey;
    }

    /**
     * @return the appId
     */
    public String getAppId() {
        return appId;
    }

    /**
     * @param appId the appId to set
     */
    public void setAppId(String appId) {
        this.appId = appId;
    }

    /**
     * @return the encryptionMethod
     */
    public String getEncryptionMethod() {
        return encryptionMethod;
    }

    /**
     * @param encryptionMethod the encryptionMethod to set
     */
    public void setEncryptionMethod(String encryptionMethod) {
        this.encryptionMethod = encryptionMethod;
    }

    /**
     * @return the overTime
     */
    public String getOverTime() {
        return overTime;
    }

    /**
     * @param overTime the overTime to set
     */
    public void setOverTime(String overTime) {
        this.overTime = overTime;
    }

    /**
     * @return the aLiUrl
     */
    public String getaLiUrl() {
        return aLiUrl;
    }

    /**
     * @param aLiUrl the aLiUrl to set
     */
    public void setaLiUrl(String aLiUrl) {
        this.aLiUrl = aLiUrl;
    }

}

控制层的很重要的逻辑是调起支付时(统一·下单) 要生成带时间戳的外部订单编号,作用是 
1.为了解决调起支付时订单参数在请求时都是不变的,在重复调起下单时支付宝服务端会返回订单已存在的问题。 
2.保存带时间戳请求在服务器中,回调时可以根据这个请求来获取相应的订单编号。 
业务层的代码没有什么好说的 都是官方demo.详情参考 
https://docs.open.alipay.com/api_1/alipay.trade.query 
配置是配置在pay.properties中

遇到的一些坑: 
1.下单时订单金额的问题。 
订单金额小数点后必须限制为两位,否则会出问题。 
2.订单查询的问题 
因为之前在订单查询时在 “out_order_no”: (这里)”外部订单编号”这里中加了个空格,导致一直都是40004的错误,联系客服帮忙查之后才找到问题。


补充一下一些枚举类

public enum AliPayOrderStatus {
    WAIT_BUYER_PAY("WAIT_BUYER_PAY", "未支付", "交易创建,等待买家付款"),
    TRADE_CLOSED("TRADE_CLOSED", "已关闭", "未付款交易超时关闭,或支付完成后全额退款"),
    TRADE_SUCCESS("TRADE_SUCCESS", "支付成功", "交易支付成功"),
    TRADE_FINISHED("TRADE_FINISHED", "交易结束", "交易结束,不可退款");
    private String index;
    private String name;
    private String remark;

    public static String findNameByIndex(String index) {
        for (AliPayOrderStatus status : AliPayOrderStatus.values()) {
            if (status.getIndex().equals(index)) {
                return status.getName();
            }
        }
        return null;
    }

    private AliPayOrderStatus(String index, String name, String remark) {
        this.index = index;
        this.name = name;
        this.remark = remark;
    }

    public String getIndex() {
        return index;
    }

    public void setIndex(String index) {
        this.index = index;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

}
public enum PayTypeEnums {
    WXAPP(1, "微信"), ALIPAY(2, "支付宝"), WEBPAY(3, "网银");

    public static String getNameByIndex(int index) {
        for (PayTypeEnums PayType : PayTypeEnums.values()) {
            if (PayType.getIndex() == index) {
                return PayType.getName();
            }
        }
        return null;
    }

    private PayTypeEnums(int index, String name) {
        this.index = index;
        this.name = name;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private int index;
    private String name;

}

payService里的业务需要根据具体实际的业务业务去写实现,这里只给出接口

public interface PayService {


    /**
     * 处理支付信息
     * @param billNo erp订单号
     * @param fee 总费用
     * @param outBillNo 外部订单号
     * @param payTime 支付时间
     * @param payType 支付类型 8微信 9支付宝
     * @param devType 设备类型 0 小程序 1App 2H5
     * @return
     */
    public boolean handlePayMsg(String billNo, BigDecimal fee, String outBillNo, Date payTime, int payType,
            int devType);

    /**
     * 根据支付单号查找订单编号
     * @param orderNo 支付单号
     * @return
     */
    public String getOrderNoByNewOrderNo(String orderNo);

    /**
     * 根据带时间戳支付单号查找系统的订单编号
     * @param orderNo 支付单号
     * @return 订单编号
     */
    public String getNewOrderNoByOrderNo(String orderNo);

    /**
     * 根据订单号生成带时间戳的支付单号
     * @param orderNo 订单号
     * @return 订单号+时间戳
     */
    public String createNewOrderNo(String orderNo);

    /**
     * 保存发起支付请求
     * @param newOrderNo 支付编号编号
     * @param order 订单
     * @param type 支付方式
     * @param source 端号
     * @return
     */
    public boolean savePayRequest(String newOrderNo, Order order, int type, int source);

    /**
     * 支付后更新支付请求信息(保存流水号)
     * @param newOrderNo 新订单编号
     * @param amount 支付金额
     * @param transactionCode 支付单号
     * @return
     */
   public boolean updatePayRequest(String newOrderNo, BigDecimal amount, String transactionCode);

}
  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值