赞
踩
支付宝开发平台地址:https://open.alipay.com/develop/sandbox/app
对于学生来说,目前网上确实没有比较统一而且质量好的支付教程。因为支付对个人开发者尤其是学生来说不太友好。因此,自己折腾两天,算是整理了一篇关于支付宝沙箱支付的文章。况且个人是不能申请支付(wx和alipay)都一样。幸亏有支付宝沙箱这个环境。其实跟正式环境差不多,就换下配置即可。
整体流程
要记住这几个重要的配置
appId
这个是appIdprivateKey
商户私钥publicKey
支付宝公钥, 即对应APPID下的支付宝公钥notifyUrl
支付成功后异步回调地址(注意是必须是公网地址)returnUrl
#支付后回调地址signType
签名类型 一般写 RSA2charset
utf-8format
jsongatewayUrl
: https://openapi.alipaydev.com/gateway.dologPath
: F:\ 日志路径
ps:如果是正式环境的支付宝支付的时候,公钥私钥处理方案:
要先下载秘钥工具
点击生成秘钥
生成后打开文件位置
然后把公钥上传到支付宝开发平台
限制与要求
• 需要使用支付宝开放平台主账号进行配置。
• 一个应用(APPID)只能配置一种接口加签方式(密钥或证书)
需要导入依赖
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.33.39.ALL</version>
</dependency>
package com.hjt.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @RefreshScope @Configuration @ConfigurationProperties(prefix = "alipay") @Data /*** * @author: hjt * @Date: 2020/11/13/19:19 * @Description: 支付宝配置类(读取配置文件) */ public class MyAliPayConfig { /** * APPID */ private String appId; /** * 商户私钥, 即PKCS8格式RSA2私钥 */ private String privateKey; /** * 支付宝公钥 */ private String publicKey; /** * 服务器异步通知页面路径,需http://格式的完整路径 * 踩坑:不能加?type=abc这类自定义参数 */ private String notifyUrl; /** * 页面跳转同步通知页面路径,需http://格式的完整路径 * 踩坑:不能加?type=abc这类自定义参数 */ private String returnUrl; /** * 签名方式 */ private String signType; /** * 字符编码格式 */ private String charset; /*** * 参数编码格式 json */ private String format; /** * 支付宝网关 */ private String gatewayUrl; /** * 日志打印地址 */ private String logPath; }
public void doPost (HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException { AlipayClient alipayClient = new DefaultAlipayClient( "https://openapi.alipay.com/gateway.do" , APP_ID, APP_PRIVATE_KEY, FORMAT, CHARSET, ALIPAY_PUBLIC_KEY, SIGN_TYPE); //获得初始化的AlipayClient AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest(); //创建API对应的request alipayRequest.setReturnUrl( "http://domain.com/CallBack/return_url.jsp" ); alipayRequest.setNotifyUrl( "http://domain.com/CallBack/notify_url.jsp" ); //在公共参数中设置回跳和通知地址 alipayRequest.setBizContent( "{" + " \"out_trade_no\":\"20150320010101001\"," + " \"product_code\":\"FAST_INSTANT_TRADE_PAY\"," + " \"total_amount\":88.88," + " \"subject\":\"Iphone6 16G\"," + " \"body\":\"Iphone6 16G\"," + " \"passback_params\":\"merchantBizType%3d3C%26merchantBizNo%3d2016010101111\"," + " \"extend_params\":{" + " \"sys_service_provider_id\":\"2088511833207846\"" + " }" + " }" ); //填充业务参数 String form= "" ; try { form = alipayClient.pageExecute(alipayRequest).getBody(); //调用SDK生成表单 } catch (AlipayApiException e) { e.printStackTrace(); } httpResponse.setContentType( "text/html;charset=" + CHARSET); httpResponse.getWriter().write(form); //直接将完整的表单html输出到页面 httpResponse.getWriter().flush(); httpResponse.getWriter().close(); }
以下是我自己业务代码的例子
@Override public void pay(PayInfoDto payInfoDTO, HttpServletResponse httpResponse) throws IOException { /*查询订单id是否存在*/ Long orderId = payInfoDTO.getOrderId(); /*商品名称*/ String subject = ""; /*判断订单是否存在*/ R<Order> orderInfo = remoteOrderService.getOrderInfo(orderId); if (orderInfo.getCode() != 200) { throw new BaseException(PayException.ORDER_ERROR_PAY_ORDER); } Order order = orderInfo.getData(); //订单总金额 BigDecimal total = order.getTotal(); String proId = order.getProId(); /*商品名称以,分割*/ String[] split = proId.split(","); for (int i = 0; i < split.length; i++) { R<Product> productInfo = remoteOrderService.getProductInfo(Long.parseLong(split[i])); if (productInfo.getCode() != 200) { throw new BaseException(PayException.ORDER_ERROR_PAY_PRODUCT); } Product product = productInfo.getData(); subject = subject + product.getProTitle() + ","; } //进行支付宝支付 AlipayOrder alipayOrder = new AlipayOrder(); //商户订单号 alipayOrder.setOut_trade_no(String.valueOf(orderId)); //订单名称 alipayOrder.setSubject(subject); alipayOrder.setDescription(subject); //订单总金额 alipayOrder.setTotal_amount(total.toString()); /*进行支付*/ String payBody = alipayUtil.payByAlipay(alipayOrder); log.info("-----------支付信息实体---------{}",payBody); //并把消息推送到mq查询是否支付成功 if(StringUtils.isBlank(payBody)){ throw new BaseException(PayException.ORDER_ERROR_PAY_BODY); } /*把支付信息写到html网页中*/ httpResponse.setContentType("text/html;charset=" + CHARSET); // 直接将完整的表单html输出到页面 httpResponse.getWriter().write(payBody); httpResponse.getWriter().flush(); httpResponse.getWriter().close(); }
@Override public String payNotifyUrl(HttpServletRequest request) throws AlipayApiException { log.info("-----------开始进行支付后的异步通知回调-------"); /*接收参数*/ Map<String, String> params = this.exchangeParams(request); String payContent = JSONUtil.toJsonStr(params); log.info("---------接收的参数--- -----{}",params); /*验证签名(支付宝公钥) 调用SDK验证签名*/ boolean signVerified = AlipaySignature.rsaCheckV1(params, aliPayConfig.getPublicKey(), aliPayConfig.getCharset(), aliPayConfig.getSignType()); if (signVerified){ /*收到支付宝异步通知,返回success,支付宝不再通知 否则会通知你三天三夜*/ log.info("-----验签成功-----"); /*应该马上返回"success",另起线程执行自己的业务逻辑*/ ExecutorService executor = ExecutorBuilder.create() .setCorePoolSize(5) .setMaxPoolSize(10) .setWorkQueue(new LinkedBlockingQueue<>(100)) .build(); executor.execute(new Runnable(){ @Override public void run() { //TODO 幂等性问题后续也要考虑 /*支付状态*/ String trade_status = params.get("trade_status"); /*订单id*/ String out_trade_no = params.get("out_trade_no"); /*支付成功*/ if (PayConstant.TRADE_FINISHED.equals(trade_status) || PayConstant.TRADE_SUCCESS.equals(trade_status)) { Long orderId = Long.valueOf(out_trade_no); /*把对于的订单id改为已支付状态*/ R<Order> order = remoteOrderService.updateOrderById(orderId); if(order.getCode()!=PayConstant.CODE){ throw new BaseException(PayException.ORDER_ERROR_PAY_ORDER); } /*存对于的支付信息*/ PayInfo payInfo = null; payInfo = PayInfo.builder() .orderIds(out_trade_no) .callbackContent(payContent) .callbackTime(LocalDateTime.now()) .tradeStatus(trade_status) .tradeNo(params.get("trade_no")) .buyerId(params.get("buyer_id")) .totalAmount(params.get("total_amount")) .version(params.get("version")) .sellerId(params.get("seller_id")) .receiptAmount(params.get("receipt_amount")) .gmtCreate(params.get("gmt_create")) .gmtPayment(params.get("gmt_payment")) .fundBillList(params.get("fund_bill_list")) .build(); payInfoMapper.insert(payInfo); log.info("-----支付信息插入成功-------"); } /* 支付失败 */ else{ log.error("-------支付失败, 订单id:------{}",out_trade_no); throw new BaseException(PayException.ORDER_ERROR_PAY_BODY); } } }); return "success"; // TODO 验签成功后,按照支付结果异步通知中的描述,对支付结果中的业务内容进行二次校验,校验成功后在response中返回success并继续商家自身业务处理,校验失败返回failure } else { log.info("-----验签失败-----"); return "failure"; // TODO 验签失败则记录异常日志,并在response中返回failure. } }
注意!!!异步通知的地址必须是公网地址,这里我采用的是frp内网穿透到本地的地址
需要注意的是,异步通知必须要严格进行验签。
运行效果图:
先生成订单
然后再浏览器直接调用接口
http://localhost:4401/pay/api/v1/pay-info/pay?Authorization=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.YWRtaW4.VaJOloHfQLjacnm6-__pSaeNZ1JbLAdlgeJT3JEptos&orderId=769532559209283584
会直接跳转到支付支付界面
账号密码都是可以在你沙箱账号看得到
支付成功后可见已经回调到我们异步通知自定义的接口了
即我们在这配置的路径
notifyUrl
支付成功后异步回调地址(注意是必须是公网地址)
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
trade_no | String | 特殊可选 | 64 | 支付宝交易号。 和商户订单号不能同时为空 | 2021081722001419121412730660 |
out_trade_no | String | 特殊可选 | 64 | 商户订单号。 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 trade_no,out_trade_no如果同时存在优先取trade_no | 2014112611001004680073956707 |
out_request_no | String | 必选 | 64 | 退款请求号。 请求退款接口时,传入的退款请求号,如果在退款请求时未传入,则该值为创建交易时的商户订单号。 | HZ01RF001 |
query_options | String[] | 可选 | 1024 | 查询选项,商户通过上送该参数来定制同步需要额外返回的信息字段,数组格式。枚举支持: refund_detail_item_list:本次退款使用的资金渠道; gmt_refund_pay:退款执行成功的时间; deposit_back_info:银行卡冲退信息; | refund_detail_item_list |
商户可使用该接口查询自已通过alipay.trade.refund提交的退款请求是否执行成功。
注意:1. 该接口的返回码10000,仅代表本次查询操作成功,不代表退款成功,当接口返回的refund_status值为REFUND_SUCCESS时表示退款成功,否则表示退款没有执行成功。
\2. 如果退款未成功,商户可以调用退款接口重试,重试时请务必保证退款请求号和退款金额一致,防止重复退款。
\3. 发起退款查询接口的时间不能离退款请求时间太短,建议之间间隔10秒以上。
个人搭建项目代码地址:
https://github.com/hongjiatao/spring-boot-anyDemo
欢迎收藏点赞三连。谢谢!有问题可以留言博主会24小时内无偿回复。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。