支付宝沙箱环境的坑
首先这是一个好东西,不需要商家认证那些,开发者可以直接整代码并且效果和实际上线效果是一样的。是一些技术研究者的福音。
好了如题,直接上步骤,百度也是遇到了不少坑。
1、首先登陆支付宝https://open.alipay.com
使用扫一扫登陆,然后选择沙箱环境
2、填写一些相关的信息就会进入沙箱应用里面,先别急找参数,先生成公匙和私匙。点击里面的生成方法,会下载一个文件。里面有使用说明。
用里面的方法生成公匙后点击查看应用公匙(RSA2)把生成的公匙填进去。支付宝应用公匙就会自动生成。官方是推荐使用2048位的。
生成的私匙也要保存,但是不用填写在该页面。下面的密匙应用不用管它。
3、提取相关应用参数:注意这个支付宝公匙不是你刚才填写的那个,是旁边系统自动生成的那一个。
4、根据这些参数提取到java应用程序中。我上面的图片是另一个项目的参考,所以回调地址与下面的参数不一致。实际应用中回调地址是要一致的。
在此改正:这是官方文档说明。也就是应用网关 和回调地址 沙箱测试不用设置。避免误导大家,本地部署项目并测试沙箱支付的就不用设置这两个值了,因为这两个值必须保证外网能访问。
因此本地项目测试支付能成功 但是回调不能成功也就没什么好纠结的了。然而我支付宝沙箱页面没设置,只是项目中设置了上面的同步和异步地址还是回调成功了!
- 应用网关:该地址用于接收开放平台的异步通知。目前沙箱环境不需要配置此参数;
- 授权回调地址;第三方应用授权或获取用户信息中用于接收授权回调信息的地址。使用相关产品时再进行配置;
1. 第三方应用授权:
授权url中的redirect_uri必须与此值相同。
2. 获取用户信息:
授权url中的redirect_uri的域名必须与此值相同。(例如:授权回调地址配置:https://auth.example.com/authCallBack 高亮部分需和授权url相同)
- RSA(SHA1)密钥:目前推荐使用RSA2(SHA256)密钥,请参考第1步进行配置;
- AES密钥:目前不再使用;
5、继续下一步,下载沙箱钱包。不过有点坑,我用手机扫一扫没能下载,是在电脑端下载了再安装到手机上的。
6、下载安装了沙箱钱包。还要解决登录账号的问题,这点支付宝都没有直接说明有点奇葩。
https://openhome.alipay.com/platform/appDaily.htm?tab=account
进这个地址,支付宝扫一扫就有了,使用下面的账号登录测试支付就好了
7、测试成功
支付宝沙箱环境还有一个很大的功能,那就是余额,你的沙箱支付宝余额几百万。
你把这余额给老丈人、小妹妹一看,瞬间被你征服啊。真是程序员泡妞把妹的神器啊,哈哈哈!
下面把支付宝支付的代码贴一下,萌新可以看看,也就是两个类。有时候失败可能是沙箱设置的参数和项目不一致,公私匙。因为实际线上环境就有重新生成公私匙解决了问题的例子。
配置文件类AlipayConfig,这个自己换成沙箱支付的请求网关:https://openapi.alipaydev.com/gateway.do
package com.sanyi.qibaobusiness.framework.payment.ali.config; public class AlipayConfig { //发起请求的应用ID。沙箱与线上不同,请更换代码中配置; public static String app_id =""; //支付宝私匙 public static String merchant_private_key = ""; //支付宝公匙 public static String alipay_public_key = ""; //服务器异步通知路径 public static String notify_url = "http://127.0.01/alipay/alipayNotifyNotice.action"; //服务器同步通知路径 public static String return_url = "http://127.0.0.1/alipay/alipayReturnNotice.action"; //公匙类型/签名类型 public static String sign_type = "RSA2"; //编码格式 public static String charset = "utf-8"; //向支付宝发起请求的网关。沙箱与线上不同,请更换代码中配置;沙箱:https://openapi.alipaydev.com/gateway.do上线https://openapi.alipay.com/gateway.do public static String gatewayUrl = "https://openapi.alipay.com/gateway.do"; }
AlipayController发起支付请求的类
package com.sanyi.qibaobusiness.framework.payment.ali.controller; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.request.AlipayTradePagePayRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * 阿里支付 */ @Controller @RequestMapping("/alipay") public class AlipayController { final static Logger log = LoggerFactory.getLogger(AlipayController.class); @Resource private RentingtoolsBiz rentingtoolsBiz; //暂时只有出租工具的订单在这里支付 @Resource private CustomerOrderBiz customerOrderBiz; @Resource private BrowseRecordsCommonUtil browseRecordsCommonUtil; /** * 前往支付宝第三方网关进行支付 * @param request * @param response * @return * @throws Exception */ @RequestMapping(value = "/goAlipay", produces = "text/html; charset=UTF-8") @ResponseBody public String goAlipay( HttpServletRequest request, HttpServletRequest response) throws Exception { BrowseRecordsUtil browseRecordsUtil= browseRecordsCommonUtil.getBrowseRecords(request); Order order=browseRecordsUtil.getPayOrder(); //获得初始化的AlipayClient AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type); //设置请求参数 AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest(); alipayRequest.setReturnUrl(AlipayConfig.return_url); alipayRequest.setNotifyUrl(AlipayConfig.notify_url); String out_trade_no =null; //付款金额,必填 String total_amount = null; //订单名称,必填 String subject = null; //商品描述,可空 String body = null; // 该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。 String timeout_express = "1c"; if(null!=total_amount){ //支付金额不等于空 alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\"," + "\"total_amount\":\""+ total_amount +"\"," + "\"subject\":\""+ subject +"\"," + "\"body\":\""+ body +"\"," + "\"timeout_express\":\""+ timeout_express +"\"," + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}"); //请求 String result = alipayClient.pageExecute(alipayRequest).getBody(); return result; } return "订单信息错误!"; } /** * 支付宝同步通知页面,成功返回首页 * @param request * @param response * @return * @throws Exception */ @RequestMapping(value = "/alipayReturnNotice") public String alipayReturnNotice(Model model,HttpServletRequest request, HttpServletRequest response) throws Exception { log.info("支付成功, 进入同步通知接口..."); //获取支付宝GET过来反馈信息 Map<String,String> params = new HashMap<String,String>(); Map<String,String[]> requestParams = request.getParameterMap(); 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] + ","; } //乱码解决,这段代码在出现乱码时使用 valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8"); params.put(name, valueStr); } boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名 //——请在这里编写您的程序(以下代码仅作参考)—— if(signVerified) { //商户订单号 String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8"); //支付宝交易号 String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8"); //付款金额,这里获取到三个参数就可以了,后面逻辑代码自己创作 String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8"); int result=0; //这里根据自身的业务写代码,我这里删掉了 if(result==0){ model.addAttribute("resultinfo","更新订单失败!请业务员联系后台管理员!"); }else { model.addAttribute("resultinfo","出租工具成功!"); } log.info("********************** 支付成功(支付宝同步通知) **********************"); log.info("* 订单号: {}", out_trade_no); log.info("* 支付宝交易号: {}", trade_no); log.info("* 实付金额: {}", total_amount); log.info("***************************************************************"); }else { log.info("支付, 验签失败..."); } return "/business/index";//成功返回首页 } /** * 支付宝异步 通知页面 * @param request * @param response * @return * @throws Exception */ @RequestMapping(value = "/alipayNotifyNotice") @ResponseBody public String alipayNotifyNotice(HttpServletRequest request, HttpServletRequest response) throws Exception { log.info("支付成功, 进入异步通知接口..."); //获取支付宝POST过来反馈信息 Map<String,String> params = new HashMap<String,String>(); Map<String,String[]> requestParams = request.getParameterMap(); 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] + ","; } //乱码解决,这段代码在出现乱码时使用 valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8"); params.put(name, valueStr); } boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名 //——请在这里编写您的程序(以下代码仅作参考)—— /* 实际验证过程建议商户务必添加以下校验: 1、需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号, 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额), 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email) 4、验证app_id是否为该商户本身。 */ if(signVerified) {//验证成功 //商户订单号 String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8"); //支付宝交易号 String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8"); //交易状态 String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8"); //付款金额 到这里获取到这些信息就可以了,下面的不用看 String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8"); if(trade_status.equals("TRADE_FINISHED")){ //判断该笔订单是否在商户网站中已经做过处理 //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序 //如果有做过处理,不执行商户的业务程序 //注意: 尚自习的订单没有退款功能, 这个条件判断是进不来的, 所以此处不必写代码 //退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知 }else if (trade_status.equals("TRADE_SUCCESS")){ //判断该笔订单是否在商户网站中已经做过处理 //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序 //如果有做过处理,不执行商户的业务程序 //注意: //付款完成后,支付宝系统发送该交易状态通知 int result=0; //这里根据自身的业务写代码,我这里删掉了 if(result==0){ log.info("resultinfo","更新订单失败!请业务员联系后台管理员!"); }else { log.info("resultinfo","出租工具成功!"); } log.info("********************** 支付成功(支付宝同步通知) **********************"); log.info("* 订单号: {}", out_trade_no); log.info("* 支付宝交易号: {}", trade_no); log.info("* 实付金额: {}", total_amount); log.info("***************************************************************"); } log.info("支付成功..."); }else {//验证失败 log.info("支付, 验签失败..."); } return "success"; } }