Java支付宝app wap支付接入

1.沙箱准备

1.1 去支付宝开放平台找到沙箱测试,appid以及秘钥都是需要的,密钥生成工具支付宝有可以去下载 密钥生成工具.
在这里插入图片描述

2.依赖引入

 <!--支付宝支付-->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>3.4.49.ALL</version>
        </dependency>

3.yml属性配置

### 支付宝支付
alipay:
  config:
    #支付宝预请求接口
    url: https://openapi.alipaydev.com/gateway.do
    #商家appid
    appId: 2016121231231
    #商家私钥
    privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDAOnh1VIED11yqUKVTmvmITyybMDcSwAG0nN3NM/zkUSvUvO3BooqYxta8CFzlZNv3xv4VLJJfM5uuPMJTZS1xEf7vWhuAfzrTOR6Qg4aQr/hGZq1ROGJW/fqm6vIxZ4TVFpTb9UMtyW8bQsItm1/LyVEGYPVIqx5qM8UPZPkMpUg0/IpI8LP0Lb0JQ9eBCY3p4BCYRkwp5Te5xCIjoc4bqH/c+BD0lz2f1oOLrsq3HQJUyFI/MDUAW1WSmsUeKmQUZsUlEwVupNQ5SM1CAsXA1Ca4T+cdH8j5rUIAeaakMqVFy7hTzIqRliFCbS6xYlgVq5yXgWUuGKNqL++YN/bxAgMBAAECggEAGUlXwS9tjUDOOsVDCj1vp8OqfXuZMwk48DZWYwD8ulgHKQP1TqyACNrXMoLzHWXoHa76i3SCiK10R+KGIyQVAsyWkDRvZ6VfXUD3CRfHJEf5iiCMiUOv8TkCW+PQKBgD/DeGXr9C36jO4H82ohVm6mFiMExPLp+f2kxfIkhrcHUwmgQc2KnISXoNZn1Hg5Nv0LpXtNBKen6dbt+rCtI4BBm5ETg+1QMlHFaAVgiqVtyQn1qG8wIN8JS6V7Mapr01N+Q/5XHZXNRtvsHo0/DEyInRbL2HgHzaC5ifBTVM+TpzV8nEue4f2yHE8hkaEWgNCaJpJVC52k7LeEJp+tzPsnCVL655dvKie6qtyBsB+6XpG+E7QfidAoGAUuoekdJdI6AIBJKC4miks2tpCd7JF2cZfw+UkNIy0sbWpclXjx5Qqnc0EBR/gNYyGZC0OYivbL8dL1ZG4w7OS9r8XSdQtGLd58Yhr1Zn3bD6YXTPBRDApa29d5tr8hyRXnXogQ+DQVRaoQd8oIstea0rvJWXbPEN/+U3yODWoe0=
    #支付宝公钥
    pubKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkVl/RJ5vsQg9/UkRFDNgRFmEDnLIkpy/LzMv12jF3aBp9esAR809iGiqpogyMuCgs143cZIitBOiAYn70E3swptQi0uG2+IqhLcPqBpsVGf6xXJ6G69GAxHx/rhHFEwIDAQAB
    charset: UTF-8
    #签名类型
    signType: RSA2
    #异步回调内部接口
    notifyUrl: https://www.baidu.com/
    #同步回调
    returnUrl: https://www.baidu.com/
    format: json
       

回调接口都是要外网能访问的域名接口,不能是ip直接访问,这个可以在测试的时候用穿透工具ngrok做穿透来实现;

4.配置类


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 *@author liuxingying
 *@description 支付宝配置文件
 *@date 2020/9/24
 */
@Data
@Component
@ConfigurationProperties(prefix = "alipay.config")
public class AlipayAppConfig {
    private String url;
    private String appId;
    private String privateKey;
    private String pubKey;
    private String charset;
    private  String signType;
    // 异步回调地址
    private String notifyUrl;
    private String returnUrl;
    // 返回格式
    private String format;

}

5.接口实现


import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.xx.core.exception.BusinessException;
import com.xx.ebc.config.AlipayAppConfig;
import com.xx.ebc.service.AlipayService;
import com.xx.ebc.utils.FormatUtils;
import com.xx.sequence.id.Sid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;


/**
 *@author liuxingying
 *@description 支付宝app支付实现类
 *@date 2020/9/24
 */
@Service
@Slf4j
public class AliPayServiceImpl implements AlipayService {

