1

场景:
针对PC网站支付场景

一、支付宝开放平台设置

1、登录支付宝开放平台

2、进入控制台,在【我的应用】选择【网页&移动应用】,然后点击【创建应用】

image.png

3、填写好响应的应用信息,点击【确认创建】

image.png

4、创建好之后,在控制台进入应用,添加"电脑网站支付能力"

说明:添加这个能力,会需要进行填写签约信息,按着步骤走就行,不过填写的网站必须和企业支付宝的营业执照一致
image.png

5、点击左侧【应用信息】,进行设置开发信息,只设置了接口加签

image.png

5.1、设置接口加签方式,有公钥证书和公钥两个选择,一般选择公钥

5.2、然后需要生成密钥,才能生成支付宝公钥,生成密钥也有三种方式:

第一种:下载密钥生成工具
1、下载密钥生成工具:https://opendocs.alipay.com/o...
windows版本电脑下载windows版本工具。
mac版本电脑下载mac_osx版本工具。
image.png

2、运行安装密钥生成工具
点击下载的“AlipayDevelopmentAssistant-1.0.1.exe”(即支付宝开放平台开放助手)进行安装并运行。
说明:本工具只会记录上传点击事件操作行为,不会记录上传用户的任何用户信息以及公私钥等敏感信息。
image.png

3、生成密钥
选择密钥格式和密钥长度,点击“生成密钥”进行密钥生成 。
(1)密钥长度
RSA2:密钥长度为2048位。
注:开放平台从2018年1月5日开始创建的应用都没有RSA密钥的设置入口,只能上传RSA2格式密钥

RSA:密钥长度为1024位。
国密:目前暂不支持国密的加签方式,即使获取后也无法设置加签。

(2)密钥格式
PKCS8:JAVA开发语言适用。
PKCS1:非JAVA开发语言适用。
image.png
4、配置密钥
开放平台(open.alipay.com)选择要上传的应用,点击应用进入详细页,选择应用信息内的“接口加签方式”进行设置。

如应用未上线,需要选择概览里面的接口加签方式进行设置。
(1)应用私钥:开放平台没有上传设置的位置,需要自己进行保存并设置到代码中,且由于其涉及资金安全不能将其提供给他人,若不小心丢失或泄露,请及时进行更新修改。
(2)应用公钥:需要将其传入开放平台应用中(每次更换密钥时都要将其重新上传开放平台),如图:
image.png
5、接口中以及支付宝开放平台配置密钥
生成密钥后切记成对妥善保管,避免测试时由于公私钥不匹配导致签名验签等一系列不必要的错误产生。

(1)正式环境中配置密钥
开发技术人员在接口中配置正式环境配置密钥,需选择要上传的应用,点击应用进入详细页,选择应用信息内的“接口加签方式”进行设置。
image.png

(2)沙箱环境中配置密钥
沙箱环境配置密钥,需选择在沙箱应用中,选择应用信息内的“RSA2(SHA256)密钥(推荐)”进行设置。

沙箱环境应用配置点击沙箱应用了解。
image.png

第二种:web在线生成密钥
1、生成密钥
1.1、打开Web 在线加密(无下载,新上线)->选择“生成密钥”->选择“保存密钥”。
1.2、选择密钥格式和密钥长度,点击“生成密钥”进行密钥生成 。
(1)密钥长度
RSA2:密钥长度为2048位。
注:开放平台从2018年1月5日开始创建的应用都没有RSA密钥的设置入口,只能上传RSA2格式密钥
RSA:密钥长度为1024位。
(2)密钥格式
PKCS8:JAVA开发语言适用。
PKCS1:非JAVA开发语言适用。
2、配置密钥
配置密钥流程与“密钥生成工具生成密钥”的配置密钥流程相同。
将应用公钥上传到开放平台应用的接口加签中,应用私钥请放在接口代码私钥位置。
image.png

第三种:使用OpenSSL生成
除了使用支付宝提供的一键生成工具外,也可以使用OpenSSL工具命令生成密钥。

使用OpenSSL工具命令生成,命令语句如下:
1、生成RSA2私钥(2048位)命令: genrsa -out app_private_key.pem 2048 ;

2、把私钥转换成PKCS8格式并输出新文件命令: pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out app_private_key_pkcs8.pem;

3、生成公钥命令: rsa -in app_private_key.pem -pubout -out app_public_key.pem;
之后把生成的公钥上传给支付宝,并获取支付宝公钥(ALIPAY_PUBLIC_KEY)。

详细见使用OpenSSL工具生成密钥在线文档

注:生成密钥后切记成对妥善保管,若不小心丢失或泄露,请及时进行生成生成并重新上传到支付宝开放平台,避免出现损失。

