环境介绍

沙箱环境

在我们的学习,考试中主要使用这个环境的信息,包含appId,支付网关地址,签名等信息。 沙箱环境

文档中心

https://opendocs.alipay.com/open/028r8t?ref=api&scene=22

调试中心

调试中心包含各api的调用例子,使我们开发学习的主要参考: https://open.alipay.com/dev/workspace/apidebug

代码示例

jsp示例,这里包含有文档中心及调试中心都不存在的支付回调验签代码。 支付回调验签

管理平台

正式的开发需要创建应用,并使用它的appId等信息,但是现在不是必须使用它。 创建应用

内网穿透工具

支付宝支付成功后有个回调地址,因为支付宝只能访问外网地址,所以我们或者自己部署到云服务器,或者使用内网穿透工具提供外网地址。 如下是两个(类)常用的穿透工具。

花生壳

需要去线上认证下:

https://hsk.oray.com/

ngrok

版本众多,可用的不多。

开发

依赖

首先需要引进依赖的jar包。

<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.33.50.ALL</version>
</dependency>

配置

使用从沙箱环境获得的信息配置AliClient。

@Configuration
public class AlipayConfiguration {

    @Bean
    public static AlipayClient alipayClient() {
        AlipayConfig config = new AlipayConfig();
        //支付宝测试环境服务器
        config.setServerUrl("https://openapi.alipaydev.com/gateway.do");
        //支付宝应用id,在沙箱环境内获取
        config.setAppId("2016092100560986");
        //引用私钥,建议使用《支付宝开放平台开发助手》生成,RSA2
        config.setPrivateKey("MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCLq2XQiqUVyxyO+vT3VIXOhIGWvL1SIs9p28kurzgXzV8JR0X2h+TrSjfyjwYRQH9aV5FFy9NIciA1ce0ZPfxniDt4Bs0pHqLJfkRaUhkotVWn8F6kqqoypy1NfLseDx8GsGXnWVST0wV7xpeOlQM6cflVwYTxx5topyZc5u6NbLssdb/DX1ABjsMsinJQpbgutJFYwy+iWIM7MTSOds4FwZpWdowZ824zfqCMyWibiP1JmguXVcl14eWyOv6LDvDe0niXiF2qMDrTUReVVb+XwEsGb1/cdJT9wU0JDreRH1SSwE6Z+sQdQClfqB/WQJZniveugGf4bXnnZRQfo44fAgMBAAECggEAH7dCHYL+Td6bk5RFQEy/PdA1JSeizh39f4pbOvCrCiymohK/PmZJg2yNG9WCiTReNwOfh3vrdI4F5l0CfDLpOBFlf7H7sJS2Xo7/sormD6pt0v7wXvAqSepQjUH/s6m3X+t6mHhejlri6eKE0+nem8z505FoQQcUsUUTnxEJpq9s1f8SxVF0+6v2va2UqyCbPwKIR0bQLLF7K8a2jl2YBaHRntRi7BCqt4z+IQmHkI6M1zDOysSVbeVX3XlAc54nseorSHAY3ywmjkFSPmBc5MDzNSFgSG2IdHzlTU7R2F4wvExbFGPh64UODzlwcyZFYaTVwUdxhS8JxyXsnHTsQQKBgQDjJs/CMee4AWTN9GNj3XfVW29xjta7XGFJh5BaQRuABpI10nfNyadfwHC7PxjauGqHX2TTpc6991m+kaHOdX5nUyjMOAkhIrmJT1xgPHEjr15Enzp5f8L9NgpMdOJlUne3Uf9cA8pww/lQlEHHcdvoofEYSHoAxMT7wwSut8VdcQKBgQCdaFrfmHtN4AOovtauy3AbdfhhkoyCzN6g/sTDyI4CbLIbMIfqlXTG/RaJYCLVevnmSGCryflDQT69JicSNOVseRZEHli3Qr3G1sS2zU495ZlQGFe6DZomCkw3RxJcX5z25rvvcW3ECDRGpEG17SvMxXWwpD2+0WQL87wEnuwcjwKBgGyak96/SZC6ad3mqNaIftDtxJzAtH4kLwee3y+nzWQqwCEnncwwS+wF8GA2TMXWQmiy/VwL/IrrBmeM7ZXuqx7vraPmbsb++UJjRUFl5JoxMJsSnjyVDz9NZSMlB1F2WnK1q6fs0A+WQ095cvHOyFuzgbggfuR7L8tHdKesiZqhAoGAR4OU3cc6JhxjrTPe95Un/uHvEe1x9y866mw1WznwAvv9Q0seRR7X6lwr9AgAa3sutEgn24SswbiP14HQ+H2dylWNHy+mYMRq0j7bKq5GIOsCZ5hXqwjpAuVk0SxyFBPAjZAwzE19cDXGAl44GH6DisofeTx1bQ9W4/M9dd/6J9MCgYBzW54BbochobX23cFy6QTOQ1zEUGVaabC4rx8/2M9qbORjVWKIcNOLNErm/BmIXkwU/7dPGdkL98poIvYP172obCHdR/vWNEfA9SzTFO2Dkgr7zsJcMM0H/DrUWVx0PFJoQX6uPMnkF71jupqbg0uYtzkVfKVBQ2GA294fWHlvew==");

        //json
        config.setFormat("json");
        //支付宝的公钥,不是咱自己的。沙箱应用处获取
        config.setAlipayPublicKey("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtovPlNX2W2/vY7e9FCyb7WTeR61VbgScDzDMpRo866u8ucubHgj9HTt4ebsvwsi5d20l1tqT77mEbEwjCqA8CtfeDyATYuHB+P7SWnotjIIXfFfIAgeAqyIl+7TlsBKt13XAVwQh2LqP2xpzqUCHlhJrvQrrWckzliL/SaqfkHbXRPHqN3O4vNORfOyu5FqXxO5oZ8ADebVkouzi6Rv/wMTazERXIwg2YXYjqkOOQzrojKuHUdHDL9vlC3b0ht1z7cUPLdQE86UoOZCj55dLUvjoZsyxKM/tsGjwvmKJGxvUnDDX88KbGexxFpUS8aN2t0OkE2yzGZgbDtAO3pDTuQIDAQAB");

        //UTF-8
        config.setCharset("UTF-8");
        //RSA2
        config.setSignType("RSA2");
        try {
            return new DefaultAlipayClient(config);
        } catch (AlipayApiException e) {
            throw new RuntimeException(e);
        }
    }

}

