支付产品
支付宝为普通商户提供如下支付产品 https://b.alipay.com/page/product-workspace/all-product:
配置支付宝沙箱环境
接入手机网站支付需要具备如下条件:
-
申请前必须拥有经过实名认证的支付宝账户;
-
企业或个体工商户可申请;
-
需提供真实有效的营业执照,且支付宝账户名称需与营业执照主体一致;
-
网站能正常访问且页面显示完整,网站需要明确经营内容且有完整的商品信息;
-
网站必须通过 ICP 备案。如为个体工商户,网站备案主体需要与支付宝账户主体名称一致;
-
如为个体工商户,则团购不开放,且古玩、珠宝等奢侈品、投资类行业无法申请本产品。具体见:https://opendocs.alipay.com/open/203
支付宝沙箱环境配置见:沙箱环境 - 支付宝文档中心 (alipay.com)
注册开放平台账号
支付宝开放平台地址:支付宝开放平台 (alipay.com)
配置密钥
进入沙箱页面:https://open.alipay.com/develop/sandbox/app
启用证书模式,并根据提示完成对应的配置。
测试环境准备
我们在测试支付宝下单接口时需要使用支付宝扫描二维码,需要在手机安装支付宝客户端(沙箱版本),用沙箱账号登录支付宝,扫二维码,二维码的地址即为下单接口的地址。
或者使用模拟器进行测试。模拟器安装地址:MuMu模拟器官网_安卓12模拟器_网易手游模拟器 (163.com)
安装完成后进行登录。
手机网站支付接口(H5)
小程序文档 - 支付宝文档中心 (alipay.com)
商家在网页应用中调用支付宝提供的网页支付接口,接口会调起支付宝客户端内的支付模块,此时会从商家网页应 用跳转到支付宝客户端中并开始支付;支付完成后会跳转回商家网页应用内,最后商家展示支付结果。
接口交互
手机网站支付快速接入 - 支付宝文档中心 (alipay.com)
-
用户在商户的 H5 网站下单支付后,商户系统按照手机网站支付接口 alipay.trade.wap.payAPI
的参数规范生成订 单数据
-
前端页面通过 Form 表单的形式请求到支付宝。此时支付宝会自动将页面跳转至支付宝 H5 收银台页面,如果用户手机上安装了支付宝 APP,则自动唤起支付宝 APP。
-
输入支付密码完成支付。
-
用户在支付宝 APP 或 H5 收银台完成支付后,会根据商户在手机网站支付 API 中传入的前台回跳地址 return_url
自动跳转回商户页面,同时在 URL 请求中以 Query String 的形式附带上支付结果参数,详细回跳参数见“手机网站支付接口 alipay.trade.wap.pay
”前台回跳参数。
-
支付宝还会根据原始支付 API 中传入的异步通知地址 notify_url,通过 POST 请求的形式将支付结果作为参数通知 到商户系统,详情见支付结果异步通知。
下单接口定义
请求地址:
公共请求参数:请求参数说明 - 支付宝文档中心 (alipay.com)
下单接口测试
-
maven 依赖
1 2 3 4 5
| <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>对应的最新版本</version> </dependency>
|
-
代码编写:可参考示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Slf4j @Controller public class PayTestController { String APP_ID = "XXX"; String APP_PRIVATE_KEY = "XXX"; String ALIPAY_PUBLIC_KEY = "XXX"; String CHARSET = "utf‐8"; String serverUrl = "https://openapi.alipaydev.com/gateway.do"; public void alipaytest(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException { AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2"); AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest(); alipayRequest.setBizContent("{" + " \"out_trade_no\":\"20150320010101123\"," + " \"total_amount\":\"0.01\"," + " \"subject\":\"Iphone6 16G\"," + " \"product_code\":\"QUICK_WAP_PAY\"" + " }"); String form = ""; try { form = alipayClient.pageExecute(alipayRequest).getBody(); } catch (AlipayApiException e) { e.printStackTrace(); } httpResponse.setContentType("text/html;charset=" + CHARSET); httpResponse.getWriter().write(form); httpResponse.getWriter().flush(); httpResponse.getWriter().close(); } }
|
-
二维码生成指定二维码的 URL,模拟器/手机必须可以访问到此地址。(本机测试需要在同一个局域网内并使用局域网地址,或者使用内网穿透的地址)
完整代码
-
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
| @Slf4j @Component public class AlipayUtil { private static AlipayConfig alipayConfig; public AlipayUtil(AlipayConfig alipayConfig){ AlipayUtil.alipayConfig = alipayConfig; }
public static AlipayClient createClient(AlipayConfig alipayConfig) { AlipayClient alipayClient = null; try { alipayConfig.setAppCertContent(ResourceUtil.readUtf8Str(alipayConfig.getAppCertPath())); alipayConfig.setAlipayPublicCertContent(ResourceUtil.readUtf8Str(alipayConfig.getAlipayPublicCertPath())); alipayConfig.setRootCertContent(ResourceUtil.readUtf8Str(alipayConfig.getRootCertPath())); alipayClient = new DefaultAlipayClient(alipayConfig); } catch (AlipayApiException e) { log.error("AlipayClient创建失败:" + e.getMessage()); val operLog = OperLog.builder().title("支付宝支付") .businessType(BusinessType.OTHER.ordinal()) .operUrl(alipayConfig.getServerUrl()) .operParam(JSONUtil.toJsonPrettyStr(alipayConfig)) .status(BusinessStatus.FAIL.ordinal()) .errorMsg(e.getErrMsg()) .build(); AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } return alipayClient; }
public static AlipayTradePrecreateResponse alipayTradePrecreate(AlipayTradePrecreateRequest alipayTradePrecreateRequest) { alipayTradePrecreateRequest.setNotifyUrl(alipayConfig.getNotifyUrl()); AlipayTradePrecreateResponse alipayTradePrecreateResponse = new AlipayTradePrecreateResponse(); try { alipayTradePrecreateResponse = createClient(alipayConfig).certificateExecute(alipayTradePrecreateRequest); log.info("[返回参数]:\n" + JSONUtil.toJsonPrettyStr(alipayTradePrecreateResponse)); if(alipayTradePrecreateResponse.isSuccess()){ return alipayTradePrecreateResponse; } else { log.error(alipayTradePrecreateResponse.getOutTradeNo() + "支付宝支付失败:" + alipayTradePrecreateResponse.getMsg()); val operLog = OperLog.builder().title("支付宝支付") .businessType(BusinessType.OTHER.ordinal()) .operUrl(alipayConfig.getServerUrl()) .requestMethod(alipayTradePrecreateRequest.getApiMethodName()) .operParam(JSONUtil.toJsonPrettyStr(alipayTradePrecreateRequest)) .status(BusinessStatus.FAIL.ordinal()) .errorMsg(JSONUtil.toJsonPrettyStr(alipayTradePrecreateResponse)) .build(); AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } } catch (AlipayApiException e) { log.error(alipayTradePrecreateResponse.getOutTradeNo() + "支付宝支付失败:" + e.getMessage()); val operLog = OperLog.builder().title("支付宝支付") .businessType(BusinessType.OTHER.ordinal()) .operUrl(alipayConfig.getServerUrl()) .operParam(JSONUtil.toJsonPrettyStr(alipayTradePrecreateRequest)) .status(BusinessStatus.FAIL.ordinal()) .errorMsg(e.getMessage()) .build(); AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } return alipayTradePrecreateResponse; }
public static AlipayTradeQueryResponse alipayTradeQuery(AlipayTradeQueryRequest alipayTradeQueryRequest) { AlipayTradeQueryResponse alipayTradeQueryResponse = new AlipayTradeQueryResponse(); try { alipayTradeQueryResponse = createClient(alipayConfig).certificateExecute(alipayTradeQueryRequest); if(alipayTradeQueryResponse.isSuccess()){ log.info("支付宝查单返回:" + JSONUtil.toJsonPrettyStr(alipayTradeQueryResponse)); return alipayTradeQueryResponse; } else { log.error(alipayTradeQueryResponse.getOutTradeNo() + "支付宝查单失败:" + alipayTradeQueryResponse.getMsg()); val operLog = OperLog.builder().title("支付宝查单") .businessType(BusinessType.OTHER.ordinal()) .operUrl(alipayConfig.getServerUrl()) .requestMethod(alipayTradeQueryRequest.getApiMethodName()) .operParam(JSONUtil.toJsonPrettyStr(alipayTradeQueryRequest)) .status(BusinessStatus.FAIL.ordinal()) .errorMsg(alipayTradeQueryResponse.getMsg()) .build(); AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } } catch (AlipayApiException e) { log.error(alipayTradeQueryResponse.getOutTradeNo() + "支付宝查单失败:" + e.getMessage()); val operLog = OperLog.builder().title("支付宝查单") .businessType(BusinessType.OTHER.ordinal()) .operUrl(alipayConfig.getServerUrl()) .operParam(JSONUtil.toJsonPrettyStr(alipayTradeQueryRequest)) .status(BusinessStatus.FAIL.ordinal()) .errorMsg(e.getMessage()) .build(); AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } return alipayTradeQueryResponse; }
public static AlipayTradeCloseResponse alipayTradeClose(AlipayTradeCloseRequest alipayTradeCloseRequest) { AlipayTradeCloseResponse alipayTradeQueryResponse = new AlipayTradeCloseResponse(); try { alipayTradeQueryResponse = createClient(alipayConfig).certificateExecute(alipayTradeCloseRequest); if(alipayTradeQueryResponse.isSuccess()){ return alipayTradeQueryResponse; } else { log.error(alipayTradeQueryResponse.getOutTradeNo() + "支付宝关单失败:" + alipayTradeQueryResponse.getMsg()); val operLog = OperLog.builder().title("支付宝关单") .businessType(BusinessType.OTHER.ordinal()) .operUrl(alipayConfig.getServerUrl()) .requestMethod(alipayTradeCloseRequest.getApiMethodName()) .operParam(JSONUtil.toJsonPrettyStr(alipayTradeCloseRequest)) .status(BusinessStatus.FAIL.ordinal()) .errorMsg(alipayTradeQueryResponse.getSubMsg()) .build(); AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } } catch (AlipayApiException e) { log.error(alipayTradeQueryResponse.getOutTradeNo() + "支付宝关单失败:" + e.getMessage()); val operLog = OperLog.builder().title("支付宝关单") .businessType(BusinessType.OTHER.ordinal()) .operUrl(alipayConfig.getServerUrl()) .operParam(JSONUtil.toJsonPrettyStr(alipayTradeCloseRequest)) .status(BusinessStatus.FAIL.ordinal()) .errorMsg(e.getMessage()) .build(); AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); } return alipayTradeQueryResponse; } }
|
-
创建支付宝 V3 版本请求对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| private AlipayTradePrecreateRequest createAlipayBeanV3ByOrder(Order order) { AlipayTradePrecreateRequest alipayTradePrecreateRequest = new AlipayTradePrecreateRequest(); JSONObject bizContent = new JSONObject(); bizContent.set("out_trade_no", order.getOrderNo()); bizContent.set("total_amount", order.getTotal());
bizContent.set("subject", subject); alipayTradePrecreateRequest.setBizContent(bizContent.toString()); return alipayTradePrecreateRequest; }
|
-
支付宝查单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| private BaseResponse<Integer> getAlipayBaseResponse(Order order, HttpServletRequest request) { AlipayTradeQueryRequest alipayTradeQueryRequest = new AlipayTradeQueryRequest(); JSONConfig jsonConfig = new JSONConfig(); jsonConfig.setIgnoreNullValue(false); JSONObject bizContent = new JSONObject(jsonConfig); bizContent.set("out_trade_no", order.getOrderNo()); bizContent.set("trade_no", null); alipayTradeQueryRequest.setBizContent(bizContent.toString()); log.info("订单:" + order.getOrderNo() + "支付宝查单:" + JSONUtil.parse(alipayTradeQueryRequest)); AlipayTradeQueryResponse alipayTradeQueryResponse = AlipayUtil.alipayTradeQuery(alipayTradeQueryRequest); log.info("订单:" + order.getOrderNo() + "支付宝查单结果:" + JSONUtil.parse(alipayTradeQueryResponse)); String tradeNo = alipayTradeQueryResponse.getTradeNo(); OtherPayInfo otherPayInfo = new OtherPayInfo(); otherPayInfo.setTradeNo(tradeNo); LocalDateTime time = null; if (ObjectUtil.isNotNull(alipayTradeQueryResponse.getSendPayDate())) { time = LocalDateTime.ofInstant(alipayTradeQueryResponse.getSendPayDate().toInstant(), ZoneId.systemDefault()); } if (ObjectUtil.isNull(alipayTradeQueryResponse.getTradeStatus())) { return new BaseResponse<>(VMSystem.ORDER_STATUS_CREATE); } if ("TRADE_SUCCESS".equals(alipayTradeQueryResponse.getTradeStatus())) { } else if ("WAIT_BUYER_PAY".equals(alipayTradeQueryResponse.getTradeStatus())) { return new BaseResponse<>(VMSystem.ORDER_STATUS_CREATE); } else { log.error(order.getOrderNo() + "支付宝支付失败:网关描述:" + alipayTradeQueryResponse.getMsg() + "业务描述:" + alipayTradeQueryResponse.getSubMsg()); OperLog operLog = OperLog.builder().title("支付宝支付") .businessType(BusinessType.OTHER.ordinal()) .operIp(IpUtils.getIpAddr(request)) .operParam(JSONUtil.toJsonPrettyStr(alipayTradeQueryRequest.getTextParams())) .status(BusinessStatus.FAIL.ordinal()) .method(alipayTradeQueryRequest.getApiMethodName()) .requestMethod("GET") .errorMsg("支付宝支付失败:网关描述:" + alipayTradeQueryResponse.getMsg() + "业务描述:" + alipayTradeQueryResponse.getSubMsg()) .build(); AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); this.updateOrderStatusByPay(order.getOrderNo(), time, VMSystem.ORDER_STATUS_FAILED, otherPayInfo); val baseResponse = new BaseResponse<>(VMSystem.ORDER_STATUS_FAILED); baseResponse.setMessage("支付宝支付失败:网关描述:" + alipayTradeQueryResponse.getMsg() + "业务描述:" + alipayTradeQueryResponse.getSubMsg()); return baseResponse; } }
|
-
订单关闭
1 2 3 4 5 6 7 8 9
| AlipayTradeCloseRequest alipayTradeCloseRequest = new AlipayTradeCloseRequest(); JSONConfig jsonConfig = new JSONConfig(); jsonConfig.setIgnoreNullValue(false); JSONObject bizContent = new JSONObject(); bizContent.set("out_trade_no", order.getOrderNo()); bizContent.set("trade_no", order.getTradeNo()); alipayTradeCloseRequest.setBizContent(bizContent.toString()); AlipayUtil.alipayTradeClose(alipayTradeCloseRequest);
|
BUG
验签出错,建议检查签名字符串或签名私钥与应用公钥是否匹配
技术文档:https://opendocs.alipay.com/support/01ravw
问题原因
密钥不匹配、编码格式不统一(本次测试后更换中文编码格式)、请求参数数据有误、接口调用加签方式和应用上选择的加签方式不对应、SDK 调用的提交方法有误、SDK 运行环境有误