瀏覽代碼

重构,utils-pay

dengsixing 3 年之前
父節點
當前提交
4ad695b648
共有 22 個文件被更改,包括 2485 次插入0 次删除
  1. 77 0
      4dkankan-utils-pay/pom.xml
  2. 92 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/AlipayDefaultConfig.java
  3. 81 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayConfig.java
  4. 60 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayGoodsDetail.java
  5. 161 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayService.java
  6. 38 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayUtil.java
  7. 113 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipaymentEx.java
  8. 56 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/PayPalDefaultConfig.java
  9. 43 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PayPalConfig.java
  10. 130 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PayPalmentEx.java
  11. 88 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalOrderAddressEx.java
  12. 59 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalOrderItemEx.java
  13. 5 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalPaymentIntent.java
  14. 5 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalPaymentMethod.java
  15. 268 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalService.java
  16. 66 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/UrlUtils.java
  17. 64 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/WXPayDefaultConfig.java
  18. 673 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPay.java
  19. 53 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayConfig.java
  20. 45 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayConstants.java
  21. 307 0
      4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayUtil.java
  22. 1 0
      pom.xml

+ 77 - 0
4dkankan-utils-pay/pom.xml

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>4dkankan-utils</artifactId>
+    <groupId>com.fdkankan</groupId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>4dkankan-utils-pay</artifactId>
+
+  <dependencies>
+
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.alipay</groupId>
+      <artifactId>alipay-sdk-java</artifactId>
+      <version>20170324180803</version>
+    </dependency>
+    <dependency>
+      <groupId>com.alipay</groupId>
+      <artifactId>alipay-trade-sdk</artifactId>
+      <version>20161215</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.zxing</groupId>
+      <artifactId>core</artifactId>
+      <version>2.1</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-configuration</groupId>
+      <artifactId>commons-configuration</artifactId>
+      <version>1.10</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.paypal.sdk</groupId>
+      <artifactId>rest-api-sdk</artifactId>
+      <version>1.14.0</version>
+    </dependency>
+
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>javax.servlet-api</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-tx</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.alibaba</groupId>
+      <artifactId>fastjson</artifactId>
+    </dependency>
+  </dependencies>
+
+
+</project>

File diff suppressed because it is too large
+ 92 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/AlipayDefaultConfig.java


+ 81 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayConfig.java

@@ -0,0 +1,81 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import com.alipay.demo.trade.service.AlipayTradeService;
+import com.alipay.demo.trade.service.impl.AlipayTradeServiceImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+@Component
+public abstract class AlipayConfig {
+
+    /** 签名类型 */
+    private String signType = "RSA2";
+    /** 格式 */
+    private String formate = "json";
+    /** 编码 */
+    private String charset = "UTF-8";
+    /** 最大查询次数 */
+    private int maxQueryRetry = 5;
+    /** 查询间隔(毫秒) */
+    private long queryDuration = 5000;
+    /** 最大撤销次数 */
+    private int maxCancelRetry = 3;
+    /** 撤销间隔(毫秒) */
+    private long cancelDuration = 3000;
+
+    /** 支付宝gatewayUrl */
+    abstract public String getGatewayUrl();
+
+    /** 商户应用id */
+    abstract public String getAppid();
+
+    /** RSA私钥,用于对商户请求报文加签 */
+    abstract public String getAppPrivateKey();
+
+    /** 支付宝RSA公钥,用于验签支付宝应答 */
+    abstract public String getAlipayPublicKey();
+
+    /** 同步地址 */
+    abstract public String getReturnUrl();
+    /** 异步地址 */
+    abstract public String getNotifyUrl();
+
+    public String getSignType(){
+        return signType;
+    }
+
+    public String getFormate(){
+        return formate;
+    }
+
+    public String getCharset(){
+        return charset;
+    }
+
+    public int getMaxQueryRetry() {
+        return maxQueryRetry;
+    }
+
+    public long getQueryDuration() {
+        return queryDuration;
+    }
+
+    public int getMaxCancelRetry() {
+        return maxCancelRetry;
+    }
+
+    public long getCancelDuration() {
+        return cancelDuration;
+    }
+
+    @Bean
+    public AlipayTradeService alipayTradeService() {
+        return new AlipayTradeServiceImpl.ClientBuilder()
+                .setGatewayUrl(getGatewayUrl())
+                .setAppid(getAppid())
+                .setPrivateKey(getAppPrivateKey())
+                .setAlipayPublicKey(getAlipayPublicKey())
+                .setSignType(getSignType())
+                .build();
+    }
+}

+ 60 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayGoodsDetail.java

