目录
微信支付
开发前准备
支付程序编写
(1)创建订单
(2)微信的支付回调
(3)查询订单状态
(4)退款
(5)退款查询与关闭订单
(5)退款回调
alipay-sdk沙箱模拟支付宝支付
官方参考文档
开发前准备
开发
支付测试
整合alipay-easysdk支付宝支付
开发流程
yml配置文件
导入依赖
支付配置类
支付程序编写
网页支付(扫码支付)
App支付
移动端网站支付
支付回调
支付查询
退款
交易取消
其他辅助工具类
简易生成订单号
沙箱模拟时利用natapp内网穿透工具进行异步/同步回调
本篇笔记包含:微信支付,支付宝沙箱环境支付模拟,支付宝简单支付版(easysdk)实现
后续有其他平台会继续更新
以小程序支付为例
接入前准备参考官方文档微信支付-开发者文档
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
场景:通过小程序调起支付-》支付完成-》微信支付成功通知
业务流程:
用户创建商户订单-》结合商户订单号,价格等调起微信支付-》完成支付更新商户订单支付1.
1.导入依赖
com.github.binarywang
weixin-java-pay
4.0.0
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 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("成功");
}
alipay.trade.page.pay(统一收单下单并支付页面接口) | API支付宝文档中心https://opendocs.alipay.com/apis/api_1/alipay.trade.page.pay
商户平台
登录 - 支付宝
创建流程创建应用 | 网页&移动应用
【沙箱测试环境流程】本文以沙箱环境模拟
1.用支付宝账号登录【开放控制平台】创建应用获取appid
2.选择沙箱模拟环境
3.沙箱应用-》获取appid(一个appid绑定一个收款支付宝账户)
4.利用开发助手工具生成RSA2密钥
生成密钥 | 开放平台支付宝文档中心https://opendocs.alipay.com/common/02kipl生成,一对 RSA 2密钥【应用公钥、应用私钥】以及公钥证书申请 CSR 文件
【公钥】传给支付宝平台
【私钥】配置代码中,签名用
一个密钥与一个应用绑定
5.生成密钥后,进行配置
返回平台-》开发信息-》自定义密钥-》设置并启用(加签)-》应用公钥
保存后生成支付宝公钥(需要配置到项目中)
【注意代码中需要配置应用私钥,支付宝公钥(非应用公钥)】
应用公钥,支付宝公钥个人理解
应用公钥配置到支付宝平台,应用私钥配置代码中,我方发起请求,支付方通过应用公钥验证。
支付宝公钥配置代码中,当支付方回调我方接口,我方进行校验
两者完成相互校验提高安全性
6.支付宝网关(配置代码中)
https://openapi.alipaydev.com/gateway.do
至此前期配置准备完成
导入依赖
com.alipay.sdk
alipay-sdk-java
4.9.9
org.springframework.boot
spring-boot-starter-thymeleaf
配置文件
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
支付模拟页面
支付完跳转的商户界面
支付成功
支付成功
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:
com.alipay.sdk alipay-easysdk 2.2.0
@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 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教程
natapp_百度百科natapp 基于ngrok的反向代理软件,通过在公网和本地运行的 Web 服务器之间建立一个安全的通道。natapp 可捕获和分析所有通道上的流量,便于后期分析和重放.https://baike.baidu.com/item/natapp/19762535?fr=aladdin