其他分享
首页 > 其他分享> > 商城项目服务端实践SSM(十二)-------前台_支付接口(2)(当面付的扫码支付)

商城项目服务端实践SSM(十二)-------前台_支付接口(2)(当面付的扫码支付)

作者:互联网

当面付的文档

package com.mmall.common;

import com.google.common.collect.Sets;

import java.util.Set;

//设置公共量
public class Const {
    public static final String CURRENT_USER = "currentUser";
    public static final String EMAIL="email";
    public static final String USERNAME="username";
    public interface Role{
        int ROLE_CUSTOMER=0;//普通用户
        int ROLE_ADMIN=1;//管理员
    }

    public enum ProductStatusEnum{
        ON_SALE(1,"在线");
        private String value;
        private int code;
        ProductStatusEnum(int code,String value){
            this.code=code;
            this.value=value;
        }
        public String getValue(){
            return value;
        }
        public int getCode(){
            return code;
        }
    }
    public enum OrderStatusEnum{
        CANCELED(0,"已取消"),
        NO_PAY(10,"未支付"),
        PAID(20,"已付款"),
        SHIPPED(40,"已发货"),
        ORDER_SUCCESS(50,"订单完成"),
        ORDER_CLOSE(60,"订单关闭");

        private String value;
        private int code;
        OrderStatusEnum(int code,String value){
            this.code=code;
            this.value=value;

        }
        public String getValue(){
            return value;
        }
        public int getCode(){
            return code;
        }

        public static OrderStatusEnum codeOf(int code){
            for(OrderStatusEnum orderStatusEnum : values()){
                if(orderStatusEnum.getCode() == code){
                    return orderStatusEnum;
                }
            }
            throw new RuntimeException("没有找到对应的枚举");
        }
    }
        public interface AlipayCallback

    {
        String TRADE_STATUS_WAIT_BUYER_PAY = "WAIT_BUYER_PAY";
        String TRADE_STATUS_TRADE_SUCCESS = "TRADE_SUCCESS";

        String RESPONSE_SUCCESS = "success";
        String RESPONSE_FAILED = "failed";

    }

    public enum PayPlatformEnum{
        ALIPAY(1,"支付宝");

        PayPlatformEnum(int code,String value){
            this.code = code;
            this.value = value;
        }
        private String value;
        private int code;

        public String getValue() {
            return value;
        }

        public int getCode() {
            return code;
        }
    }

    public enum PaymentTypeEnum{
        ONLINE_PAY(1,"在线支付");

        PaymentTypeEnum(int code,String value){
            this.code = code;
            this.value = value;
        }
        private String value;
        private int code;

        public String getValue() {
            return value;
        }

        public int getCode() {
            return code;
        }

        public static PaymentTypeEnum codeOf(int code){
            for(PaymentTypeEnum paymentTypeEnum : values()){
                if(paymentTypeEnum.getCode() == code){
                    return paymentTypeEnum;
                }
            }
            throw new RuntimeException("没有找到对应的枚举");
        }

    }

}

 

一、支付(当面付的扫码支付)

1、当用户生成订单,发起支付的时候,商户将用户生成的订单数据和异步通知回调地址作为请求参数向支付宝发起预下单的请求。预下单成功后,支付宝返回一个支付二维码给商户,商户把这个支付二维码储存到FTP服务器上,然后在展示给用户支付。

2、用户扫码进行支付,支付宝会将该笔订单的变更信息,沿着商户调用预下单请求时所传入的回调地址主动回调给商户。

3、商户对支付宝回调的交易结果使用RSA2对支付宝公钥验证,确认该结果是支付宝返回的。除此之外还要验证支付宝返回的数据订单号是否为商户创建的,交易总金额是否正确等等。

