支付宝开放平台
官方文档:https://opendocs.alipay.com/common/02kkv7
进入支付宝开放平台的沙箱环境进行开发测试:
设置自定义 RSA2 密钥:
点击【支付宝密钥生成器】下载支付宝开放平台开发助手,生成应用私钥和应用公钥:
在【加签管理】中添加应用公钥,支付宝将为我们生成支付宝公钥:
基础概念
使用私钥和公钥进行加密解密的方法为非对称加密。双方使用同一把钥匙进行加密解密为对称加密。
什么是公钥、私钥、加密、签名和验签?
公钥私钥
公钥和私钥是一个相对概念。它们的公私性是相对于生成者来说的。一对密钥生成后,保存在生成者手里的就是私钥,生成者发布出去大家用的就是公钥。
例如:支付宝自己拥有一个私钥,给每个商户发放一个支付宝公钥。商户自己拥有一个私钥,同时给支付宝保存一个商户公钥。
签名和验签
为保证发送方发来的消息是正确无误的(没有被黑客拦截篡改消息),需要进行签名和验签:
- 签名(加签):发送方使用自己的私钥对传输数组进行加密,生成一段数字
sign
- 验签:接收方收到该消息后,使用商户公钥验证该
sign
信息是否匹配消息内容,如果匹配则说明消息无误;否则说明消息已在中途被篡改
商户和支付宝间的传输过程示意图:
Spring Boot 整合 Alipay
环境配置
- 导入 Maven 依赖:
1 2 3 4 5
| <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.9.28.ALL</version> </dependency>
|
- 注入
AlipayTemplate
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| package com.atguigu.gulimall.order.config;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.request.AlipayTradePagePayRequest; import com.atguigu.gulimall.order.vo.PayVo; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "alipay") @Component @Data public class AlipayTemplate { private String app_id = "2016092200568607";
private String merchant_private_key = "XXX"; private String alipay_public_key = "XXX"; private String notify_url = "http://member.yunmall.com/memberOrder.html";
private String return_url = "http://member.yunmall.com/memberOrder.html";
private String sign_type = "RSA2";
private String charset = "utf-8"; private String timeout = "30m";
private String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
public String pay(PayVo vo) throws AlipayApiException { AlipayClient alipayClient = new DefaultAlipayClient(gatewayUrl, app_id, merchant_private_key, "json", charset, alipay_public_key, sign_type);
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest(); alipayRequest.setReturnUrl(return_url); alipayRequest.setNotifyUrl(notify_url); String out_trade_no = vo.getOut_trade_no(); String total_amount = vo.getTotal_amount(); String subject = vo.getSubject(); String body = vo.getBody();
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\"," + "\"total_amount\":\""+ total_amount +"\"," + "\"subject\":\""+ subject +"\"," + "\"body\":\""+ body +"\"," + "\"timeout_express\":\"" + timeout + "\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
String result = alipayClient.pageExecute(alipayRequest).getBody();
System.out.println("支付宝的响应:"+result);
return result; } }
|
其中:
notify_url
:支付成功异步回调。当支付成功后支付宝将一直发出该回调请求,返回支付成功相关信息。如果该地址无响应,则会不断发送直到对方应答(最大努力型通知)
return_url
:同步通知,支付成功后页面跳转到那里
支付数据模型
- 定义发送给支付宝的支付对象
PayVo
:
1 2 3 4 5 6 7 8 9 10
|
@Data public class PayVo { private String out_trade_no; private String subject; private String total_amount; private String body; }
|
- 定义支付宝异步回调返回的信息实体类
PayAsyncVo
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
@ToString @Data public class PayAsyncVo { private String gmt_create; private String charset; private String gmt_payment; private Date notify_time; private String subject; private String sign; private String buyer_id; private String body; private String invoice_amount; private String version; private String notify_id; private String fund_bill_list; private String notify_type; private String out_trade_no; private String total_amount; private String trade_status; private String trade_no; private String auth_app_id; private String receipt_amount; private String point_amount; private String app_id; private String buyer_pay_amount; private String sign_type; private String seller_id; }
|
订单服务收集支付信息跳转到支付页
- 编写
Controller
跳转到支付宝支付页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Controller public class PayWebController {
@Autowired AlipayTemplate alipayTemplate;
@Autowired OrderService orderService;
@ResponseBody @GetMapping(value = "/payOrder", produces = "text/html") public String payOrder(@RequestParam("orderSn") String orderSn) throws AlipayApiException { PayVo payvo = orderService.payOrder(orderSn); String pay = alipayTemplate.pay(payvo); return pay; } }
|
alipayTemplate.pay(payvo)
返回的是一个 html 文本,其内容为:
它其实就是一个表单,配置了订单的数据信息。并且该表单会立即提交,带着我们传入的订单数据重定向到支付宝网关,从而重定向到支付宝的支付页面:
在该页面支付完成后,支付宝就会立即重定向到我们在 AlipayTemplate
中配置的 return_url
地址,即会员服务的订单详情页面:
Service
层封装需要支付的信息 PayVo
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Override public PayVo payOrder(String orderSn) { PayVo payVo = new PayVo(); OrderEntity orderEntity = this.getOrderByOrderSn(orderSn); BigDecimal decimal = orderEntity.getPayAmount().setScale(2, BigDecimal.ROUND_UP); payVo.setTotal_amount(decimal.toString()); payVo.setOut_trade_no(orderSn); List<OrderItemEntity> itemEntities = orderItemService.list(new QueryWrapper<OrderItemEntity>() .eq("order_sn", orderSn)); OrderItemEntity itemEntity = itemEntities.get(0); payVo.setSubject(itemEntity.getSkuName()); payVo.setBody(itemEntity.getSkuAttrsVals()); return payVo; }
|
支付成功后异步回调到会员服务
- 支付成功后,支付宝将异步回调到
AlipayTemplate
中配置的 notify_url
地址。先验证签名后,保存订单流水,并更改订单的支付状态为已支付:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
|
@RestController public class OrderPayedListener { @Autowired AlipayTemplate alipayTemplate;
@Autowired OrderService orderService;
@PostMapping("/payed/notify") public String handleAlipayed(PayAsyncVo vo, HttpServletRequest request) throws UnsupportedEncodingException, AlipayApiException {
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); } boolean signVerified = AlipaySignature.rsaCheckV1(params , alipayTemplate.getAlipay_public_key() , alipayTemplate.getCharset() , alipayTemplate.getSign_type()); if (signVerified) { String result = orderService.handleAlipayed(vo); return result; } else { return "error"; } } }
|
Service
层保存订单流水,并更改订单的支付状态为已支付:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Override public String handleAlipayed(PayAsyncVo vo) { PaymentInfoEntity infoEntity = new PaymentInfoEntity(); infoEntity.setOrderSn(vo.getOut_trade_no()); infoEntity.setAlipayTradeNo(vo.getTrade_no()); infoEntity.setPaymentStatus(vo.getTrade_status()); infoEntity.setCallbackTime(vo.getNotify_time()); paymentInfoService.save(infoEntity);
if (vo.getTrade_status().equals("TRADE_SUCCESS") || vo.getTrade_status().equals("TRADE_FINISHED")) { String outTradeNo = vo.getOut_trade_no(); this.baseMapper.updateOrderStatus(outTradeNo,OrderStatusEnum.PAYED.getCode()); } return "success"; }
|