6、配置好开发信息,然后再编辑应用信息,发布上线,即可开始开发正式环境

image.png

二、Springboot代码编写

1、首先添加maven依赖

 <!--支付宝支付依赖-->
        <!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>4.10.209.ALL</version>
        </dependency>

2、创建支付宝支付工具类

/**
 * 支付宝支付工具类
 */
public class ALiPayUtils {
    /*======沙箱环境=======*/
    //沙箱网关
//    private static final String GATEWAY_URL = "https://openapi.alipaydev.com/gateway.do";
    //沙箱APPID
//    private static final String APP_ID = "换成自己沙箱APPID";
    //自己应用私钥
//    private static final String PRIVATE_KEY = "生成的应用私钥";
    //支付宝公钥(非应用公钥)
//    private static final String PUBLIC_KEY = "生成的支付宝公钥";
    //沙箱支付完成跳转页面
    private static final String RETURN_URL = PropertiesValues.getPropertiesValue("pay.service.return_url", "application.properties");
    //沙箱支付完成回调接口
    private static final String NOTIFY_URL = PropertiesValues.getPropertiesValue("pay.service.notify_url", "application.properties");

    /*========基础数据===========*/
    //网关
    private static final String GATEWAY_URL = "https://openapi.alipay.com/gateway.do";
    //APPID
    public static final String APP_ID = "换成自己正式APPID";
    //自己应用私钥
    private static final String PRIVATE_KEY = "生成的应用私钥";
    //支付宝公钥(非应用公钥)
    private static final String PUBLIC_KEY = "生成的支付宝公钥";
    //返回数据格式
    private static final String FORMAT = "json";
    //编码类型
    private static final String CHART_TYPE = "utf-8";
    //签名类型
    private static final String SIGN_TYPE = "RSA2";

    /*支付销售产品码,目前支付宝只支持FAST_INSTANT_TRADE_PAY*/
    public static final String PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY";

    private static AlipayClient alipayClient = null;

    /*=====商户对应数据=====*/
    //商户PID
    public static final String PID = "换成自己商户PID";

    /*========相应接口=========*/
    //支付完成跳转页面
//    private static final String RETURN_URL = "";
    //支付完成回调接口
//    private static final String NOTIFY_URL = "";

    public ALiPayUtils(){
        if(alipayClient == null){
            alipayClient = new DefaultAlipayClient(
                    GATEWAY_URL,
                    APP_ID,
                    PRIVATE_KEY,
                    FORMAT,
                    CHART_TYPE,
                    PUBLIC_KEY,
                    SIGN_TYPE);
        }
    };

