参考阿里云文档:https://docs.open.alipay.com/203/105285/
1.调用流程
手机网站支付产品包含两类API:
- 页面跳转类:需要从前端页面以Form表单的形式发起请求,浏览器会自动跳转至支付宝的相关页面(一般是收银台或签约页面),用户在该页面完成相关业务操作后再回跳到商户指定页面。例如本产品中的手机网站支付接口alipay.trade.wap.pay。
- 系统调用类:直接从服务端发起HTTP请求,支付宝会同步返回请求结果。例如本产品中的交易查询等配套API。
调用流程:
- 如上图所示,用户在商户的H5网站下单支付后,商户系统按照手机网站支付接口alipay.trade.wap.payAPI的参数规范生成订单数据,然后在前端页面通过Form表单的形式请求到支付宝。此时支付宝会自动将页面跳转至支付宝H5收银台页面,如果用户手机上安装了支付宝APP,则自动唤起支付宝APP。开发者需要关注安装了支付宝和未安装支付宝的两种测试场景,对于在手机浏览器唤起H5页面的模式下,如果安装了支付宝却没有唤起,大部分原因是当前浏览器不在支付宝配置的白名单内。
- 对于商户app内嵌webview中的支付场景,建议集成支付宝App支付产品。或者您可以使用手机网站支付转Native支付的方案,不建议在您的APP中直接接入手机网站支付。
- 用户在支付宝APP或H5收银台完成支付后,会根据商户在手机网站支付API中传入的前台回跳地址return_url自动跳转回商户页面,同时在URL请求中以Query String的形式附带上支付结果参数,详细回跳参数见“手机网站支付接口alipay.trade.wap.pay”前台回跳参数。
注意:在ios系统中,唤起支付宝App支付完成后,不会自动回到浏览器或商户APP。用户可手工切回到浏览器或商户APP;支付宝H5收银台会自动跳转回商户return_url指定的页面。
- 支付宝还会根据原始支付API中传入的异步通知地址notify_url,通过POST请求的形式将支付结果作为参数通知到商户系统,详情见支付结果异步通知。
- 除了正向支付流程外,支付宝也提供交易查询、关闭、退款、退款查询以及对账等配套API。
特别注意:
- 由于前台回跳的不可靠性,前台回跳只能作为商户支付结果页的入口,最终支付结果必须以异步通知或查询接口返回为准,不能依赖前台回跳。
- 商户系统接收到异步通知以后,必须通过验签(验证通知中的sign参数)来确保支付通知是由支付宝发送的。详细验签规则参考异步通知验签。
- 接受到异步通知并验签通过后,一定要检查通知内容,包括通知中的app_id, out_trade_no, total_amount是否与请求中的一致,并根据trade_status进行后续业务处理
2.环境接入
2.1SDK包下载:
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.3.0.ALL</version>
</dependency>
2.2公私钥文件配置
3.沙箱测试
沙箱环境提供测试账户:买家账号,商户账户。可自行充值
3.1java后端发送支付请求:
@RequestMapping("/pay")
public void pay(HttpServletResponse response, String amount) {
String form = null;
try {
form = PayUtil.pay(amount);
} catch (AlipayApiException e) {
form = "pay error!";
e.printStackTrace();
}
response.setContentType("text/html;charset=" + "utf-8");
try {
response.getWriter().write(form);//直接将完整的表单html输出到页面
response.getWriter().flush();
response.getWriter().close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static String pay(String amount) throws AlipayApiException {
AlipayClient alipay_client = new DefaultAlipayClient(server_url, app_id, private_key, format, charset, alipay_pubic_key, sign_type);
AlipayTradeWapPayRequest alipay_request = new AlipayTradeWapPayRequest();
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo(getOutOrderNo());
model.setSubject(subject);
model.setTotalAmount(amount);
model.setTimeoutExpress(timeout_express);
model.setProductCode(product_code);
alipay_request.setBizModel(model);
// 设置异步通知地址
alipay_request.setNotifyUrl(notify_url);
// 设置同步地址
alipay_request.setReturnUrl(return_url);
// 调用SDK生成表单
AlipayResponse alipay_response = alipay_client.pageExecute(alipay_request);
String form = alipay_response.getBody();
System.out.println(alipay_response.isSuccess() + ":" + form);
return form;
}
后端向支付宝发送支付请求,SDK将根据私钥内容对请求进行签名,支付宝根据提前配置的公钥进行验签通过后响应。返回的字符内容前端以H5的形式展示,依据内容来看form表单自动提交至支付宝付款界面。登录之前的沙箱买家账户进行支付即可。
3.2支付回调接口和 结果页面回调接口
@RequestMapping("/notify")
public void alipayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
//获取支付宝POST过来反馈信息
Map<String, String> params = new HashMap<String, String>();
Map requestParams = request.getParameterMap();
for (Iterator 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] + ",";
}
//乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");
params.put(name, valueStr);
}
//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表(以下仅供参考)//
String out_trade_no = "";
String trade_no = "";
String trade_status = "";
try {
//商户订单号
out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
//支付宝交易号
trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
//交易状态
trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表(以上仅供参考)//
//计算得出通知验证结果
//boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
boolean verify_result = false;
try {
verify_result = AlipaySignature.rsaCheckV1(params, PayUtil.alipay_pubic_key, PayUtil.charset, "RSA2");
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (verify_result) {//验证成功
//////////////////////////////////////////////////////////////////////////////////////////
//请在这里加上商户的业务逻辑程序代码
System.out.println("商户的业务逻辑程序代码-notify:" + out_trade_no + " trade_status:" + trade_status);
//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
if (trade_status.equals("TRADE_FINISHED")) {
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
//如果有做过处理,不执行商户的业务程序
System.out.println("TRADE_FINISHED");
//注意:
//如果签约的是可退款协议,退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
//如果没有签约可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。
} else if (trade_status.equals("TRADE_SUCCESS")) {
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的
//如果有做过处理,不执行商户的业务程序
System.out.println("TRADE_SUCCESS");
//注意:
//如果签约的是可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。
}
//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
//writer.clear();
writer.println("success"); //请不要修改或删除
//////////////////////////////////////////////////////////////////////////////////////////
} else {//验证失败
writer.println("fail");
}
}
支付回调时,首先进行验签操作,验证成功后根据提供的订单号,支付状态做相应的业务处理。
@RequestMapping("/returnUrl")
public void alipayReturn(HttpServletRequest request, HttpServletResponse response) throws IOException {
PrintWriter writer = response.getWriter();
//获取支付宝GET过来反馈信息
Map<String, String> params = new HashMap<String, String>();
Map requestParams = request.getParameterMap();
for (Iterator 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] + ",";
}
//乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
try {
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
params.put(name, valueStr);
}
String out_trade_no = "";
String trade_no = "";
try {
//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表(以下仅供参考)//
//商户订单号
out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
//支付宝交易号
trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println("商户的业务逻辑程序代码-return_url:" + out_trade_no);
//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表(以上仅供参考)//
//计算得出通知验证结果
//boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
boolean verify_result = false;
try {
verify_result = AlipaySignature.rsaCheckV1(params, PayUtil.alipay_pubic_key, PayUtil.charset, "RSA2");
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (verify_result) {//验证成功
//////////////////////////////////////////////////////////////////////////////////////////
//请在这里加上商户的业务逻辑程序代码
//该页面可做页面美工编辑
//out.clear();
writer.println("check success<br />");
//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
//////////////////////////////////////////////////////////////////////////////////////////
} else {
//该页面可做页面美工编辑
//out.clear();
writer.println("check fail");
}
}
结果页面回调时,首先进行验签操作,验证成功后根据提供的订单号,做相应的业务处理,并可以控制跳转的结果页面。