赞
踩
目录
本篇笔记包含:微信支付,支付宝沙箱环境支付模拟,支付宝简单支付版(easysdk)实现
后续有其他平台会继续更新
以小程序支付为例
接入前准备参考官方文档微信支付-开发者文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
场景:通过小程序调起支付-》支付完成-》微信支付成功通知
业务流程:
用户创建商户订单-》结合商户订单号,价格等调起微信支付-》完成支付更新商户订单支付1.
1.导入依赖
- <!-- 微信支付 SDK-->
- <dependency>
- <groupId>com.github.binarywang</groupId>
- <artifactId>weixin-java-pay</artifactId>
- <version>4.0.0</version>
- </dependency>
2.yml配置基本参数
wxpayconfig:
appId: xxxxxxxx (自己的appid)
mch-id: xxxxxxxx (自己的商户id)
mchKey: xxxxxxxx (自己的api密钥,非appSecret)
appSecret:xxxxxxxx
keyPath: classpath:test/apiclient_cert.p12 (微信商户下载的安全证书存放位置)
notifyUrl: xxxxxxxx (微信支付回调的接口)
refundNotifyUrl:xxxxxxxx
officialAppId: xxxxxxxx (服务号appid)
officialAppSecret: xxxxxxxx (服务号appSecret)
3.config配置类
- @Configuration
- @ConditionalOnClass(WxPayService.class)
- public class MyWxPayConfig {
- @Bean
- @ConditionalOnMissingBean
- public WxPayService wxService() {
- WxPayConfig payConfig = new WxPayConfig();
- payConfig.setAppId("xxxxxxxxx");
- payConfig.setMchId("xxxxxxxxx");
- payConfig.setMchKey("xxxxxxxxx");
- payConfig.setKeyPath("xxxxxxxxx");
- payConfig.setNotifyUrl("xxxxxxxx"); //微信支付后回调的接口
- payConfig.setRefundNotifyUrl("xxxxxxxx"); //微信退款后回调的接口
- payConfig.setTradeType(WxPayConstants.TradeType.JSAPI);
- payConfig.setUseSandboxEnv(false); //是否使用沙箱支付环境
- WxPayService wxPayService = new WxPayServiceImpl();
- wxPayService.setConfig(payConfig);
- return wxPayService;
- }
- }
公共部分
- @RestController
- @RequestMapping("/wxpay")
- @Api(value = "微信支付")
- public class WXPayController{
- @Autowired
- private WxPayService wxPayService;
- ......
- }
- @ApiOperation(value = "确认支付")
- @GetMapping(value = "/confirm")
- public ResponseEntity<WxPayMpOrderResult> confirm(String orderId, HttpServletRequest request) throws Exception {
- /**
- *orderId 业务系统创建的订单表id
- *校验业务系统是否存在订单 且订单状态为待支付,不能为已取消,已支付
- *一些针对业务系统的业务处理 业务订单实体 bizOrder orderNum业务创建的订单号
- *BizAppUser appUser = appUserService.getById(bizOrder.getAuthId()); //获取当前登录小
- *程序用户
- **/
- try{
- //通过redis 杜绝 订单重复提交
- if(Boolean.toString(true).equals(redisUtils.get("PAY_" + bizOrder.getOrderNum()))) {
- return ResultUtil.error("订单已提交");
- }
- //将订单号存入redis 设置状态为 true表示已提交 5s后过期
- redisUtils.set("PAY_" + bizOrder.getOrderNum(), "true", 5);
- WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest =
- WxPayUnifiedOrderRequest.newBuilder()
- .outTradeNo(bizOrder.getOrderNum()) //订单号
- .body(bizOrder.getOrderNum())
- .totalFee(bizOrder.getAmount())) //支付金额
- .openid(appUser.getOpenid()) //用户小程序id
- .spbillCreateIp(getIpAddress(request))
- .build();
- //将参数传给微信 进行订单处理
- WxPayMpOrderResult result = this.wxPayService.createOrder(wxPayUnifiedOrderRequest);
- //更新支付操作至业务订单表 支付结果通过支付回调更新 业务订单中的支付状态
- bizOrder.setPayTime(LocalDateTime.now());
- orderService.updateById(order);
- }catch(Exception e){
- log.error("支付失败");
- return ResultUtil.error("支付失败");
- }finally{
- //最后删除 次订单的重复提交限制 标识
- redisUtils.delete("PAY_" + bizOrder.getOrderNum());
- }
-
- }
- @ApiOperation(value = "微信支付回调")
- @PostMapping(value = "/callback")
- public String wxParentNotifyPage(@RequestBody String xmlData) throws Exception {
- final WxPayOrderNotifyResult notifyResult =
- this.wxPayService.parseOrderNotifyResult(xmlData);
- //获取支付结果后 更新 业务订单表 更新订单支付状态 等业务操作
- if (WxPayConstants.ResultCode.SUCCESS.equals(notifyResult.getReturnCode()) &&
- WxPayConstants.WxpayTradeStatus.SUCCESS.equals(notifyResult.getReturnCode())) {
- //更新支付状态
- BizOrder bizOrder = new BizOrder();
- bizOrder.setPayStatus("PAID");
- ......
- Wrappers.lambdaUpdate(BizOrder.class).eq(BizOrder::getOrderNum,
- notifyResult.getOutTradeNo()));
- }
- return WxPayNotifyResponse.success("成功");
- }
- //查询订单是否支付成功
- @ApiOperation(value = "查询支付状态")
- @GetMapping(value = "/queryOrder")
- public WxPayOrderQueryResult queryOrder(String orderId) throws Exception {
- BizOrder order = orderService.getById(orderId);
- Assert.notNull(order, "未查询到订单信息");
- //第一个参数为流水号 第二个参数为订单号
- return this.wxPayService.queryOrder(null,order.getOrderNum());
- }
- @ApiOperation(value = "退款")
- @PostMapping("/refund")
- public WxPayRefundResult refund(@RequestBody WxPayRefundRequest request) throws WxPayException {
- return this.wxPayService.refund(request);
- }
- //根据微信订单号/商户订单号/商户退款单号/微信退款单号查询退款
- @GetMapping("/refund/query")
- public WxPayRefundQueryResult refundQuery(@RequestParam(required = false) String transactionId,
- @RequestParam(required = false) String outTradeNo,
- @RequestParam(required = false) String outRefundNo,
- @RequestParam(required = false) String refundId) throws Exception {
- return this.wxPayService.refundQuery(transactionId, outTradeNo, outRefundNo, refundId);
- }
-
- //关闭订单
- @GetMapping("/close")
- public WxPayOrderCloseResult closeOrder(@RequestParam(required = false) String outTradeNo) throws Exception {
- return this.wxPayService.closeOrder(outTradeNo);
- }
- @ApiOperation(value = "退款回调")
- @PostMapping("/notify/refund")
- public String parseRefundNotifyResult(@RequestBody String xmlData) throws WxPayException{
- final WxPayRefundNotifyResult result =
- this.wxPayService.parseRefundNotifyResult(xmlData);
- if(WxPayConstants.RefundStatus.SUCCESS.equals(result.getReqInfo().getRefundStatus())){
- //更新业务订单表支付状态等业务操作
- ......
- }
- return WxPayNotifyResponse.success("成功");
- }
商户平台
【沙箱测试环境流程】本文以沙箱环境模拟
1.用支付宝账号登录【开放控制平台】创建应用获取appid
2.选择沙箱模拟环境
3.沙箱应用-》获取appid(一个appid绑定一个收款支付宝账户)
4.利用开发助手工具生成RSA2密钥
生成密钥 | 开放平台支付宝文档中心https://opendocs.alipay.com/common/02kipl生成,一对 RSA 2密钥【应用公钥、应用私钥】以及公钥证书申请 CSR 文件
【公钥】传给支付宝平台
【私钥】配置代码中,签名用
一个密钥与一个应用绑定
5.生成密钥后,进行配置
返回平台-》开发信息-》自定义密钥-》设置并启用(加签)-》应用公钥
保存后生成支付宝公钥(需要配置到项目中)
【注意代码中需要配置应用私钥,支付宝公钥(非应用公钥)】
应用公钥,支付宝公钥个人理解
应用公钥配置到支付宝平台,应用私钥配置代码中,我方发起请求,支付方通过应用公钥验证。
支付宝公钥配置代码中,当支付方回调我方接口,我方进行校验
两者完成相互校验提高安全性
6.支付宝网关(配置代码中)
至此前期配置准备完成
导入依赖
- <dependency>
- <groupId>com.alipay.sdk</groupId>
- <artifactId>alipay-sdk-java</artifactId>
- <version>4.9.9</version>
- </dependency>
- <!-- thymeleaf 依赖 用于渲染html页面 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-thymeleaf</artifactId>
- </dependency>
-
配置文件
- alipay:
- appId: 收款账号对应的应用id
- privateKey: 应用私钥
- publicKey: 支付宝公钥
- returnUrl: 127.0.0.1:9999/order/return
- notifyUrl: 127.0.0.1:9999/order/notify-url(异步回调地址,http/https开头必须外网能访问)
- refundNotifyUrl: https://blog.csdn.net/qq_37630282(同步回调地址,需要外网能够访问)
- gatewayUrl: https://openapi.alipaydev.com/gateway.do(沙箱官网与正式网关不同,此处为沙箱网关)
- charset: utf-8
- signType: RSA2
-
- server:
- port: 9999
-
- spring:
- application:
- name: alipay-demo
- thymeleaf:
- prefix: classpath:templates/
- suffix: .html
配置类
- @Data
- @Component
- @ConfigurationProperties(prefix = "alipay")
- public class AlipayConfig {
- private String appId;
- private String privateKey;
- private String publicKey;
- private String returnUrl;
- private String notifyUrl;
- private String refundNotifyUrl;
- private String gatewayUrl;
- private String charset;
- private String signType;
- }
请求实体
此处不能用驼峰,不然请求参数会错误 支付宝接口参数接收规范
- @Data
- public class AlipayDTO {
- //商户订单号
- private String out_trade_no;
- //订单名称
- private String subject;
- //金额
- private String total_amount;
- //商品描述
- private String body;
- //超时时间参数
- private String timeout_express = "50m";
- //产品编号
- private String product_code = "FAST_INSTANT_TRADE_PAY";
- }
模拟支付界面html
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>支付模拟页面</title>
- </head>
- <body>
- <form action="/order/pay" method="post">
- 订单号:<input type="text" name="outTradeNo" required><br/>
- 订单名称:<input type="text" name="subject" required><br/>
- 支付金额:<input type="text" name="totalAmount" required><br/>
- 商品描述:<input type="text" name="body" ><br/>
- <input type="submit" value="支付"> <input type="reset" value="重置">
- </form>
- </body>
- </html>
支付完跳转的商户界面
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>支付成功</title>
- </head>
- <body>
- 支付成功
- </body>
- </html>
Controller
- @Controller
- @RequestMapping("/order")
- public class PayController {
-
- @Resource
- private PayService payService;
-
- @RequestMapping("/index")
- public String payPage(){
- //模拟支付界面
- return "index";
- }
-
- @PostMapping("/pay")
- @ResponseBody
- public String pay(@RequestBody AlipayDTO dto) throws AlipayApiException{
- //支付 整合实际业务创建或者更新订单
- return this.payService.pay(dto);
- }
-
- @RequestMapping("/notify-url")
- @ResponseBody
- public String afterPay(){
- //支付后回调 整合实际业务更新订单数据
- return "支付完成";
- }
-
- @RequestMapping(value = "/return")
- public String returnPage(){
- //模拟支付界面
- return "success";
- }
- }
servie
- @Service
- public class PayService {
-
- @Resource
- private AlipayConfig alipayConfig;
-
- public String pay(AlipayDTO dto) throws AlipayApiException {
- AlipayClient alipayClient = new DefaultAlipayClient(alipayConfig.getGatewayUrl(),alipayConfig.getAppId(),alipayConfig.getPrivateKey(),
- "json",alipayConfig.getCharset(),alipayConfig.getPublicKey(),alipayConfig.getSignType());
- AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
- //回调地址 结合业务更新我方数据库
- request.setNotifyUrl(alipayConfig.getNotifyUrl());
- //支付完 支付界面跳转的界面
- request.setReturnUrl(alipayConfig.getReturnUrl());
- request.setBizContent(JSON.toJSONString(dto));
- String result = alipayClient.pageExecute(request).getBody();
- return result;
- }
- }
代码编写完成
1.可能存在的问题
除了上方参数格式错误报错【订单信息无法识别,建议联系卖家】外
还可能存在支付成功后,返回界面【支付存在钓鱼风险!防钓鱼网站的方法】问题
解决方法:
1.关闭支付宝界面,清除浏览器缓存 2.换个未开过支付宝沙页面的箱浏览器
2.解决完后,出现支付宝登录界面
3.输入沙箱提供的买家账号,登录支付
支付成功
支付完-》同步回调return_url,异步回调notify_url
支付完稍等一会儿会跳转配置return_url对应的接口页面(此处设置return_url为博客地址)
[不跳转的情况排查]
1.return_url必须外网能访问,且http/https开头
2.考虑延迟,或者跟换浏览器尝试或者为本地服务地址等情况。
*以上支付宝沙箱支付流程整合完毕,以下为进一步整合内容
相关密钥配置同上
alipay: appId: 应用id privateKey: 应用私钥 publicKey: 支付宝公钥(非应用公钥) serverUrl: 应用地址 domain: openapi.alipay.com returnUrl: notifyUrl: 回调地址 需外网可调用 refundNotifyUrl:
<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-easysdk</artifactId> <version>2.2.0</version> </dependency>
- @Data
- @Component
- @ConfigurationProperties(prefix = "alipay")
- public class AliPayPropertiesConfig {
- //应用id
- private String appId;
- private String privateKey;
- private String publicKey;
- private String appCertPath;
- private String aliPayCertPath;
- private String aliPayRootCertPath;
- private String serverUrl;
- private String domain;
- private String returnUrl;
- //支付回调的接口
- private String notifyUrl;
- private String refundNotifyUrl;
- }
- @Resource
- private AliPayPropertiesConfig aliPayConfig;
- ......
- AlipayTradePagePayResponse response = Factory.Payment.Page().pay("支付标题", "订单号", "50", aliPayConfig.getReturnUrl());
-
- String form = response.getBody();
- return form;
AlipayTradePagePayResponse alipayTradePagePayResponse = Factory.Payment.Page().pay(subject, outTradeNo, totalAmount, returnUrl);
AlipayTradeWapPayResponse alipayTradeWapPayResponse = Factory.Payment.Wap().pay(subject, outTradeNo, totalAmount, quitUrl, returnUrl);
- @ApiOperation("阿里支付异步回调")
- @PostMapping("notify")
- public String notifyUrl(HttpServletRequest request) throws Exception{
- Map<String, String> params = request.getParameterMap();
- boolean flag = Factory.Payment.Common().verifyNotify(params);
- String out_trade_no = params.get("out_trade_no");
- String trade_no = params.get("trade_no");
- String total_amount = params.get("total_amount");
- if(flag){
- if(params.get("trade_status").equals("TRADE_SUCCESS")){
- // todo 业务
- }
- }
- return null;
-
- }
- AlipayTradeQueryResponse alipayTradeQueryResponse =
- Factory.Payment.Common().query(outTradeNo);
-
- Assert.isTrue(ResponseChecker.success(alipayTradeQueryResponse),"查询异常");
- return alipayTradeQueryResponse;
- AlipayTradeRefundResponse alipayTradeRefundResponse = Factory.Payment.Common().refund(outTradeNo, refundAmount);
-
- Assert.isTrue(ResponseChecker.success(alipayTradeRefundResponse), "退款异常");
- return alipayTradeRefundResponse;
- AlipayTradeCloseResponse alipayTradeCloseResponse = Factory.Payment.Common().close(outTradeNo);
-
- Assert.isTrue(ResponseChecker.success(alipayTradeCloseResponse),"交易取消失败");
- return alipayTradeCloseResponse;
- public class OrderNumerUtils {
- //生成yyyyMMddHHmmss+随机数的订单号
- public static String getOrderNumer(){
- Date date = new Date();
- SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
- String strDate = formatter.format(date);
- String strRandom = RandomStringUtils.randomNumeric(8);
- return strDate + strRandom;
- }
- }
利用natapp注册本地ip即可生成web隧道,外网即可临时调用本地回调接口,具体搜索natapp教程
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。