    @Resource
    private AlipayAppConfig alipayAppConfig;
    @Resource
    private Sid sid;

    private AlipayClient alipayClient;

    /**
     * 执行所有方法前都进行init初始化 读取配置文件的信息封装进对象
     * @params []
     * @return void
    */
    @PostConstruct
    public void init() {
        alipayClient = new DefaultAlipayClient(alipayAppConfig.getUrl(),
                alipayAppConfig.getAppId(),
                alipayAppConfig.getPrivateKey(),
                alipayAppConfig.getFormat(),
                alipayAppConfig.getCharset(),
                alipayAppConfig.getPubKey(),
                alipayAppConfig.getSignType());
    }

    /** @description 请求支付宝接口
     * @params [orderNo, totalFee]
     * @return java.lang.String
     * @author lxy
     * @date 2020/9/25 14:22
     **/
    @Override
    public String tradeAppPay(String orderNo, Integer totalFee) {
        log.info("支付宝app支付");
        AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
        //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)
        //根据不同的需要new不同的对象 app支付的对象是AlipayTradeAppPayModel 对应更改就可以 这里是wap网页支付
        AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
        model.setBody("支付宝付款");
        model.setSubject("开通VIP会员");
        model.setOutTradeNo(orderNo);
        model.setTotalAmount(FormatUtils.fenToYuan(totalFee));

        request.setBizModel(model);
        //设置异步回调内部服务的接口 处理业务
        request.setNotifyUrl(alipayAppConfig.getNotifyUrl());
        //同步回调跳转到需要的前端页面及接口
        request.setReturnUrl(alipayAppConfig.getReturnUrl());
        //对应的根据不同的支付修改对象 wap app
        AlipayTradeWapPayResponse response;
        try {
            //app支付的接口是sdkExecute()
            //response = this.alipayClient.sdkExecute(request);
            //wap支付的接口是pageExecute()
            response = this.alipayClient.pageExecute(request);
        } catch (AlipayApiException e) {
            log.error("支付宝支付失败{}", e.getErrMsg());
            throw new BusinessException(e.getErrMsg());
        }
        if (response.isSuccess()) {
            log.info("调用成功" + response.getBody());
            return response.getBody();
        } else {
            log.error("调用失败" + response.getBody());
            return null;
        }
    }

    /** @description 支付宝退款
     * @params [orderNo, refundFee]
     * @return java.lang.Boolean
     * @author lxy
     * @date 2020/9/25 14:28
     **/
    @Override
    public Boolean tradeRefund(String orderNo, Integer refundFee) {
        log.info("支付宝退款 {}");
        String outRequestNo = orderNo + sid.next();
        AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
        AlipayTradeRefundModel model = new AlipayTradeRefundModel();
        model.setOutTradeNo(orderNo);
        model.setRefundAmount(FormatUtils.fenToYuan(refundFee));
        model.setRefundReason("正常退款");
        model.setOutRequestNo(outRequestNo);
        refundRequest.setBizModel(model);
        AlipayTradeRefundResponse response;
        try {
            response = alipayClient.execute(refundRequest);
        } catch (AlipayApiException e) {
            log.error("支付宝退款失败{}", e.getLocalizedMessage());
            return false;
        }
        if (response != null && "10000".equals(response.getCode())) {
            log.info("调用成功" + response);
            return true;
        } else {
            log.error("调用失败" + response);
            return false;
        }
    }



}
  • 关于钱的转换工具类如果有需要也可以加入使用

import java.math.BigDecimal;

/**
 *@author liuxingying
 *@description
 *@date 2020/9/24
 */
public class FormatUtils {
    /**
     * 1. 钱的元到分
     * 2. 佣金比例的百分之到万分之
     *
     * @param yuan
     * @return
     */
    public static int yuan2Fen(BigDecimal yuan) {
        if (yuan == null){
            return 0;
        }
        return yuan.multiply(BigDecimal.valueOf(100)).intValue();
    }

    public static Integer yuanToFen(String yuan) {
        return (new BigDecimal(yuan)).setScale(2, 4).multiply(new BigDecimal(100)).intValue();
    }

    public static int yuan2Fen(Double yuan) {
        if (yuan == null) {
            return 0;
        }
        BigDecimal bigDecimal = new BigDecimal(String.valueOf(yuan));
        return yuan2Fen(bigDecimal);
    }

