Java服务端支付功能模块--(一)支付宝支付

来源:互联网 发布:红警3 for mac版 编辑:程序博客网 时间:2024/05/16 04:45

这阵子在公司做了关于网上支付的功能模块,包含了微信支付和支付宝支付,特此写下该笔记总结遇到的问题和解决方法。
编程语言为JAVA,采用的框架是SpringBoot+Mybatis,此篇主要介绍支付宝支付(包含H5,App)。

先上代码

控制层

/** * @author Huang * @version 创建时间:2017年6月2日 下午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 Huang * @version 创建时间:2017年6月5日 上午11:02:21 */@Servicepublic 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 Huang * @version 创建时间:2017年6月5日 下午1: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);}