支付:

建议参考如下文档: https://opendocs.alipay.com/open/028r8t?ref=api&scene=22
或者如下代码

    @GetMapping("/pay/{id}")
    public String pay(@PathVariable int id) throws AlipayApiException {
        Pay pay = payService.getById(id);

        AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
        AlipayTradePagePayModel model = new AlipayTradePagePayModel();
        model.setOutTradeNo(pay.getUuid());
        model.setTotalAmount(pay.getTotal()+"");
        model.setSubject("房租");
        model.setProductCode("FAST_INSTANT_TRADE_PAY");//固定写死这个
        request.setBizModel(model);

        request.setReturnUrl("http://localhost:8081/order"); // 前端跳转地址
        request.setNotifyUrl("https://6217to9865.goho.co/pay/cj/ali_callback");  //支付宝通过后端回调我们的地址。
        AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
        return response.getBody();
    }

打开支付页面

在前端页面打开支付页面时,需要在前端el-button的click事件处理中使用类似如下的代码打开

//order 是要支付的订单
    pay(order) {
      //以下为新开页面打开
      window.open("http://localhost:8080/order/pay/pay/"+order.id);
      //以下为原页面打开
      //window.location.href="http://localhost:8080/order/pay/pay/"+order.id
    }
    

后端支付回调(简化实现--内网穿透工具可用时)

    @RequestMapping("/ali_callback")
    public String callback(String out_trade_no) {  //OutTradeNo  --> out_trade_no
       Pay pay =  payService.getByUUID(out_trade_no);
       pay.setStatus(PayStatus.PAID.name());
       boolean success = payService.updateById(pay);
       // 成功返回 succes  失败 返回 fail
       return success ? "success" : "fail";
    }

前端支付回调 (简化实现--内网穿透工具不可用时)

    //本实现方式为简化实现方式,仅用于内网穿透不可用时,工作中不可这样使用。
    @GetMapping("/callback")
    public void callback(@RequestParam String out_trade_no, HttpServletResponse response) throws IOException {
        QueryWrapper query = new QueryWrapper();
        query.eq("uuid", out_trade_no); //根据传入的参数out_trade_no(数据库中的uuid字段)构建查询
        Order order = orderService.getOne(query);
        order.setStatus("PAID");    //更新支付单为已支付
        orderService.updateById(order);
        response.sendRedirect("http://localhost:8081/products"); //跳转页面到目的页面
    }

支付回调(完整实现--实际工作时):

@RequestMapping("/ali_callback")
public Object aliCallback(HttpServletRequest request) {
    Map<String,String> params = combineRequestParams(request);
    try {
        boolean verified = AlipaySignature.rsaCheckV2(params, aliProperties.getPublicKey(), "utf-8", aliProperties.getSignType());
        if(!verified) {
            return ServerResponse.badRequest().body("非法请求,验证不通过");
        }
        String status = params.get("trade_status");
        if("TRADE_SUCCESS".equals(status)) {
            String out_trade_no = params.get("out_trade_no");
            Pay pay = payService.getByUUID(out_trade_no);
            pay.setPayDate(new Date());
            pay.setStatus(PayStatus.PAID.name());
            payService.saveOrUpdate(pay);
            return "success";
        }
        //确认支付状态是支付成功
        //确认价格一致
        //需要添加更新订单处理。
        return "fail";
    } catch (AlipayApiException e) {
        log.info("支付宝回调异常", e);
        return "fail";
    }
}

private static Map<String, String> combineRequestParams(HttpServletRequest request) {
    Map<String,String> params = new HashMap<>();
    Map requestParams = request.getParameterMap();
    for(Iterator it = requestParams.keySet().iterator(); it.hasNext();) {
        String name = (String) it.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);
    }
    params.remove("sign_type");
    return params;
}