responseMap = WXPayUtil.xmlToMap(responseXml);
+
+ return responseMap;
+ }
+
+
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/configuration/WXPayConfiguration.java b/springboot-pay-example/src/main/java/com/example/pay/configuration/WXPayConfiguration.java
new file mode 100644
index 0000000..5d9068d
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/configuration/WXPayConfiguration.java
@@ -0,0 +1,34 @@
+package com.example.pay.configuration;
+
+
+import com.github.wxpay.sdk.WXPay;
+import com.github.wxpay.sdk.WXPayConstants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 微信支付配置
+ */
+@Configuration
+@EnableConfigurationProperties(MyWXPayConfig.class)
+public class WXPayConfiguration {
+
+ @Autowired
+ private MyWXPayConfig wxPayConfig;
+
+ /**
+ * useSandbox 沙盒环境
+ * @return
+ */
+ @Bean
+ public WXPay wxPay() {
+ return new WXPay(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox() );
+ }
+
+ @Bean
+ public WXPayClient wxPayClient() {
+ return new WXPayClient(wxPayConfig, WXPayConstants.SignType.MD5, wxPayConfig.getUseSandbox());
+ }
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/configuration/WebMvcConfiguration.java b/springboot-pay-example/src/main/java/com/example/pay/configuration/WebMvcConfiguration.java
new file mode 100644
index 0000000..e1c407d
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/configuration/WebMvcConfiguration.java
@@ -0,0 +1,21 @@
+package com.example.pay.configuration;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
+
+/**
+ * WebMVC 配置.
+ *
+ * 添加路径和页面的映射关系
+ *
+ */
+@Configuration
+public class WebMvcConfiguration extends WebMvcConfigurationSupport {
+ @Override
+ protected void addViewControllers(ViewControllerRegistry registry) {
+ registry.addViewController("/gotoWapPage").setViewName("gotoWapPay");
+ registry.addViewController("/gotoPagePage").setViewName("gotoPagePay");
+ super.addViewControllers(registry);
+ }
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayController.java b/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayController.java
new file mode 100644
index 0000000..472bc5d
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayController.java
@@ -0,0 +1,252 @@
+package com.example.pay.controller;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.domain.AlipayTradeCloseModel;
+import com.alipay.api.domain.AlipayTradeFastpayRefundQueryModel;
+import com.alipay.api.domain.AlipayTradeRefundModel;
+import com.alipay.api.internal.util.AlipaySignature;
+import com.alipay.api.request.AlipayTradeCloseRequest;
+import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
+import com.alipay.api.request.AlipayTradeRefundRequest;
+import com.alipay.api.response.AlipayTradeCloseResponse;
+import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
+import com.alipay.api.response.AlipayTradeQueryResponse;
+import com.alipay.api.response.AlipayTradeRefundResponse;
+import com.alipay.demo.trade.model.builder.AlipayTradeQueryRequestBuilder;
+import com.alipay.demo.trade.model.result.AlipayF2FQueryResult;
+import com.alipay.demo.trade.service.AlipayTradeService;
+import com.example.pay.configuration.AlipayProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 支付宝通用接口
+ */
+@Slf4j
+@RestController
+@RequestMapping("/alipay")
+public class AlipayController {
+
+ @Autowired
+ private AlipayProperties aliPayProperties;
+
+ @Autowired
+ private AlipayClient alipayClient;
+
+ @Autowired
+ private AlipayTradeService alipayTradeService;
+
+ /**
+ * 支付异步通知
+ *
+ * 接收到异步通知并验签通过后,一定要检查通知内容,包括通知中的app_id、out_trade_no、total_amount是否与请求中的一致,并根据trade_status进行后续业务处理。
+ *
+ * https://docs.open.alipay.com/194/103296
+ */
+ @RequestMapping("/notify")
+ public String notify(HttpServletRequest request) throws AlipayApiException, UnsupportedEncodingException {
+ // 一定要验签,防止黑客篡改参数
+ Map parameterMap = request.getParameterMap();
+ StringBuilder notifyBuild = new StringBuilder("/****************************** alipay notify ******************************/\n");
+ parameterMap.forEach((key, value) -> notifyBuild.append(key + "=" + value[0] + "\n") );
+ log.info(notifyBuild.toString());
+
+
+ boolean flag = this.rsaCheckV1(request);
+ if (flag) {
+ /**
+ * TODO 需要严格按照如下描述校验通知数据的正确性
+ *
+ * 商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
+ * 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
+ * 同时需要校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email),
+ *
+ * 上述有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。
+ * 在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
+ * 在支付宝的业务通知中,只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
+ */
+
+ //交易状态
+ String tradeStatus = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");
+ // 商户订单号
+ String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
+ //支付宝交易号
+ String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
+ //付款金额
+ String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8");
+ // TRADE_FINISHED(表示交易已经成功结束,并不能再对该交易做后续操作);
+ // TRADE_SUCCESS(表示交易已经成功结束,可以对该交易做后续操作,如:分润、退款等);
+ if(tradeStatus.equals("TRADE_FINISHED")){
+ //判断该笔订单是否在商户网站中已经做过处理
+ //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,
+ // 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),并执行商户的业务程序
+ //请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
+ //如果有做过处理,不执行商户的业务程序
+
+ //注意:
+ //如果签约的是可退款协议,退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
+ //如果没有签约可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。
+ } else if (tradeStatus.equals("TRADE_SUCCESS")){
+ //判断该笔订单是否在商户网站中已经做过处理
+ //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,
+ // 并判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),并执行商户的业务程序
+ //请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
+ //如果有做过处理,不执行商户的业务程序
+
+ //注意:
+ //如果签约的是可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。
+
+ }
+
+ return "success";
+ }
+
+ return "fail";
+ }
+
+ /**
+ * 订单查询(最主要用于查询订单的支付状态)
+ * @param orderNo 商户订单号
+ * @return
+ */
+ @GetMapping("/query")
+ public String query(String orderNo){
+
+ AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder()
+ .setOutTradeNo(orderNo);
+ AlipayF2FQueryResult result = alipayTradeService.queryTradeResult(builder);
+ switch (result.getTradeStatus()) {
+ case SUCCESS:
+ log.info("查询返回该订单支付成功: )");
+
+ AlipayTradeQueryResponse resp = result.getResponse();
+ log.info(resp.getTradeStatus());
+// log.info(resp.getFundBillList());
+ break;
+
+ case FAILED:
+ log.error("查询返回该订单支付失败!!!");
+ break;
+
+ case UNKNOWN:
+ log.error("系统异常,订单支付状态未知!!!");
+ break;
+
+ default:
+ log.error("不支持的交易状态,交易返回异常!!!");
+ break;
+ }
+ return result.getResponse().getBody();
+ }
+
+ /**
+ * 退款
+ * @param orderNo 商户订单号
+ * @return
+ */
+ @PostMapping("/refund")
+ @ResponseBody
+ public String refund(String orderNo) throws AlipayApiException {
+ AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
+
+ AlipayTradeRefundModel model=new AlipayTradeRefundModel();
+ // 商户订单号
+ model.setOutTradeNo(orderNo);
+ // 退款金额
+ model.setRefundAmount("0.01");
+ // 退款原因
+ model.setRefundReason("无理由退货");
+ // 退款订单号(同一个订单可以分多次部分退款,当分多次时必传)
+// model.setOutRequestNo(UUID.randomUUID().toString());
+ alipayRequest.setBizModel(model);
+
+ AlipayTradeRefundResponse alipayResponse = alipayClient.execute(alipayRequest);
+ System.out.println(alipayResponse.getBody());
+
+ return alipayResponse.getBody();
+ }
+
+ /**
+ * 退款查询
+ * @param orderNo 商户订单号
+ * @param refundOrderNo 请求退款接口时,传入的退款请求号,如果在退款请求时未传入,则该值为创建交易时的外部订单号
+ * @return
+ * @throws AlipayApiException
+ */
+ @GetMapping("/refundQuery")
+ @ResponseBody
+ public String refundQuery(String orderNo, String refundOrderNo) throws AlipayApiException {
+ AlipayTradeFastpayRefundQueryRequest alipayRequest = new AlipayTradeFastpayRefundQueryRequest();
+
+ AlipayTradeFastpayRefundQueryModel model=new AlipayTradeFastpayRefundQueryModel();
+ model.setOutTradeNo(orderNo);
+ model.setOutRequestNo(refundOrderNo);
+ alipayRequest.setBizModel(model);
+
+ AlipayTradeFastpayRefundQueryResponse alipayResponse = alipayClient.execute(alipayRequest);
+ System.out.println(alipayResponse.getBody());
+
+ return alipayResponse.getBody();
+ }
+
+ /**
+ * 关闭交易
+ * @param orderNo
+ * @return
+ * @throws AlipayApiException
+ */
+ @PostMapping("/close")
+ @ResponseBody
+ public String close(String orderNo) throws AlipayApiException {
+ AlipayTradeCloseRequest alipayRequest = new AlipayTradeCloseRequest();
+ AlipayTradeCloseModel model =new AlipayTradeCloseModel();
+ model.setOutTradeNo(orderNo);
+ alipayRequest.setBizModel(model);
+
+ AlipayTradeCloseResponse alipayResponse = alipayClient.execute(alipayRequest);
+ System.out.println(alipayResponse.getBody());
+
+ return alipayResponse.getBody();
+ }
+
+ /**
+ * 校验签名
+ * @param request
+ * @return
+ */
+ public boolean rsaCheckV1(HttpServletRequest request){
+ // https://docs.open.alipay.com/54/106370
+ // 获取支付宝POST过来反馈信息
+ Map params = new HashMap<>();
+ Map requestParams = request.getParameterMap();
+ for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
+ String name = (String) iter.next();
+ String[] values = (String[]) requestParams.get(name);
+ String valueStr = "";
+ for (int i = 0; i < values.length; i++) {
+ valueStr = (i == values.length - 1) ? valueStr + values[i]
+ : valueStr + values[i] + ",";
+ }
+ params.put(name, valueStr);
+ }
+
+ try {
+ boolean verifyResult = AlipaySignature.rsaCheckV1(params,
+ aliPayProperties.getAlipayPublicKey(),
+ aliPayProperties.getCharset(),
+ aliPayProperties.getSignType());
+
+ return verifyResult;
+ } catch (AlipayApiException e) {
+ log.debug("verify sigin error, exception is:{}", e);
+ return false;
+ }
+ }
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayF2FPayController.java b/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayF2FPayController.java
new file mode 100644
index 0000000..d03fa48
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayF2FPayController.java
@@ -0,0 +1,298 @@
+package com.example.pay.controller;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.response.AlipayTradePrecreateResponse;
+import com.alipay.demo.trade.model.GoodsDetail;
+import com.alipay.demo.trade.model.builder.AlipayTradePayRequestBuilder;
+import com.alipay.demo.trade.model.builder.AlipayTradePrecreateRequestBuilder;
+import com.alipay.demo.trade.model.builder.AlipayTradeRefundRequestBuilder;
+import com.alipay.demo.trade.model.result.AlipayF2FPayResult;
+import com.alipay.demo.trade.model.result.AlipayF2FPrecreateResult;
+import com.alipay.demo.trade.model.result.AlipayF2FRefundResult;
+import com.alipay.demo.trade.service.AlipayTradeService;
+import com.alipay.demo.trade.utils.ZxingUtils;
+import com.example.pay.configuration.AlipayProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.ResourceUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 支付宝-当面付 控制器.
+ * https://openclub.alipay.com/read.php?tid=1720&fid=40
+ *
+ * https://docs.open.alipay.com/203/105910
+ *
+ */
+@Slf4j
+@RestController
+@RequestMapping("/alipay/f2fpay")
+public class AlipayF2FPayController {
+
+ @Autowired
+ private AlipayTradeService alipayTradeService;
+
+ @Autowired
+ private AlipayProperties aliPayProperties;
+
+
+ /**
+ * 当面付-条码付
+ *
+ * 商家使用扫码工具(扫码枪等)扫描用户支付宝的付款码
+ *
+ * @param authCode
+ */
+ @PostMapping("/barCodePay")
+ public String barCodePay(String authCode){
+ // 实际使用时需要根据商品id查询商品的基本信息并计算价格(可能还有什么优惠),这里只是写死值来测试
+
+ // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
+ String outTradeNo = UUID.randomUUID().toString();
+
+ // (必填) 订单标题,粗略描述用户的支付目的。如“喜士多(浦东店)消费”
+ String subject = "测试订单";
+
+ // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
+ String body = "购买商品2件共20.05元";
+
+ // (必填) 订单总金额,单位为元,不能超过1亿元
+ // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
+ String totalAmount = "0.01";
+
+ // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
+ // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
+ String undiscountableAmount = "";
+
+ // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
+ String storeId = "test_store_id";
+
+ // 商户操作员编号,添加此参数可以为商户操作员做销售统计
+ String operatorId = "test_operator_id";
+
+
+ // 商品明细列表,需填写购买商品详细信息,
+ List goodsDetailList = new ArrayList<>();
+ GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "全麦小面包", 1, 1);
+ goodsDetailList.add(goods1);
+ GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "黑人牙刷", 1, 2);
+ goodsDetailList.add(goods2);
+
+ // 支付超时,线下扫码交易定义为5分钟
+ String timeoutExpress = "5m";
+
+
+ AlipayTradePayRequestBuilder builder = new AlipayTradePayRequestBuilder()
+ .setOutTradeNo(outTradeNo)
+ .setSubject(subject)
+ .setBody(body)
+ .setTotalAmount(totalAmount)
+ .setAuthCode(authCode)
+ .setTotalAmount(totalAmount)
+ .setStoreId(storeId)
+ .setOperatorId(operatorId)
+ .setGoodsDetailList(goodsDetailList)
+ .setTimeoutExpress(timeoutExpress);
+
+ // 当面付,面对面付,face to face pay -> face 2 face pay -> f2f pay
+ // 同步返回支付结果
+ AlipayF2FPayResult f2FPayResult = alipayTradeService.tradePay(builder);
+ // 注意:一定要处理支付的结果,因为不是每次支付都一定会成功,可能会失败
+ switch (f2FPayResult.getTradeStatus()) {
+ case SUCCESS:
+ log.info("支付宝支付成功: )");
+ break;
+
+ case FAILED:
+ log.error("支付宝支付失败!!!");
+ break;
+
+ case UNKNOWN:
+ log.error("系统异常,订单状态未知!!!");
+ break;
+
+ default:
+ log.error("不支持的交易状态,交易返回异常!!!");
+ break;
+ }
+
+ /**
+ * {
+ * "alipay_trade_pay_response": {
+ * "code": "10000",
+ * "msg": "Success",
+ * "buyer_logon_id": "ekf***@sandbox.com",
+ * "buyer_pay_amount": "0.01",
+ * "buyer_user_id": "2088102176027680",
+ * "buyer_user_type": "PRIVATE",
+ * "fund_bill_list": [
+ * {
+ * "amount": "0.01",
+ * "fund_channel": "ALIPAYACCOUNT"
+ * }
+ * ],
+ * "gmt_payment": "2018-06-10 14:54:16",
+ * "invoice_amount": "0.01",
+ * "out_trade_no": "91fbd3fa-8558-443a-82c2-bd8e941d7e71",
+ * "point_amount": "0.00",
+ * "receipt_amount": "0.01",
+ * "total_amount": "0.01",
+ * "trade_no": "2018061021001004680200326495"
+ * },
+ * "sign": "BNgMmA2t8fwFZNSa39kyEKgL6hV45DVOKOsdyyzTzsQnX8HEkKOzVevQEDyK083dNYewip1KK/K92BTDY2KMAsrOEqcCNxsk9NLAvK9ZQVxQzFbAFKqs5EBAEzncSWnChJcb7VMhDakUxHZFmclHg38dLJiHE2bEcF8ar9R1zj0p4V0Jr+BXO10kLtaSTc9NeaCwJZ89sPHKitNnUWRroU7t0xPHc1hWpstObwixKmAWnsFyb9eyGwPQnqNBsUVNSNWCJ7Pg3rb03Tx6J3zNsqH5f0YhWilMi09npPe33URFc6zG1HJSfhEm4Gq1zwQrPoA/anW8BbdmEUUmNo1dEw=="
+ * }
+ */
+ String result = f2FPayResult.getResponse().getBody();
+
+ return result;
+ }
+
+ /**
+ * 当面付-扫码付
+ *
+ * 扫码支付,指用户打开支付宝钱包中的“扫一扫”功能,扫描商户针对每个订单实时生成的订单二维码,并在手机端确认支付。
+ *
+ * 发起预下单请求,同步返回订单二维码
+ *
+ * 适用场景:商家获取二维码展示在屏幕上,然后用户去扫描屏幕上的二维码
+ * @return
+ * @throws AlipayApiException
+ */
+ @PostMapping("/precreate")
+ public void precreate(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ // 实际使用时需要根据商品id查询商品的基本信息并计算价格(可能还有什么优惠),这里只是写死值来测试
+
+ // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
+ String outTradeNo = UUID.randomUUID().toString();
+
+ // (必填) 订单标题,粗略描述用户的支付目的。如“喜士多(浦东店)消费”
+ String subject = "测试订单";
+
+ // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
+ String body = "购买商品2件共20.05元";
+
+ // (必填) 订单总金额,单位为元,不能超过1亿元
+ // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
+ String totalAmount = "0.01";
+
+ // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
+ // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
+ String undiscountableAmount = "";
+
+ // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
+ // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
+ String sellerId = "";
+
+ // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
+ String storeId = "test_store_id";
+
+ // 商户操作员编号,添加此参数可以为商户操作员做销售统计
+ String operatorId = "test_operator_id";
+
+
+ // 商品明细列表,需填写购买商品详细信息,
+ List goodsDetailList = new ArrayList<>();
+ GoodsDetail goods1 = GoodsDetail.newInstance("goods_id001", "全麦小面包", 1, 1);
+ goodsDetailList.add(goods1);
+ GoodsDetail goods2 = GoodsDetail.newInstance("goods_id002", "黑人牙刷", 1, 2);
+ goodsDetailList.add(goods2);
+
+ // 支付超时,线下扫码交易定义为5分钟
+ String timeoutExpress = "5m";
+
+ AlipayTradePrecreateRequestBuilder builder =new AlipayTradePrecreateRequestBuilder()
+ .setSubject(subject)
+ .setTotalAmount(totalAmount)
+ .setOutTradeNo(outTradeNo)
+ .setUndiscountableAmount(undiscountableAmount)
+ .setSellerId(sellerId)
+ .setBody(body)
+ .setOperatorId(operatorId)
+ .setStoreId(storeId)
+ .setTimeoutExpress(timeoutExpress)
+ //支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
+ .setNotifyUrl(aliPayProperties.getNotifyUrl())
+ .setGoodsDetailList(goodsDetailList);
+
+ AlipayF2FPrecreateResult result = alipayTradeService.tradePrecreate(builder);
+ String qrCodeUrl = null;
+ switch (result.getTradeStatus()) {
+ case SUCCESS:
+ log.info("支付宝预下单成功: )");
+
+ AlipayTradePrecreateResponse res = result.getResponse();
+ File file = ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX + "images/");
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ String absolutePath = file.getAbsolutePath();
+ String fileName = String.format("%sqr-%s.png", File.separator, res.getOutTradeNo());
+ String filePath = new StringBuilder(absolutePath).append(fileName).toString();
+
+ // 这里只是演示将图片写到服务器中,实际可以返回二维码让前端去生成
+ String basePath = request.getScheme()+ "://"+request.getServerName()+":"+ request.getServerPort()+request.getContextPath()+"/";
+ qrCodeUrl = basePath + fileName;
+ response.getWriter().write("");
+ ZxingUtils.getQRCodeImge(res.getQrCode(), 256, filePath);
+ break;
+
+ case FAILED:
+ log.error("支付宝预下单失败!!!");
+ break;
+
+ case UNKNOWN:
+ log.error("系统异常,预下单状态未知!!!");
+ break;
+
+ default:
+ log.error("不支持的交易状态,交易返回异常!!!");
+ break;
+ }
+ }
+
+
+
+ /**
+ * 退款
+ * @param orderNo 商户订单号
+ * @return
+ */
+ @PostMapping("/refund")
+ public String refund(String orderNo){
+ AlipayTradeRefundRequestBuilder builder = new AlipayTradeRefundRequestBuilder()
+ .setOutTradeNo(orderNo)
+ .setRefundAmount("0.01")
+ .setRefundReason("当面付退款测试")
+ .setOutRequestNo(String.valueOf(System.nanoTime()))
+ .setStoreId("A1");
+ AlipayF2FRefundResult result = alipayTradeService.tradeRefund(builder);
+ switch (result.getTradeStatus()) {
+ case SUCCESS:
+ log.info("支付宝退款成功: )");
+ break;
+
+ case FAILED:
+ log.error("支付宝退款失败!!!");
+ break;
+
+ case UNKNOWN:
+ log.error("系统异常,订单退款状态未知!!!");
+ break;
+
+ default:
+ log.error("不支持的交易状态,交易返回异常!!!");
+ break;
+ }
+
+ return result.getResponse().getBody();
+ }
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayPagePayController.java b/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayPagePayController.java
new file mode 100644
index 0000000..5d7bf6a
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayPagePayController.java
@@ -0,0 +1,81 @@
+package com.example.pay.controller;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.domain.AlipayTradePagePayModel;
+import com.alipay.api.request.AlipayTradePagePayRequest;
+import com.example.pay.configuration.AlipayProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.UUID;
+
+/**
+ * 支付宝-电脑网站支付.
+ * 电脑网站支付 https://docs.open.alipay.com/270/105898/
+ */
+@Controller
+@RequestMapping("/alipay/page")
+public class AlipayPagePayController {
+
+ @Autowired
+ private AlipayProperties alipayProperties;
+
+ @Autowired
+ private AlipayClient alipayClient;
+
+ @Autowired
+ private AlipayController alipayController;
+
+
+ @PostMapping("/gotoPayPage")
+ public void gotoPayPage(HttpServletResponse response) throws AlipayApiException, IOException {
+ // 订单模型
+ String productCode = "FAST_INSTANT_TRADE_PAY";
+ AlipayTradePagePayModel model = new AlipayTradePagePayModel();
+ model.setOutTradeNo(UUID.randomUUID().toString());
+ model.setSubject("支付测试");
+ model.setTotalAmount("0.01");
+ model.setBody("支付测试,共0.01元");
+ model.setProductCode(productCode);
+
+ AlipayTradePagePayRequest pagePayRequest =new AlipayTradePagePayRequest();
+ pagePayRequest.setReturnUrl("http://s9v2cw.natappfree.cc/alipay/page/returnUrl");
+ pagePayRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
+ pagePayRequest.setBizModel(model);
+
+ // 调用SDK生成表单, 并直接将完整的表单html输出到页面
+ String form = alipayClient.pageExecute(pagePayRequest).getBody();
+ response.setContentType("text/html;charset=" + alipayProperties.getCharset());
+ response.getWriter().write(form);
+ response.getWriter().flush();
+ response.getWriter().close();
+ }
+
+ @RequestMapping("/returnUrl")
+ public String returnUrl(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException, AlipayApiException {
+ response.setContentType("text/html;charset=" + alipayProperties.getCharset());
+
+ boolean verifyResult = alipayController.rsaCheckV1(request);
+ if(verifyResult){
+ //验证成功
+ //请在这里加上商户的业务逻辑程序代码,如保存支付宝交易号
+ //商户订单号
+ String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
+ //支付宝交易号
+ String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
+
+ return "pagePaySuccess";
+
+ }else{
+ return "pagePayFail";
+
+ }
+ }
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayWAPPayController.java b/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayWAPPayController.java
new file mode 100644
index 0000000..cacd628
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/controller/AlipayWAPPayController.java
@@ -0,0 +1,116 @@
+package com.example.pay.controller;
+
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.domain.AlipayTradeWapPayModel;
+import com.alipay.api.internal.util.AlipaySignature;
+import com.alipay.api.request.AlipayTradeWapPayRequest;
+import com.example.pay.configuration.AlipayProperties;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 支付宝-手机网站支付.
+ */
+@Slf4j
+@Controller
+@RequestMapping("/alipay/wap")
+public class AlipayWAPPayController {
+
+ @Autowired
+ private AlipayProperties alipayProperties;
+
+ @Autowired
+ private AlipayClient alipayClient;
+
+ /**
+ * 去支付
+ *
+ * 支付宝返回一个form表单,并自动提交,跳转到支付宝页面
+ *
+ * @param response
+ * @throws Exception
+ */
+ @PostMapping("/gotoPayPage")
+ public void gotoPayPage(HttpServletResponse response) throws AlipayApiException, IOException {
+ // 订单模型
+ String productCode="QUICK_WAP_WAY";
+ AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
+ model.setOutTradeNo(UUID.randomUUID().toString());
+ model.setSubject("支付测试");
+ model.setTotalAmount("0.01");
+ model.setBody("支付测试,共0.01元");
+ model.setTimeoutExpress("5m");
+ model.setProductCode(productCode);
+
+ AlipayTradeWapPayRequest wapPayRequest =new AlipayTradeWapPayRequest();
+ wapPayRequest.setReturnUrl("http://yxep7y.natappfree.cc/alipay/wap/returnUrl");
+ wapPayRequest.setNotifyUrl(alipayProperties.getNotifyUrl());
+ wapPayRequest.setBizModel(model);
+
+ // 调用SDK生成表单, 并直接将完整的表单html输出到页面
+ String form = alipayClient.pageExecute(wapPayRequest).getBody();
+ System.out.println(form);
+ response.setContentType("text/html;charset=" + alipayProperties.getCharset());
+ response.getWriter().write(form);
+ response.getWriter().flush();
+ response.getWriter().close();
+ }
+
+
+ /**
+ * 支付宝页面跳转同步通知页面
+ * @param request
+ * @return
+ * @throws UnsupportedEncodingException
+ * @throws AlipayApiException
+ */
+ @RequestMapping("/returnUrl")
+ public String returnUrl(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException, AlipayApiException {
+ response.setContentType("text/html;charset=" + alipayProperties.getCharset());
+
+ //获取支付宝GET过来反馈信息
+ Map params = new HashMap<>();
+ Map requestParams = request.getParameterMap();
+ for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
+ String name = (String) iter.next();
+ String[] values = (String[]) requestParams.get(name);
+ String valueStr = "";
+ for (int i = 0; i < values.length; i++) {
+ valueStr = (i == values.length - 1) ? valueStr + values[i]
+ : valueStr + values[i] + ",";
+ }
+ //乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
+ valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
+ params.put(name, valueStr);
+ }
+
+ boolean verifyResult = AlipaySignature.rsaCheckV1(params, alipayProperties.getAlipayPublicKey(), alipayProperties.getCharset(), "RSA2");
+ if(verifyResult){
+ //验证成功
+ //请在这里加上商户的业务逻辑程序代码,如保存支付宝交易号
+ //商户订单号
+ String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");
+ //支付宝交易号
+ String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");
+
+ return "wapPaySuccess";
+
+ }else{
+ return "wapPayFail";
+
+ }
+ }
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayController.java b/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayController.java
new file mode 100644
index 0000000..8e2b7ec
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayController.java
@@ -0,0 +1,166 @@
+package com.example.pay.controller;
+
+import com.example.pay.configuration.MyWXPayConfig;
+import com.example.pay.configuration.WXPayClient;
+import com.github.wxpay.sdk.WXPay;
+import com.github.wxpay.sdk.WXPayUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+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.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信支付 - 通用API.
+ *
+ *
+ * 类似支付宝中的条码支付.
+ */
+@Slf4j
+@RestController
+@RequestMapping("/wxpay")
+public class WXPayController {
+
+ @Autowired
+ private WXPay wxPay;
+
+ @Autowired
+ private WXPayClient wxPayClient;
+
+ @Autowired
+ private MyWXPayConfig wxPayConfig;
+
+
+
+ /**
+ * 订单查询
+ * @param orderNo
+ * @return
+ * @throws Exception
+ */
+ @GetMapping("/orderQuery")
+ public Object orderQuery(String orderNo) throws Exception {
+ Map data = new HashMap<>();
+ data.put("out_trade_no", orderNo);
+ Map result = wxPay.orderQuery(data);
+
+ return result;
+ }
+
+ /**
+ * 退款
+ * 注意:调用申请退款、撤销订单接口需要商户证书
+ * 注意:沙箱环境响应结果可能会是"沙箱支付金额不正确,请确认验收case",但是正式环境不会报这个错误
+ * 微信支付的最小金额是0.1元,所以在测试支付时金额必须大于0.1元,否则会提示微信支付配置错误,可以将microPay的total_fee大于1再退款
+ */
+ @PostMapping("/refund")
+ public Object refund(String orderNo) throws Exception {
+ Map reqData = new HashMap<>();
+ // 商户订单号
+ reqData.put("out_trade_no", orderNo);
+ // 授权码
+ reqData.put("out_refund_no", orderNo);
+ // 订单总金额,单位为分,只能为整数
+ reqData.put("total_fee", "1010");
+ //退款金额
+ reqData.put("refund_fee", "2");
+ // 退款异步通知地址
+ reqData.put("notify_url", wxPayConfig.getNotifyUrl());
+ reqData.put("refund_fee_type", "CNY");
+ reqData.put("op_user_id", wxPayConfig.getMchID());
+
+ Map resultMap = wxPay.refund(reqData);
+ log.info(resultMap.toString());
+
+ return resultMap;
+ }
+
+
+ /**
+ * 退款结果通知
+ *
+ * 特别说明:退款结果对重要的数据进行了加密,商户需要用商户秘钥进行解密后才能获得结果通知的内容
+ * @param request
+ * @throws Exception
+ */
+ @RequestMapping("/refund/notify")
+ public String refundNotify(HttpServletRequest request) throws Exception {
+
+// Map notifyMap = new HashMap<>();
+// notifyMap.put("nonce_str", "9b4e428ae262d5dca96178027e849fa9");
+// notifyMap.put("req_info", "VKGj8c81RwQ45LcyWEVBE9/HsNfADGbgmbIAQZ2ydcbIFhIIcJFKFQwGfcSGgFGtQlWvg6KDNsRjmCjN+PvipJ3roynJ7cME0LOFG50VGtk4EYHqdjFzUVANI7GpT+i6Ok+ZWivH0MwoGK2fsz3WG+bYs2XJBwav/K89tKjFhZGitCKKBeGqcP99fa/gAL0swNXXNQHmL806Zi+QcACzL3E89BtP9FlXM2Gi+wPQafvPr+/iE+LrPdMlNUa5LiZnenZXUF24kMdhaTafczcKL4sZjRXQHEfEyc/pIZPbIjcNIETvHsskyzKuHVr/SAFkxaM6RR1Kl9pyWZGUjkH5SOeqsT8uL7YQmTlDXrnXmno3AvZdnepTGL5w69yIOmQNPeMqsd01ES9WX36GZYOigfi2+BJ9RRXjIffmpB/MFF+zryyvLTaJE2obCwFSHrmOD8YbaJqrZXOUvWZQrn7wIQgaCypo8V57cD3w5d2RSgIHNrdnEDYlbRcLNYgKuL+T9+1HPhU/frowZgwPN9IB53OahZV3p1Yvos23kvhqPCLn3BYgUihRbey6QhEtL2QyifiQ9e8WVLzWpRZ+DOa8VrhYvSuTfjRdjoNanqHFvXGP6uEsEa+DETqnexpB7xOS9m/CdmlNCwbdUplPEVzNQQdzYT4kybi00Y8A+EdairxfVyK9A7MAYAMtAO9yxV2ht0bn3SofFyZe/YSzdJgxdtcxBf1CVYN6x+yHcSueCSgq4cM/2VCwh4J1+pUVmNpEm0OVcdKbV5USkaxJR0h7Yd+n5FTz5Q2S/qvyDo202cUzLFPI5UqQm5X+FOrWDAkmmr5yVcDQIm3dAdb31jkz0X2TPYt5g7ciQ1h9heyVxJ67FexKvEM4pKCCubtWx6nyxcOUghHMrh8DSoBtewtNjbnwGVIbLsSb6X9MIYAkWIDbqNVP1f63GiZU+cJlhBmvcb8aeQUdTTj7EX5pOTIVSVv5D6SkKmpGU4FGvV+WjufuGX4ZRZo+01p6xl0sfZVmucG1UtxhX6bMCJb06yDwxpv7tGwkwS4TCK4SQp40Xe0=");
+// notifyMap.put("appid", "xxx");
+// notifyMap.put("mch_id", "xxx");
+// notifyMap.put("return_code", "SUCCESS");
+
+ // 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
+ // 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
+ // 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
+ // TODO 处理业务
+ Map requstInfoMap = wxPayClient.decodeRefundNotify(request);
+
+ // 商户处理退款通知参数后同步返回给微信参数
+ Map responseMap = new HashMap<>();
+ responseMap.put("return_code", "SUCCESS");
+ responseMap.put("return_msg", "OK");
+ String responseXml = WXPayUtil.mapToXml(responseMap);
+ return responseXml;
+ }
+
+ /**
+ * 退款查询
+ * @param orderNo
+ * @return
+ * @throws Exception
+ */
+ @GetMapping("/refundQuery")
+ public Object refundQuery(String orderNo) throws Exception {
+ Map reqData = new HashMap<>();
+ reqData.put("out_trade_no", orderNo);
+ Map result = wxPay.refundQuery(reqData);
+
+ return result;
+ }
+
+
+ /**
+ * 下载对账单
+ * 注意:
+ * 微信在次日9点启动生成前一天的对账单,建议商户10点后再获取;
+ * 对账单接口只能下载三个月以内的账单。
+ * @throws Exception
+ */
+ @PostMapping("/downloadBill")
+ public Object downloadBill(String billDate) throws Exception {
+ Map reqData = new HashMap<>();
+ reqData.put("bill_date", billDate);
+ reqData.put("bill_type", "ALL");
+ Map resultMap = wxPay.downloadBill(reqData);
+
+ return resultMap;
+ }
+
+
+
+
+ /**
+ * 获取沙箱环境API秘钥,
+ *
+ * 这里只是为了获取,可以放在main方法下运行,这里作为api来运行的,实际情况下不应该暴露出来
+ * @return
+ * @throws Exception
+ */
+ @GetMapping("/sandbox/getSignKey")
+ public Object getSignKey() throws Exception {
+ Map signKey = wxPayClient.getSignKey();
+ log.info(signKey.toString());
+
+ return signKey;
+ }
+
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayH5PayController.java b/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayH5PayController.java
new file mode 100644
index 0000000..a822d27
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayH5PayController.java
@@ -0,0 +1,106 @@
+package com.example.pay.controller;
+
+import com.example.pay.configuration.WXPayClient;
+import com.github.wxpay.sdk.WXPay;
+import com.github.wxpay.sdk.WXPayConstants;
+import com.github.wxpay.sdk.WXPayUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信支付-H5支付.
+ */
+@Slf4j
+@RestController
+@RequestMapping("/wxpay/h5pay")
+public class WXPayH5PayController {
+
+ @Autowired
+ private WXPay wxPay;
+
+ @Autowired
+ private WXPayClient wxPayClient;
+
+ /**
+ * 使用沙箱支付的金额必须是用例中指定的金额,也就是 1.01 元,1.02元等,不能是你自己的商品的实际价格,必须是这个数。
+ * 否则会报错:沙箱支付金额(2000)无效,请检查需要验收的case
+ * @return
+ * @throws Exception
+ */
+ @PostMapping("/order")
+ public Object h5pay() throws Exception {
+ Map reqData = new HashMap<>();
+ reqData.put("out_trade_no", String.valueOf(System.nanoTime()));
+ reqData.put("trade_type", "MWEB");
+ reqData.put("product_id", "1");
+ reqData.put("body", "商户下单");
+ // 订单总金额,单位为分
+ reqData.put("total_fee", "101");
+ // APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
+ reqData.put("spbill_create_ip", "14.23.150.211");
+ // 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
+ reqData.put("notify_url", "http://3sbqi7.natappfree.cc/wxpay/h5pay/notify");
+ // 自定义参数, 可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
+ reqData.put("device_info", "");
+ // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
+ reqData.put("attach", "");
+ reqData.put("scene_info", "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\": \"http://3sbqi7.natappfree.cc\",\"wap_name\": \"腾讯充值\"}}");
+
+ Map responseMap = wxPay.unifiedOrder(reqData);
+ log.info(responseMap.toString());
+ String returnCode = responseMap.get("return_code");
+ String resultCode = responseMap.get("result_code");
+ if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
+ // 预支付交易会话标识
+ String prepayId = responseMap.get("prepay_id");
+ // 支付跳转链接(前端需要在该地址上拼接redirect_url,该参数不是必须的)
+ // 正常流程用户支付完成后会返回至发起支付的页面,如需返回至指定页面,则可以在MWEB_URL后拼接上redirect_url参数,来指定回调页面
+ // 需对redirect_url进行urlencode处理
+
+ // TODO 正常情况下这里应该是普通的链接,不知道这里为何是weixin://这样的链接,不知道是不是微信公众平台上的配置少配置了;
+ // 由于没有实际账号,还没找到为啥不是普通链接的原因
+ String mwebUrl = responseMap.get("mweb_url");
+ }
+
+ return responseMap;
+ }
+
+ /**
+ * 注意:如果是沙箱环境,一提交订单就会立即异步通知,而无需拉起微信支付收银台的中间页面
+ * @param request
+ * @throws Exception
+ */
+ @RequestMapping("/notify")
+ public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception{
+ Map reqData = wxPayClient.getNotifyParameter(request);
+ log.info(reqData.toString());
+
+
+ String returnCode = reqData.get("return_code");
+ String resultCode = reqData.get("result_code");
+ if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
+ boolean signatureValid = wxPay.isPayResultNotifySignatureValid(reqData);
+
+ if (signatureValid) {
+ // TODO 业务处理
+
+ Map responseMap = new HashMap<>(2);
+ responseMap.put("return_code", "SUCCESS");
+ responseMap.put("return_msg", "OK");
+ String responseXml = WXPayUtil.mapToXml(responseMap);
+
+ response.setContentType("text/xml");
+ response.getWriter().write(responseXml);
+ response.flushBuffer();
+ }
+ }
+ }
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayMicroPayController.java b/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayMicroPayController.java
new file mode 100644
index 0000000..2d7fa65
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayMicroPayController.java
@@ -0,0 +1,57 @@
+package com.example.pay.controller;
+
+import com.example.pay.configuration.WXPayClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信支付-刷卡支付.
+ */
+@Slf4j
+@RestController
+@RequestMapping("/wxpay/microPay")
+public class WXPayMicroPayController {
+
+ @Autowired
+ private WXPayClient wxPayClient;
+
+ /**
+ * 刷卡支付(类似支付宝的条码支付)
+ *
+ * 和支付宝的好像不一样,支付宝有支付通知,但是微信好像没,微信有退款通知
+ *
+ * 微信支付后台系统收到支付请求,根据验证密码规则判断是否验证用户的支付密码,不需要验证密码的交易直接发起扣款,
+ * 需要验证密码的交易会弹出密码输入框。支付成功后微信端会弹出成功页面,支付失败会弹出错误提示
+ * 注意该接口有可能返回错误码为USERPAYING用户支付中
+ *
+ * 验证密码规则
+ * ◆ 支付金额>1000元的交易需要验证用户支付密码
+ * ◆ 用户账号每天最多有5笔交易可以免密,超过后需要验证密码
+ * ◆ 微信支付后台判断用户支付行为有异常情况,符合免密规则的交易也会要求验证密码
+ *
+ * 用户刷卡条形码规则:18位纯数字,以10、11、12、13、14、15开头
+ */
+ @PostMapping("")
+ public Object microPay(String authCode) throws Exception {
+ Map reqData = new HashMap<>();
+ // 商户订单号
+ reqData.put("out_trade_no", String.valueOf(System.nanoTime()));
+ // 订单总金额,单位为分,只能为整数
+ reqData.put("total_fee", "1010");
+ // 授权码
+ reqData.put("auth_code", authCode);
+ // 商品描述
+ reqData.put("body", "测试");
+ Map resultMap = wxPayClient.microPayWithPOS(reqData);
+ log.info(resultMap.toString());
+
+ return resultMap;
+ }
+
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayPrecreateController.java b/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayPrecreateController.java
new file mode 100644
index 0000000..4e5a6eb
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/controller/WXPayPrecreateController.java
@@ -0,0 +1,139 @@
+package com.example.pay.controller;
+
+import com.example.pay.configuration.WXPayClient;
+import com.example.pay.util.PayUtil;
+import com.github.wxpay.sdk.WXPay;
+import com.github.wxpay.sdk.WXPayConstants;
+import com.github.wxpay.sdk.WXPayUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.image.BufferedImage;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 微信支付-扫码支付.
+ */
+@Slf4j
+@RestController
+@RequestMapping("/wxpay/precreate")
+public class WXPayPrecreateController {
+ @Autowired
+ private WXPay wxPay;
+
+ @Autowired
+ private WXPayClient wxPayClient;
+
+ /**
+ * 扫码支付 - 统一下单
+ * 相当于支付不的电脑网站支付
+ *
+ * 扫码支付API
+ */
+ @PostMapping("/order")
+ public void precreate(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ Map reqData = new HashMap<>();
+ reqData.put("out_trade_no", String.valueOf(System.nanoTime()));
+ reqData.put("trade_type", "NATIVE");
+ reqData.put("product_id", "1");
+ reqData.put("body", "商户下单");
+ // 订单总金额,单位为分
+ reqData.put("total_fee", "2");
+ // APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
+ reqData.put("spbill_create_ip", "14.23.150.211");
+ // 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
+ reqData.put("notify_url", "http://3sbqi7.natappfree.cc/wxpay/precreate/notify");
+ // 自定义参数, 可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
+ reqData.put("device_info", "");
+ // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
+ reqData.put("attach", "");
+
+ /**
+ * {
+ * code_url=weixin://wxpay/bizpayurl?pr=vvz4xwC,
+ * trade_type=NATIVE,
+ * return_msg=OK,
+ * result_code=SUCCESS,
+ * return_code=SUCCESS,
+ * prepay_id=wx18111952823301d9fa53ab8e1414642725
+ * }
+ */
+ Map responseMap = wxPay.unifiedOrder(reqData);
+ log.info(responseMap.toString());
+ String returnCode = responseMap.get("return_code");
+ String resultCode = responseMap.get("result_code");
+ if (WXPayConstants.SUCCESS.equals(returnCode) && WXPayConstants.SUCCESS.equals(resultCode)) {
+ String prepayId = responseMap.get("prepay_id");
+ String codeUrl = responseMap.get("code_url");
+
+ BufferedImage image = PayUtil.getQRCodeImge(codeUrl);
+
+ response.setContentType("image/jpeg");
+ response.setHeader("Pragma","no-cache");
+ response.setHeader("Cache-Control","no-cache");
+ response.setIntHeader("Expires",-1);
+ ImageIO.write(image, "JPEG", response.getOutputStream());
+ }
+
+ }
+
+ /**
+ *
+ * @param request
+ * @return
+ * @throws Exception
+ */
+ @RequestMapping("/notify")
+ public void precreateNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
+ Map reqData = wxPayClient.getNotifyParameter(request);
+
+ /**
+ * {
+ * transaction_id=4200000138201806180751222945,
+ * nonce_str=aaaf3fe4d3aa44d8b245bc6c97bda7a8,
+ * bank_type=CFT,
+ * openid=xxx,
+ * sign=821A5F42F5E180ED9EF3743499FBCF13,
+ * fee_type=CNY,
+ * mch_id=xxx,
+ * cash_fee=1,
+ * out_trade_no=186873223426017,
+ * appid=xxx,
+ * total_fee=1,
+ * trade_type=NATIVE,
+ * result_code=SUCCESS,
+ * time_end=20180618131247,
+ * is_subscribe=N,
+ * return_code=SUCCESS
+ * }
+ */
+ log.info(reqData.toString());
+
+ // 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。
+ boolean signatureValid = wxPay.isPayResultNotifySignatureValid(reqData);
+ if (signatureValid) {
+ /**
+ * 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
+ * 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,
+ * 判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
+ * 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
+ */
+
+ Map responseMap = new HashMap<>(2);
+ responseMap.put("return_code", "SUCCESS");
+ responseMap.put("return_msg", "OK");
+ String responseXml = WXPayUtil.mapToXml(responseMap);
+
+ response.setContentType("text/xml");
+ response.getWriter().write(responseXml);
+ response.flushBuffer();
+ }
+ }
+}
diff --git a/springboot-pay-example/src/main/java/com/example/pay/util/PayUtil.java b/springboot-pay-example/src/main/java/com/example/pay/util/PayUtil.java
new file mode 100644
index 0000000..849aa3f
--- /dev/null
+++ b/springboot-pay-example/src/main/java/com/example/pay/util/PayUtil.java
@@ -0,0 +1,38 @@
+package com.example.pay.util;
+
+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 PayUtil {
+
+ /**
+ * 根据url生成二位图片对象
+ *
+ * @param codeUrl
+ * @return
+ * @throws WriterException
+ */
+ public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
+ Map hints = new Hashtable();
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+ hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
+ int width = 256;
+ 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;
+ }
+}
diff --git a/springboot-pay-example/src/main/resources/application.yml b/springboot-pay-example/src/main/resources/application.yml
new file mode 100644
index 0000000..3bc4dd9
--- /dev/null
+++ b/springboot-pay-example/src/main/resources/application.yml
@@ -0,0 +1,25 @@
+# 沙箱账号
+pay:
+ alipay:
+ gatewayUrl: https://openapi.alipaydev.com/gateway.do
+ appid: 2016091400508498
+ appPrivateKey: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtXKWs+trRSuCxEUvlsEeSAuLWs3B/Dh74R8223BnfzoA29aGeoycAqfKlBMcbzU2G1KayESvZKGpwBLeejSbecRYjgZsQDyEaDimQQJtGFvZVV6u4XNnvIJ72eQzEaEIQfuiorjBTLm6DQuds4R0GxftqON6QFoIZkWB9ZrZKd02cuy16dW+UqtLVGGAHcCIAkB63zUiKSNfweMddneZ7MVs3lvu3xhMnD+5us/+n2Vp4qhfmpYLcdqIW6InU4GypeoOpyUTrfUGpgdR0l924vHy/GQJZEKFaRcK3cYK+ECyKpSIoqaJJFLHbkqsliuPpMUG+rM3jiqeIAH4psAznAgMBAAECggEBAJ5jyEbbxrJzrAh7GhHX1fwUQPYSadTbrPYAfHX2cHlnrQMJtsk+nTLhEv0r+VJwZ8WpYkfMong8kcqYtL7ajcmsHqMAFhE9EWxBxj2ymWsXLabZe93sj30IG9Rq0nxcGQgDO0RqKWLGSFgK93Al2KRInKT3InkY53K+vR61ihVLmGf7+GwPtIZteBy+tuAyvcj2RlkYvjiFi3ySyZ1wA3sJIlgrGiixd6fj20XFGNE3CnAwu0BJgXXbP/S9J4C0RRa3ZXj8fX7oONhVxz2xKxn7AT4e8OWjt7J41H2LRct8Fgl9pqgz2FJYv/WfbkG8x9fGiKYYvPXoxjjrk/tkewkCgYEA8f9Lcu5JPrE9rpw9zlwhm5cOO81xLxdwL5R5/1bRP48BZGIYuqlCbVvjJVqtO8eTnLhUwH7fG8B7cmoeO9bGr9GQrtfyCqz6FtVymTBieJlfgZDVhtzyv2qKOBMIFE8jsbSBK/NHHMvykJ+XdQ1riwCeQDdXICRuYTTFwGk2OsUCgYEAt2SoN95tVmVrvKG6ATLNEtge/ozeVywA4GjltrSw/G9vqp+DkkT2pY19uROuzMazoTzKWpPho2q/qzNlv/ANbOFM2GEmKamQ7CO88JgRxMsPTvc/HxCLU/ClMJU8LcOf9LfP2KYZpPwuheKJoF4vDGj8NsbFmccJyYSdpkNEk7sCgYBJlL2FMaz1sgC2Ue19DIhvfaunRV1P20mSPgwmNmijccETm7w3LXX0OIdFeV/JGHLqqSWj7i+6iXk/ncKZoUGCfi8G6sQ+uL/GJ5qTt6GJV+ExTS+PtSjeSO/EAw1m13Vb+C16hpstx1l23f+4aJ81gbecgPct38XsKpaiXZtOnQKBgQCMsN7QRYYxwoq9YoDUzIlAzKYyeBVWYL6najHYUZR5hG/xQIBqZRem9/4cTvpJxKInrwA6LrrqaEl0aHDFp75U6h7O3PCvA5PXZK9dD/yJsZIj7U/yX/nTQokn1UUegrYiwiTkusBvrrtuINWePsLvTVc4GpObHnPmsiNTWsWwYwKBgENaeTNOCHV2km/ysXQSEIhKbtlAMQPsgWHCt/bzHlF9m18izb1LrJyjzcSsd+Zy78R+pv4G50Q27c3e/DFPz/wYxN/yHWRbyLBA8ipJbCtMtPEdS9krpmN6cChIdLGbz4CVUqOPSRzNb9lhhgPCcCNRq6DG3HBceb1Se9VnO3zk
+ alipayPublicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApFQKccMq+wPoWh93DcX3XYrnT7FJ3gntJA/jEwgk6Jd3iEVS2CyUCCgFVcQn8xjXT81YbZHYvoC50IBuu+A+Ey+J8VIgsBm5g9uwbOLRf66GrZjuKOlalHm5gHXjcL2gZRMBJEStOxstcO2YQriqhQzdL3EKp+HQc9u14IOVFpZdR8qq1l7CzKHn1vQo/1fUPfUrTLQqSuQTU9Ospv/QHFqOJA7DPetUQ+jnaZ082f3clr4ITw4EE8A6IWqTXcETTx5j/udCGP84g2Y4j+8i9DqYGyD5ePVgt4G0ICBX1bi1qNlylfxRg8Y3c1DFrRGyr0NpKQxSVXkYaVNvrCoudQIDAQAB
+ returnUrl: http://s9v2cw.natappfree.cc/alipay/return
+ notifyUrl: http://s9v2cw.natappfree.cc/alipay/notify
+ wxpay:
+ appID: wxab8acb865bb1637e
+ mchID: 11473623
+ key: 2ab9071b06b9f739b950ddb41db2690d
+ sandboxKey: 3639bc1370e105aa65f10cd4fef2a3ef
+ certPath: /var/local/cert/apiclient_cert.p12
+ notifyUrl: http://65ta5j.natappfree.cc/wxpay/refund/notify
+ useSandbox: true
+spring:
+ thymeleaf:
+ prefix: classpath:/templates/
+ suffix: .html
+ mode: HTML5
+ encoding: UTF-8
+
+
diff --git a/springboot-pay-example/src/main/resources/templates/gotoH5Page.html b/springboot-pay-example/src/main/resources/templates/gotoH5Page.html
new file mode 100644
index 0000000..f8b547a
--- /dev/null
+++ b/springboot-pay-example/src/main/resources/templates/gotoH5Page.html
@@ -0,0 +1,34 @@
+
+
+
+
+ Title
+
+
+
+购买商品:iphone
+价格:8888
+数量:1个
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/springboot-pay-example/src/main/resources/templates/gotoPagePay.html b/springboot-pay-example/src/main/resources/templates/gotoPagePay.html
new file mode 100644
index 0000000..8b9d02a
--- /dev/null
+++ b/springboot-pay-example/src/main/resources/templates/gotoPagePay.html
@@ -0,0 +1,17 @@
+
+
+
+
+ Title
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/springboot-pay-example/src/main/resources/templates/gotoWapPay.html b/springboot-pay-example/src/main/resources/templates/gotoWapPay.html
new file mode 100644
index 0000000..583d18d
--- /dev/null
+++ b/springboot-pay-example/src/main/resources/templates/gotoWapPay.html
@@ -0,0 +1,18 @@
+
+
+
+
+ Title
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/springboot-pay-example/src/main/resources/templates/h5PaySuccess.html b/springboot-pay-example/src/main/resources/templates/h5PaySuccess.html
new file mode 100644
index 0000000..efc3a19
--- /dev/null
+++ b/springboot-pay-example/src/main/resources/templates/h5PaySuccess.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Title
+
+
+
+微信支付-H5支付成功
+
+
+
\ No newline at end of file
diff --git a/springboot-pay-example/src/main/resources/templates/pagePayFail.html b/springboot-pay-example/src/main/resources/templates/pagePayFail.html
new file mode 100644
index 0000000..6bf70c9
--- /dev/null
+++ b/springboot-pay-example/src/main/resources/templates/pagePayFail.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Title
+
+
+
+电脑网站支付失败,请重新支付
+
+
+
\ No newline at end of file
diff --git a/springboot-pay-example/src/main/resources/templates/pagePaySuccess.html b/springboot-pay-example/src/main/resources/templates/pagePaySuccess.html
new file mode 100644
index 0000000..7a8a92f
--- /dev/null
+++ b/springboot-pay-example/src/main/resources/templates/pagePaySuccess.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Title
+
+
+
+电脑网站支付成功
+
+
+
\ No newline at end of file
diff --git a/springboot-pay-example/src/main/resources/templates/wapPayFail.html b/springboot-pay-example/src/main/resources/templates/wapPayFail.html
new file mode 100644
index 0000000..dd70097
--- /dev/null
+++ b/springboot-pay-example/src/main/resources/templates/wapPayFail.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Title
+
+
+
+手机网站支付失败,请重新支付
+
+
+
\ No newline at end of file
diff --git a/springboot-pay-example/src/main/resources/templates/wapPaySuccess.html b/springboot-pay-example/src/main/resources/templates/wapPaySuccess.html
new file mode 100644
index 0000000..882eff3
--- /dev/null
+++ b/springboot-pay-example/src/main/resources/templates/wapPaySuccess.html
@@ -0,0 +1,12 @@
+
+
+
+
+ Title
+
+
+
+手机网站支付成功
+
+
+
\ No newline at end of file
diff --git a/springboot-pay-example/src/test/java/com/example/pay/SpringbootPayExampleApplicationTests.java b/springboot-pay-example/src/test/java/com/example/pay/SpringbootPayExampleApplicationTests.java
new file mode 100644
index 0000000..0df7aa8
--- /dev/null
+++ b/springboot-pay-example/src/test/java/com/example/pay/SpringbootPayExampleApplicationTests.java
@@ -0,0 +1,16 @@
+package com.example.pay;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SpringbootPayExampleApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}