4、如果验证通过,商户则向支付宝返回“success”,即为支付成功。(把订单的支付金额和支付状态写进订单表中,也要把支付的信息存入支付的数据库表中)

    //支付宝扫码当面付付款
    @RequestMapping("pay.do")
    @ResponseBody
    public ServerResponse pay(HttpSession session, Long orderNo, HttpServletRequest request){
        User user= (User) session.getAttribute(Const.CURRENT_USER);
        if (user == null){
            return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),ResponseCode.NEED_LOGIN.getDesc());
        }
        String path=request.getSession().getServletContext().getRealPath("upload");
        return iOrderService.pay(orderNo,user.getId(),path);
    }
 //日志
    private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
    //支付
    public ServerResponse pay(Long orderNo, Integer userId, String path) {

        Map<String, String> resultMap = Maps.newHashMap();
        //查询用户是否有该订单
        Order order = orderMapper.selectByUserIdAndOrderNo(userId, orderNo);
        if (order == null) {
            return ServerResponse.createByErrorMessage("用户没有该订单");
        }
        resultMap.put("orderNo", String.valueOf(order.getOrderNo()));

        // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
        // 需保证商户系统端不能重复,建议通过数据库sequence生成,
        String outTradeNo = order.getOrderNo().toString();

        // (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店当面付扫码消费”
        String subject = new StringBuilder().append("mmall在线商城扫码支付,订单号").append(outTradeNo).toString();

        // (必填) 订单总金额,单位为元,不能超过1亿元
        // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
        String totalAmount = order.getPayment().toString();

        // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
        // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
        String undiscountableAmount = "0";

        // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
        // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
        String sellerId = "";

        // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
        String body = new StringBuilder().append("订单").append(outTradeNo).append("购买商品共").append(totalAmount).append("元").toString();

        // 商户操作员编号,添加此参数可以为商户操作员做销售统计
        String operatorId = "test_operator_id";

        // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
        String storeId = "test_store_id";

        // 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
        ExtendParams extendParams = new ExtendParams();
        extendParams.setSysServiceProviderId("2088100200300400500");

        // 支付超时,定义为120分钟
        String timeoutExpress = "120m";

        // 支付宝里声明的商品明细列表,需填写购买商品详细信息,
        List<GoodsDetail> goodsDetailList = new ArrayList<GoodsDetail>();
        //OrderItem为自己商城里的订单明细表
        List<OrderItem> orderItemList = orderItemMapper.getByOderNoAndUserId(orderNo, userId);

        //foreach循环,每个商品的信息都添加到GoodsDetail中
        for (OrderItem orderItem : orderItemList) {
            // 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetail
            GoodsDetail goods = GoodsDetail.newInstance(orderItem.getProductId().toString(), orderItem.getProductName(), BigDecimalUtil.mul(orderItem.getCurrentUnitPrice().doubleValue(), new Double(100).doubleValue()).longValue(), orderItem.getQuantity());
            // 创建好一个商品后添加至商品明细列表
            goodsDetailList.add(goods);
        }

        // 创建扫码支付请求builder,设置请求参数
        AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
                .setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
                .setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
                .setOperatorId(operatorId).setStoreId(storeId).setExtendParams(extendParams)
                .setTimeoutExpress(timeoutExpress)
                .setNotifyUrl(PropertiesUtil.getProperty("alipay.callback.url"))//支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
                .setGoodsDetailList(goodsDetailList);

        /** 一定要在创建AlipayTradeService之前调用Configs.init()设置默认参数
         *  Configs会读取classpath下的zfbinfo.properties文件配置信息,如果找不到该文件则确认该文件是否在classpath目录
         */
        Configs.init("zfbinfo.properties");

        /** 使用Configs提供的默认参数
         *  AlipayTradeService可以使用单例或者为静态成员对象,不需要反复new
         */
        AlipayTradeService tradeService = new AlipayTradeServiceImpl.ClientBuilder().build();

        AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);

        switch (result.getTradeStatus()) {
            case SUCCESS:
                logger.info("支付宝预下单成功: )");

                AlipayTradePrecreateResponse response = result.getResponse();
                dumpResponse(response);

                // 支付宝预下单成功生成二维码,把二维码图片上传到FTP服务器上
                //声明上传的文件保存的文件夹
                File folder = new File(path);
                //如果文件夹不存在则要创建它
                if (!folder.exists()) {
                    //文件夹的权限为可写
                    folder.setWritable(true);
                    //创建文件夹
                    folder.mkdirs();
                }
                // 需要修改为运行机器上的路径
                //二维码路径
                String qrPath = String.format(path + "/qr-%s.png", response.getOutTradeNo());
                //生成的二维码文件名,用订单号命名,会自动替换到%s上,例如qr-11111111.png
                String qrFileName = String.format("qr-%s.png", response.getOutTradeNo());
                //获得二维码生成的图片
                ZxingUtils.getQRCodeImge(response.getQrCode(), 256, qrPath);

                //创建上传的文件
                File targetFile = new File(path, qrFileName);
                try {
                    //上传至FTP服务器
                    FTPUtil.uploadFile(Lists.<File>newArrayList(targetFile));
                } catch (IOException e) {
                    logger.error("上传二维码异常", e);
                }
                logger.info("qrPath:" + qrPath);
                //二维码的URL
                String qrUrl = PropertiesUtil.getProperty("ftp.server.http.prefix") + targetFile.getName();
                resultMap.put("quUrl", qrUrl);
                return ServerResponse.createBySuccess(resultMap);


            case FAILED:
                logger.error("支付宝预下单失败!!!");
                return ServerResponse.createByErrorMessage("支付宝预下单失败!!!");

            case UNKNOWN:
                logger.error("系统异常,预下单状态未知!!!");
                return ServerResponse.createByErrorMessage("系统异常,预下单状态未知!!!");

            default:
                logger.error("不支持的交易状态,交易返回异常!!!");
                return ServerResponse.createByErrorMessage("不支持的交易状态,交易返回异常!!!");

        }

    }

    // 简单打印应答
    private void dumpResponse(AlipayResponse response) {
        if (response != null) {
            logger.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
            if (StringUtils.isNotEmpty(response.getSubCode())) {
                logger.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
                        response.getSubMsg()));
            }
            logger.info("body:" + response.getBody());
        }
    }