@@ -0,0 +1,60 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import java.io.Serializable;
+
+public class AlipayGoodsDetail implements Serializable {
+
+    private static final long serialVersionUID = -8875896136641533825L;
+
+    public AlipayGoodsDetail() {
+    }
+
+    public static AlipayGoodsDetail newInstance(String goodsId, String goodsName, long price, int quantity) {
+        AlipayGoodsDetail info = new AlipayGoodsDetail();
+        info.setGoodsId(goodsId);
+        info.setGoodsName(goodsName);
+        info.setPrice(price);
+        info.setQuantity(quantity);
+        return info;
+    }
+    // 必填,商品的编号,length=32
+    private String goodsId;
+    // 必填,商品的名称,length=256
+    private String goodsName;
+    // 必填,商品数量
+    private int quantity;
+    // 必填,商品单价,单位为元
+    private long price;
+
+    public String getGoodsId() {
+        return goodsId;
+    }
+
+    public void setGoodsId(String goodsId) {
+        this.goodsId = goodsId;
+    }
+
+    public String getGoodsName() {
+        return goodsName;
+    }
+
+    public void setGoodsName(String goodsName) {
+        this.goodsName = goodsName;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public long getPrice() {
+        return price;
+    }
+
+    public void setPrice(long price) {
+        this.price = price;
+    }
+}

+ 161 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayService.java

@@ -0,0 +1,161 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayResponse;
+import com.alipay.api.domain.TradeFundBill;
+import com.alipay.api.response.AlipayTradePrecreateResponse;
+import com.alipay.api.response.AlipayTradeQueryResponse;
+import com.alipay.demo.trade.model.GoodsDetail;
+import com.alipay.demo.trade.model.builder.AlipayTradePrecreateRequestBuilder;
+import com.alipay.demo.trade.model.builder.AlipayTradeQueryRequestBuilder;
+import com.alipay.demo.trade.model.result.AlipayF2FPrecreateResult;
+import com.alipay.demo.trade.model.result.AlipayF2FQueryResult;
+import com.alipay.demo.trade.service.AlipayTradeService;
+import com.alipay.demo.trade.utils.Utils;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class AlipayService {
+    private static Logger log = LoggerFactory.getLogger("programLog");
+
+    @Autowired
+    private AlipayTradeService alipayTradeService;
+
+    /**
+     * 当面付-扫码付
+     *
+     * 扫码支付,指用户打开支付宝钱包中的“扫一扫”功能,扫描商户针对每个订单实时生成的订单二维码,并在手机端确认支付。
+     *
+     * 发起预下单请求,同步返回订单二维码
+     *
+     * 适用场景:商家获取二维码展示在屏幕上,然后用户去扫描屏幕上的二维码
+     * @return
+     * @throws AlipayApiException
+     */
+    public Map<String, String> tradePrecreate(AlipaymentEx alipaymentEx, String notifyUrl) throws AlipayApiException {
+        if (StringUtils.isEmpty(alipaymentEx.getOutTradeNo())){
+            throw new AlipayApiException("outTradeNo should not be null");
+        }else if (StringUtils.isEmpty(alipaymentEx.getSubject())){
+            throw new AlipayApiException("subject should not be null");
+        }else if (StringUtils.isEmpty(alipaymentEx.getBody())){
+            throw new AlipayApiException("body should not be null");
+        }else if (alipaymentEx.getTotalAmount() == null){
+            throw new AlipayApiException("totalAmout should not be null");
+        }else if (StringUtils.isEmpty(alipaymentEx.getStoreId())){
+            throw new AlipayApiException("storeid should not be null");
+        }else if (alipaymentEx.getGoodsDetailList() == null || alipaymentEx.getGoodsDetailList().size() == 0){
+            throw new AlipayApiException("goodsDetailList should not be null");
+        }else if (StringUtils.isEmpty(notifyUrl)){
+            throw new AlipayApiException("notifyUrl should not be null");
+        }
+
+        List<AlipayGoodsDetail> alipayGoodsDetails = alipaymentEx.getGoodsDetailList();
+        List<GoodsDetail> goodsDetailList = new ArrayList<>();
+        for (AlipayGoodsDetail alipayGoodsDetail : alipayGoodsDetails){
+            GoodsDetail goods1 = GoodsDetail.newInstance(alipayGoodsDetail.getGoodsId(),
+                    alipayGoodsDetail.getGoodsName(), alipayGoodsDetail.getPrice(), alipayGoodsDetail.getQuantity());
+            goodsDetailList.add(goods1);
+        }
+
+        AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
+                .setSubject(alipaymentEx.getSubject())
+                .setTotalAmount(alipaymentEx.getTotalAmount().toString())
+                .setOutTradeNo(alipaymentEx.getOutTradeNo())
+                .setUndiscountableAmount(alipaymentEx.getUndiscountableAmount() == null ? "" : alipaymentEx.getUndiscountableAmount().toString())
+                .setSellerId(alipaymentEx.getSellerId() == null ? "" : alipaymentEx.getSellerId())
+                .setBody(alipaymentEx.getBody())
+                .setOperatorId(alipaymentEx.getOperatorId() == null ? "" : alipaymentEx.getOperatorId())
+                .setStoreId(alipaymentEx.getStoreId())
+                .setTimeoutExpress(alipaymentEx.getTimeoutExpress())
+                //支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
+                .setNotifyUrl(notifyUrl)
+                .setGoodsDetailList(goodsDetailList);
+
+        Map<String, String> resp = new HashMap<>();
+        AlipayF2FPrecreateResult result = alipayTradeService.tradePrecreate(builder);
+        AlipayTradePrecreateResponse res = result.getResponse();
+        switch (result.getTradeStatus()) {
+            case SUCCESS:
+                dumpResponse(res);
+                log.info("支付宝预下单成功。");
+                resp.put("code", "0");
+                resp.put("out_trade_no", res.getOutTradeNo());
+                resp.put("qr_code", res.getQrCode());
+                break;
+
+            case FAILED:
+                log.error("支付宝预下单失败,code:" + res.getCode() + ",msg:" + res.getMsg());
+                resp.put("code", "-1");
+                break;
+
+            case UNKNOWN:
+                log.error("系统异常,预下单状态未知,code:" + res.getCode() + ",msg:" + res.getMsg());
+                resp.put("code", "-2");
+                break;
+
+            default:
+                log.error("不支持的交易状态,交易返回异常,code:" + res.getCode() + ",msg:" + res.getMsg());
+                resp.put("code", "-3");
+                break;
+        }
+        return resp;
+    }
+
+    public String tradeQuery(String outTradeNo) {
+        // (必填) 商户订单号,通过此商户订单号查询当面付的交易状态
+        String Type;
+        // 创建查询请求builder,设置请求参数
+        AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder()
+                .setOutTradeNo(outTradeNo);
+        System.out.println("请求参数:" + builder);
+        AlipayF2FQueryResult result = alipayTradeService.queryTradeResult(builder);
+        switch (result.getTradeStatus()) {
+            case SUCCESS:
+                log.info("查询返回该订单支付成功: )");
+                Type = "SUCCESS";
+                AlipayTradeQueryResponse response = result.getResponse();
+                dumpResponse(response);
+                log.info(response.getTradeStatus());
+                if (Utils.isListNotEmpty(response.getFundBillList())) {
+                    for (TradeFundBill bill : response.getFundBillList()) {
+                        log.info(bill.getFundChannel() + ":" + bill.getAmount());
+                    }
+                }
+                break;
+            case FAILED:
+                log.error("查询返回该订单支付失败或被关闭!!!");
+                Type = "FAILED";
+                break;
+            case UNKNOWN:
+                log.error("系统异常,订单支付状态未知!!!");
+                Type = "UNKNOWN";
+                break;
+            default:
+                log.error("不支持的交易状态,交易返回异常!!!");
+                Type = "default";
+                break;
+        }
+        return Type;
+    }
+
+    // 简单打印应答
+    private void dumpResponse(AlipayResponse response) {
+        if (response != null) {
+            log.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
+            if (StringUtils.isNotEmpty(response.getSubCode())) {
+                log.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
+                        response.getSubMsg()));
+            }
+            log.info("body:" + response.getBody());
+        }
+    }
+}

+ 38 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipayUtil.java

@@ -0,0 +1,38 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import java.awt.image.BufferedImage;
+import java.util.Hashtable;
+import java.util.Map;
+
+public class AlipayUtil {
+
+    /**
+     * 根据url生成二位图片对象
+     *
+     * @param codeUrl
+     * @return
+     * @throws WriterException
+     */
+    public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
+        Map<EncodeHintType, Object> hints = new Hashtable();
+        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
+        int width = 300;
+        BitMatrix bitMatrix = (new MultiFormatWriter()).encode(codeUrl, BarcodeFormat.QR_CODE, width, width, hints);
+        BufferedImage image = new BufferedImage(width, width, 1);
+        for(int x = 0; x < width; ++x) {
+            for(int y = 0; y < width; ++y) {
+                image.setRGB(x, y, bitMatrix.get(x, y) ? -16777216 : -1);
+            }
+        }
+
+        return image;
+    }
+}

+ 113 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/alipay/sdk/AlipaymentEx.java

