更多最新文章欢迎大家访问我的个人博客😄:豆腐别馆
一、准备工作
- 创建应用
- 配置密钥
- 搭建和配置开发环境
↓ 戳官网文档有详尽介绍
支付宝开放平台文档中心-电脑网站支付
总的来说,需要提前准备好的参数有:
package com.yby.api.ali.pay;
/**
* 支付宝支付相关配置
*
* @author lwx
*/
public class AlipayConfig {
/**
* 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
*/
public static final String APP_ID = "************";
/**
* 商户私钥,您的PKCS8格式RSA2私钥
*/
public static final String MERCHANT_PRIVATE_KEY = "************";
/**
* 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
*/
public static final String ALIPAY_PUBLIC_KEY = "************";
/**
* 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
*/
public static final String NOTIFY_URL = "http://************/notify";
/**
* 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
*/
public static final String RETURN_URL = "http://************/return";
/**
* 签名方式
*/
public static final String SIGN_TYPE = "RSA2";
/**
* 字符编码格式
*/
public static final String CHARSET = "utf-8";
/**
* 支付宝网关 - 正式环境
*/
// public static String gatewayUrl = "https://openapi.alipay.com/gateway.do";
/**
* 支付宝网关 - 沙箱环境
*/
public static final String GATEWAY_URL = "https://openapi.alipaydev.com/gateway.do";
/**
* 日志存放路径
*/
public static final String LOG_PATH = "D:\\181-duanzu\\alipay\\";
}
二、接入代码
注:以下代码只展示支付宝调用相关代码,业务代码不展示,需根据各自需求开发。
- maven依赖:
<!-- 蚂蚁金服(支付宝) -->
<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.0.0</version>
</dependency>
1. 异步通知(NOTIFY_URL)与同步通知(RETURN_URL):在调用电脑支付接口前都绕不开这两个通知页,首先需要捋清楚这两个通知分别是干嘛的,能做怎样的事
- NOTIFY_URL:
服务器后台通知。这个页面只在后台程序异步运行,即买家和卖家都看不到。买家付完款后,支付宝会调用该接口并把反馈信息POST发送至该页面。在这个页面可根据支付宝传递过来的参数进行商户本身的业务操作且需要在页面上打印出一个success给支付宝,如果反馈给支付宝的不是success,支付宝将会继续调用这个页面。代码如下:
@RequestMapping("notify")
public void notifyUrl(HttpServletResponse response) throws Exception {
// 获取支付宝POST过来反馈信息
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
// 签名验证
boolean sign = aliPayService.verifySign(params, requestParams);
response.setContentType("text/html;charset=" + AlipayConfig.CHARSET);
// 验签成功,执行商户操作
if (sign) {
/**
* 实际验证过程建议商户务必添加以下校验:
* 1、验证app_id是否为该商户本身。
* 2、需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号
* 3、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额)
* 4、校验通知中的seller_id(或者seller_email)是否为out_trade_no这笔单据的对应的操作方
* (有的时候,一个商户可能有多个seller_id/seller_email)
*/
/**
* <pre>
* 交易状态 {
* TRADE_FINISHED:通知触发条件是商户签约的产品不支持退款功能的前提下,买家付款成功;
* 或者,商户签约的产品支持退款功能的前提下,交易已经成功并且已经超过可退款期限。
*
* TRADE_SUCCESS:通知触发条件是商户签约的产品支持退款功能的前提下,买家付款成功;
* }
* </pre>
*/
if ("TRADE_FINISHED".equals(trade_status) || "TRADE_SUCCESS".equals(trade_status)) {
// 执行商户操作
orderService.signSuccess(params, false, ClientUtil.getClientIP(request), user);
} else {
// 如果返回不是支付成功,将进行订单查询支付结果查询,当结果为支付成功时,重新执行商户操作
orderService.tradeQuery(out_trade_no, ClientUtil.getClientIP(request), user);
}
response.getWriter().write("success");
} else {
response.getWriter().write("fail");
// 调试用,写文本函数记录程序运行情况是否正常
String sWord = AlipaySignature.getSignCheckContentV1(params);
AlipayFunction.logResult(sWord);
}
response.getWriter().flush();
response.getWriter().close();
}
- RETURN_URL:
买家付款成功后,如果接口中指定有return_url,买家付完款后会跳到return_url所在的页面,这个页面可以展示给客户看,且该页面只有当付款成功时才会跳转。代码如下:
@RequestMapping("return")
public void returnUrl(HttpServletResponse response) throws IOException {
// 获取支付宝GET过来反馈信息
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
try {
// 签名验证,此处可不验签,因该页面只有当用户付款成功才会跳转,但异步通知页必须进行验签。
boolean sign = aliPayService.verifySign(params, requestParams);
if (sign) {
String orderSn = params.get(AliPayConstant.Param.OUT_TRADE_NO);
DzOrder order = orderService.getBySn(orderSn);
String url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/booking/except?orderIdCard=" + order.getId() + "&status_step=" + order.getStatus();
response.sendRedirect(url);
}
} catch (Exception e) {
e.printStackTrace();
}
}
2. 电脑支付各接口调用工具类
package com.yby.api.ali.pay;
import java.util.Iterator;
import java.util.Map;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeCloseModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.internal.util.StringUtils;
import com.alipay.api.request.AlipayDataDataserviceBillDownloadurlQueryRequest;
import com.alipay.api.request.AlipayTradeCloseRequest;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayDataDataserviceBillDownloadurlQueryResponse;
import com.alipay.api.response.AlipayTradeCloseResponse;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
/**
* 支付宝支付工具类
*
* @author lwx
*/
public class AliPayUtil {
// 获得初始化的AlipayClient
public static AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.GATEWAY_URL, AlipayConfig.APP_ID,
AlipayConfig.MERCHANT_PRIVATE_KEY, "json", AlipayConfig.CHARSET, AlipayConfig.ALIPAY_PUBLIC_KEY,
AlipayConfig.SIGN_TYPE);
/**
* 电脑网站支付接口 - 简单参数(PC场景下单并支付,可传递的其它非必要参数可查阅官方文档)
*
* @api alipay.trade.page.pay
*
* @param out_trade_no
* 商户订单号,商户网站订单系统中唯一订单号,必填
* @param subject
* 订单名称,必填
* @param total_amount
* 付款金额,必填
* @param body
* 商品描述,可空
* @param timeout_express
* 该笔订单允许的最晚付款时间,逾期将关闭交易,该参数在请求到支付宝时开始计时。
* 取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。
* 该参数数值不接受小数点, 如 1.5h,可转换为 90m。
* @param passback_params
* 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。支付宝只会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝,
* 如:merchantBizType%3d3C%26merchantBizNo%3d2016010101111
*/
public static AlipayTradePagePayResponse simpleParamPagePay(String out_trade_no, String subject,
String total_amount, String body, String timeout_express, String passback_params) {
// 设置请求参数,请求参数可查阅【电脑网站支付的API文档-alipay.trade.page.pay-请求参数】章节
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
// 页面跳转同步通知页面路径
alipayRequest.setReturnUrl(AlipayConfig.RETURN_URL);
// 服务器异步通知页面路径,在公共参数中设置回跳和通知地址
alipayRequest.setNotifyUrl(AlipayConfig.NOTIFY_URL);
// 填充业务参数
alipayRequest.setBizContent("{" + "\"out_trade_no\":\"" + out_trade_no + "\"," + "\"total_amount\":\""
+ total_amount + "\"," + "\"subject\":\"" + subject + "\"," + "\"body\":\"" + body + "\","
+ "\"timeout_express\":\"" + timeout_express + "\"," + "\"passback_params\":\"" + passback_params
+ "\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
AlipayTradePagePayResponse response = null;
try {
// 调用SDK生成表单
response = alipayClient.pageExecute(alipayRequest);
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (response == null) {
response = new AlipayTradePagePayResponse();
}
return response;
}
/**
* 统一收单线下交易查询
*
* @api alipay.trade.query
* @param out_trade_no
* 商户订单号,商户网站订单系统中唯一订单号(与支付宝交易号二选一设置)
* @param trade_no
* 支付宝交易号(与商户订单号二选一设置) out_trade_no、trade_no如果同时存在优先取trade_no
*/
public static AlipayTradeQueryResponse tradeQuery(String out_trade_no, String trade_no) {
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
if (!StringUtils.isEmpty(trade_no)) {
request.setBizContent(
"{" + "\"out_trade_no\":\"" + out_trade_no + "\"," + "\"trade_no\":\"" + trade_no + "\"" + " }");
} else if (!StringUtils.isEmpty(out_trade_no)) {
request.setBizContent("{" + "\"out_trade_no\":\"" + out_trade_no + "\"," + "\"trade_no\":\"\"" + " }");
}
AlipayTradeQueryResponse response = null;
try {
response = alipayClient.execute(request);
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (response == null) {
response = new AlipayTradeQueryResponse();
}
return response;
}
/**
* 统一收单交易退款接口
*
* @api alipay.trade.refund
*
* @param out_trade_no
* 商户订单号,商户网站订单系统中唯一订单号(请二选一设置:out_trade_no/trade_no)
* @param trade_no
* 支付宝交易号(请二选一设置:out_trade_no/trade_no)
* out_trade_no、trade_no如果同时存在优先取trade_no
* @param refund_amount
* 需要退款的金额,该金额不能大于订单金额,必填
* @param refund_reason
* 退款的原因说明
* @param out_request_no
* 标识一次退款请求,同一笔交易多次退款需要保证唯一,如需部分退款,则此参数必传
*/
public static AlipayTradeRefundResponse refund(String out_trade_no, String trade_no, String refund_amount,
String refund_reason, String out_request_no) {
// 设置请求参数
AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();
alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\"," + "\"trade_no\":\"" + trade_no + "\","
+ "\"refund_amount\":\"" + refund_amount + "\"," + "\"refund_reason\":\"" + refund_reason + "\","
+ "\"out_request_no\":\"" + out_request_no + "\"}");
AlipayTradeRefundResponse response = null;
try {
response = alipayClient.execute(alipayRequest);
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (response == null) {
response = new AlipayTradeRefundResponse();
}
return response;
}
/**
* 统一收单交易退款查询接口
*
* @api alipay.trade.fastpay.refund.query
*
* @param out_trade_no
* 商户订单号,商户网站订单系统中唯一订单号(请二选一设置:out_trade_no/trade_no)
* @param trade_no
* 支付宝交易号(请二选一设置:out_trade_no/trade_no)
* out_trade_no、trade_no如果同时存在优先取trade_no
* @param out_request_no
* 请求退款接口时,传入的退款请求号,如果在退款请求时未传入,则该值为创建交易时的外部交易号,必填
*/
public static AlipayTradeFastpayRefundQueryResponse refundQuery(String out_trade_no, String trade_no,
String out_request_no) {
AlipayTradeFastpayRefundQueryRequest alipayRequest = new AlipayTradeFastpayRefundQueryRequest();
if (!StringUtils.isEmpty(trade_no)) {
alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\"," + "\"trade_no\":\"" + trade_no
+ "\"," + "\"out_request_no\":\"" + out_request_no + "\"}");
} else if (!StringUtils.isEmpty(out_trade_no)) {
alipayRequest.setBizContent("{\"out_trade_no\":\"" + out_trade_no + "\"," + "\"trade_no\":\"\","
+ "\"out_request_no\":\"" + out_request_no + "\"}");
}
AlipayTradeFastpayRefundQueryResponse response = null;
try {
response = alipayClient.execute(alipayRequest);
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (response == null) {
response = new AlipayTradeFastpayRefundQueryResponse();
}
return response;
}
/**
* 统一收单交易关闭接口
*
* @api alipay.trade.close
* @param out_trade_no
* 商户订单号,商户网站订单系统中唯一订单号(请二选一设置:out_trade_no/trade_no)
* @param trade_no
* 支付宝交易号(请二选一设置:out_trade_no/trade_no)
* out_trade_no、trade_no如果同时存在优先取trade_no
*/
public static AlipayTradeCloseResponse close(String out_trade_no, String trade_no) {
AlipayTradeCloseRequest alipay_request = new AlipayTradeCloseRequest();
AlipayTradeCloseModel model = new AlipayTradeCloseModel();
model.setOutTradeNo(out_trade_no);
model.setTradeNo(trade_no);
alipay_request.setBizModel(model);
AlipayTradeCloseResponse response = null;
try {
response = alipayClient.execute(alipay_request);
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (response == null) {
response = new AlipayTradeCloseResponse();
}
return response;
}
/**
* 验签签名
*
* @param params
* 参数集
* @param requestParams
* 支付宝GET过来的反馈信息
* @return
*/
public static boolean verifySign(Map<String, String> params, Map<String, String[]> requestParams) {
for (Iterator<String> 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] + ",";
}
// 乱码解决,这段代码在出现乱码时使用
/*
* try { valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8"); }
* catch (UnsupportedEncodingException e) { e.printStackTrace(); }
*/
params.put(name, valueStr);
}
boolean signVerified = false;
try {
// 调用SDK验证签名
signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET,
AlipayConfig.SIGN_TYPE);
} catch (AlipayApiException e) {
e.printStackTrace();
}
return signVerified;
}
}
3. 支付宝相关工具类
package com.yby.api.ali.pay;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 类名:AlipayFunction
* 功能:支付宝接口公用函数类
* 详细:该类是请求、通知返回两个文件所调用的公用函数核心处理文件,不需要修改
*/
public class AlipayFunction {
/**
* 除去数组中的空值和签名参数
*
* @param sArray
* 签名参数组
* @return 去掉空值与签名参数后的新签名参数组
*/
public static Map<String, String> paraFilter(Map<String, String> sArray) {
Map<String, String> result = new HashMap<String, String>();
if (sArray == null || sArray.size() <= 0) {
return result;
}
for (String key : sArray.keySet()) {
String value = sArray.get(key);
if (value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")) {
continue;
}
result.put(key, value);
}
return result;
}
/**
* 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
*
* @param params
* 需要排序并参与字符拼接的参数组
* @return 拼接后字符串
*/
public static String createLinkString(Map<String, String> params) {
List<String> keys = new ArrayList<String>(params.keySet());
Collections.sort(keys);
String prestr = "";
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
// 拼接时,不包括最后一个&字符
if (i == keys.size() - 1) {
prestr = prestr + key + "=" + value;
} else {
prestr = prestr + key + "=" + value + "&";
}
}
return prestr;
}
/**
* 写日志,方便测试(看网站需求,也可以改成把记录存入数据库)
*
* @param sWord
* 要写入日志里的文本内容
*/
public static void logResult(String sWord) {
FileWriter writer = null;
try {
writer = new FileWriter(AlipayConfig.LOG_PATH + "alipay_log_" + System.currentTimeMillis() + ".txt");
writer.write(sWord);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
三、接口逻辑说明
首先给出支付宝官方文档所提供的接口流程图,建议新手先花时间看明白,别急着调接口。
简言之,就商户系统(即我们自己开发的后台)而言,其流程大致就是(emmm,画得粗略显得更复杂了(▽)):
四、统一收单交易关闭接口(alipay.trade.close)注意事项
该接口的调用只在支付宝成功创建订单后才可调用正常。说这个什么意思呢,主要是防止出现以下场景:
用户正常下单,正常去到二维码支付页,此时商户在用户不知情的情况下在后台执行了取消订单操作(商户后台取消待支付订单的同时应调用支付宝取消订单接口),接着用户依旧进行支付操作,那么将出现以下两种情形:
- 商户取消订单前用户已扫码,支付宝生成订单,交易关闭接口正常关闭订单,用户无法支付。
- 商户取消订单前用户未扫码,支付宝未生成订单,交易关闭接口关闭订单异常,用户支付成功。
第一种情形倒没什么问题,如若是第二种,即商户的订单已是取消状态,但用户却付款成功,此时如无特殊需求,直接让该订单走入退款流程即可。
ps:
- 支付宝支付的官网文档较为详尽,看着文档基本不会出现什么问题,主要是要注意商户自身业务逻辑的处理。
- 其它支付宝支付,如app支付,H5支付等大同小异,明白一个其它接口自然不难理解。
- 如有不当之处欢迎提出,喷子请圆润地离开。
下一篇博客将带来微信扫码支付。