    public static int yuan2Fen(int yuan) {
        if (yuan <= 0) {
            return 0;
        }
        return yuan * 100;
    }

    /**
     * 1. 钱的元到分
     * 2. 佣金比例的万分之到百分之
     *
     * @param fen
     * @return
     */
    public static BigDecimal fen2Yuan(Integer fen) {
        if (fen == null) {
            return BigDecimal.valueOf(0);
        }
        return BigDecimal.valueOf(fen).divide(BigDecimal.valueOf(100)).setScale(2, BigDecimal.ROUND_FLOOR);
    }

    public static Integer fen2YuanInt(Integer fen) {
        if (fen == null) {
            return 0;
        }
        return fen / 100;
    }

    public static String fenToYuan(Integer fen) {
        BigDecimal bigDecimal = fen2Yuan(fen);
        bigDecimal = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP);
        return bigDecimal.toString();
    }

    /**
     * 返回 (金额 * 百分比)(分)
     *
     * @param amount 金额 分
     * @param rate   百分比
     * @return
     */
    public static Integer amountToRate(Integer amount, Integer rate) {
        if ((amount == null) || (rate == null)) {
            return null;
        }
        BigDecimal multiply = BigDecimal.valueOf(amount).multiply(BigDecimal.valueOf(rate).divide(BigDecimal.valueOf(100)));
        return multiply.intValue();
    }

    /**
     * 返回 (金额 * 万分比)(分)
     *
     * @param amount 金额 分
     * @param rate   万分比
     * @return
     */
    public static Integer amountToRateV2(Integer amount, Integer rate) {
        if ((amount == null) || (rate == null)) {
            return null;
        }
        BigDecimal multiply = BigDecimal.valueOf(amount).multiply(BigDecimal.valueOf(rate).divide(BigDecimal.valueOf(10000)));
        return multiply.intValue();
    }

}

  • 在回调接口处理业务的时候需要用到返回的参数,用对象接

import lombok.Data;

import java.math.BigDecimal;
import java.util.Date;

/**
 *@author liuxingying
 *@description 支付宝参数回调对象
 *@date 2020/9/24
 */
@Data
public class AlipayNotifyParam {
    private String appId;
    private String tradeNo; // 支付宝交易凭证号
    private String outTradeNo; // 原支付请求的商户订单号
    private String outBizNo; // 商户业务ID,主要是退款通知中返回退款申请的流水号
    private String buyerId; // 买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字
    private String buyerLogonId; // 买家支付宝账号
    private String sellerId; // 卖家支付宝用户号
    private String sellerEmail; // 卖家支付宝账号
    private String tradeStatus; // 交易目前所处的状态,见交易状态说明
    private BigDecimal totalAmount; // 本次交易支付的订单金额
    private BigDecimal receiptAmount; // 商家在交易中实际收到的款项
    private BigDecimal buyerPayAmount; // 用户在交易中支付的金额
    private BigDecimal refundFee; // 退款通知中,返回总退款金额,单位为元,支持两位小数
    private String subject; // 商品的标题/交易标题/订单标题/订单关键字等
    private String body; // 该订单的备注、描述、明细等。对应请求时的body参数,原样通知回来
    private Date gmtCreate; // 该笔交易创建的时间。格式为yyyy-MM-dd HH:mm:ss
    private Date gmtPayment; // 该笔交易的买家付款时间。格式为yyyy-MM-dd HH:mm:ss
    private Date gmtRefund; // 该笔交易的退款时间。格式为yyyy-MM-dd HH:mm:ss.S
    private Date gmtClose; // 该笔交易结束时间。格式为yyyy-MM-dd HH:mm:ss
    private String fundBillList; // 支付成功的各个渠道金额信息,array
    private String passbackParams; // 公共回传参数,如果请求时传递了该参数,则返回给商户时会在异步通知时将该参数原样返回。
}

  • 回调的接口总处理业务需要的一些解析返回参数以及验证签名等,选择使用方法即可

import cn.hutool.core.thread.ThreadUtil;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.x.core.domain.Result;
import com.x.ebc.config.AlipayAppConfig;
import com.x.ebc.pojo.dto.PayMealDTO;
import com.x.ebc.pojo.vo.AlipayNotifyParam;
import com.x.ebc.service.AlipayService;
import com.x.ebc.service.WxPayService;
import com.x.ebc.utils.FormatUtils;
import com.x.sequence.id.Sid;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 *@author liuxingying
 *@description 微信支付/支付宝支付 前端控制器
 *@date 2020/9/24
 */
