杨杰 il y a 6 ans
Parent
commit
ba368d3e64
31 fichiers modifiés avec 1485 ajouts et 335 suppressions
  1. 12 10
      pom.xml
  2. 1 1
      readme.md
  3. 0 40
      src/main/java/com/llisoft/service/file/controller/FileController.java
  4. 0 105
      src/main/java/com/llisoft/service/file/service/OssService.java
  5. 0 23
      src/main/java/com/llisoft/service/file/vo/DownloadRequestVo.java
  6. 0 20
      src/main/java/com/llisoft/service/file/vo/DownloadResponseVo.java
  7. 0 35
      src/main/java/com/llisoft/service/file/vo/UploadCdnResponseVo.java
  8. 0 35
      src/main/java/com/llisoft/service/file/vo/UploadRequestVo.java
  9. 0 52
      src/main/java/com/llisoft/service/file/vo/UploadResponseVo.java
  10. 1 1
      src/main/java/com/llisoft/service/pay/Application.java
  11. 1 1
      src/main/java/com/llisoft/service/pay/config/ErrorConfig.java
  12. 1 1
      src/main/java/com/llisoft/service/pay/config/ExceptionConfig.java
  13. 1 1
      src/main/java/com/llisoft/service/pay/config/SpringMvcConfig.java
  14. 1 1
      src/main/java/com/llisoft/service/pay/config/SwaggerConfig.java
  15. 94 0
      src/main/java/com/llisoft/service/pay/controller/AliPayController.java
  16. 124 0
      src/main/java/com/llisoft/service/pay/controller/OrderController.java
  17. 69 0
      src/main/java/com/llisoft/service/pay/controller/PayController.java
  18. 17 0
      src/main/java/com/llisoft/service/pay/dao/AppDao.java
  19. 30 0
      src/main/java/com/llisoft/service/pay/dao/ItemDao.java
  20. 29 0
      src/main/java/com/llisoft/service/pay/dao/OrderDao.java
  21. 113 0
      src/main/java/com/llisoft/service/pay/entity/App.java
  22. 139 0
      src/main/java/com/llisoft/service/pay/entity/Item.java
  23. 126 0
      src/main/java/com/llisoft/service/pay/entity/Order.java
  24. 235 0
      src/main/java/com/llisoft/service/pay/service/AliPayService.java
  25. 60 0
      src/main/java/com/llisoft/service/pay/service/AppService.java
  26. 130 0
      src/main/java/com/llisoft/service/pay/service/OrderService.java
  27. 142 0
      src/main/java/com/llisoft/service/pay/service/PayService.java
  28. 46 0
      src/main/java/com/llisoft/service/pay/vo/OrderRequestVo.java
  29. 90 0
      src/main/java/com/llisoft/service/pay/vo/OrderResponseVo.java
  30. 22 0
      src/main/resources/application.yml
  31. 1 9
      src/test/java/com/llisoft/service/pay/ServiceTest.java

+ 12 - 10
pom.xml

@@ -5,12 +5,9 @@
 	<modelVersion>4.0.0</modelVersion>
 	
 	<groupId>com.llisoft</groupId>
-    <artifactId>mta-service-file</artifactId>
+    <artifactId>mta-service-pay</artifactId>
     <version>1.0.0</version>
     <packaging>jar</packaging>
-
-    <name>mta-service-file</name>
-    <description>Demo project for Spring Boot</description>
 	
 	<parent>
         <groupId>org.springframework.boot</groupId>
@@ -36,17 +33,22 @@
 			<artifactId>spring-boot-starter-web</artifactId>
 		</dependency>
 		<dependency>
-			<groupId>org.springframework.boot</groupId>
-			<artifactId>spring-boot-starter-data-redis</artifactId>
-		</dependency>
-		<dependency>
 			<groupId>de.codecentric</groupId>
 			<artifactId>spring-boot-admin-starter-client</artifactId>
 			<version>2.0.1</version>
 		</dependency>
 		<dependency>
-			<groupId>com.aliyun.oss</groupId>
-			<artifactId>aliyun-sdk-oss</artifactId>
+			<groupId>org.mybatis.spring.boot</groupId>
+			<artifactId>mybatis-spring-boot-starter</artifactId>
+			<version>2.0.0</version>
+		</dependency>
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-java</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.alipay.sdk</groupId>
+			<artifactId>alipay-sdk-java</artifactId>
 			<version>3.1.0</version>
 		</dependency>
 		<dependency>

+ 1 - 1
readme.md

@@ -1 +1 @@
-#### 文件服务
+#### 支付服务

+ 0 - 40
src/main/java/com/llisoft/service/file/controller/FileController.java

@@ -1,40 +0,0 @@
-package com.llisoft.service.file.controller;
-
-import javax.validation.Valid;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import com.llisoft.service.file.service.OssService;
-import com.llisoft.service.file.vo.DownloadRequestVo;
-import com.llisoft.service.file.vo.UploadRequestVo;
-import com.llisoft.service.file.vo.UploadResponseVo;
-
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-
-@Api(tags="文件接口")
-@RestController
-@RequestMapping("/file")
-public class FileController {
-	
-	@Autowired
-	private OssService ossService;
-	
-	
-	@ApiOperation(value="上传")
-	@PostMapping("/upload")
-	public UploadResponseVo upload(@Valid @RequestBody UploadRequestVo vo) throws Exception{
-		return ossService.upload(vo.getPrefix(), vo.getSuffix(), vo.isCdn());
-	}
-	
-	@ApiOperation(value="下载")
-	@PostMapping("/download")
-	public String download(@Valid @RequestBody DownloadRequestVo vo) throws Exception{
-		return ossService.download(vo.getKey());
-	}
-	
-}

+ 0 - 105
src/main/java/com/llisoft/service/file/service/OssService.java

@@ -1,105 +0,0 @@
-package com.llisoft.service.file.service;
-
-import java.net.URL;
-import java.util.Date;
-import java.util.Objects;
-import java.util.UUID;
-
-import javax.annotation.PostConstruct;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import com.aliyun.oss.ClientConfiguration;
-import com.aliyun.oss.OSSClient;
-import com.aliyun.oss.common.auth.DefaultCredentialProvider;
-import com.aliyun.oss.common.utils.BinaryUtil;
-import com.aliyun.oss.model.MatchMode;
-import com.aliyun.oss.model.PolicyConditions;
-import com.llisoft.service.file.vo.UploadCdnResponseVo;
-import com.llisoft.service.file.vo.UploadResponseVo;
-
-/**
- * 阿里云OSS接口调用
- * @author YangJie [2019年2月18日]
- */
-@Service
-public class OssService {
-	
-	private Logger logger = LoggerFactory.getLogger(OssService.class);
-	
-	@Value("${mta.debug}")
-	private boolean debug;
-	
-	@Value("${oss.url}")
-	private String url;
-	@Value("${oss.bucket}")
-	private String bucket;
-	@Value("${oss.endpoint}")
-	private String endpoint;
-	@Value("${oss.accessKeyId}")
-	private String accessKeyId;
-	@Value("${oss.accessKeySecret}")
-	private String accessKeySecret;
-	
-	
-	// OSS客户端
-	private OSSClient ossClient;
-	
-	@PostConstruct
-	public void init() {
-		ossClient = new OSSClient(endpoint,new DefaultCredentialProvider(accessKeyId, accessKeySecret), new ClientConfiguration());
-	}
-
-	/**
-	 * 上传
-	 * @return
-	 * @throws Exception 
-	 */
-	public UploadResponseVo upload(String prefix, String suffix, boolean cdn) throws Exception {
-		suffix = Objects.nonNull(suffix) ? "."+suffix : "";
-		prefix = Objects.nonNull(prefix) ? prefix : "";
-		prefix = cdn ? "public/" + prefix : "private/" + prefix; // 区分公开资源和私有资源
-		prefix = debug ? "test/" + prefix : prefix; // 测试环境下添加固定前缀, 定时清理
-		PolicyConditions policyConditions = new PolicyConditions();
-		policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1024*1024*1024*1); // 内容长度范围1G
-		policyConditions.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, prefix); // key前缀
-		String postPolicy = ossClient.generatePostPolicy(this.getExpiration(10), policyConditions); // 有效期10分钟
-		UploadResponseVo uploadBean = cdn ? new UploadCdnResponseVo() : new UploadResponseVo();
-		uploadBean.setKey(prefix + UUID.randomUUID().toString().replace("-", "") + suffix);
-		uploadBean.setPolicy(BinaryUtil.toBase64String(postPolicy.getBytes("utf-8")));
-		uploadBean.setSignature(ossClient.calculatePostSignature(postPolicy));
-		uploadBean.setAccessid(accessKeyId);
-		uploadBean.setUrl(url);
-		return uploadBean;
-	}
-	
-	/**
-	 * 下载
-	 * @param key
-	 * @return
-	 */
-	public String download(String key) {
-		logger.info("oss下载请求: {}", key);
-		if(Objects.isNull(key) || key.trim().isEmpty()) {
-			return null;
-		}
-		URL url = ossClient.generatePresignedUrl(bucket, key, this.getExpiration(30)); // 授权有效期30分钟
-		String result = url.toString();
-		logger.info("oss下载返回: {}", result);
-		return result;
-	}
-
-	/**
-	 * 获取有效期
-	 * @param minute 分钟
-	 * @return
-	 */
-	private Date getExpiration(int minute) {
-		return new Date(System.currentTimeMillis() + 1000*60*minute);
-	}
-	
-	
-}

