一、搭建pay-consumer环境
MAVEN依赖
其他的依赖与前面的consumer项目相类似,主要增加了支付宝支付接口的依赖:alipay-sdk-java
<dependencies>
<dependency>
<groupId>org.fall</groupId>
<artifactId>crowdfunding17-member-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- 阿里支付接口依赖 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.10.124.ALL</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
支付宝接口需要的配置类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
@ConfigurationProperties(prefix = "ali.pay")
public class PayProperties {
private String appId;
private String merchantPrivateKey;
private String aliPayPublicKey;
private String notifyUrl;
private String returnUrl;
private String signType;
private String charset;
private String gatewayUrl;
}
application.yml
server:
port: 7000
spring:
application:
name: crowd-pay
thymeleaf:
prefix: classpath:/templates/
suffix: .html
redis:
host: 192.168.0.101
session:
store-type: redis
eureka:
client:
service-url:
defaultZone: http://localhost:1000/eureka/
# 在配置文件中设置支付宝公钥等信息
ali:
pay:
ali-pay-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqfvJJ7MdtFp+OEO6NbBo0q9VDkh996CU7f2uy8cVl3sbss+CxiRJCcdRO3kqI8tQUneRk8QdBqatbQnBttIEBxPa0pCFQ0CSw3dGOp/r8hs/K7gWQ2OrBQaGHXTGqWzJPrIRK8IEzQpCz1O6Ta4VaPLIvnFnszfzGbbQFAXnNCIMnlOr3vUWeABAkX2PfVhMAHLUUecudC3Dm2rX8Df3QBceVJl8pWw0A7GH4MsbaxAtAOmSoFLDCNoxybmCPnvnpwQh2sE+TwiETDXG9iUcUr9TON7Oqy+i0CD0EWgmt3uhCbXftRsV2FZypZ8/8lTViy0cB6Uy8KBPgUOCMrN0swIDAQAB
app-id: 2021000116694364
charset: utf-8
gateway-url: https://openapi.alipaydev.com/gateway.do
merchant-private-key: MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCnNtznnSJKvG5m6YTviQOoB0qF8pi+/sowJ8JjotKcRZ0A2kk6DTkbssmKgFrv4WYYTgd7k8DJFQEHGXTTA+F6EOBBRwDXXIKB2YoKoJmH8LrRWFhRMyUwzkKPlWAIw9uDWGX74ihF6kFpQg9xN8Z/ouhSsjkLA5/ECv1q7420cQ5gPuqgOPEIz9xE9/1Mo/cKSvOZ0BDJ5A+ZjHvkBsK8gqYyydPb69CLxGkGMU6JHK8f4mIEVJaBE4V9KzBJszl4mg5mW1h1sEXcK6LKNPkALbazDu4ni2E1q9TuQ7e4BuUdwpXVDdEswC/5hUV77jarFTa20PYkIE1N5sC2iP4RAgMBAAECggEARNU+xlFW5nD++XozcO90A91MfitJWYZE8OHNEXrbO90kLk+tSSpcUdydPlhsFscwYdztE5/VAnGVBLkj8CFu1fLQfc6euAiq6I0GWC2YRe+A1+CIKpTlQFwH5ldVOwZ0/HKNOMz2SyGNX6kD9lVBbxU+kZwwAyqwv+DORRDrwlUEjX9xwIebRHqjSuL+QgOZSCCVufs/MIn4xYRmxGbL/ta+CzK2vQiTtJOqeOiAEDL1U/jKyd7+vOi/2UqvopZrsYjylg7zDqVzQrUrsYYPkanBp0FKwWDN4653lgnADXEimBBHjmZ0hEWdo+DWOn6Ve8S0fKmcUaPTsbumGDpC4QKBgQDl8gw+XLRlE+jwbbgyC7QVDP/xNaVpdhK17CTTH1snQOEvZAQnFqRF6D0mGSQ+79v8pYB4jWMV2Gr8fHFIdqKMwHpAbc5M11st2r2Q0IICtcCxGf6/XaVDehRGiiT8gV+EpBa80HjNSV0svzQUt7m2h9YPpJVf54YDALdXKzppRQKBgQC6KTFBszsaJGfrdzwQNjJVOqHukopoW+kZTrECF5J+wMf2g5xDuji2X4QBk+tKAfJvREE+LZMnsFl2vsVlz2baYbp1aHPBaqsV/gw2sSp1d8C0SOOtolVCwU0XgWQdlhqh66W9gv9N/flq37DNg/5MYVLGHc8FXM7Fm8f2ZznAXQKBgDow0+pgOYKu1C8sBQYdMhh1rruTEOYsbDW/qd4HvGClqZb9n5Noxm+ugvMGP8bxmmFIXMelOCMKHqEAuQZWh3JJL2boouGNSlTDW1+vdIdNCZOH+ZDiGPWHimQDh7Ki5tJBWEO2GiwRWdP+Gyb3C7dmjsRsqSSZCa/u2XbkyKZlAoGAWvXBoY6HtFBRjpXl/S3eDhLmCIIFqtQt4zW1+4W1CMX9bpAEDHuzNjkZcQPrlW+6G2/ISS9PlbbvH7w7C3H5mX7i8nclCD6sdcR6B0ctrdPjjrOGu9JzwnhcyL1qQ6KSRMe1ZhHZ2SvdcMD3zJNP79Yl8bOi8cXVKmzhmZ36c+kCgYADngv9iZhfU1xm6LvlkwHofv+LuUqhlrpnhb8sFEkhC0PHXOtm9Yx3tRiu9fYZ21RD7pwCODn+rC9qsNJrKlIJcYqg1Kg5QS6Ki7cOk1ORTW8v8xpvuVRx4Z/oy+LyatLEMiSfghzoKZ1QyydoOKM0UHJpVwKKJvk1bP+iXRfZWg==
notify-url: http://8bwgvf.natappfree.cc/pay/notify
return-url: http://localhost/pay/return
sign-type: RSA2
二、“立即付款”跳转
1、前端页面
order-confirm-order.html
<!--按钮标签-->
<li style="margin-top:10px;">
<button id="payBtn" disabled="disabled" type="button" class="btn btn-warning btn-lg">
<i class="glyphicon glyphicon-credit-card"></i>
立即付款
</button>
</li>
<!--添加一个初始无内容的表单,用于提交付款请求时携带数据-->
<form id="summaryForm" action="/pay/generate/order" method="post"></form>
<!--id=payBtn对应的按钮的单击响应函数-->
<script>
// 支付按钮的单击响应事件
$("#payBtn").click(function () {
// 收集要提交给表单的数据
var addressId = $("[name=addressId]:checked").val();
var invoice = $("[name=invoiceRadio]:checked").val();
var invoiceTitle = $.trim($("[name=invoiceTitle]").val());
var remark = $.trim($("[name=remark]").val());
// 提交表单
$("#summaryForm")
.append("<input type='hidden' name='addressId' value='"+ addressId +"' />")
.append("<input type='hidden' name='invoice' value='"+ invoice +"' />")
.append("<input type='hidden' name='invoiceTitle' value='"+ invoiceTitle +"' />")
.append("<input type='hidden' name='orderRemark' value='"+ remark +"' />")
.submit();
});
</script>
2、后端handler方法
@Controller
public class PayHandler {
@Autowired
private PayProperties payProperties;
@Autowired
private MySQLRemoteService mySQLRemoteService;
private Logger logger = LoggerFactory.getLogger(PayHandler.class);
// 通过ResponseBody注解,让当前方法的返回值成为响应体,在浏览器上显示支付宝的支付界面
@ResponseBody
@RequestMapping("generate/order")
public String generateOrder(OrderVO orderVO, HttpSession session) throws UnsupportedEncodingException, AlipayApiException {
// 得到session域中的orderProjectVO
OrderProjectVO orderProjectVO = (OrderProjectVO)session.getAttribute("orderProjectVO");
// 将orderProjectVO赋给前端传来的orderVO
orderVO.setOrderProjectVO(orderProjectVO);
// 生成支付宝订单号
// 使用uuid生成用户id部分
String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
// 根据日期生成字符串
String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
// 组装
String orderNum = time + uuid;
// 存入orderVO
orderVO.setOrderNum(orderNum);
// 计算订单金额
Double orderAmount = (double)(orderProjectVO.getReturnCount() * orderProjectVO.getSupportPrice() + orderProjectVO.getFreight());
// 存入orderVO
orderVO.setOrderAmount(orderAmount);
// 把orderVO存入session域
session.setAttribute("orderVO", orderVO);
return sendRequestToAliPay(orderNum,orderAmount,orderProjectVO.getProjectName(),orderProjectVO.getReturnContent());
}
/**
*
* @param orderNum 订单号
* @param orderAmount 总金额
* @param subject 订单名称,这里用项目名称
* @param body 商品描述,这里用回报的描述
* @return 返回页面
* @throws AlipayApiException
* @throws UnsupportedEncodingException
*/
private String sendRequestToAliPay(String orderNum, Double orderAmount, String subject, String body) throws AlipayApiException, UnsupportedEncodingException {
//获得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(
payProperties.getGatewayUrl(),
payProperties.getAppId(),
payProperties.getMerchantPrivateKey(),
"json",
payProperties.getCharset(),
payProperties.getAliPayPublicKey(),
payProperties.getSignType());
//设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(payProperties.getReturnUrl());
alipayRequest.setNotifyUrl(payProperties.getNotifyUrl());
alipayRequest.setBizContent("{\"out_trade_no\":\""+ orderNum +"\","
+ "\"total_amount\":\""+ orderAmount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
// 返回
return alipayClient.pageExecute(alipayRequest).getBody();
}
// return请求对应方法
@ResponseBody
@RequestMapping("/return")
public String returnUrlMethod(HttpServletRequest request,HttpSession session) throws UnsupportedEncodingException, AlipayApiException {
//获取支付宝GET过来反馈信息
Map<String,String> params = new HashMap<>();
Map<String,String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = iter.next();
String[] values = 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,
payProperties.getAliPayPublicKey(),
payProperties.getCharset(),
payProperties.getSignType()); //调用SDK验证签名
//——请在这里编写您的程序(以下代码仅作参考)——
if(signVerified) {
//商户订单号
String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
//支付宝交易号
String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
//付款金额
String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8");
// 得到session域中的orderVO
OrderVO orderVO = (OrderVO)session.getAttribute("orderVO");
// 给orderVO设置支付宝交易号
orderVO.setPayOrderNum(trade_no);
// 存入mysql数据库
ResultEntity<String> resultEntity = mySQLRemoteService.saveOrderRemote(orderVO);
logger.info("save order result: " + resultEntity.getResult());
return "trade_no:"+trade_no+"<br/>out_trade_no:"+out_trade_no+"<br/>total_amount:"+total_amount;
}else {
return "验签失败";
}
}
// notify请求对应方法
@RequestMapping("/notify")
public void notifyUrlMethod(HttpServletRequest request) throws AlipayApiException, UnsupportedEncodingException {
//获取支付宝POST过来反馈信息
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,
payProperties.getAliPayPublicKey(),
payProperties.getCharset(),
payProperties.getSignType()); //调用SDK验证签名
if(signVerified) {//验证成功
//商户订单号
String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
//支付宝交易号
String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
//交易状态
String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");
if(trade_status.equals("TRADE_FINISHED")){
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
//注意:
//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
}else if (trade_status.equals("TRADE_SUCCESS")){
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//如果有做过处理,不执行商户的业务程序
//注意:
//付款完成后,支付宝系统发送该交易状态通知
}
logger.info("success");
logger.info("out_trade_no" + out_trade_no);
logger.info("trade_no" + trade_no);
logger.info("trade_status" + trade_status);
}else {//验证失败
logger.info("fail");
//调试用,写文本函数记录程序运行情况是否正常
//String sWord = AlipaySignature.getSignCheckContentV1(params);
//AlipayConfig.logResult(sWord);
}
}
}
3、将订单存入数据库
① 修改OrderVO类的代码
将原本为Integer类型的addressId改为String类型,使其与OrderPO对象对应,否则会出现无法存入addressId的情况。
(记得让OrderVO实现序列化接口,因为需要存入session,存入redis)
② 远程api接口
@RequestMapping("save/order/remote")
ResultEntity<String> saveOrderRemote(@RequestBody OrderVO orderVO);
③ mysql模块
handler:
@RequestMapping("save/order/remote")
ResultEntity<String> saveOrderRemote(@RequestBody OrderVO orderVO) {
try {
orderService.saveOrder(orderVO);
return ResultEntity.successWithoutData();
} catch (Exception e) {
e.printStackTrace();
return ResultEntity.failed(e.getMessage());
}
}
service:
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
@Override
public void saveOrder(OrderVO orderVO) {
// 创建OrderPO对象
OrderPO orderPO = new OrderPO();
// 从传入的OrderVO给OrderPO赋值
BeanUtils.copyProperties(orderVO,orderPO);
// 将OrderPO存入数据库
orderPOMapper.insert(orderPO);
// 得到存入后自增产生的order id
Integer orderId = orderPO.getId();
// 得到orderProjectVO
OrderProjectVO orderProjectVO = orderVO.getOrderProjectVO();
// 创建OrderProjectPO对象
OrderProjectPO orderProjectPO = new OrderProjectPO();
// 赋值
BeanUtils.copyProperties(orderProjectVO,orderProjectPO);
// 给orderProjectPO设置orderId
orderProjectPO.setOrderId(orderId);
// 存入数据库
orderProjectPOMapper.insert(orderProjectPO);
}
这里因为需要在存入OrderPO后得到自增的orderId,需要修改MyBatis的代码:
即增加 useGeneratedKeys=”true” keyProperty=”id”
<insert id="insert" parameterType="org.fall.entity.po.OrderPO" useGeneratedKeys="true" keyProperty="id" >
insert into t_order (id, order_num, pay_order_num,
order_amount, invoice, invoice_title,
order_remark, address_id)
values (#{id,jdbcType=INTEGER}, #{orderNum,jdbcType=CHAR}, #{payOrderNum,jdbcType=CHAR},
#{orderAmount,jdbcType=DOUBLE}, #{invoice,jdbcType=INTEGER}, #{invoiceTitle,jdbcType=CHAR},
#{orderRemark,jdbcType=CHAR}, #{addressId,jdbcType=CHAR})
</insert>
此时支付功能就基本完成了。
三、注意点
此处使用的是支付宝支付的沙箱环境,具体在支付宝开放平台查看,如果需要使用正规环境,则必须提供企业信息等。
另外,想要使用支付功能,必须对外有公网地址,而不能仅仅使用内网地址,这样外部接口是无法调用到我们本地的代码的,因此这里我使用了natapp(https://natapp.cn/),进行内网穿透,打开软件后使用它提供的域名就可以访问我们内网的80端口了(需要在自己的natapp账号等地方设置好)