@Slf4j
@RestController
public class PayController {
    //private ExecutorService executorService = Executors.newFixedThreadPool(20);

    @Resource
    private AlipayAppConfig alipayAppConfig;
    @Resource
    private AlipayService alipayService;
    @Resource
    private WxPayService wxPayService;
    @Resource
    private Sid sid;


    @GetMapping("/aliPay")
    public String callback() {
        String orderId = sid.next();
        String s = alipayService.tradeAppPay(orderId, 1000);
        return s;
    }

    /** @description 支付宝回调接口
     * @params [request]
     * @return java.lang.String
     * @author lxy
     * @date 2020/9/24 18:45
     **/
    @RequestMapping(value = "/aliPayCallback", method = RequestMethod.POST)
    public String callback(HttpServletRequest request) {
        // 将异步通知中收到的待验证所有参数都存放到map中
        Map<String, String> params = convertRequestParamsToMap(request);
        String paramsJson = JSON.toJSONString(params);
        log.info("支付宝回调,{}", paramsJson);
        try {
            // 调用SDK验证签名
            boolean signVerified = AlipaySignature.rsaCheckV1(params, alipayAppConfig.getPubKey(),
                    alipayAppConfig.getCharset(), alipayAppConfig.getSignType());
            log.info("signVerified={}", signVerified);
            if (signVerified) {
                log.info("支付宝回调签名认证成功");
                // 按照支付结果异步通知中的描述,对支付结果中的业务内容进行1\2\3\4二次校验,校验成功后在response中返回success,校验失败返回failure
                check(params);
                // 另起线程处理业务
                log.info("另起线程处理业务");
                ThreadUtil.execAsync(()->{
                    AlipayNotifyParam param = buildAlipayNotifyParam(params);
                    String tradeStatus = param.getTradeStatus();
                    // 支付成功
                    if (tradeStatus.equals("TRADE_SUCCESS")) {
                        log.info("支付成功");
                        //TODO 用户处理自己逻辑
                    } else {
                        log.info("支付失败");
                        //TODO 用户处理自己逻辑
                    }
                });

                // 如果签名验证正确,立即返回success,后续业务另起线程单独处理
                return "success";
            } else {
                log.info("支付宝回调签名认证失败, paramsJson:{}", paramsJson);
                return "failure";
            }
        } catch (AlipayApiException e) {
            log.info("支付宝回调发生异常{}", e.getLocalizedMessage());
            return "failure";
        }
    }

    /** @description 将request中的参数转换成Map
     * @params [request]
     * @return java.util.Map<java.lang.String,java.lang.String>
     * @author lxy
     * @date 2020/9/25 10:14
     **/
    private Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
        Map<String, String> retMap = new HashMap<>(16);
        //1.从支付宝回调的request域中取值
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
            String name = iter.next();
            String[] values = requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
            }
            // 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
            // valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");
            retMap.put(name, valueStr);
        }
        return retMap;
    }

    private AlipayNotifyParam buildAlipayNotifyParam(Map<String, String> params) {
        String json = JSON.toJSONString(params);
        return JSON.parseObject(json, AlipayNotifyParam.class);
    }

    private void check(Map<String, String> params) throws AlipayApiException {
        String outTradeNo = params.get("out_trade_no");

        // 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
        OrderInfo order = orderService.selectOrderByOrderNo(outTradeNo);
        if (order == null) {
            throw new AlipayApiException("out_trade_no错误");
        }

        // 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
        Integer totalAmount = FormatUtils.yuan2Fen(new BigDecimal(params.get("total_amount")));
        log.info("totalAmount{}", totalAmount);
        if (!totalAmount.equals(order.getSnapshotTotalFee())) {
            throw new AlipayApiException("error total_amount");
        }

        // 3、验证app_id是否为该商户本身。
        if (!params.get("app_id").equals(alipayAppConfig.getAppId())) {
            throw new AlipayApiException("app_id不一致");
        }
    }
 
}

总结

  • 支付宝支付的开放平台中,有一个沙箱模式可以供我们测试使用,相比较微信来说要方便很多,而且java代码集成的复杂度以及上手容易度来说也更比微信来的快的多
  • 总得来说这些代码可以直接使用 然后修改商户信息后直接可以引用实现
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值