+ 0 - 23
src/main/java/com/llisoft/service/file/vo/DownloadRequestVo.java

@@ -1,23 +0,0 @@
-package com.llisoft.service.file.vo;
-
-import javax.validation.constraints.NotBlank;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-
-@ApiModel("下载请求实体")
-public class DownloadRequestVo {
-	
-	@ApiModelProperty("KEY")
-	@NotBlank(message="KEY不能为空")
-	private String key;
-
-	public String getKey() {
-		return key;
-	}
-
-	public void setKey(String key) {
-		this.key = key;
-	}
-	
-}

+ 0 - 20
src/main/java/com/llisoft/service/file/vo/DownloadResponseVo.java

@@ -1,20 +0,0 @@
-package com.llisoft.service.file.vo;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-
-@ApiModel("下载返回实体")
-public class DownloadResponseVo {
-	
-	@ApiModelProperty("URL")
-	private String url;
-
-	public String getUrl() {
-		return url;
-	}
-
-	public void setUrl(String url) {
-		this.url = url;
-	}
-	
-}

+ 0 - 35
src/main/java/com/llisoft/service/file/vo/UploadCdnResponseVo.java

@@ -1,35 +0,0 @@
-package com.llisoft.service.file.vo;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-
-@ApiModel("上传返回实体CDN")
-public class UploadCdnResponseVo extends UploadResponseVo{
-	
-	@ApiModelProperty("宽度200缩略图")
-	private String w200 = "!w200";
-	@ApiModelProperty("宽度缩400略图")
-	private String w400 = "!w400";
-	@ApiModelProperty("宽度800缩略图")
-	private String w800 = "!w800";
-	
-	public String getW200() {
-		return w200;
-	}
-	public void setW200(String w200) {
-		this.w200 = w200;
-	}
-	public String getW400() {
-		return w400;
-	}
-	public void setW400(String w400) {
-		this.w400 = w400;
-	}
-	public String getW800() {
-		return w800;
-	}
-	public void setW800(String w800) {
-		this.w800 = w800;
-	}
-	
-}

+ 0 - 35
src/main/java/com/llisoft/service/file/vo/UploadRequestVo.java

@@ -1,35 +0,0 @@
-package com.llisoft.service.file.vo;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-
-@ApiModel("上传请求实体")
-public class UploadRequestVo {
-	
-	@ApiModelProperty("前缀")
-	private String prefix;
-	@ApiModelProperty("后缀")
-	private String suffix;
-	@ApiModelProperty("CDN")
-	private boolean cdn;
-	
-	public String getPrefix() {
-		return prefix;
-	}
-	public void setPrefix(String prefix) {
-		this.prefix = prefix;
-	}
-	public String getSuffix() {
-		return suffix;
-	}
-	public void setSuffix(String suffix) {
-		this.suffix = suffix;
-	}
-	public boolean isCdn() {
-		return cdn;
-	}
-	public void setCdn(boolean cdn) {
-		this.cdn = cdn;
-	}
-	
-}

+ 0 - 52
src/main/java/com/llisoft/service/file/vo/UploadResponseVo.java

@@ -1,52 +0,0 @@
-package com.llisoft.service.file.vo;
-
-import io.swagger.annotations.ApiModel;
-import io.swagger.annotations.ApiModelProperty;
-
-@ApiModel("上传返回实体")
-public class UploadResponseVo {
-	
-	@ApiModelProperty("地址")
-	private String url;
-	@ApiModelProperty("ID")
-	private String accessid;
-	@ApiModelProperty("凭证")
-	private String policy;
-	@ApiModelProperty("签名")
-	private String signature;
-	@ApiModelProperty("KEY")
-	private String key;
-	
-	public String getAccessid() {
-		return accessid;
-	}
-	public void setAccessid(String accessid) {
-		this.accessid = accessid;
-	}
-	public String getPolicy() {
-		return policy;
-	}
-	public void setPolicy(String policy) {
-		this.policy = policy;
-	}
-	public String getSignature() {
-		return signature;
-	}
-	public void setSignature(String signature) {
-		this.signature = signature;
-	}
-	public String getKey() {
-		return key;
-	}
-	public void setKey(String key) {
-		this.key = key;
-	}
-	public String getUrl() {
-		return url;
-	}
-	public void setUrl(String url) {
-		this.url = url;
-	}
-
-	
-}

+ 1 - 1
src/main/java/com/llisoft/service/file/Application.java → src/main/java/com/llisoft/service/pay/Application.java

@@ -1,4 +1,4 @@
-package com.llisoft.service.file;
+package com.llisoft.service.pay;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 1 - 1
src/main/java/com/llisoft/service/file/config/ErrorConfig.java → src/main/java/com/llisoft/service/pay/config/ErrorConfig.java

@@ -1,4 +1,4 @@
-package com.llisoft.service.file.config;
+package com.llisoft.service.pay.config;
 
 import java.util.Objects;
 

+ 1 - 1
src/main/java/com/llisoft/service/file/config/ExceptionConfig.java → src/main/java/com/llisoft/service/pay/config/ExceptionConfig.java

@@ -1,4 +1,4 @@
-package com.llisoft.service.file.config;
+package com.llisoft.service.pay.config;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 1 - 1
src/main/java/com/llisoft/service/file/config/SpringMvcConfig.java → src/main/java/com/llisoft/service/pay/config/SpringMvcConfig.java

@@ -1,4 +1,4 @@
-package com.llisoft.service.file.config;
+package com.llisoft.service.pay.config;
 
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;

+ 1 - 1
src/main/java/com/llisoft/service/file/config/SwaggerConfig.java → src/main/java/com/llisoft/service/pay/config/SwaggerConfig.java

@@ -1,4 +1,4 @@
-package com.llisoft.service.file.config;
+package com.llisoft.service.pay.config;
 
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.context.annotation.Bean;

+ 94 - 0
src/main/java/com/llisoft/service/pay/controller/AliPayController.java