二、支付宝回调验证

//支付宝回调函数
    //使用sdk验签方法 https://openclub.alipay.com/club/history/read/2214
    @RequestMapping("alipay_callback.do")
    @ResponseBody
    public Object alipayCallback(HttpServletRequest request){
        Map<String,String> params= Maps.newHashMap();
        //记录着支付宝里的参数和参数的值
        Map requestParams=request.getParameterMap();
        //传来的参数的value取出来
        for(Iterator iter=requestParams.keySet().iterator();iter.hasNext();){
            String name=(String)iter.next();
            String[] values=(String[]) requestParams.get(name);
            String valueStr="";
            for(int i=0;i<values.length;i++){
                valueStr=(i == values.length-1)?valueStr+values[i]:valueStr+values[i]+",";
            }

            params.put(name,valueStr);
        }
        logger.info("支付宝回调,sign:{},trade_statux;{},参数:{}",params.get("sign"),params.get("trade_status"),params.toString());

        //验证回调的正确性,是不是支付宝发的
        //在通知返回参数列表中,除去sign、sign_type两个参数外,凡是通知返回回来的参数皆是待验签的参数。
        //由于支付宝中已经自动除去sign,所以我们还得自己除去sign_type
        params.remove("sign_type");
        try {
            //调用SDK验证签名,验证支付宝公钥
            boolean alipayRSACheckedV2= AlipaySignature.rsaCheckV2(params, Configs.getAlipayPublicKey(),"utf-8",Configs.getSignType());
            //验证不通过
            if(!alipayRSACheckedV2){
                return ServerResponse.createByErrorMessage("非法请求,验证不通过");
            }

        } catch (AlipayApiException e) {
            logger.error("支付宝验证回调异常",e);
        }
	/* 实际验证过程建议商户务必添加以下校验:
	1、需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
	2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
	3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
	4、验证app_id是否为该商户本身。
	*/
        //验证各种数据
        ServerResponse serverResponse=iOrderService.aliCallback(params);

        //验证成功,则给支付宝返回success
        if (serverResponse.isSuccess()){
           return Const.AlipayCallback.RESPONSE_SUCCESS;
       }
        return Const.AlipayCallback.RESPONSE_FAILED;
    }
