|
@@ -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));
|
|
|
+ }
|
|
|
+}
|