@@ -0,0 +1,94 @@
+package com.llisoft.service.pay.controller;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.llisoft.common.exception.MtaException;
+import com.llisoft.service.pay.service.AliPayService;
+
+
+/**
+ * 支付宝
+ */
+@Controller
+@RequestMapping("/alipay")
+public class AliPayController {
+	
+	private Logger logger = LoggerFactory.getLogger(AliPayController.class);
+	
+	@Autowired
+	private AliPayService aliPayService;
+	
+	
+	/**
+	 * 支付宝同步回调
+	 * @param request
+	 * @return
+	 */
+	@GetMapping("/return")
+	public String retur(HttpServletRequest request) {
+		Map<String, String> resultMap = this.getParamMap(request);
+		logger.info("收到支付宝同步回调: {}", resultMap);
+		try {
+			if (aliPayService.doReturn(resultMap)) {
+				logger.info("支付宝同步回调处理成功!");
+				return "redirect:/pay/"+resultMap.get("out_trade_no")+"/ok";
+			}
+		} catch (MtaException e) {
+			e.printStackTrace();
+		}
+		logger.error("支付宝同步回调处理失败!");
+		return null;
+	}
+	
+	/**
+	 * 支付宝异步通知
+	 * @param request
+	 * @return
+	 */
+	@PostMapping(value="/notify")
+	public @ResponseBody String notify(HttpServletRequest request) {
+		Map<String, String> resultMap = this.getParamMap(request);
+		logger.info("收到支付宝异步通知: {}", resultMap);
+		try {
+			if (aliPayService.doNotify(resultMap)) {
+				logger.info("支付宝异步通知处理成功!");
+				return "success";
+			}
+		} catch (MtaException e) {
+			e.printStackTrace();
+		}
+		logger.error("支付宝异步通知处理失败!");
+		return null;
+	}
+	
+	
+	/**
+	 * 获取参数Map<String, String>
+	 * @param request
+	 * @return
+	 */
+	private Map<String, String> getParamMap(HttpServletRequest request){
+		Map<String, String> returnMap = new HashMap<String, String>();
+		Map<String, String[]> paramMap = request.getParameterMap();
+		for (String key: paramMap.keySet()) {
+			String[] values = paramMap.get(key);
+			if (values!=null && values.length>0) {
+				returnMap.put(key, values[0]);
+			}
+		}
+		return returnMap;
+	}
+
+}

+ 124 - 0
src/main/java/com/llisoft/service/pay/controller/OrderController.java

@@ -0,0 +1,124 @@
+package com.llisoft.service.pay.controller;
+
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.llisoft.common.exception.MtaException;
+import com.llisoft.common.util.JsonUtil;
+import com.llisoft.service.pay.entity.Order;
+import com.llisoft.service.pay.service.AppService;
+import com.llisoft.service.pay.service.OrderService;
+import com.llisoft.service.pay.service.PayService;
+import com.llisoft.service.pay.vo.OrderRequestVo;
+import com.llisoft.service.pay.vo.OrderResponseVo;
+
+
+/**
+ * 订单
+ */
+@Controller
+@RequestMapping("/order")
+public class OrderController{
+	
+	private Logger logger = LoggerFactory.getLogger(OrderController.class);
+	
+	@Autowired
+	private OrderService orderService;
+	@Autowired
+	private PayService payService;
+	@Autowired
+	private AppService appService;
+	
+	
+	/**
+	 * 添加订单
+	 * 暂时不加认证签名
+	 * @param request
+	 */
+	@PostMapping
+	public @ResponseBody OrderResponseVo add(OrderRequestVo vo) throws MtaException{
+		logger.info("添加订单请求: {}", JsonUtil.toJson(vo));
+		if(Objects.isNull(vo.getAppkey()) || vo.getAppkey().trim().isEmpty() || vo.getMoney()<=0) {
+			throw new MtaException("添加订单: 非法入参: " + JsonUtil.toJson(vo));
+		}
+		String ordernum = orderService.add(vo.getAppkey(), vo.getMoney(), vo.getOrdernum(), vo.getTitle());
+		return this.get(ordernum);
+	}
+	
+	/**
+	 * 支付
+	 * @param orderId
+	 * @param type
+	 * @return
+	 */
+	@GetMapping("/{orderId}/pay")
+	public String pay(@PathVariable int orderId, // 默认支付宝
+			@RequestParam(required=false,defaultValue="1") byte type) throws MtaException{
+		if(orderId <= 0) {
+			throw new MtaException("订单号非法: " + orderId);
+		}
+		Order order = orderService.get(orderId);
+		if(order.getOrderStatus() == OrderService.STATUS_PAYED){
+			logger.info("订单支付: 订单已经支付过: {}", orderId);
+			return "redirect:/order/"+orderId+"/ok"; // 已经支付过
+		}
+		logger.info("订单支付: {}, 支付类型: {}", orderId, type==1 ? "支付宝" : "微信");
+		String paynum = payService.add(orderId, type);
+		return "redirect:/pay/" + paynum;
+	}
+	
+	/**
+	 * 支付成功
+	 * @return
+	 * @throws MtaException 
+	 */
+	@GetMapping(value="/{orderId}/ok")
+	public String ok(@PathVariable int orderId, HttpServletRequest request) throws MtaException{
+		Order order = orderService.get(orderId);
+		if (order.getOrderStatus() == OrderService.STATUS_PAYED) {
+			// 重定向到业务回调地址
+			String returnUrl = appService.getReturnUrl(order.getAppId(), order.getOrderNum());
+			if (Objects.nonNull(returnUrl)) {
+				return "redirect:" + returnUrl;
+			} 
+			// 未配置成功地址的返回默认
+			request.setAttribute("order", order);
+			return "/payok.jsp";
+		}
+		return null;
+	}
+	
+	/**
+	 * 获取订单
+	 * @param paynum
+	 * @return
+	 * @throws MtaException 
+	 */
+	@GetMapping("/{orderNum}")
+	public @ResponseBody OrderResponseVo get(@PathVariable String orderNum) throws MtaException{
+		OrderResponseVo responseBean = null;
+		if(Objects.nonNull(orderNum) && !orderNum.trim().isEmpty()){
+			Order order = orderService.getByOrderNum(orderNum);
+			responseBean = new OrderResponseVo();
+			BeanUtils.copyProperties(order, responseBean);
+			responseBean.setPayed(order.getOrderStatus()==OrderService.STATUS_PAYED);
+			responseBean.setPayUrl("/order/"+orderNum+"/pay");
+		}
+		logger.info("获取订单请求: {}, 返回结果: {}", orderNum, JsonUtil.toJson(responseBean));
+		return responseBean;
+	}
+	
+}

+ 69 - 0
src/main/java/com/llisoft/service/pay/controller/PayController.java

@@ -0,0 +1,69 @@
+package com.llisoft.service.pay.controller;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import com.llisoft.common.exception.MtaException;
+import com.llisoft.service.pay.entity.Item;
+import com.llisoft.service.pay.service.PayService;
+
+
+/**
+ * 支付
+ */
+@Controller
+@RequestMapping("/pay")
+public class PayController{
+	
+	private Logger logger = LoggerFactory.getLogger(PayController.class);
+	
+	@Autowired
+	private PayService payService;
+	
+
+	/**
+	 * 发起支付
+	 * @param paynum
+	 * @param type 支付方式(1微信/2支付宝)
+	 * @param device
+	 * @return
+	 * @throws IOException 
+	 * @throws MtaException 
+	 */
+	@GetMapping("/{paynum}")
+	public void pay(@PathVariable String paynum, boolean isMobile, HttpServletResponse response) throws IOException, MtaException{
+		logger.info("发起支付请求: {}", paynum);
+		if(Objects.isNull(paynum) || paynum.trim().isEmpty()) {
+			logger.error("发起支付: 非法支付号: {}", paynum);
+			return;
+		}
+		String html = payService.pay(paynum, isMobile);
+		response.setContentType("text/html; charset=utf-8"); 
+		response.getWriter().write(html);
+		response.getWriter().flush();
+		response.getWriter().close();
+	}
+	
+	/**
+	 * 支付成功
+	 * @return
+	 * @throws MtaException 
+	 */
+	@GetMapping("/{paynum}/ok")
+	public String payok(@PathVariable String paynum) throws MtaException{
+		Item pay = payService.get(paynum);
+		return Objects.nonNull(pay) && pay.getPayStatus()==PayService.STATUS_PAYED ? 
+				"redirect:/order/"+pay.getOrderId()+"/ok" : null;
+	}
+
+}

+ 17 - 0
src/main/java/com/llisoft/service/pay/dao/AppDao.java