//此链接可以查看支付宝异步通知参数https://docs.open.alipay.com/194/103296#s5
    public ServerResponse aliCallback(Map<String, String> params) {
        //订单号
        Long orderNo = Long.parseLong(params.get("out_trade_no"));
        //支付宝交易号
        String tradeNo = params.get("trade_no");
        //交易状态
        String tradeStatus = params.get("trade_status");
        //查询是否有该订单
        Order order = orderMapper.selectByOrderNo(orderNo);
        if (order == null) {
            return ServerResponse.createByErrorMessage("非该商场的订单");
        }
        //如果有该订单,并且该订单已付款
        if (order.getStatus() >= Const.OrderStatusEnum.PAID.getCode()) {
            return ServerResponse.createBySuccess("订单重复使用");
        }
        //在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
        //如果交易状态tradeStatus的值等于TRADE_SUCCESS
        if (Const.AlipayCallback.TRADE_STATUS_TRADE_SUCCESS.equals(tradeStatus)) {
            //把该订单信息添加至订单表中
            order.setPaymentTime(DateTimeUtil.strToDate(params.get("gmt_payment")));
            order.setStatus(Const.OrderStatusEnum.PAID.getCode());
            orderMapper.updateByPrimaryKeySelective(order);
        }
        //在支付信息表中设置该订单的支付信息
        PayInfo payInfo = new PayInfo();
        payInfo.setUserId(order.getUserId());
        payInfo.setOrderNo(order.getOrderNo());
        payInfo.setPayPlatform(Const.PayPlatformEnum.ALIPAY.getCode());
        payInfo.setPlatformNumber(tradeNo);
        payInfo.setPlatformStatus(tradeStatus);

        payInfoMapper.insert(payInfo);
        return ServerResponse.createBySuccess();
    }

三、查询订单支付状态

        //付款后,跳到订单页面,查询订单支付状态
        @RequestMapping("query_order_pay_status.do")
        @ResponseBody
        public ServerResponse<Boolean> queryOrderPayStatus(HttpSession session, Long orderNo){
            User user= (User) session.getAttribute(Const.CURRENT_USER);
            if (user == null){
                return ServerResponse.createByErrorCodeMessage(ResponseCode.NEED_LOGIN.getCode(),ResponseCode.NEED_LOGIN.getDesc());
            }
                    ServerResponse serverResponse=iOrderService.queryOrderPayStatus(user.getId(),orderNo);
            if (serverResponse.isSuccess()){
                return ServerResponse.createBySuccess(true);
            }
            return ServerResponse.createBySuccess(false);
        }
//付款后,跳到订单,查询订单支付状态
    public ServerResponse queryOrderPayStatus(Integer userId, Long orderNo) {
        Order order = orderMapper.selectByUserIdAndOrderNo(userId, orderNo);
        if (order == null) {
            return ServerResponse.createByErrorMessage("非该商场的订单");
        }
        if (order.getStatus() >= Const.OrderStatusEnum.PAID.getCode()) {
            return ServerResponse.createBySuccess();
        }
        return ServerResponse.createByError();
    }

 

标签:支付宝,扫码,code,return,String,SSM,ServerResponse,支付,public
来源: https://blog.csdn.net/qq_29642549/article/details/98509416