环境介绍
沙箱环境
在我们的学习,考试中主要使用这个环境的信息,包含appId,支付网关地址,签名等信息。 沙箱环境
文档中心
https://opendocs.alipay.com/open/028r8t?ref=api&scene=22
调试中心
调试中心包含各api的调用例子,使我们开发学习的主要参考: https://open.alipay.com/dev/workspace/apidebug
代码示例
jsp示例,这里包含有文档中心及调试中心都不存在的支付回调验签代码。 支付回调验签
管理平台
正式的开发需要创建应用,并使用它的appId等信息,但是现在不是必须使用它。 创建应用
内网穿透工具
支付宝支付成功后有个回调地址,因为支付宝只能访问外网地址,所以我们或者自己部署到云服务器,或者使用内网穿透工具提供外网地址。 如下是两个(类)常用的穿透工具。
花生壳
需要去线上认证下:
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;
}