@@ -0,0 +1,17 @@
+package com.llisoft.service.pay.dao;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+
+import com.llisoft.service.pay.entity.App;
+
+@Mapper
+public interface AppDao {
+
+	@Select("select * from mta_pay_app where app_id=#{appId}")
+	App select(int appId);
+	
+	@Select("select * from mta_pay_app where app_key=#{appKey}")
+	App selectByAppKey(String appKey);
+	
+}

+ 30 - 0
src/main/java/com/llisoft/service/pay/dao/ItemDao.java

@@ -0,0 +1,30 @@
+package com.llisoft.service.pay.dao;
+
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.SelectKey;
+import org.apache.ibatis.annotations.Update;
+
+import com.llisoft.service.pay.entity.Item;
+
+@Mapper
+public interface ItemDao {
+
+	@Select("select * from mta_pay_item where item_id=#{itemId}")
+	Item select(int payId);
+	
+	@Select("select * from mta_pay_item where item_num=#{itemNum}")
+	Item selectByPayNum(String itemNum);
+	
+    @Insert("insert into mta_pay_item (pay_id,pay_num,pay_status,pay_type,order_id,create_time) "
+    		+ "values (#{payId},#{payNum},#{payStatus},#{payType},#{orderId},now())")
+    @SelectKey(statement="SELECT LAST_INSERT_ID()", keyProperty="orderId", before=false, resultType=Integer.class)
+    boolean insert(Item item);
+
+    @Update("update mta_pay_item set pay_status=#{payStatus},pay_flag=#{payFlag},trade_num=#{tradeNum},pay_time=now() where auth_id=#{authId}")
+	boolean updatePay(@Param("payId")int payId, @Param("tradeNum")String tradeNum, 
+			@Param("payStatus")byte payStatus, @Param("payFlag")byte payFlag);
+	
+}

+ 29 - 0
src/main/java/com/llisoft/service/pay/dao/OrderDao.java

@@ -0,0 +1,29 @@
+package com.llisoft.service.pay.dao;
+
+import org.apache.ibatis.annotations.Insert;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.SelectKey;
+import org.apache.ibatis.annotations.Update;
+
+import com.llisoft.service.pay.entity.Order;
+
+@Mapper
+public interface OrderDao {
+
+	@Select("select * from mta_pay_order where order_id=#{orderId}")
+	Order select(int orderId);
+	
+	@Select("select * from mta_pay_order where order_num=#{order}")
+	Order selectByOrderNum(String orderNum);
+	
+    @Insert("insert into mta_pay_order (order_id,order_num,order_status,order_money,order_title,app_id,create_time) "
+    		+ "values (#{orderId},#{orderNum},#{orderStatus},#{orderMoney},#{orderTitle},#{appId},now())")
+    @SelectKey(statement="SELECT LAST_INSERT_ID()", keyProperty="orderId", before=false, resultType=Integer.class)
+    boolean insert(Order order);
+
+    @Update("update mta_pay_order set order_status=#{orderStatus},pay_time=now() where auth_id=#{authId}")
+	boolean updatePay(@Param("orderId")int orderId, @Param("orderStatus")byte orderStatus);
+	
+}

+ 113 - 0
src/main/java/com/llisoft/service/pay/entity/App.java

