https://open.alipay.com/platform/home.htm
https://openhome.alipay.com/platform/appDaily.htm
采用RSA2非对称加密方式
https://opendocs.alipay.com/open/291/105971
生成密钥:
内网穿透可以允许我们使用外网的网址来访问主机。
正常的外网需要访问应用的流程是:
natapp:https://natapp.cn
续断:http://www.zhexi.tech
花生壳:https://www.oray.com
https://natapp.cn/#download
支付宝SDK:https://opendocs.alipay.com/open/54/cyz7do
Alipay Easy SDK:https://github.com/alipay/alipay-easysdk
API接口文档:https://github.com/alipay/alipay-easysdk/blob/master/APIDoc.md
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.2.0</version>
</dependency>
# 支付宝-沙箱
alipay.protocol=https
alipay.gatewayHost=openapi.alipaydev.com
alipay.signType=RSA2
alipay.appId=9021000122694995
# 商户私钥
alipay.merchantPrivateKey=static/alipay/zh_sy.txt
# 支付宝公钥
alipay.alipayPublicKey=static/alipay/zh_gy.txt
# fsp6n6.natappfree.cc 内网穿透地址
alipay.notifyUrl=http://server.natappfree.cc/alipay/notify
# 支付成功之后跳转的链接地址
alipay.returnUrl=http://member.gmall.com/memberOrder.html
# 支付限时
alipay.timeoutExpress=1m
AlipayProperties
package com.atguigu.gmall.order.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 支付宝-沙箱环境参数 {@link AlipayProperties}
*
* @author zhangwen
* @email: [email protected]
*/
@ConfigurationProperties(prefix = "alipay")
@Data
public class AlipayProperties {
private String protocol;
private String gatewayHost;
private String signType;
/**
* 应用id
*/
private String appId;
/**
* 应用私钥
*/
private String merchantPrivateKey;
/**
* 支付宝公钥
*/
private String alipayPublicKey;
/**
* 异步通知接收服务地址
*/
private String notifyUrl;
}
AlipayConfig
package com.atguigu.gmall.order.config;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.atguigu.common.utils.ReadTxtUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
/**
* 支付宝配置 {@link AlipayConfig}
*
* @author zhangwen
* @email: [email protected]
*/
@EnableConfigurationProperties(AlipayProperties.class)
@Configuration
public class AlipayConfig {
@Bean
public Config config(AlipayProperties alipayProperties) throws IOException {
Config config = new Config();
config.protocol = alipayProperties.getProtocol();
config.gatewayHost = alipayProperties.getGatewayHost();
config.signType = alipayProperties.getSignType();
config.appId = alipayProperties.getAppId();
// 为避免私钥随源码泄露,推荐从文件中读取私钥字符串而不是写入源码中
config.merchantPrivateKey = ReadTxtUtils.readTxtFile(
new ClassPathResource(alipayProperties.getMerchantPrivateKey()).getInputStream());
//注:证书文件路径支持设置为文件系统中的路径或CLASS_PATH中的路径,优先从文件系统中加载,加载失败后会继续尝试从CLASS_PATH中加载
// config.merchantCertPath = "<-- 请填写您的应用公钥证书文件路径,例如:/foo/appCertPublicKey_2019051064521003.crt -->";
// config.alipayCertPath = "<-- 请填写您的支付宝公钥证书文件路径,例如:/foo/alipayCertPublicKey_RSA2.crt -->";
// config.alipayRootCertPath = "<-- 请填写您的支付宝根证书文件路径,例如:/foo/alipayRootCert.crt -->";
//注:如果采用非证书模式,则无需赋值上面的三个证书路径,改为赋值如下的支付宝公钥字符串即可
config.alipayPublicKey = ReadTxtUtils.readTxtFile(
new ClassPathResource(alipayProperties.getAlipayPublicKey()).getInputStream());
//可设置异步通知接收服务地址(可选)
config.notifyUrl = alipayProperties.getNotifyUrl();
Factory.setOptions(config);
return config;
}
}
支付宝支付页面,使用沙箱账号 -> 买家信息账号登录付款
OrderPayController
package com.atguigu.gmall.order.web;
import com.atguigu.gmall.order.service.AliPayService;
import com.atguigu.gmall.order.vo.PayAsyncVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* 订单支付 {@link OrderPayController}
*
* @author zhangwen
* @email: [email protected]
*/
@RestController
public class OrderPayController {
@Autowired
private AliPayService aliPayService;
/**
* 跳转到支付宝支付页
* @param orderSn
* @return
*/
@GetMapping(value = "/payOrder", produces = "text/html")
public String payOrder(@RequestParam("orderSn") String orderSn){
// 返回的是支付宝收银页面
String pay = aliPayService.pay(orderSn);
return pay;
}
}
AliPayServiceImpl
package com.atguigu.gmall.order.service.impl;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.atguigu.gmall.order.constant.OrderStatusEnum;
import com.atguigu.gmall.order.entity.PaymentInfoEntity;
import com.atguigu.gmall.order.service.AliPayService;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.order.service.PaymentInfoService;
import com.atguigu.gmall.order.vo.PayAsyncVO;
import com.atguigu.gmall.order.vo.PayVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* 支付宝支付接口实现 {@link AliPayServiceImpl}
*
* @author zhangwen
* @email: [email protected]
*/
@Slf4j
@Service
public class AliPayServiceImpl implements AliPayService {
@Value("${alipay.returnUrl}")
private String returnUrl;
@Value("${alipay.timeoutExpress}")
private String timeoutExpress;
@Autowired
private OrderService orderService;
@Autowired
private PaymentInfoService paymentInfoService;
/**
* 支付
* @param orderSn
* @return
*/
@Override
public String pay(String orderSn) {
PayVO payVO = orderService.getOrderPay(orderSn);
payVO.setSubject("谷粒订单支付");
payVO.setBody("");
try {
AlipayTradePagePayResponse response = Factory.Payment.Page()
// 订单允许的最晚付款时间,逾期将关闭交易
.optional("timeout_express", timeoutExpress)
.pay(payVO.getSubject(), payVO.getOut_trade_no(), payVO.getTotal_amount(), returnUrl);
// body: 支付宝的收银台页面
return response.body;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
支付成功,跳转到:会员系统 -> 订单列表页面(orderList.html)
支付成功后异步通知回调地址(notifyUrl)使用的是内网穿透地址,需要在nginx进行精确配置,才能访问
/mydata/nginx/conf/conf.d/gmall.conf
server {
listen 80;
listen [::]:80;
# gqjzd6.natappfree.cc 内网穿透地址
server_name *.gmall.com gmall.com gqjzd6.natappfree.cc;
location /static/ {
root /usr/share/nginx/html;
}
# 精确配置内网穿透访问地址
location /alipay/ {
proxy_pass http://gmall;
proxy_set_header Host order.gmall.com;
}
location / {
proxy_pass http://gmall;
proxy_set_header Host $host;
}
}
OrderPayController
package com.atguigu.gmall.order.web;
import com.atguigu.gmall.order.service.AliPayService;
import com.atguigu.gmall.order.vo.PayAsyncVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* 订单支付 {@link OrderPayController}
*
* @author zhangwen
* @email: [email protected]
*/
@RestController
public class OrderPayController {
@Autowired
private AliPayService aliPayService;
/**
* 支付宝支付成功异步通知
* @param vo
* @return
*/
@PostMapping("/alipay/notify")
public String handleAlipay(PayAsyncVO vo, HttpServletRequest request){
String result = null;
try {
result = aliPayService.payNotify(vo, request);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
AliPayServiceImpl
package com.atguigu.gmall.order.service.impl;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.atguigu.gmall.order.constant.OrderStatusEnum;
import com.atguigu.gmall.order.entity.PaymentInfoEntity;
import com.atguigu.gmall.order.service.AliPayService;
import com.atguigu.gmall.order.service.OrderService;
import com.atguigu.gmall.order.service.PaymentInfoService;
import com.atguigu.gmall.order.vo.PayAsyncVO;
import com.atguigu.gmall.order.vo.PayVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* 支付宝支付接口实现 {@link AliPayServiceImpl}
*
* @author zhangwen
* @email: [email protected]
*/
@Slf4j
@Service
public class AliPayServiceImpl implements AliPayService {
@Value("${alipay.returnUrl}")
private String returnUrl;
@Value("${alipay.timeoutExpress}")
private String timeoutExpress;
@Autowired
private OrderService orderService;
@Autowired
private PaymentInfoService paymentInfoService;
/**
* 支付成功异步回调
* @param vo
* @param request
* @return
* @throws Exception
*/
@Override
public String payNotify(PayAsyncVO vo, HttpServletRequest request) throws Exception {
// 验签
Boolean signVerified = verifyNotify(request);
// 验证成功
if (signVerified) {
log.info("异步通知验签成功...");
// 保存交易流水
PaymentInfoEntity paymentInfoEntity = new PaymentInfoEntity();
paymentInfoEntity.setAlipayTradeNo(vo.getTrade_no());
paymentInfoEntity.setOrderSn(vo.getOut_trade_no());
paymentInfoEntity.setSubject(vo.getSubject());
paymentInfoEntity.setTotalAmount(new BigDecimal(vo.getTotal_amount()));
paymentInfoEntity.setPaymentStatus(vo.getTrade_status());
Date parseDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(vo.getNotify_time());
paymentInfoEntity.setCallbackTime(parseDate);
paymentInfoService.save(paymentInfoEntity);
// 修改订单状态信息
if ("TRADE_SUCCESS".equals(vo.getTrade_status()) || "TRADE_FINISHED".equals(vo.getTrade_status())) {
String outTradeNo = vo.getOut_trade_no();
orderService.updateOrderStatus(outTradeNo, OrderStatusEnum.PAYED.getCode());
}
// 只要收到支付宝异步通知,告知订单支付成功,返回success,支付宝就不会再通知
return "success";
} else {
log.error("异步通知验签失败...");
return "fail";
}
}
/**
* 异步通知验签
* @param request
* @return
* @throws Exception
*/
public Boolean verifyNotify(HttpServletRequest request) throws Exception {
log.info("异步通知验签...");
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] + ",";
}
params.put(name, valueStr);
}
// 异步通知验签
Boolean result = Factory.Payment.Common().verifyNotify(params);
return result;
}
}
AlipayTradePagePayResponse response = Factory.Payment.Page()
//订单允许的最晚付款时间,逾期将关闭交易
.optional("timeout_express", timeoutExpress)
.pay(payVO.getSubject(), payVO.getOut_trade_no(),
payVO.getTotal_amount(), returnUrl);
@RabbitHandler
public void handlerOrderClose(OrderEntity entity, Message message,
Channel channel) {
log.info("收到过期的订单信息,准备关闭订单,{}", entity);
try{
orderService.closeOrder(entity);
//手动调用支付宝收单
aliPayService.close(entity.getOrderSn());
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
try {
channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
网络阻塞问题,订单支付成功的异步通知一直不到达
其它各种问题