目录
微信支付
开发前准备
支付程序编写
(1)创建订单
(2)微信的支付回调
(3)查询订单状态
(4)退款
(5)退款查询与关闭订单
(5)退款回调
alipay-sdk沙箱模拟支付宝支付
官方参考文档
开发前准备
开发
支付测试
整合alipay-easysdk支付宝支付
开发流程
yml配置文件
导入依赖
支付配置类
支付程序编写
网页支付(扫码支付)
App支付
移动端网站支付
支付回调
支付查询
退款
交易取消
其他辅助工具类
简易生成订单号
沙箱模拟时利用natapp内网穿透工具进行异步/同步回调
本篇笔记包含:微信支付,支付宝沙箱环境支付模拟,支付宝简单支付版(easysdk)实现
后续有其他平台会继续更新
微信支付
开发前准备
以小程序支付为例
接入前准备参考官方文档微信支付-开发者文档https://pay./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@ConditionalOnMissingBeanpublic 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{@Autowiredprivate WxPayService wxPayService;......}
(1)创建订单
@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());}}
(2)微信的支付回调
@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("成功");}
(3)查询订单状态
//查询订单是否支付成功@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());}
(4)退款
@ApiOperation(value = "退款")@PostMapping("/refund")public WxPayRefundResult refund(@RequestBody WxPayRefundRequest request) throws WxPayException {return this.wxPayService.refund(request);}
(5)退款查询与关闭订单
//根据微信订单号/商户订单号/商户退款单号/微信退款单号查询退款@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);}
(5)退款回调
@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("成功");}
alipay-sdk沙箱模拟支付宝支付
官方参考文档
alipay.trade.page.pay(统一收单下单并支付页面接口) | API支付宝文档中心/apis/api_1/alipay.trade.page.pay
开发前准备
商户平台
登录 - 支付宝
创建流程创建应用 | 网页&移动应用
【沙箱测试环境流程】本文以沙箱环境模拟
1.用支付宝账号登录【开放控制平台】创建应用获取appid
2.选择沙箱模拟环境
3.沙箱应用-》获取appid(一个appid绑定一个收款支付宝账户)
4.利用开发助手工具生成RSA2密钥
生成密钥 | 开放平台支付宝文档中心/common/02kipl生成,一对RSA 2密钥【应用公钥、应用私钥】以及公钥证书申请 CSR 文件
【公钥】传给支付宝平台
【私钥】配置代码中,签名用
一个密钥与一个应用绑定
5.生成密钥后,进行配置
返回平台-》开发信息-》自定义密钥-》设置并启用(加签)-》应用公钥
保存后生成支付宝公钥(需要配置到项目中)
【注意代码中需要配置应用私钥,支付宝公钥(非应用公钥)】
应用公钥,支付宝公钥个人理解
应用公钥配置到支付宝平台,应用私钥配置代码中,我方发起请求,支付方通过应用公钥验证。
支付宝公钥配置代码中,当支付方回调我方接口,我方进行校验
两者完成相互校验提高安全性
6.支付宝网关(配置代码中)
/gateway.do
至此前期配置准备完成
开发
导入依赖
<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: 收款账号对应的应用idprivateKey: 应用私钥publicKey: 支付宝公钥returnUrl: 127.0.0.1:9999/order/returnnotifyUrl: 127.0.0.1:9999/order/notify-url(异步回调地址,http/https开头必须外网能访问)refundNotifyUrl: /qq_37630282(同步回调地址,需要外网能够访问)gatewayUrl: /gateway.do(沙箱官网与正式网关不同,此处为沙箱网关)charset: utf-8signType: RSA2server:port: 9999spring:application:name: alipay-demothymeleaf: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;}
请求实体
此处不能用驼峰,不然请求参数会错误支付宝接口参数接收规范
@Datapublic 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 {@Resourceprivate PayService payService;@RequestMapping("/index")public String payPage(){//模拟支付界面return "index";}@PostMapping("/pay")@ResponseBodypublic String pay(@RequestBody AlipayDTO dto) throws AlipayApiException{//支付 整合实际业务创建或者更新订单return this.payService.pay(dto);}@RequestMapping("/notify-url")@ResponseBodypublic String afterPay(){//支付后回调 整合实际业务更新订单数据return "支付完成";}@RequestMapping(value = "/return")public String returnPage(){//模拟支付界面return "success";}}
servie
@Servicepublic class PayService {@Resourceprivate 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-easysdk支付宝支付
开发流程
相关密钥配置同上
yml配置文件
alipay:appId: 应用idprivateKey: 应用私钥publicKey: 支付宝公钥(非应用公钥)serverUrl: 应用地址domain: 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 {//应用idprivate 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;}
支付程序编写
网页支付(扫码支付)
@Resourceprivate AliPayPropertiesConfig aliPayConfig;......AlipayTradePagePayResponse response = Factory.Payment.Page().pay("支付标题", "订单号", "50", aliPayConfig.getReturnUrl());String form = response.getBody();return form;
App支付
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 = mon().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 = mon().query(outTradeNo);Assert.isTrue(ResponseChecker.success(alipayTradeQueryResponse),"查询异常");return alipayTradeQueryResponse;
退款
AlipayTradeRefundResponse alipayTradeRefundResponse = mon().refund(outTradeNo, refundAmount);Assert.isTrue(ResponseChecker.success(alipayTradeRefundResponse), "退款异常");return alipayTradeRefundResponse;
交易取消
AlipayTradeCloseResponse alipayTradeCloseResponse = mon().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内网穿透工具进行异步/同步回调
利用natapp注册本地ip即可生成web隧道,外网即可临时调用本地回调接口,具体搜索natapp教程
natapp_百度百科natapp 基于ngrok的反向代理软件,通过在公网和本地运行的 Web 服务器之间建立一个安全的通道。natapp 可捕获和分析所有通道上的流量,便于后期分析和重放./item/natapp/19762535?fr=aladdin
如果觉得《开发笔记 | Springboot整合多平台支付(微信/支付宝)》对你有帮助,请点赞、收藏,并留下你的观点哦!