@@ -0,0 +1,113 @@
+package com.llisoft.service.pay.entity;
+
+import java.util.Date;
+
+/**
+ * 
+ * 数据库实体, 禁止人为改动
+ */
+public class App {
+    /**
+     * ID
+     */
+    private Integer appId;
+
+    /**
+     * 业务唯一标识
+     */
+    private String appKey;
+
+    /**
+     * 业务名称, 在第三方支付页面显示
+     */
+    private String appName;
+
+    /**
+     * 支付成功后异步通知地址(post/可选)
+     */
+    private String notifyUrl;
+
+    /**
+     * 支付成功后同步回调地址(get/可选)
+     */
+    private String returnUrl;
+
+    /**
+     * 是否测试状态(测试中支付金额为1分)
+     */
+    private Boolean debug;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    public Integer getAppId() {
+        return appId;
+    }
+
+    public void setAppId(Integer appId) {
+        this.appId = appId;
+    }
+
+    public String getAppKey() {
+        return appKey;
+    }
+
+    public void setAppKey(String appKey) {
+        this.appKey = appKey;
+    }
+
+    public String getAppName() {
+        return appName;
+    }
+
+    public void setAppName(String appName) {
+        this.appName = appName;
+    }
+
+    public String getNotifyUrl() {
+        return notifyUrl;
+    }
+
+    public void setNotifyUrl(String notifyUrl) {
+        this.notifyUrl = notifyUrl;
+    }
+
+    public String getReturnUrl() {
+        return returnUrl;
+    }
+
+    public void setReturnUrl(String returnUrl) {
+        this.returnUrl = returnUrl;
+    }
+
+    public Boolean getDebug() {
+        return debug;
+    }
+
+    public void setDebug(Boolean debug) {
+        this.debug = debug;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+}

+ 139 - 0
src/main/java/com/llisoft/service/pay/entity/Item.java

@@ -0,0 +1,139 @@
+package com.llisoft.service.pay.entity;
+
+import java.util.Date;
+
+/**
+ * 
+ * 数据库实体, 禁止人为改动
+ */
+public class Item {
+    /**
+     * ID
+     */
+    private Integer payId;
+
+    /**
+     * 支付号
+     */
+    private String payNum;
+
+    /**
+     * 支付状态(1未支付/2已支付)
+     */
+    private Byte payStatus;
+
+    /**
+     * 支付类型(1支付宝/2微信)
+     */
+    private Byte payType;
+
+    /**
+     * 支付成功标记(1异步通知/2同步回调/3主动查询/4对账)
+     */
+    private Byte payFlag;
+
+    /**
+     * 第三方交易号(由第三方通知返回)
+     */
+    private String tradeNum;
+
+    /**
+     * 订单ID
+     */
+    private Integer orderId;
+
+    /**
+     * 支付时间
+     */
+    private Date payTime;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    public Integer getPayId() {
+        return payId;
+    }
+
+    public void setPayId(Integer payId) {
+        this.payId = payId;
+    }
+
+    public String getPayNum() {
+        return payNum;
+    }
+
+    public void setPayNum(String payNum) {
+        this.payNum = payNum;
+    }
+
+    public Byte getPayStatus() {
+        return payStatus;
+    }
+
+    public void setPayStatus(Byte payStatus) {
+        this.payStatus = payStatus;
+    }
+
+    public Byte getPayType() {
+        return payType;
+    }
+
+    public void setPayType(Byte payType) {
+        this.payType = payType;
+    }
+
+    public Byte getPayFlag() {
+        return payFlag;
+    }
+
+    public void setPayFlag(Byte payFlag) {
+        this.payFlag = payFlag;
+    }
+
+    public String getTradeNum() {
+        return tradeNum;
+    }
+
+    public void setTradeNum(String tradeNum) {
+        this.tradeNum = tradeNum;
+    }
+
+    public Integer getOrderId() {
+        return orderId;
+    }
+
+    public void setOrderId(Integer orderId) {
+        this.orderId = orderId;
+    }
+
+    public Date getPayTime() {
+        return payTime;
+    }
+
+    public void setPayTime(Date payTime) {
+        this.payTime = payTime;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+}

+ 126 - 0
src/main/java/com/llisoft/service/pay/entity/Order.java

@@ -0,0 +1,126 @@
+package com.llisoft.service.pay.entity;
+
+import java.util.Date;
+
+/**
+ * 
+ * 数据库实体, 禁止人为改动
+ */
+public class Order {
+    /**
+     * ID
+     */
+    private Integer orderId;
+
+    /**
+     * 订单号
+     */
+    private String orderNum;
+
+    /**
+     * 订单状态(1待付款/2已付款)
+     */
+    private Byte orderStatus;
+
+    /**
+     * 订单金额(分)
+     */
+    private Integer orderMoney;
+
+    /**
+     * 订单标题
+     */
+    private String orderTitle;
+
+    /**
+     * 业务ID
+     */
+    private Integer appId;
+
+    /**
+     * 支付时间
+     */
+    private Date payTime;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    public Integer getOrderId() {
+        return orderId;
+    }
+
+    public void setOrderId(Integer orderId) {
+        this.orderId = orderId;
+    }
+
+    public String getOrderNum() {
+        return orderNum;
+    }
+
+    public void setOrderNum(String orderNum) {
+        this.orderNum = orderNum;
+    }
+
+    public Byte getOrderStatus() {
+        return orderStatus;
+    }
+
+    public void setOrderStatus(Byte orderStatus) {
+        this.orderStatus = orderStatus;
+    }
+
+    public Integer getOrderMoney() {
+        return orderMoney;
+    }
+
+    public void setOrderMoney(Integer orderMoney) {
+        this.orderMoney = orderMoney;
+    }
+
+    public String getOrderTitle() {
+        return orderTitle;
+    }
+
+    public void setOrderTitle(String orderTitle) {
+        this.orderTitle = orderTitle;
+    }
+
+    public Integer getAppId() {
+        return appId;
+    }
+
+    public void setAppId(Integer appId) {
+        this.appId = appId;
+    }
+
+    public Date getPayTime() {
+        return payTime;
+    }
+
+    public void setPayTime(Date payTime) {
+        this.payTime = payTime;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+}

+ 235 - 0
src/main/java/com/llisoft/service/pay/service/AliPayService.java

@@ -0,0 +1,235 @@
+package com.llisoft.service.pay.service;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.annotation.PostConstruct;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.AlipayTradePagePayModel;
+import com.alipay.api.domain.AlipayTradeQueryModel;
+import com.alipay.api.domain.AlipayTradeWapPayModel;
+import com.alipay.api.internal.util.AlipaySignature;
+import com.alipay.api.request.AlipayTradePagePayRequest;
+import com.alipay.api.request.AlipayTradeQueryRequest;
+import com.alipay.api.request.AlipayTradeWapPayRequest;
+import com.llisoft.common.exception.MtaException;
+import com.llisoft.common.util.JsonUtil;
+import com.llisoft.common.util.MoneyUtil;
+
+/**
+ * 支付宝支付
+ */
+@Service
+public class AliPayService {
+	
+	private Logger logger = LoggerFactory.getLogger(AliPayService.class);
+	
+	@Value("${mta.domain}")
+	private String domain;
+	
+	@Value("${mta.ali.appid}")
+	private String appid;
+	@Value("${mta.ali.serverUrl}")
+	private String serverUrl;
+	@Value("${mta.ali.publicKey}")
+	private String publicKey;
+	@Value("${mta.ali.privateKey}")
+	private String privateKey;
+	
+	/** json(固定) */
+	private String format = "json";
+	/** 编码集,支持GBK/UTF-8 */
+	private String charset = "UTF-8";
+	/** 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 */
+	private String signType = "RSA2";
+	
+	@Autowired
+	private PayService payService;
+	private AlipayClient alipayClient;
+	
+	
+	/**
+	 * 初始化支付宝客户端
+	 */
+	@PostConstruct
+	public void init() {
+		alipayClient = new DefaultAlipayClient(serverUrl, appid, privateKey, format, charset, publicKey, signType);
+	}
+	
+	
+	/**
+	 * 支付
+	 * @param paynum
+	 * @param money
+	 * @param subject
+	 * @return
+	 * https://docs.open.alipay.com/270/alipay.trade.page.pay
+	 */
+	public String pay(String paynum, int money, String subject) {
+		AlipayTradePagePayRequest payRequest = new AlipayTradePagePayRequest();
+		payRequest.setReturnUrl(domain+"/alipay/return");
+		payRequest.setNotifyUrl(domain+"/alipay/notify");
+		AlipayTradePagePayModel payModel = new AlipayTradePagePayModel();
+		payModel.setOutTradeNo(paynum); // Y 商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
+		payModel.setProductCode("FAST_INSTANT_TRADE_PAY"); // Y 销售产品码,与支付宝签约的产品码名称。 注:目前仅支持FAST_INSTANT_TRADE_PAY
+		payModel.setTotalAmount(MoneyUtil.toYuan(money)); // Y 订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]
+		payModel.setSubject(subject); // Y 订单标题
+//		payModel.setBody(""); // N 订单描述
+//		payModel.setPassbackParams(""); // N 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。支付宝只会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝
+//		payModel.setExtendParams(null); // N 业务扩展参数,详见https://docs.open.alipay.com/#kzcs
+		payRequest.setBizModel(payModel);
+		String result = null;
+		try {
+			result = alipayClient.pageExecute(payRequest).getBody();
+		} catch (AlipayApiException e) {
+			logger.error("请求支付宝支付接口(pc)异常: ", e);
+		}
+		logger.info("请求支付宝支付接口(pc)返回: {}", result);
+		return result;
+	}
+	
+	/**
+	 * 支付 移动端
+	 * @param paynum
+	 * @param money
+	 * @param subject
+	 * @return
+	 * https://docs.open.alipay.com/270/alipay.trade.page.pay
+	 */
+	public String paym(String paynum, int money, String subject) {
+		AlipayTradeWapPayRequest payRequest = new AlipayTradeWapPayRequest();
+		payRequest.setReturnUrl(domain+"/alipay/return");
+		payRequest.setNotifyUrl(domain+"/alipay/notify");
+		AlipayTradeWapPayModel payModel = new AlipayTradeWapPayModel();
+		payModel.setOutTradeNo(paynum); // Y 商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
+		payModel.setProductCode("FAST_INSTANT_TRADE_PAY"); // Y 销售产品码,与支付宝签约的产品码名称。 注:目前仅支持FAST_INSTANT_TRADE_PAY
+		payModel.setTotalAmount(MoneyUtil.toYuan(money)); // Y 订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]
+		payModel.setSubject(subject); // Y 订单标题
+//		payModel.setBody(""); // N 订单描述
+//		payModel.setPassbackParams(""); // N 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。支付宝只会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝
+//		payModel.setExtendParams(null); // N 业务扩展参数,详见https://docs.open.alipay.com/#kzcs
+		payRequest.setBizModel(payModel);
+		String result = null;
+		try {
+			result = alipayClient.pageExecute(payRequest).getBody();
+		} catch (AlipayApiException e) {
+			logger.error("请求支付宝支付接口(wap)异常: ", e);
+		}
+		logger.info("请求支付宝支付接口(wap)返回: {}", result);
+		return result;
+	}
+	
+	/**
+	 * 查询
+	 * @param paynum
+	 * @return
+	 * https://docs.open.alipay.com/api_1/alipay.trade.query
+	 */
+	public String query(String paynum) {
+		AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
+		AlipayTradeQueryModel queryModel = new AlipayTradeQueryModel();
+		queryModel.setOutTradeNo(paynum); // 商户订单号
+		request.setBizModel(queryModel);
+		String result = null;
+		try {
+			result = alipayClient.execute(request).getBody();
+		} catch (AlipayApiException e) {
+			logger.error("请求支付宝查询接口异常: ", e);
+		}
+		logger.info("请求支付宝查询接口返回: {}", result);
+		return result;
+	}
+	
+	/**
+	 * 处理异步通知
+	 * @param map
+	 * @return
+	 * @throws MtaException 
+	 */
+	public boolean doNotify(Map<String, String> map) throws MtaException {
+		// 验证签名
+		if(!this.checkSign(map)) {
+			logger.error("支付宝异步通知: 签名验证失败: {}", map);
+			return false;
+		}
+		// 验证支付宝交易状态
+		if(!this.checkStatus(map)) {
+			logger.error("支付宝异步通知: 交易状态异常: {}", map);
+			return false;
+		}
+		// 完成支付
+		String paynum = map.get("out_trade_no"); // paynum
+		String tradenum = map.get("trade_no"); // 支付宝交易号
+		int totalAmount = MoneyUtil.toFen(map.get("total_amount"));	// 实际支付金额
+		return payService.finish(paynum, tradenum, totalAmount, PayService.FLAG_NOTIFY);
+	}
+
+
+	/**
+	 * 处理同步回调
+	 * @throws MtaException 
+	 */
+	@SuppressWarnings("unchecked")
+	public boolean doReturn(Map<String, String> map) throws MtaException {
+		// 验证签名
+		if(!this.checkSign(map)) {
+			logger.error("支付宝同步回调: 签名验证失败: {}", map);
+			return false;
+		}
+		// 查询订单 (同步回调不可靠)
+		String paynum = map.get("out_trade_no");
+		
+		String result = this.query(paynum);
+		Map<String, Object> resultMap = JsonUtil.toObject(result, HashMap.class);
+		// {"alipay_trade_query_response":{"code":"10000","msg":"Success","buyer_logon_id":"ipb***@sandbox.com","buyer_pay_amount":"0.00","buyer_user_id":"2088102170394382","buyer_user_type":"PRIVATE","invoice_amount":"0.00","out_trade_no":"1529637131201603","point_amount":"0.00","receipt_amount":"0.00","send_pay_date":"2018-06-22 11:12:23","total_amount":"12.00","trade_no":"2018062221001004380200826678","trade_status":"TRADE_SUCCESS"},"sign":"gnWtxKDDhkRmoWnfwLVs2RGtt4GbiY+xaVs5+G47D5e9SnQHqGnElKme4jlDdnqhAroX3aqyJLVexohBjzA+DlpRzDhIu5EZLXnDr/B2FZEVjyiU66ImVH6vSyWyOVEjbGIgmVhUslhXqXYK5KxAx3QAdLrJLQeMO/gUdjs0qWcGO9yTU/suDy0YcdLSLlFyUutwD2MBz4Ri0tBk+uHjpxXoTZeb9/lQ7e4BixwCy+wB3hcESWN1b/m77YLKztmbLy67auhxzP9TRwQQ+6WRrTdMe/9rouIt6AW9T+6XSFd/LwL73Qo+1MfQa+iEUx2Iq+AYekhSgVyLJMRgz/qoZA=="}
+		Map<String, String> responseMap = (Map<String, String>) resultMap.get("alipay_trade_query_response");
+		if(!"10000".equals(responseMap.get("code"))) {
+			logger.error("支付宝同步回调: 查询状态异常: {}", responseMap);
+			return false;
+		}
+		// 验证支付宝交易状态
+		if(!this.checkStatus(responseMap)) {
+			logger.error("支付宝同步回调: 交易状态异常: {}", responseMap);
+			return false;
+		}
+		// 完成支付
+		String tradenum = responseMap.get("trade_no"); // 支付宝交易号
+		int totalAmount = MoneyUtil.toFen(map.get("total_amount"));	// 实际支付金额
+		return payService.finish(paynum, tradenum, totalAmount, PayService.FLAG_RETURN);
+	}
+
+	/**
+	 * 验证签名
+	 * @param map
+	 * @return
+	 */
+	private boolean checkSign(Map<String, String> map) {
+		boolean result = false;
+		try {
+			result = AlipaySignature.rsaCheckV1(map, publicKey, charset, signType);
+		} catch (AlipayApiException e) {
+			logger.error("支付宝异步通知: 签名验证异常: {}", e);
+		}
+		return result;
+	}
+	
+	/**
+	 * 验证状态
+	 * @param status
+	 * @return
+	 */
+	private boolean checkStatus(Map<String, String> map) {
+		String tradeStatus = map.get("trade_status"); // 支付宝交易状态 https://docs.open.alipay.com/#s1
+		return Objects.nonNull(tradeStatus) && ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISHED".equals(tradeStatus));
+	}
+}

+ 60 - 0
src/main/java/com/llisoft/service/pay/service/AppService.java

@@ -0,0 +1,60 @@
+package com.llisoft.service.pay.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.llisoft.service.pay.dao.AppDao;
+import com.llisoft.service.pay.entity.App;
+
+@Service
+public class AppService {
+	
+	@Autowired
+	private AppDao appDao;
+	
+	
+	/**
+	 * 获取
+	 * @param appKey
+	 * @return
+	 */
+	public App get(String appKey){
+		return appDao.selectByAppKey(appKey);
+	}
+	
+	/**
+	 * 获取异步通知地址
+	 * @param appId
+	 * @param orderNum
+	 * @return
+	 */
+	public String getNotifyUrl(int appId, String orderNum) {
+		return packUrl(appDao.select(appId).getNotifyUrl(), orderNum);
+	}
+	
+	/**
+	 * 获取同步通知地址
+	 * @param appId
+	 * @param orderNum
+	 * @return
+	 */
+	public String getReturnUrl(int appId, String orderNum) {
+		return packUrl(appDao.select(appId).getReturnUrl(), orderNum);
+	}
+	
+	/**
+	 * 封装回调地址
+	 * @param url
+	 * @param orderNum
+	 * @return
+	 */
+	public String packUrl(String url, String orderNum) {
+		if (url.contains("{orderNum}")) { // rest地址
+			url = url.replace("{orderNum}", orderNum);
+		}else{ // param地址
+			url = url + "?orderNum="+orderNum;
+		}
+		return url;
+	}
+	
+}

+ 130 - 0
src/main/java/com/llisoft/service/pay/service/OrderService.java

@@ -0,0 +1,130 @@
+package com.llisoft.service.pay.service;
+
+import java.util.Date;
+import java.util.Objects;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.llisoft.common.exception.MtaException;
+import com.llisoft.common.util.DateUtil;
+import com.llisoft.common.util.HttpUtil;
+import com.llisoft.common.util.JsonUtil;
+import com.llisoft.common.util.RandomUtil;
+import com.llisoft.service.pay.dao.OrderDao;
+import com.llisoft.service.pay.entity.App;
+import com.llisoft.service.pay.entity.Order;
+
+
+@Service
+public class OrderService {
+	
+	// 支付状态 未支付
+	public static final byte STATUS_WAIT = 1;
+	// 支付状态 已支付
+	public static final byte STATUS_PAYED = 2;
+	
+	private Logger logger = LoggerFactory.getLogger(OrderService.class);
+	
+	@Autowired
+	private OrderDao orderDao;
+	@Autowired
+	private AppService appService;
+	
+	
+	/**
+	 * 获取
+	 * @param orderId
+	 * @return
+	 * @throws MtaException 
+	 */
+	public Order get(int orderId) throws MtaException{
+		Order order = orderDao.select(orderId);
+		if (Objects.isNull(order)) {
+			throw new MtaException("订单不存在: " + orderId);
+		}
+		return order;
+	}
+	
+	/**
+	 * 获取
+	 * @param orderNum
+	 * @return
+	 * @throws MtaException 
+	 */
+	public Order getByOrderNum(String orderNum) throws MtaException{
+		Order order = orderDao.selectByOrderNum(orderNum);
+		if (Objects.isNull(order)) {
+			throw new MtaException("订单不存在: " + orderNum);
+		}
+		return order;
+	}
+	
+	/**
+	 * 添加
+	 * @param appKey 支付key(由支付服务为业务单独分配)
+	 * @param orderMoney 订单金额(分)
+	 * @param orderNum 订单号(如果客户端没有传会自动生成)
+	 * @param orderTitle 标题(如果客户端没有传会使用app的name)
+	 * @return
+	 * @throws MtaException 
+	 */
+	public String add(String appKey, int orderMoney, String orderNum, String orderTitle) throws MtaException{
+		// 验证入参合法性
+		if (Objects.isNull(appKey) || appKey.trim().isEmpty() || orderMoney<=0) {
+			throw new MtaException("非法入参: appKey="+appKey+", orderMoney="+orderMoney);
+		}
+		// 验证appKey合法性
+		App app = appService.get(appKey);
+		if (Objects.isNull(app)) {
+			throw new MtaException("appKey不存在: "+appKey);
+		}
+		// 验证订单号
+		if(Objects.nonNull(orderNum) && !orderNum.trim().isEmpty()) {
+			throw new MtaException("订单号重复: "+orderNum);
+		}
+		// 创建订单
+		if(Objects.isNull(orderNum) || orderNum.trim().isEmpty()) {
+			orderNum = DateUtil.formatMillisecond(new Date()) + RandomUtil.getRandomInt(5); // 当前时间+5位随机数
+		}
+		if(Objects.isNull(orderTitle) || orderTitle.trim().isEmpty()) {
+			orderTitle = app.getAppName(); // 没有传title时使用业务名称
+		}
+		Order order = new Order();
+		order.setOrderNum(orderNum);
+		order.setOrderMoney(orderMoney);
+		order.setOrderTitle(orderTitle); // 订单描述
+		order.setOrderStatus(STATUS_WAIT);
+		order.setAppId(app.getAppId());
+		orderDao.insert(order);
+		logger.info("添加订单成功: {}", orderNum);
+		return orderNum;
+	}
+	
+	/**
+	 * 完成
+	 * @param orderNum
+	 * @return
+	 * @throws MtaException 
+	 */
+	public boolean finish(int orderId) throws MtaException{
+		Order order = this.get(orderId);
+		if (Objects.isNull(order)) {
+			throw new MtaException("订单不存在: " + orderId);
+		}
+		// 更新订单状态
+		orderDao.updatePay(order.getOrderId(), STATUS_PAYED);
+		// 发送支付成功异步通知
+		String notifyUrl = appService.getNotifyUrl(order.getAppId(), order.getOrderNum());
+		if(Objects.nonNull(notifyUrl)) {
+			logger.info("订单完成: 异步通知业务地址: {}, 参数: {}", notifyUrl, JsonUtil.toJson(order));
+			String result = HttpUtil.postJson(notifyUrl, JsonUtil.toJson(order));
+			logger.info("订单完成: 业务端返回信息: {}", result);
+			return "success".equals(result);
+		}
+		return true;
+	}
+	
+}

+ 142 - 0
src/main/java/com/llisoft/service/pay/service/PayService.java

@@ -0,0 +1,142 @@
+package com.llisoft.service.pay.service;
+
+import java.util.Objects;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.llisoft.common.exception.MtaException;
+import com.llisoft.common.util.RandomUtil;
+import com.llisoft.service.pay.dao.ItemDao;
+import com.llisoft.service.pay.entity.Item;
+import com.llisoft.service.pay.entity.Order;
+
+@Service
+public class PayService {
+	
+	// 支付状态 未支付
+	public static final byte STATUS_WAIT = 1;
+	// 支付状态 已支付
+	public static final byte STATUS_PAYED = 2;
+	
+	// 支付类型 支付宝
+	public static final byte TYPE_ALI = 1;
+	// 支付类型 微信
+	public static final byte TYPE_WX = 2;
+	
+	// 支付标记 - 异步通知 
+	public static final byte FLAG_NOTIFY = 1;
+	// 支付标记 - 同步回调 
+	public static final byte FLAG_RETURN = 2;
+	// 支付标记 - 主动查询 
+	public static final byte FLAG_QUERY = 3;
+	// 支付标记 - 对账 
+	public static final byte FLAG_CHECK = 4;
+	
+	
+	private Logger logger = LoggerFactory.getLogger(PayService.class);
+	
+	@Autowired
+	private ItemDao payDao;
+	@Autowired
+	private OrderService orderService;
+	@Autowired
+	private AliPayService aliPayService;
+	
+	
+	/**
+	 * 获取
+	 * @param payNum
+	 * @return
+	 * @throws MtaException 
+	 */
+	public Item get(String payNum) throws MtaException{
+		Item pay = payDao.selectByPayNum(payNum);
+		if (Objects.isNull(pay)) {
+			throw new MtaException("支付记录不存在: " + payNum);
+		}
+		return pay;
+	}
+	
+	/**
+	 * 添加支付
+	 * @param orderNum 订单号
+	 * @param payType 支付类型
+	 * @return 返回支付跳转地址
+	 */
+	public String add(int orderId, byte payType){
+		String payNum = String.valueOf(System.currentTimeMillis() + RandomUtil.getRandomInt(5));
+		Item pay = new Item();
+		pay.setPayNum(payNum);
+		pay.setPayType(payType);
+		pay.setPayStatus(STATUS_WAIT);
+		pay.setOrderId(orderId);
+		payDao.insert(pay);
+		logger.info("添加支付成功: {}", payNum);
+		return payNum;
+	}
+	
+	/**
+	 * 发起支付
+	 * @param payNum 支付号
+	 * @param isMobile 是否移动端页面
+	 * @return
+	 * @throws MtaException 
+	 */
+	public String pay(String payNum, boolean isMobile) throws MtaException{
+		Item item = this.get(payNum);
+		if (Objects.isNull(item)) {
+			throw new MtaException("支付记录不存在: " + payNum);
+		}
+		Order order = orderService.get(item.getOrderId());
+		switch (item.getPayType()) {
+		case TYPE_ALI: // 支付宝
+			return isMobile ? aliPayService.paym(payNum, order.getOrderMoney(), order.getOrderTitle()) : 
+				aliPayService.pay(payNum, order.getOrderMoney(), order.getOrderTitle());
+		}
+		return null;
+	}
+	
+	/**
+	 * 完成支付
+	 * 验证订单信息 + 更新支付状态 + 更新订单状态(包括支付方式)
+	 * @param payNum
+	 * @param tradeNum 第三方交易号(由第三方通知返回)
+	 * @param money 实际支付金额(分)
+	 * @param flag 支付成功标记(1异步通知/2同步通知/3主动查询/4对账)
+	 * @return
+	 * @throws MtaException 
+	 */
+	@Transactional
+	public boolean finish(String payNum, String tradeNum, int money, byte flag) throws MtaException{
+		Item item = this.get(payNum);
+		Order order = orderService.get(item.getOrderId());
+		if (order.getOrderMoney() != money) { // 核对金额
+			throw new MtaException("支付金额异常: " + money);
+		}
+		// 更新支付状态
+		payDao.updatePay(item.getPayId(), tradeNum, STATUS_PAYED, flag);
+		logger.info("支付处理成功, 状态更新为已支付: {}", payNum);
+		// 更新订单
+		return orderService.finish(item.getOrderId());
+	}
+	
+	/**
+	 * 验证
+	 * @param payNum
+	 * @return
+	 * @throws MtaException 
+	 */
+	public boolean check(String payNum) throws MtaException {
+		Item pay = this.get(payNum);
+		if (pay.getPayStatus() == STATUS_PAYED) { // 已经处理过
+			logger.warn("支付结果处理: 已经处理过, 收到重复通知: {}", payNum);
+			return true;
+		}
+		return true;
+	}
+	
+}

+ 46 - 0
src/main/java/com/llisoft/service/pay/vo/OrderRequestVo.java

@@ -0,0 +1,46 @@
+package com.llisoft.service.pay.vo;
+
+/**
+ * 订单请求实体
+ */
+public class OrderRequestVo {
+
+    private String appkey;
+    private String ordernum; // 订单号 不传自动生成
+    private String title; // 商品描述 不传使用app的name 
+    private int money; // 单位: 分
+
+
+    public String getAppkey() {
+        return appkey;
+    }
+
+    public void setAppkey(String appkey) {
+        this.appkey = appkey == null ? null : appkey.trim();
+    }
+
+	public String getOrdernum() {
+		return ordernum;
+	}
+
+	public void setOrdernum(String ordernum) {
+        this.ordernum = ordernum == null ? null : ordernum.trim();
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public int getMoney() {
+		return money;
+	}
+
+	public void setMoney(int money) {
+		this.money = money;
+	}
+  
+}

+ 90 - 0
src/main/java/com/llisoft/service/pay/vo/OrderResponseVo.java

@@ -0,0 +1,90 @@
+package com.llisoft.service.pay.vo;
+
+import java.util.Date;
+
+/**
+ * 订单返回实体
+ */
+public class OrderResponseVo {
+	
+    private String appKey;
+    
+    private String orderNum;
+
+    private int orderMoney;
+
+    private String orderTitle;
+
+    private boolean payed;
+
+    private Date createTime;
+
+    private Date payTime;
+    
+    private String payUrl;
+    
+
+    public String getPayUrl() {
+		return payUrl;
+	}
+
+	public void setPayUrl(String payUrl) {
+		this.payUrl = payUrl;
+	}
+
+	public String getAppKey() {
+		return appKey;
+	}
+
+	public void setAppKey(String appKey) {
+		this.appKey = appKey;
+	}
+
+	public String getOrderNum() {
+		return orderNum;
+	}
+
+	public void setOrderNum(String orderNum) {
+		this.orderNum = orderNum;
+	}
+
+	public int getOrderMoney() {
+		return orderMoney;
+	}
+
+	public void setOrderMoney(int orderMoney) {
+		this.orderMoney = orderMoney;
+	}
+
+	public String getOrderTitle() {
+		return orderTitle;
+	}
+
+	public void setOrderTitle(String orderTitle) {
+		this.orderTitle = orderTitle;
+	}
+
+	public boolean isPayed() {
+		return payed;
+	}
+
+	public void setPayed(boolean payed) {
+		this.payed = payed;
+	}
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getPayTime() {
+        return payTime;
+    }
+
+    public void setPayTime(Date payTime) {
+        this.payTime = payTime;
+    }
+}

+ 22 - 0
src/main/resources/application.yml

@@ -5,6 +5,13 @@ server:
 ##自定义配置
 mta: 
   debug: true #是否测试
+  domain: http://localhost:8080  #本工程域名(用于支付回调)
+  ali: #支付宝
+    appid: 2016080300154811
+    serverUrl: https://openapi.alipaydev.com/gateway.do
+    publicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuHV05fB6aafE1lMvFtU/eawP0s4uFa2zZThPFGx80l1JZNWaMfQoa8+Gu8reRc/PRnx1N9syTc/zeZYZUfstPn22y8qWPLYwqxyJH53knCj9263M8P6Q4n+MF7pUVUQbkn0FTlyo8eWZlpCOJe9MDTTIhPdIMlrDn31HiFZ8TY0pySh5tEZzhYIaaJjmvQUdQuCWMwdq9uzVtHu++QFmbHCApZbekJcQ1ebaGv76DOiGpPPtlH3mrSGqR/1z0uVwC5iRn5vuax/73a/OPvQGHOVTBq2QBiZHRf23DPO7SMuCpampEVaXIwIW2weCfNnvLCWMQ+v0dxpOVIUx1BlLjwIDAQAB
+    privateKey: MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCZ2eFSd0f+SC3Djep7v3PwNludUmrPaiJNf6Wf3+5XVIVQGeomsFCIF1qS3hecj6x9k/JEayxQTDjMnwADZLMUcqsMm/OuOa+36kzSg/yFTs2Jq23GjjsHI6/g4ioj9inl9t4QfimBN6RczGzLWBUe1trhmNICFVJqbPomUgz19ey8uCaMbttIaHi8yNLTflFEHysl9LZSmNNBlz50MtLhIc77/UKbah2ot3xllHnVnvz0jXVTdMqIP//H4Oitcucy018EcMctgvdmFNG5VIste6yOmd+uox82QyFRRAngmw+66EpYHzmbwKpGHD0KKi/35ti4IGA+uoXxY50Xcm+tAgMBAAECggEACxvMRSQPsrCLfV812xGL0YBbRRS6WZeUOnxI/S4yC2Qy7AGzAnAgoHLvO0OF+Ov2zGpuR7plTLb0MvIYEianN4bACv0Hr0DDC0iDm3+kMAvsk1ApcPi8ElvlSxuJZ2dSxyP4DMDPRmE6IOkxwczuWXex/jAGmdfoR37u4O1zuh6Wu+JpKbYfTiwmhw0OAJBq1gm1wiTN9thDr1Qf4Ifr/k0Mv9TVnOWQLAg9qGGfQHEU/UtU92Rlp4S2WB2UMl8gpq0ZgC7brxeegS7OW2uv8LtTrdekhVzC62zyE+V47xQ1hukAv06Sw9+XgjI0fLHS3xxNCUOVWqwiOnhluJjHgQKBgQDinyhksk7pHKra9ryy03hGTPZt2LYGNCogfdIuG8ShNrXva6MV4TuGcgjcJUNed+w3QW/F9C/kRUP7VQvEL/j/1lfKy4FRWvui7Mr9LyouaTlANXyVc0KzoS3u3NIy5eKTOE0QrUNs7G4x16R84Gsx3GQOGUhmFi+WPhQ0SqkiiQKBgQCty7NoCjQ7g6DMIE7vcGuRAACAEZi0P9qLZvHmwujdJlyPctYX2/wcpT1bnn1FqyavSDKNrtSIIfa5GNCUzcv4ysBiYZlXNGDw4wleQ/ENG/DRj0A4AAOR3/bR5TaAY9QnFInuj2mlKvmQWuYvc6EnLDfo1OjuWc0bDkWpO6nrBQKBgQCzJ6OlR5k+jJ09kUXIEYnJ+dnvKR1tdhu/p/ha3zTpRfk6l1tMVszaEpiSgRrrOd8SJ7cXRV3/FgsSTD+LD2nHx0mMVqmbCVRZjt1CnuC35BOQgThGZeJbY5aOeR/rgPVH8YBrKK5fE+JhoNq2pivYNSUcSDyGCBACtH0Age2IEQKBgQCQMg1VSfoSUuDL3BaB9O1abfz8RR1EmCIUPUKBKsAYFKcBYc8eFqgzgCnLZVEcx6ceETHYefDeTgethVgxzhno/xflyIJ4Zv3zfvub2ZUbQk5pIIiwrdpIYuEfPyUcGze/gPv8EcMehexwB+sp/a78mGR+6n+kmp9hTlMf2V/enQKBgQDPH5exzUjktHYwlEhEsXOGCUh1gs7KI5d7FcWWubZiWgz3I2F4iVMW9yd1godxr0XUJMuWJqft0c0A/ayPDOCRwxvnxMDEB9qhA4hdfyBdUYKU4sDxn8NmT7/srMbSUyQIzbprA6UPg4Hh68nunvMwf/vX8VK4641y+W3rYYyDjw==
+  
   
 ##oss配置
 oss: 
@@ -23,6 +30,21 @@ spring:
   boot.admin.client:
     instance.preferIp: true #使用IP注册
     url: http://192.168.0.179:1111 #监控服务地址
+  ##数据源配置
+  datasource: 
+    driverClassName: com.mysql.jdbc.Driver
+    url: jdbc:mysql://192.168.0.172:3306/mta_platform?useUnicode=true&characterEncoding=utf8&useSSL=false
+    username: dkuser
+    password: dkuser
+    initialSize: 3        ##初始化连接数, 默认: 10
+    maxActive: 10     ##连接池中保留的最大连接数, 默认: 100
+    minIdle: 3          ##最小空闲连接数量, 默认: initialSize
+    maxIdle: 5         ##最大空闲连接数量, 默认: maxActive
+    testOnBorrow: true    ##访问前验证链接有效性, 默认: false
+    validationQuery: SELECT 1       ##验证数据库连接的有效性sql, 默认: null
+    validationInterval: 30000         ##验证数据库连接频率(毫秒), 默认: 30000
+    removeAbandoned: true           ##是否进行无用链接回收, 默认: false
+    removeAbandonedTimeout: 60    ##链接有效期,超时将被回收(秒), 默认: 60
     
 ##注册中心配置
 eureka:

+ 1 - 9
src/test/java/com/llisoft/service/file/ServiceTest.java → src/test/java/com/llisoft/service/pay/ServiceTest.java

@@ -1,25 +1,17 @@
-package com.llisoft.service.file;
+package com.llisoft.service.pay;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 
-import com.llisoft.common.util.JsonUtil;
-import com.llisoft.service.file.service.OssService;
-
 @SpringBootTest
 @RunWith(SpringRunner.class)
 public class ServiceTest {
 	
-	@Autowired
-	private OssService ossService;
-	
 	@Test
 	public void test() throws Exception{
 		long begin = System.currentTimeMillis();
-		System.out.println(JsonUtil.toJson(ossService.upload("", "", true)));
 		System.out.println("耗时:"+ (System.currentTimeMillis() - begin));
 	}