1.搭建pay-consumer环境
1.MAVEN依赖
其他的依赖与前面的consumer项目相类似,主要增加了支付宝支付接口的依赖:alipay-sdk-java
项目结构:
依赖:
<dependencies>
<dependency>
<groupId>com.zh.crowd</groupId>
<artifactId>crowdfunding17-member-api</artifactId>
<version>0.0.1-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>
2.支付宝接口需要的配置类
项目结构:
代码:
@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;
}
3.配置application.yml
项目结构:
代码:
server:
port: 7000
spring:
application:
name: crowd-pay
thymeleaf:
prefix: classpath:/templates/
suffix: .html
redis:
host: 127.0.0.1
session:
store-type: redis
eureka:
client:
service-url:
defaultZone: http://localhost:1000/eureka/
# 在配置文件中设置支付宝公钥等信息
ali:
pay:
ali-pay-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxWd5LWnUoSSR8EEDFQURvlRnPmgu0ib/1DA+bZ6FNXK7ycgWY+QkAJ3Tq8MElZWo3rtGVOzX69sI5otYw2xME1zenB4q5vixJTE6RNvu2b72AcSBCXqtfIRGPbHZasUVBJAOphzb2yfWWj0eGkFageGOmQEbpD3hfO+axhrTnf9LleFS7uBsq6sMCoLFVKbZjPsVeGJasAGVhM82Q9zkmNcO77a0gltBDc4TGFFJ6nBwIk3lVM0gllf1X1QyEwEpsVjssvzlOsliH3Ol4ZYunW3p4YZS4V2iFwEHwPByVZUmt/N/W3dOmFjOeV1CiMl8UQ+7qVr4OfuOCSIdOXCbzwIDAQAB
app-id: 2021000119603139
charset: utf-8
gateway-url: https://openapi.alipaydev.com/gateway.do
merchant-private-key: MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCTQK5X2K+3NDW0wPevI26hc6mNA8tVKKTwYFnRuhqY1yz7nxt4+2rt9d0IMIyZwO6MN4ikBHeypzYmmhZWPTYbpvquigO4WaHlsF5ERSQ5ZE29pWgf7a1/iGFXYO96d9n3rO23/xxOXvztcEGKRa2JVIpoeE7m+nTjUNtZUU9Vyg7IWdMl5nKIpnV4GvmqkLerxbVsAlf89bF9qih+ELsG9rrzLfyZ1Qg45o//OqWBMMciyuRDQvwKCf8ZstwsHoKyKrXAgPXkPJE+bcbbsI4HV7z7nwAZqLoxfztU6QBWKK+ImDges1rEtRgBOgbZCQtnX3i/2z2Lz3E3bnPf8DmDAgMBAAECggEAF8dPIAJuxZ/ZuFmc5ZxMJig+TfY55IOOtv5oIrTxzJKawfs8fXnkF48nZUYpvj5ohx4Eo6/RAHl+BhfF2aFC1o5NVtgUp7VZrnSflFix886duy01tko0RS7pwSl3IoiuGw/Cx36bvJAcOd9xxflgf5e2Tg53d1ckfvy0nyTBMfITSHZz+GeQvR2yBvW38+/ZwBtoGKVaRxi+6oYROVizPrvEtDXuujyxUpGiVO1X/8zSgAuHuOr8l/v5HNF1QoNNJysTrRsBjz99K5ATJcVeH/IUYCt5G6ZEt+nhpoCcwOooTNHD0gEF3Gmxe5ClQA9QAHoaL+bq4IXqFvgy2aJiAQKBgQDNel4LLF30FFrAuQCo8uT+lorItUxYWvS8DIyd/CGs7pWJ5KY+QsKQTbWX+uXC+YQlXzQIQ0ia/rTLwK5UaYxQVUevjAdCdxFpjhtvO7/xLoaO0ktLAx6RzHnoEmINh9ntjY1eX1MddanC9I8bkI+Z9Lw0WCffT0ANRVKao2x3AQKBgQC3dV7splscTM69Bxj8qpzzW2abPcy9H9aT+ZHyuysm+doU0+1j6i8Yda23uzCNvdC3OTknaaVW+2b6SgfLRjjlc8n3qZwbVuN+sq+yjsbjTdeVNCS5ZA8F63ofs8byxNoDSyIOp/bMuBxv2HBcNTrc4Sh8/Bon2O15Qnv7o2NUgwKBgCx6/961VQVgXqD3q3/nTNEb76ExNfyue+o1YT9V9EhGQZLfL2ms9Ade+x+STaiecQ/SAyaCwjXjS2oMJPDbBGfjfigvTOcDdX5/J8s1iQCBzNkgBvKPE1AGjqFBUX6SWQfGq8KxKgHnlb9BR7V03tE6HH/MUZ1Sv4/f4Nje5b0BAoGAA4qHb4fygERXRTzK1a2xY90iyOJTfl8L9EU/9PB578Go3PgOP/x4Nj+nP8GkOUgtrCu1l7YpU9l8bl5ZcioD1SH8BwTGF50AvSRT0d2Bp0eMXrvn7ZAp3hJihBQjRfJYxvF/UcsaL29qVRpXaOA6J6NM9cSj+JruUoXfAMdxlmMCgYBNooQBfhfoddZn3RlRgKCPSJoBhdniMkVJ7+ZQYegwJGLmHCr+niPtOHUVJtvWNCrHpPh57tY9rRLXckMnwP3h9mollpRqlpRHWcmQKw1E9XPRaKivITluBxFET3LfO372NeYDUsgosdiwAP5+tJSL1QfqQHncpURjiEKq49UPow==
notify-url: http://k64izg.natappfree.cc/pay/notify
return-url: http://localhost/pay/return
sign-type: RSA2
4.主启动类
项目结构:
代码:
@EnableFeignClients
@SpringBootApplication
public class CrowdMainPayApp {
public static void main(String[] args) {
SpringApplication.run(CrowdMainPayApp.class,args);
}
}
2.“立即付款”跳转
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);
}
}
}
支付宝对订单号的要求:
https://opendocs.alipay.com/support/01ray5
// 生成支付宝订单号
// 使用uuid生成用户id部分
String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
// 根据日期生成字符串
String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
// 组装
String orderNum = time + uuid;
3.将订单存入数据库
1.修改OrderVO类的代码
将原本为Integer类型的addressId改为String类型,使其与OrderPO对象对应,否则会出现无法存入addressId的情况。
(记得让OrderVO实现序列化接口,因为需要存入session,存入redis)
2. 远程api接口
项目结构:
代码:
@RequestMapping("save/order/remote")
ResultEntity<String> saveOrderRemote(@RequestBody OrderVO orderVO);
3. mysql工程模块
1.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());
}
}
2.service接口
项目结构:/
代码:
void saveOrder(OrderVO orderVO);
3.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);
}
4.注意
这里因为需要在存入OrderPO后得到自增的orderId,需要修改MyBatis的代码:
即增加 useGeneratedKeys=”true” keyProperty=”id”
<insert id="insert" parameterType="com.zh.crowd.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>