    /*================PC网页支付====================*/
    /**
     * 统一下单并调用支付页面接口
     * @param outTradeNo
     * @param totalAmount
     * @param subject
     * @return
     */
    public Map<String, Object> placeOrderAndPayForPCWeb(String outTradeNo, float totalAmount, String subject){
        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
        request.setNotifyUrl(RETURN_URL);
        request.setReturnUrl(NOTIFY_URL);
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", outTradeNo);
        bizContent.put("total_amount", totalAmount);
        bizContent.put("subject", subject);
        bizContent.put("product_code", PRODUCT_CODE);
        //bizContent.put("time_expire", "2022-08-01 22:00:00");

        //// 商品明细信息,按需传入
        //JSONArray goodsDetail = new JSONArray();
        //JSONObject goods1 = new JSONObject();
        //goods1.put("goods_id", "goodsNo1");
        //goods1.put("goods_name", "子商品1");
        //goods1.put("quantity", 1);
        //goods1.put("price", 0.01);
        //goodsDetail.add(goods1);
        //bizContent.put("goods_detail", goodsDetail);

        //// 扩展信息,按需传入
        //JSONObject extendParams = new JSONObject();
        //extendParams.put("sys_service_provider_id", "2088511833207846");
        //bizContent.put("extend_params", extendParams);

        request.setBizContent(bizContent.toString());
        AlipayTradePagePayResponse response = null;
        try {
            response = alipayClient.pageExecute(request);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("isSuccess", response.isSuccess());
        if(response.isSuccess()){
            System.out.println("调用成功");
            System.out.println(JSON.toJSONString(response));
            resultMap.put("body", response.getBody());
        } else {
            System.out.println("调用失败");
            System.out.println(response.getSubMsg());
            resultMap.put("subMsg", response.getSubMsg());
        }
        return resultMap;
    }

    /**
     * 交易订单查询
     * @param out_trade_no
     * @return
     */
    public Map<String, Object> tradeQueryForPCWeb(String out_trade_no){
        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
        JSONObject bizContent = new JSONObject();
//        bizContent.put("out_trade_no", out_trade_no);
        bizContent.put("trade_no", out_trade_no);
        request.setBizContent(bizContent.toString());
        AlipayTradeQueryResponse response = null;
        try {
            response = alipayClient.execute(request);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("isSuccess", response.isSuccess());
        if(response.isSuccess()){
            System.out.println("调用成功");
            System.out.println(JSON.toJSONString(response));
            resultMap.put("status", response.getTradeStatus());
        } else {
            System.out.println("调用失败");
            System.out.println(response.getSubMsg());
            resultMap.put("subMsg", response.getSubMsg());
        }
        return resultMap;
    }

    /**
     * 验证签名是否正确
     * @param sign
     * @param content
     * @return
     */
    public static boolean CheckSignIn(String sign, String content){
        try {
            return AlipaySignature.rsaCheck(content, sign, PUBLIC_KEY, CHART_TYPE, SIGN_TYPE);
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 将异步通知的参数转化为Map
     * @return
     */
    public static Map<String, String> paramstoMap(HttpServletRequest request) throws UnsupportedEncodingException {
        Map<String, String> params = new HashMap<String, String>();
        Map<String, String[]> requestParams = request.getParameterMap();
        for (Iterator<String> 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] + ",";
            }
            // 乱码解决,这段代码在出现乱码时使用。
//            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }
        return params;
    }
}

3、编写下单接口以及回调处理接口

@RestController
@CrossOrigin
@RequestMapping("lcAliPay")
public class LcALiPayController {

    @Autowired
    LcAliPayService lcAliPayService;

    @Autowired
    LcGoodsOrderService lcGoodsOrderService;

    /*==========PC网页支付============*/
    /*===支付宝===*/

    /**
     * 统一下单并调用支付页面,PC
     * @param lcGoodsOrder
     * @return
     */
    @PostMapping("/tradeOrderForPCWeb")
    @NoVerify
    public ResultMap tradeOrderForPCWeb(@RequestBody LcGoodsOrder lcGoodsOrder){
        return lcAliPayService.tradeOrderForPCWeb(lcGoodsOrder);
    }

    /**
     * 支付完成回调接口,PC
     * @return
     */
    @RequestMapping("/notifyForPCWeb")
    @NoVerify
    public String notifyForPCWeb(HttpServletRequest request){
        try {
            Map<String, String> map = ALiPayUtils.paramstoMap(request);
            String tradeNo = map.get("trade_no");
//            Map<String, Object> payData = aLiPayUtils.tradeQueryForPCWeb(tradeNo);
            String sign = map.get("sign");
            String content = AlipaySignature.getSignCheckContentV1(map);
            boolean signVerified = ALiPayUtils.CheckSignIn(sign, content);
            //验证业务数据是否一致
            if(!lcAliPayService.checkData(map)){
                System.out.println("返回业务数据验证失败,订单:" + tradeNo );
                return "fail";
            }
            //签名验证成功
            if(signVerified){
                System.out.println("支付宝签名验证成功,订单:" + tradeNo);
                //验证支付状态
                String tradeStatus = request.getParameter("trade_status");
                if(tradeStatus.equals("TRADE_SUCCESS")){
                    System.out.println("支付成功,订单:"+tradeNo);
                    LcGoodsOrder lcGoodsOrder = new LcGoodsOrder();
                    lcGoodsOrder.setTradeNo(map.get("trade_no"));
                    lcGoodsOrder.setSellerId(map.get("seller_id"));
                    lcGoodsOrder.setOrderStatus("2");//支付成功
                    lcGoodsOrderService.updateById(lcGoodsOrder);
                    return "success";
                }else{
                    System.out.println("支付失败,订单:" + tradeNo );
                    return "fail";
                }
            }else{
                System.out.println("签名验证失败,订单:" + tradeNo );
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "fail";
    }
}

4、service层代码

@Service
public class LcAliPayServiceImpl implements LcAliPayService {

    @Resource
    LcGoodsOrderMapper lcGoodsOrderMapper;
    //日志对象
    private static Logger logger = LoggerFactory.getLogger(LcAliPayServiceImpl.class);

    @Override
    public ResultMap tradeOrderForPCWeb(LcGoodsOrder lcGoodsOrder) {
        logger.info("【请求开始-在线购买-交易创建】*********统一下单开始*********");
        ALiPayUtils aLiPayUtils = new ALiPayUtils();
        String tradeNo = ALiPayUtils.generateOrderNum();
        Map<String, Object> map = aLiPayUtils.placeOrderAndPayForPCWeb(tradeNo, Float.parseFloat(lcGoodsOrder.getTotalAmount()), lcGoodsOrder.getSubject());
        ResultMap resultMap = new ResultMap();
        if(Boolean.parseBoolean(String.valueOf(map.get("isSuccess")))){
            logger.info("【请求开始-在线购买-交易创建】统一下单成功,开始保存订单数据");
            resultMap.setCode("200");
            resultMap.setData(map.get("body"));
            String lcGoodsOrderId = UUID.getRandom();
            lcGoodsOrder.setOrderId(lcGoodsOrderId);
            lcGoodsOrder.setOrderCreateTime(DateTime.getNowDateStamp());
            lcGoodsOrder.setOutTradeNo(tradeNo);
            lcGoodsOrder.setOrderStatus("1");//订单状态
            lcGoodsOrder.setProductCode(ALiPayUtils.PRODUCT_CODE);
            //保存订单信息
            int count = lcGoodsOrderMapper.insert(lcGoodsOrder);
            if(count > 0){
                logger.info("【成功-在线购买-交易创建】保存订单数据成功,添加条数:{}", count);
                resultMap.setCode(ResponseCodeUtis.SERVER_TRUE_200);
                resultMap.setMsg(ResponseCodeUtis.MSG_TRUE_InsertById);
                resultMap.setData(map.get("body"));
            }else{
                logger.error("【失败-在线购买-交易创建】保存订单数据失败");
                resultMap.setCode(ResponseCodeUtis.SERVER_ERROR_500);
                resultMap.setMsg("添加数据失败");
            }
            logger.info("【请求成功-在线购买-交易创建】*********统一下单结束*********");
            return resultMap;
        }else{
            resultMap.setCode("501");
            resultMap.setMsg(String.valueOf(map.get("subMsg")));
            logger.info("【失败:请求失败-在线购买-交易创建】*********统一下单结束*********");
        }
        return resultMap;
    }

    @Override
    public boolean checkData(Map<String, String> map) {
        logger.info("【请求开始-交易回调-订单确认】*********校验订单确认开始*********");
        LcGoodsOrder lcGoodsOrder2 = lcGoodsOrderMapper.selectByOutTradeNo(map.get("out_trade_no"));
        //验证订单号是否准确,并且订单状态为待支付
        if(lcGoodsOrder2 != null && lcGoodsOrder2.getOrderStatus().equals("1")){
            float amount1 = Float.parseFloat(map.get("total_amount"));
            float amount2 = Float.parseFloat(lcGoodsOrder2.getTotalAmount());
            //判断金额是否相等
            if(amount1 == amount2){
                //验证收款商户id是否一致
                if(map.get("seller_id").equals(ALiPayUtils.PID)){
                    //判断appid是否一致
                    if(map.get("app_id").equals(ALiPayUtils.APP_ID)){
                        logger.info("【成功:请求开始-交易回调-订单确认】*********校验订单确认成功*********");
                        return true;
                    }
                }
            }
        }
        logger.info("【失败:请求开始-交易回调-订单确认】*********校验订单确认失败*********");
        return false;
    }
}

5、网站页面支付下单请求代码

/**
 * 调用支付宝支付接口
 * @param {Object} data
 */
function aliPay(data){
    var url = "服务器接口";
    $.ajax({
        url: url,
        type: 'post',
        data: JSON.stringify({
            payUserName: data.payUserName,
            payUserPhone: data.payUserPhone,
            payMessage: data.payMessage,
            goodsId: data.goodsId,
            totalAmount: data.totalAmount,
            subject: data.subject,
        }),
        contentType: "application/json",
        dataType: 'json',
        success: function(res){
            if(res.code == "200"){
                
                const div=document.createElement('divform');
                div.innerHTML=res.data;
                document.body.appendChild(div);
                document.forms[0].setAttribute('target', '_blank') 
                document.forms[0].submit();
            }else{
                alert("网络异常");
                alert(res.msg);
            }
        },
        fail: function(res){
            console.log("失败")
            console.log(res);
        }
      })
}

6、下单接口回调,处理返回表单处理问题

if(res.code == "200"){
    const div=document.createElement('divform');
    div.innerHTML=res.data;
    document.body.appendChild(div);
    document.forms[0].setAttribute('target', '_blank') 
    document.forms[0].submit();
}else{
    alert("网络异常");
    alert(res.msg);
}

7、请求成功图

image.png

8、支付完成回调

这里跳转回去的页面就是之前AlipayUtils类中的return_url 在这之前需要进入notify_url 接口进行验签.
image.png


稳之楠
130 声望25 粉丝

行之稳,为之楠!