@@ -0,0 +1,113 @@
+package com.fdkankan.pay.alipay.sdk;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+public class AlipaymentEx implements Serializable {
+
+    private static final long serialVersionUID = 8082279987959002563L;
+    // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
+    private String outTradeNo;
+    // (必填) 订单标题,粗略描述用户的支付目的。如“喜士多(浦东店)消费”
+    private String subject;
+    // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
+    private String body;
+    // (必填) 订单总金额,单位为元,不能超过1亿元
+    // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
+    private BigDecimal totalAmount;
+    // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
+    // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
+    private BigDecimal undiscountableAmount;
+    // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
+    // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
+    private String sellerId;
+    // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
+    private String storeId;
+    // 商户操作员编号,添加此参数可以为商户操作员做销售统计
+    private String operatorId;
+    // 支付超时,线下扫码交易定义为5分钟
+    private String timeoutExpress = "5m";
+    // 商品明细列表,需填写购买商品详细信息
+    private List<AlipayGoodsDetail> goodsDetailList;
+
+    public String getOutTradeNo() {
+        return outTradeNo;
+    }
+
+    public void setOutTradeNo(String outTradeNo) {
+        this.outTradeNo = outTradeNo;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public void setBody(String body) {
+        this.body = body;
+    }
+
+    public BigDecimal getTotalAmount() {
+        return totalAmount;
+    }
+
+    public void setTotalAmount(BigDecimal totalAmount) {
+        this.totalAmount = totalAmount;
+    }
+
+    public BigDecimal getUndiscountableAmount() {
+        return undiscountableAmount;
+    }
+
+    public void setUndiscountableAmount(BigDecimal undiscountableAmount) {
+        this.undiscountableAmount = undiscountableAmount;
+    }
+
+    public String getSellerId() {
+        return sellerId;
+    }
+
+    public void setSellerId(String sellerId) {
+        this.sellerId = sellerId;
+    }
+
+    public String getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(String storeId) {
+        this.storeId = storeId;
+    }
+
+    public String getOperatorId() {
+        return operatorId;
+    }
+
+    public void setOperatorId(String operatorId) {
+        this.operatorId = operatorId;
+    }
+
+    public String getTimeoutExpress() {
+        return timeoutExpress;
+    }
+
+    public void setTimeoutExpress(String timeoutExpress) {
+        this.timeoutExpress = timeoutExpress;
+    }
+
+    public List<AlipayGoodsDetail> getGoodsDetailList() {
+        return goodsDetailList;
+    }
+
+    public void setGoodsDetailList(List<AlipayGoodsDetail> goodsDetailList) {
+        this.goodsDetailList = goodsDetailList;
+    }
+}

+ 56 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/PayPalDefaultConfig.java

@@ -0,0 +1,56 @@
+package com.fdkankan.pay.paypal;
+
+import com.fdkankan.pay.paypal.sdk.PayPalConfig;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PayPalDefaultConfig extends PayPalConfig {
+
+    @Override
+    public String getClientId() {
+        return "AZJEGWlvvI2q52bR4k_mC1ftW8tEnlaJj30huGQTBsdAjwmKlMDiEiMixVKbfrdw6fB55NSj_BAE8FPP";
+    }
+
+    @Override
+    public String getSecret() {
+        return "EL-RGNmsbFpcKT7QuIlxMxX7MQplp8rCyaGDZ5KOCMQ9BkOhY5OYZyVInAeHT8_4tXoPth8tOEZY_3s_";
+    }
+
+    @Override
+    public String getMode() {
+        return "live";
+    }
+
+    public String getPaySuccessUrl(){
+        return "api/order/pay/paypal/callback";
+    }
+
+    public String getH5PaySuccessUrl(){
+        return "api/order/pay/paypal/callbackH5";
+    }
+
+    public String getCancelUrl(){
+        return "api/order/pay/paypal/cancel";
+    }
+
+    public String getH5CancelUrl(){
+        return "api/order/pay/paypal/cancelH5";
+    }
+
+    public String getSuccessUrl(){
+        return "index.html#/payresult/success/recharge";
+    }
+
+    public String getFailUrl(){
+        return "#/payresult/fail/recharge";
+    }
+
+    public String getH5SuccessUrl(){
+        return "mobile.html#/payresult/success";
+    }
+
+    public String getH5FailUrl(){
+        return "mobile.html#/payresult/fail";
+    }
+
+}

+ 43 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PayPalConfig.java

@@ -0,0 +1,43 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import com.paypal.base.rest.APIContext;
+import com.paypal.base.rest.OAuthTokenCredential;
+import com.paypal.base.rest.PayPalRESTException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Component
+public abstract class PayPalConfig {
+
+    abstract public String getClientId();
+
+    abstract public String getSecret();
+
+    abstract public String getMode();
+
+    @Bean
+    public Map<String, String> paypalSdkConfig(){
+        Map<String, String> sdkConfig = new HashMap<>();
+        sdkConfig.put("mode", getMode());
+        return sdkConfig;
+    }
+
+    @Bean
+    public OAuthTokenCredential authTokenCredential(){
+        return new OAuthTokenCredential(getClientId(), getSecret(), paypalSdkConfig());
+    }
+
+    @Bean
+    public APIContext apiContext() throws PayPalRESTException {
+        String accessToken = authTokenCredential().getAccessToken();
+        log.info("paypal新的accessToken:" + accessToken);
+        APIContext apiContext = new APIContext(accessToken);
+        apiContext.setConfigurationMap(paypalSdkConfig());
+        return apiContext;
+    }
+}

+ 130 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PayPalmentEx.java

@@ -0,0 +1,130 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+public class PayPalmentEx implements Serializable {
+
+    private static final long serialVersionUID = 5027458587257867876L;
+    /**
+     * 订单Id
+     */
+    private String orderSn;
+    /**
+     * 支付的总价
+     */
+    private BigDecimal orderTotal;
+    /**
+     * 商品总价
+     */
+    private BigDecimal subTotal;
+    /**
+     * 税费
+     */
+    private BigDecimal tax;
+    /**
+     * 描述
+     */
+    private String description;
+    /**
+     * 成功回调url
+     */
+    private String successUrl;
+    /**
+     * 支付失败url
+     */
+    private String failUrl;
+    /**
+     * 取消支付url
+     */
+    private String cancelUrl;
+    /**
+     * 收货地址
+     */
+    private PaypalOrderAddressEx paypayOrderAddressEx;
+    /**
+     * 订单商品明细
+     */
+    private List<PaypalOrderItemEx> paypalOrderItemExList;
+
+    public String getOrderSn() {
+        return orderSn;
+    }
+
+    public void setOrderSn(String orderSn) {
+        this.orderSn = orderSn;
+    }
+
+    public BigDecimal getOrderTotal() {
+        return orderTotal;
+    }
+
+    public void setOrderTotal(BigDecimal orderTotal) {
+        this.orderTotal = orderTotal;
+    }
+
+    public BigDecimal getSubTotal() {
+        return subTotal;
+    }
+
+    public void setSubTotal(BigDecimal subTotal) {
+        this.subTotal = subTotal;
+    }
+
+    public BigDecimal getTax() {
+        return tax;
+    }
+
+    public void setTax(BigDecimal tax) {
+        this.tax = tax;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getSuccessUrl() {
+        return successUrl;
+    }
+
+    public void setSuccessUrl(String successUrl) {
+        this.successUrl = successUrl;
+    }
+
+    public String getFailUrl() {
+        return failUrl;
+    }
+
+    public void setFailUrl(String failUrl) {
+        this.failUrl = failUrl;
+    }
+
+    public String getCancelUrl() {
+        return cancelUrl;
+    }
+
+    public void setCancelUrl(String cancelUrl) {
+        this.cancelUrl = cancelUrl;
+    }
+
+    public PaypalOrderAddressEx getPaypayOrderAddressEx() {
+        return paypayOrderAddressEx;
+    }
+
+    public void setPaypayOrderAddressEx(PaypalOrderAddressEx paypayOrderAddressEx) {
+        this.paypayOrderAddressEx = paypayOrderAddressEx;
+    }
+
+    public List<PaypalOrderItemEx> getPaypalOrderItemExList() {
+        return paypalOrderItemExList;
+    }
+
+    public void setPaypalOrderItemExList(List<PaypalOrderItemEx> paypalOrderItemExList) {
+        this.paypalOrderItemExList = paypalOrderItemExList;
+    }
+}

+ 88 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalOrderAddressEx.java

@@ -0,0 +1,88 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import java.io.Serializable;
+
+public class PaypalOrderAddressEx implements Serializable {
+
+    private static final long serialVersionUID = 5667747195118555705L;
+
+    private String firstName;
+
+    private String lastName;
+
+    private String countryCode;
+
+    private String state;
+
+    private String city;
+
+    private String address;
+
+    private String phone;
+
+    private String postalCode;
+
+    public String getFirstName() {
+        return firstName;
+    }
+
+    public void setFirstName(String firstName) {
+        this.firstName = firstName;
+    }
+
+    public String getLastName() {
+        return lastName;
+    }
+
+    public void setLastName(String lastName) {
+        this.lastName = lastName;
+    }
+
+    public String getCountryCode() {
+        return countryCode;
+    }
+
+    public void setCountryCode(String countryCode) {
+        this.countryCode = countryCode;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    public String getCity() {
+        return city;
+    }
+
+    public void setCity(String city) {
+        this.city = city;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public String getPostalCode() {
+        return postalCode;
+    }
+
+    public void setPostalCode(String postalCode) {
+        this.postalCode = postalCode;
+    }
+}

+ 59 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalOrderItemEx.java

@@ -0,0 +1,59 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+public class PaypalOrderItemEx implements Serializable {
+
+    private static final long serialVersionUID = 7764234386178614511L;
+
+    private String sku;
+
+    private String name;
+
+    private BigDecimal price;
+
+    private int quantity;
+
+    private String currency;
+
+    public String getSku() {
+        return sku;
+    }
+
+    public void setSku(String sku) {
+        this.sku = sku;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public void setCurrency(String currency) {
+        this.currency = currency;
+    }
+}

+ 5 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalPaymentIntent.java

@@ -0,0 +1,5 @@
+package com.fdkankan.pay.paypal.sdk;
+
+public enum PaypalPaymentIntent {
+	sale, authorize, order
+}

+ 5 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalPaymentMethod.java

@@ -0,0 +1,5 @@
+package com.fdkankan.pay.paypal.sdk;
+
+public enum PaypalPaymentMethod {
+	credit_card, paypal
+}

+ 268 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/PaypalService.java

@@ -0,0 +1,268 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.fdkankan.pay.paypal.PayPalDefaultConfig;
+import com.paypal.api.payments.*;
+import com.paypal.base.codec.binary.Base64;
+import com.paypal.base.rest.APIContext;
+import com.paypal.base.rest.PayPalRESTException;
+import org.apache.commons.lang.StringUtils;
+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 java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class PaypalService {
+    private static Logger log = LoggerFactory.getLogger("programLog");
+
+    @Autowired
+    private APIContext apiContext;
+    @Autowired
+    private PayPalDefaultConfig config;
+
+    private static final String CURRENCY = "USD";
+
+    /**
+     * 创建支付
+     *
+     * @param paymentEx
+     * @return
+     * @throws PayPalRESTException
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public Payment createPayment(PayPalmentEx paymentEx, String successUrl, String cancelUrl) throws PayPalRESTException {
+        if (StringUtils.isEmpty(successUrl) || StringUtils.isEmpty(cancelUrl)) {
+            throw new PayPalRESTException("缺少url");
+        }
+        Transaction transaction = new Transaction();
+        transaction.setDescription(paymentEx.getDescription());
+        // 将我们的订单ID保存到支付信息中,用于后面支付回传
+        if (null != paymentEx.getOrderSn()) {
+            transaction.setCustom(paymentEx.getOrderSn());
+        }
+        //订单价格
+        Amount amount = new Amount();
+        amount.setCurrency(CURRENCY);
+        // 支付的总价,paypal会校验 total = subTotal + tax + ...
+        amount.setTotal(paymentEx.getOrderTotal().toString());
+        // 设置各种费用
+        Details details = new Details();
+        // 商品总价
+        if (paymentEx.getSubTotal() != null) {
+            details.setSubtotal(paymentEx.getSubTotal().toString());
+        }
+        // 税费
+        if (paymentEx.getTax() != null) {
+            details.setTax(paymentEx.getTax().toString());
+        }
+
+        amount.setDetails(details);
+
+        transaction.setAmount(amount);
+
+        ItemList itemList = new ItemList();
+        // 收货地址
+        PaypalOrderAddressEx orderAddress = paymentEx.getPaypayOrderAddressEx();
+        if (orderAddress != null) {
+            ShippingAddress shippingAddress = new ShippingAddress();
+            if (StringUtils.isNotEmpty(orderAddress.getFirstName()) && StringUtils.isNotEmpty(orderAddress.getLastName())) {
+                shippingAddress.setRecipientName(orderAddress.getFirstName() + "." + orderAddress.getLastName());
+            }
+            shippingAddress.setCountryCode(orderAddress.getCountryCode());
+            shippingAddress.setState(orderAddress.getState());
+            shippingAddress.setCity(orderAddress.getCity());
+            shippingAddress.setLine1(orderAddress.getAddress());
+            shippingAddress.setPhone(orderAddress.getPhone());
+            shippingAddress.setPostalCode(orderAddress.getPostalCode());
+
+            itemList.setShippingAddress(shippingAddress);
+            itemList.setShippingPhoneNumber(orderAddress.getPhone());
+        }
+
+        // 商品明细
+        List<PaypalOrderItemEx> orderItemList = paymentEx.getPaypalOrderItemExList();
+        if (orderItemList != null && orderItemList.size() > 0) {
+            List<Item> items = new ArrayList<>();
+            for (PaypalOrderItemEx orderItemEx : orderItemList) {
+                Item item = new Item();
+                item.setSku(orderItemEx.getSku());
+                item.setName(orderItemEx.getName());
+                item.setPrice(orderItemEx.getPrice().toString());
+                item.setQuantity(String.valueOf(orderItemEx.getQuantity()));
+                item.setCurrency(CURRENCY);
+                items.add(item);
+            }
+            itemList.setItems(items);
+            transaction.setItemList(itemList);
+        }
+
+        List<Transaction> transactions = new ArrayList<>();
+        transactions.add(transaction);
+
+        // 支付信息
+        Payer payer = new Payer();
+        payer.setPaymentMethod(PaypalPaymentMethod.paypal.toString());
+
+        Payment payment = new Payment();
+        //刷新accessToken时间;
+        apiContext = new APIContext("Bearer " + getAccessToken());
+        apiContext.setConfigurationMap(config.paypalSdkConfig());
+
+        payment.setIntent(PaypalPaymentIntent.sale.toString());
+        payment.setPayer(payer);
+        payment.setTransactions(transactions);
+
+        //回调地址
+        RedirectUrls redirectUrls = new RedirectUrls();
+        redirectUrls.setReturnUrl(successUrl);
+        redirectUrls.setCancelUrl(cancelUrl);
+        payment.setRedirectUrls(redirectUrls);
+
+        return payment.create(apiContext);
+    }
+
+    /**
+     * 执行支付
+     *
+     * @param paymentId
+     * @param payerId
+     * @return
+     * @throws PayPalRESTException
+     */
+    public Payment executePayment(String paymentId, String payerId) throws PayPalRESTException {
+        Payment payment = new Payment();
+        payment.setId(paymentId);
+        PaymentExecution paymentExecute = new PaymentExecution();
+        paymentExecute.setPayerId(payerId);
+        apiContext = new APIContext("Bearer " + getAccessToken());
+        apiContext.setConfigurationMap(config.paypalSdkConfig());
+        return payment.execute(apiContext, paymentExecute);
+    }
+
+    /**
+      * 获取token
+      * 了解更多:https://developer.paypal.com/webapps/developer/docs/integration/mobile/verify-mobile-payment/
+      * @return
+      */
+    private String getAccessToken() {
+        BufferedReader reader = null;
+        try {
+            URL url = new URL(UrlUtils.TOKEN_URL);
+            String authorization = config.getClientId() + ":" + config.getSecret();
+            authorization = Base64.encodeBase64String(authorization.getBytes());
+
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("POST");// 提交模式
+            //设置请求头header
+            conn.setRequestProperty("Accept", "application/json");
+            conn.setRequestProperty("Accept-Language", "en_US");
+            conn.setRequestProperty("Authorization", "Basic " + authorization);
+            // conn.setConnectTimeout(10000);//连接超时 单位毫秒
+            // conn.setReadTimeout(2000);//读取超时 单位毫秒
+            conn.setDoOutput(true);// 是否输入参数
+            String params = "grant_type=client_credentials";
+            conn.getOutputStream().write(params.getBytes());// 输入参数
+
+            InputStreamReader inStream = new InputStreamReader(conn.getInputStream());
+            reader = new BufferedReader(inStream);
+            StringBuilder result = new StringBuilder();
+            String lineTxt = null;
+            while ((lineTxt = reader.readLine()) != null) {
+                result.append(lineTxt);
+            }
+            reader.close();
+            String accessTokey = JSONObject.parseObject(result.toString()).getString("access_token");
+            log.info("getAccessToken:" + accessTokey);
+            return accessTokey;
+        } catch (Exception err) {
+            err.printStackTrace();
+        } finally {
+            if (reader != null){
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return null;
+    }
+
+    public String getPaymentDetails(String paymentId) {
+        BufferedReader reader = null;
+        try {
+            URL url = new URL(UrlUtils.PAYMENT_DETAIL + paymentId);
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("GET");// 提交模式
+            //设置请求头header
+            conn.setRequestProperty("Accept", "application/json");
+            conn.setRequestProperty("Authorization", "Bearer " + getAccessToken());
+            // conn.setConnectTimeout(10000);//连接超时 单位毫秒
+            // conn.setReadTimeout(2000);//读取超时 单位毫秒
+            InputStreamReader inStream = new InputStreamReader(conn.getInputStream());
+            reader = new BufferedReader(inStream);
+            StringBuilder result = new StringBuilder();
+            String lineTxt = null;
+            while ((lineTxt = reader.readLine()) != null) {
+                result.append(lineTxt);
+            }
+            reader.close();
+            return result.toString();
+        } catch (Exception err) {
+            err.printStackTrace();
+        }finally {
+            if (reader != null){
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return null;
+    }
+
+    public boolean verifyPayment(String paymentId){
+        //从PayPal获取付款详情
+        String str = getPaymentDetails(paymentId);
+        JSONObject detail = JSONObject.parseObject(str);
+        //校验订单是否完成
+        if("approved".equals(detail.getString("state"))){
+            JSONObject transactions = detail.getJSONArray("transactions").getJSONObject(0);
+            JSONObject amount = transactions.getJSONObject("amount");
+            JSONArray relatedResources = transactions.getJSONArray("related_resources");
+            //从数据库查询总金额与Paypal校验支付总金额
+            double total = 0;
+            if( total != amount.getDouble("total") ){
+                return false;
+            }
+            //校验交易货币类型
+            String currency = "USD";
+            if( !currency.equals(amount.getString("currency")) ){
+                return false;
+            }
+            //校验每个子订单是否完成
+            for (int i = 0,j = relatedResources.size(); i < j; i++) {
+                JSONObject sale = relatedResources.getJSONObject(i).getJSONObject("sale");
+                if( !"completed".equals(sale.getString("state")) ){
+                    System.out.println("子订单未完成,订单状态:"+sale.getString("state"));
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+
+}

+ 66 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/paypal/sdk/UrlUtils.java

@@ -0,0 +1,66 @@
+package com.fdkankan.pay.paypal.sdk;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class UrlUtils {
+
+    //沙箱链接
+//    public static final String TOKEN_URL = "https://api.sandbox.paypal.com/v1/oauth2/token";
+    public static final String TOKEN_URL = "https://api.paypal.com/v1/oauth2/token";
+    public static final String PAYMENT_DETAIL = "https://api.sandbox.paypal.com/v1/payments/payment/";
+
+    public static String getBaseURl(HttpServletRequest request) {
+        String scheme = request.getScheme();
+        String serverName = request.getServerName();
+        int serverPort = request.getServerPort();
+        String contextPath = request.getContextPath();
+        StringBuffer url =  new StringBuffer();
+        url.append(scheme).append("://").append(serverName);
+        if ((serverPort != 80) && (serverPort != 443)) {
+            url.append(":").append(serverPort);
+        }
+        url.append(contextPath);
+        if(url.toString().endsWith("/")){
+            url.append("/");
+        }
+        return url.toString();
+    }
+
+    /**
+     * 获取用户实际ip
+     * @param request
+     * @return
+     */
+    public static String getIpAddr(HttpServletRequest request){
+        String ipAddress = request.getHeader("x-forwarded-for");
+        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getHeader("Proxy-Client-IP");
+        }
+        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getRemoteAddr();
+            if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
+                //根据网卡取本机配置的IP
+                InetAddress inet=null;
+                try {
+                    inet = InetAddress.getLocalHost();
+                } catch (UnknownHostException e) {
+                    e.printStackTrace();
+                }
+                ipAddress= inet.getHostAddress();
+            }
+        }
+        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+        if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
+            if(ipAddress.indexOf(",")>0){
+                ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
+            }
+        }
+        return ipAddress;
+    }
+
+}

+ 64 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/WXPayDefaultConfig.java

@@ -0,0 +1,64 @@
+package com.fdkankan.pay.wx;
+
+import com.fdkankan.pay.wx.sdk.WXPayConfig;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+public class WXPayDefaultConfig implements WXPayConfig {
+
+    private byte[] certData;
+
+    public WXPayDefaultConfig() throws Exception {
+        /*String certPath = CONSTANTFILEPATH.WEIXINCERT;
+        File file = new File(certPath);
+        InputStream certStream = new FileInputStream(file);
+        this.certData = new byte[(int) file.length()];
+        certStream.read(this.certData);
+        certStream.close();*/
+    }
+
+    public String getAppID() {
+        return "wx779dbafb46bab697";
+    }
+
+    public String getMchID() {
+        return "1505605401";
+    }
+
+    public String getKey() {
+        return "4DAGE1684DAGE1684DAGE1684DAGE168";
+    }
+
+    public String getSecret() {
+        return "b578c41b560f37c2c2c1981114830d23";
+    }
+
+    public String getCreateIP() {
+        return "47.104.99.106";
+    }
+
+    public String getH5RedirectURL() {
+        return "mobile.html#/check";
+    }
+
+    public String getNotifyURL() {
+        return "api/order/pay/wechatPay/notify";
+    }
+
+    public String getOrderURL() {
+        return "https://api.mch.weixin.qq.com/pay/unifiedorder";
+    }
+
+    public InputStream getCertStream() {
+        return new ByteArrayInputStream(this.certData);
+    }
+
+    public int getHttpConnectTimeoutMs() {
+        return 8000;
+    }
+
+    public int getHttpReadTimeoutMs() {
+        return 10000;
+    }
+}

+ 673 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPay.java

@@ -0,0 +1,673 @@
+package com.fdkankan.pay.wx.sdk;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+public class WXPay {
+
+    private WXPayConfig config;
+    private WXPayConstants.SignType signType;
+    private boolean useSandbox;
+
+    public WXPay(final WXPayConfig config) {
+        this(config, WXPayConstants.SignType.MD5, false);
+    }
+
+    public WXPay(final WXPayConfig config, final WXPayConstants.SignType signType) {
+        this(config, signType, false);
+    }
+
+    public WXPay(final WXPayConfig config, final WXPayConstants.SignType signType, final boolean useSandbox) {
+        this.config = config;
+        this.signType = signType;
+        this.useSandbox = useSandbox;
+    }
+
+
+    /**
+     * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
+     * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
+     *
+     * @param reqData
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
+        reqData.put("appid", config.getAppID());
+        reqData.put("mch_id", config.getMchID());
+        reqData.put("nonce_str", WXPayUtil.generateNonceStr());
+        if (WXPayConstants.SignType.MD5.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.MD5);
+        }
+        else if (WXPayConstants.SignType.HMACSHA256.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.HMACSHA256);
+        }
+        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
+        return reqData;
+    }
+
+    /**
+     * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
+        // 返回数据的签名方式和请求中给定的签名方式是一致的
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
+    }
+
+    /**
+     * 判断支付结果通知中的sign是否有效
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) throws Exception {
+        String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
+        WXPayConstants.SignType signType;
+        if (signTypeInData == null) {
+            signType = WXPayConstants.SignType.MD5;
+        }
+        else {
+            signTypeInData = signTypeInData.trim();
+            if (signTypeInData.length() == 0) {
+                signType = WXPayConstants.SignType.MD5;
+            }
+            else if (WXPayConstants.MD5.equals(signTypeInData)) {
+                signType = WXPayConstants.SignType.MD5;
+            }
+            else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
+                signType = WXPayConstants.SignType.HMACSHA256;
+            }
+            else {
+                throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
+            }
+        }
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);
+    }
+
+
+    /**
+     * 不需要证书的请求
+     * @param strUrl String
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public String requestWithoutCert(String strUrl, Map<String, String> reqData,
+                                     int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String UTF8 = "UTF-8";
+        String reqBody = WXPayUtil.mapToXml(reqData);
+        URL httpUrl = new URL(strUrl);
+        HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
+        httpURLConnection.setDoOutput(true);
+        httpURLConnection.setRequestMethod("POST");
+        httpURLConnection.setConnectTimeout(connectTimeoutMs);
+        httpURLConnection.setReadTimeout(readTimeoutMs);
+        httpURLConnection.connect();
+        OutputStream outputStream = httpURLConnection.getOutputStream();
+        outputStream.write(reqBody.getBytes(UTF8));
+
+        // if (httpURLConnection.getResponseCode()!= 200) {
+        //     throw new Exception(String.format("HTTP response code is %d, not 200", httpURLConnection.getResponseCode()));
+        // }
+
+        //获取内容
+        InputStream inputStream = httpURLConnection.getInputStream();
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
+        final StringBuffer stringBuffer = new StringBuffer();
+        String line = null;
+        while ((line = bufferedReader.readLine()) != null) {
+            stringBuffer.append(line).append("\n");
+        }
+        String resp = stringBuffer.toString();
+        if (stringBuffer!=null) {
+            try {
+                bufferedReader.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (inputStream!=null) {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (outputStream!=null) {
+            try {
+                outputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        // if (httpURLConnection!=null) {
+        //     httpURLConnection.disconnect();
+        // }
+
+        return resp;
+    }
+
+
+    /**
+     * 需要证书的请求
+     * @param strUrl String
+     * @param reqData 向wxpay post的请求数据  Map
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public String requestWithCert(String strUrl, Map<String, String> reqData,
+                                  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String UTF8 = "UTF-8";
+        String reqBody = WXPayUtil.mapToXml(reqData);
+        URL httpUrl = new URL(strUrl);
+        char[] password = config.getMchID().toCharArray();
+        InputStream certStream = config.getCertStream();
+        KeyStore ks = KeyStore.getInstance("PKCS12");
+        ks.load(certStream, password);
+
+        // 实例化密钥库 & 初始化密钥工厂
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        kmf.init(ks, password);
+
+        // 创建SSLContext
+        SSLContext sslContext = SSLContext.getInstance("TLS");
+        sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
+        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
+
+        HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
+
+        httpURLConnection.setDoOutput(true);
+        httpURLConnection.setRequestMethod("POST");
+        httpURLConnection.setConnectTimeout(connectTimeoutMs);
+        httpURLConnection.setReadTimeout(readTimeoutMs);
+        httpURLConnection.connect();
+        OutputStream outputStream = httpURLConnection.getOutputStream();
+        outputStream.write(reqBody.getBytes(UTF8));
+
+        // if (httpURLConnection.getResponseCode()!= 200) {
+        //     throw new Exception(String.format("HTTP response code is %d, not 200", httpURLConnection.getResponseCode()));
+        // }
+
+        //获取内容
+        InputStream inputStream = httpURLConnection.getInputStream();
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
+        final StringBuffer stringBuffer = new StringBuffer();
+        String line = null;
+        while ((line = bufferedReader.readLine()) != null) {
+            stringBuffer.append(line);
+        }
+        String resp = stringBuffer.toString();
+        if (stringBuffer!=null) {
+            try {
+                bufferedReader.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        if (inputStream!=null) {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        if (outputStream!=null) {
+            try {
+                outputStream.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        if (certStream!=null) {
+            try {
+                certStream.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        // if (httpURLConnection!=null) {
+        //     httpURLConnection.disconnect();
+        // }
+
+        return resp;
+    }
+
+    /**
+     * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
+     * @param xmlStr API返回的XML格式数据
+     * @return Map类型数据
+     * @throws Exception
+     */
+    public Map<String, String> processResponseXml(String xmlStr) throws Exception {
+        String RETURN_CODE = "return_code";
+        String return_code;
+        Map<String, String> respData = WXPayUtil.xmlToMap(xmlStr);
+        if (respData.containsKey(RETURN_CODE)) {
+            return_code = respData.get(RETURN_CODE);
+        }
+        else {
+            throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
+        }
+
+        if (return_code.equals(WXPayConstants.FAIL)) {
+            return respData;
+        }
+        else if (return_code.equals(WXPayConstants.SUCCESS)) {
+           if (this.isResponseSignatureValid(respData)) {
+               return respData;
+           }
+           else {
+               throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
+           }
+        }
+        else {
+            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
+        }
+    }
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> microPay(Map<String, String> reqData) throws Exception {
+        return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_MICROPAY_URL;
+        }
+        else {
+            url = WXPayConstants.MICROPAY_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
+        return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> unifiedOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL;
+        }
+        else {
+            url = WXPayConstants.UNIFIEDORDER_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception {
+        return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据 int
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_ORDERQUERY_URL;
+        }
+        else {
+            url = WXPayConstants.ORDERQUERY_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData) throws Exception {
+        return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REVERSE_URL;
+        }
+        else {
+            url = WXPayConstants.REVERSE_URL;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception {
+        return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_CLOSEORDER_URL;
+        }
+        else {
+            url = WXPayConstants.CLOSEORDER_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData) throws Exception {
+        return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUND_URL;
+        }
+        else {
+            url = WXPayConstants.REFUND_URL;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception {
+        return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUNDQUERY_URL;
+        }
+        else {
+            url = WXPayConstants.REFUNDQUERY_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception {
+        return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:对账单下载<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:无论是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data,
+     *      其中return_code为`SUCCESS`,data为对账单数据。
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return 经过封装的API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL;
+        }
+        else {
+            url = WXPayConstants.DOWNLOADBILL_URL;
+        }
+        String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();
+        Map<String, String> ret;
+        // 出现错误,返回XML数据
+        if (respStr.indexOf("<") == 0) {
+            ret = WXPayUtil.xmlToMap(respStr);
+        }
+        else {
+            // 正常返回csv数据
+            ret = new HashMap<String, String>();
+            ret.put("return_code", WXPayConstants.SUCCESS);
+            ret.put("return_msg", "sdk");
+            ret.put("data", respStr);
+        }
+        return ret;
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData) throws Exception {
+        return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REPORT_URL;
+        }
+        else {
+            url = WXPayConstants.REPORT_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return WXPayUtil.xmlToMap(respXml);
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception {
+        return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_SHORTURL_URL;
+        }
+        else {
+            url = WXPayConstants.SHORTURL_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception {
+        return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL;
+        }
+        else {
+            url = WXPayConstants.AUTHCODETOOPENID_URL;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+} // end class

+ 53 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayConfig.java

@@ -0,0 +1,53 @@
+package com.fdkankan.pay.wx.sdk;
+
+import java.io.InputStream;
+
+public interface WXPayConfig {
+
+
+    /**
+     * 获取 App ID
+     *
+     * @return App ID
+     */
+    public String getAppID();
+
+
+    /**
+     * 获取 Mch ID
+     *
+     * @return Mch ID
+     */
+    public String getMchID();
+
+
+    /**
+     * 获取 API 密钥
+     *
+     * @return API密钥
+     */
+    public String getKey();
+
+
+    /**
+     * 获取商户证书内容
+     *
+     * @return 商户证书内容
+     */
+    public InputStream getCertStream();
+
+    /**
+     * HTTP(S) 连接超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpConnectTimeoutMs();
+
+    /**
+     * HTTP(S) 读数据超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpReadTimeoutMs();
+
+}

+ 45 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayConstants.java

@@ -0,0 +1,45 @@
+package com.fdkankan.pay.wx.sdk;
+
+/**
+ * 常量
+ */
+public class WXPayConstants {
+
+    public enum SignType {
+        MD5, HMACSHA256
+    }
+
+    public static final String FAIL     = "FAIL";
+    public static final String SUCCESS  = "SUCCESS";
+    public static final String HMACSHA256 = "HMAC-SHA256";
+    public static final String MD5 = "MD5";
+
+    public static final String FIELD_SIGN = "sign";
+    public static final String FIELD_SIGN_TYPE = "sign_type";
+
+    public static final String MICROPAY_URL     = "https://api.mch.weixin.qq.com/pay/micropay";
+    public static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
+    public static final String ORDERQUERY_URL   = "https://api.mch.weixin.qq.com/pay/orderquery";
+    public static final String REVERSE_URL      = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
+    public static final String CLOSEORDER_URL   = "https://api.mch.weixin.qq.com/pay/closeorder";
+    public static final String REFUND_URL       = "https://api.mch.weixin.qq.com/secapi/pay/refund";
+    public static final String REFUNDQUERY_URL  = "https://api.mch.weixin.qq.com/pay/refundquery";
+    public static final String DOWNLOADBILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";
+    public static final String REPORT_URL       = "https://api.mch.weixin.qq.com/payitil/report";
+    public static final String SHORTURL_URL     = "https://api.mch.weixin.qq.com/tools/shorturl";
+    public static final String AUTHCODETOOPENID_URL = "https://api.mch.weixin.qq.com/tools/authcodetoopenid";
+
+    // sandbox
+    public static final String SANDBOX_MICROPAY_URL     = "https://api.mch.weixin.qq.com/sandboxnew/pay/micropay";
+    public static final String SANDBOX_UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder";
+    public static final String SANDBOX_ORDERQUERY_URL   = "https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery";
+    public static final String SANDBOX_REVERSE_URL      = "https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/reverse";
+    public static final String SANDBOX_CLOSEORDER_URL   = "https://api.mch.weixin.qq.com/sandboxnew/pay/closeorder";
+    public static final String SANDBOX_REFUND_URL       = "https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/refund";
+    public static final String SANDBOX_REFUNDQUERY_URL  = "https://api.mch.weixin.qq.com/sandboxnew/pay/refundquery";
+    public static final String SANDBOX_DOWNLOADBILL_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/downloadbill";
+    public static final String SANDBOX_REPORT_URL       = "https://api.mch.weixin.qq.com/sandboxnew/payitil/report";
+    public static final String SANDBOX_SHORTURL_URL     = "https://api.mch.weixin.qq.com/sandboxnew/tools/shorturl";
+    public static final String SANDBOX_AUTHCODETOOPENID_URL = "https://api.mch.weixin.qq.com/sandboxnew/tools/authcodetoopenid";
+
+}

+ 307 - 0
4dkankan-utils-pay/src/main/java/com/fdkankan/pay/wx/sdk/WXPayUtil.java

@@ -0,0 +1,307 @@
+package com.fdkankan.pay.wx.sdk;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.security.MessageDigest;
+import java.util.*;
+
+
+public class WXPayUtil {
+
+    /**
+     * XML格式字符串转换为Map
+     *
+     * @param strXML XML字符串
+     * @return XML数据转换后的Map
+     * @throws Exception
+     */
+    public static Map<String, String> xmlToMap(String strXML) throws Exception {
+        Map<String, String> data = new HashMap<String, String>();
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+
+        // 禁用XML外部实体注入
+        /**
+         * XXE
+         * XML 外部实体注入漏洞(XML External Entity Injection,简称 XXE),
+         * 是一种容易被忽视,但危害巨大的漏洞。它可以利用 XML 外部实体加载注入,
+         * 执行不可预控的代码,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
+         */
+
+        /**
+         * 原理:
+         * 通常,我们在使用微信支付时,商家会有一个通知的 URL 来接收异步支付结果。
+         * 然而,微信在 JAVA 版本的 SDK 存在一个 XXE 漏洞来处理这个结果。
+         * 由此攻击者可以向通知的 URL 中构建恶意的回调数据,以便根据需要窃取商家服务器上的任意信息。
+         * 一旦攻击者获得商家的关键安全密钥(md5-key 和merchant-Id),
+         * 那么他们可以通过发送伪造的信息来欺骗商家而无需付费购买任意商品。
+         */
+        documentBuilderFactory.setExpandEntityReferences(false);
+        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
+        InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
+        org.w3c.dom.Document doc = documentBuilder.parse(stream);
+        doc.getDocumentElement().normalize();
+        NodeList nodeList = doc.getDocumentElement().getChildNodes();
+        for (int idx=0; idx<nodeList.getLength(); ++idx) {
+            Node node = nodeList.item(idx);
+            if (node.getNodeType() == Node.ELEMENT_NODE) {
+                org.w3c.dom.Element element = (org.w3c.dom.Element) node;
+                data.put(element.getNodeName(), element.getTextContent());
+            }
+        }
+        try {
+            stream.close();
+        }
+        catch (Exception ex) {
+
+        }
+        return data;
+    }
+
+    /**
+     * 将Map转换为XML格式的字符串
+     *
+     * @param data Map类型数据
+     * @return XML格式的字符串
+     * @throws Exception
+     */
+    public static String mapToXml(Map<String, String> data) throws Exception {
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
+        org.w3c.dom.Document document = documentBuilder.newDocument();
+        org.w3c.dom.Element root = document.createElement("xml");
+        document.appendChild(root);
+        for (String key: data.keySet()) {
+            String value = data.get(key);
+            if (value == null) {
+                value = "";
+            }
+            value = value.trim();
+            org.w3c.dom.Element filed = document.createElement(key);
+            filed.appendChild(document.createTextNode(value));
+            root.appendChild(filed);
+        }
+        TransformerFactory tf = TransformerFactory.newInstance();
+        Transformer transformer = tf.newTransformer();
+        DOMSource source = new DOMSource(document);
+        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+        StringWriter writer = new StringWriter();
+        StreamResult result = new StreamResult(writer);
+        transformer.transform(source, result);
+        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
+        try {
+            writer.close();
+        }
+        catch (Exception ex) {
+        }
+        return output;
+    }
+
+
+    /**
+     * 生成带有 sign 的 XML 格式字符串
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @return 含有sign字段的XML
+     */
+    public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
+        return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 生成带有 sign 的 XML 格式字符串
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @param signType 签名类型
+     * @return 含有sign字段的XML
+     */
+    public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        String sign = generateSignature(data, key, signType);
+        data.put(WXPayConstants.FIELD_SIGN, sign);
+        return mapToXml(data);
+    }
+
+
+    /**
+     * 判断签名是否正确
+     *
+     * @param xmlStr XML格式数据
+     * @param key API密钥
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
+        Map<String, String> data = xmlToMap(xmlStr);
+        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
+            return false;
+        }
+        String sign = data.get(WXPayConstants.FIELD_SIGN);
+        return generateSignature(data, key).equals(sign);
+    }
+
+    /**
+     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
+        return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 判断签名是否正确,必须包含sign字段,否则返回false。
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @param signType 签名方式
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
+            return false;
+        }
+        String sign = data.get(WXPayConstants.FIELD_SIGN);
+        return generateSignature(data, key, signType).equals(sign);
+    }
+
+    /**
+     * 生成签名
+     *
+     * @param data 待签名数据
+     * @param key API密钥
+     * @return 签名
+     */
+    public static String generateSignature(final Map<String, String> data, String key) throws Exception {
+        return generateSignature(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
+     *
+     * @param data 待签名数据
+     * @param key API密钥
+     * @param signType 签名方式
+     * @return 签名
+     */
+    public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        Set<String> keySet = data.keySet();
+        String[] keyArray = keySet.toArray(new String[keySet.size()]);
+        Arrays.sort(keyArray);
+        StringBuilder sb = new StringBuilder();
+        for (String k : keyArray) {
+            if (k.equals(WXPayConstants.FIELD_SIGN)) {
+                continue;
+            }
+            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
+                sb.append(k).append("=").append(data.get(k).trim()).append("&");
+        }
+        sb.append("key=").append(key);
+        if (WXPayConstants.SignType.MD5.equals(signType)) {
+            return MD5(sb.toString()).toUpperCase();
+        }
+        else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
+            return HMACSHA256(sb.toString(), key);
+        }
+        else {
+            throw new Exception(String.format("Invalid sign_type: %s", signType));
+        }
+    }
+
+
+    /**
+     * 获取随机字符串 Nonce Str
+     *
+     * @return String 随机字符串
+     */
+    public static String generateNonceStr() {
+        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
+    }
+
+
+    /**
+     * 生成 MD5
+     *
+     * @param data 待处理数据
+     * @return MD5结果
+     */
+    public static String MD5(String data) throws Exception {
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        byte[] array = md.digest(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
+
+    /**
+     * 生成 HMACSHA256
+     * @param data 待处理数据
+     * @param key 密钥
+     * @return 加密结果
+     * @throws Exception
+     */
+    public static String HMACSHA256(String data, String key) throws Exception {
+        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
+        sha256_HMAC.init(secret_key);
+        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
+
+    /**
+     * 根据url生成二位图片对象
+     *
+     * @param codeUrl
+     * @return
+     * @throws WriterException
+     */
+    public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
+        int width = 300;
+        //根据url生成二维码
+        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
+        // 设置二维码参数
+        Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+        BitMatrix bitMatrix = multiFormatWriter.encode(codeUrl, BarcodeFormat.QR_CODE, width, width, hints);
+        BufferedImage image = new BufferedImage(width, width, 1);
+        for(int x = 0; x < width; ++x) {
+            for(int y = 0; y < width; ++y) {
+                image.setRGB(x, y, bitMatrix.get(x, y) ? -16777216 : -1);
+            }
+        }
+        return image;
+
+    }
+}

+ 1 - 0
pom.xml

@@ -17,6 +17,7 @@
         <module>4dkankan-utils-rabbitmq</module>
         <module>4dkankan-utils-rubber-sheeting</module>
         <module>4dkankan-common-web</module>
+      <module>4dkankan-utils-pay</module>
     </modules>
 
     <groupId>com.fdkankan</groupId>