支付宝支付——沙箱环境

0x1. 前言

蚂蚁沙箱环境 (Beta) 是协助开发者进行接口功能开发及主要功能联调的辅助环境,通过配置参数、搭建页面及下载沙箱版支付宝可以测试PC端扫码支付功能以及达到快速熟悉搭建流程的目的


注意:

  • 由于沙箱为模拟环境,在沙箱完成接口开发及主要功能调试后,请务必在蚂蚁正式环境进行完整的功能验收测试。所有返回码及业务逻辑以正式环境为准。
  • 为保证沙箱稳定,沙箱环境测试数据会进行定期数据清理。Beta 测试阶段每周日中午12点至每周一中午12点为维护时间(也就是无法使用沙箱环境)。在此时间内沙箱环境部分功能可能会不可用。
  • 请勿在沙箱进行压力测试,以免触发相应的限流措施,导致无法正常使用沙箱环境。
  • 沙箱支持的各个开放产品,沙箱使用的特别说明请参考各产品的快速接入文档或技术接入文档章节

0x2. 简介

登录支付宝开放平台——到达沙箱环境页面(首次登录需要入驻为自研开发者)

登录成功后可得如下界面:

UTOOLS1586511207484.png

沙箱与线上不同,请注意更改代码配置。

  • APPID:发起请求的应用ID。
  • 支付宝网关:向支付宝发起请求的网关。
  • RSA2密钥:即加密公钥,需要手动设置,必须配置,否则无法调用接口。

注意:

  • 密钥和应用(APPID)一一对应,即开发者需要为名下的每个应用分别设置密钥,且不同应用的密钥不能混用(意思就是一个应用只能对应一个密钥)
  • 支付宝开放平台支持开发者使用 普通公钥公钥证书 两种签名方式;以下步骤采用普通公钥方式说明,如需采用公钥证书签名方式,请查阅官方帮助手册

0x3. 配置RSA2密钥

支付宝为技术开发人员提供了一键生成密钥工具(Windows版下载MacOS版下载),可根据该软件生成一对RSA密钥、公钥证书申请CSR文件(在线申请应用公钥证书需要)。

普通公钥方式

  1. 下载相应环境工具并安装后即可使用,如下图所示:

生成.jpg

  1. 开发者根据开发语言选择密钥格式和密钥长度,新建应用请务必使用 RSA2 密钥长度 即 2048 位(目前已使用 RSA 密钥长度即 1024 位密钥长度的应用仍然可以正常调用接口)。点击 生成密钥 后,工具会自动生成商户应用公钥(public key)和应用私钥(private key),如下图所示:

生成成功.jpg

  1. 开发者点击工具界面下方的 打开文件位置,即可找到生成的公私钥文件,如下图所示:

img

  1. 生成密钥后,点击页面上的RSA2密钥 — 设置 — 公钥
  2. 复制生成的公钥,点击 保存设置,即可完成公钥的设置,如下图所示。

image


注意

生成的私钥需妥善保管,避免遗失,不要泄露。应用私钥需填写到代码中供签名时使用。应用公钥需提供给支付宝账号管理者上传到支付宝开放平台。

0x4. 测试账户

沙箱环境提供了沙箱版支付宝与测试账户,其中包含商户号和买家号,但是该账户只能在支付宝提供的测试APK上才能使用。沙箱环境下方有提供二维码可以下载沙箱版支付宝(也可以点击此处下载)。

UTOOLS1586513392118.png

下载完成后,可根据左侧栏中沙箱账号获取相应的账号密码等信息进行登录,如下图所示。

UTOOLS1586513460816.png

0x5. 项目配置(Java语言)

可自行下载官方DEMO进行查阅:https://opendocs.alipay.com/open/54/cyz7do

  1. 导入Maven依赖:
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.9.100.ALL</version>
</dependency>
  1. 在项目中新建AlipayConfig类:
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
import java.io.FileWriter;
import java.io.IOException;

/**
* 支付配置
*
* @author zxchengb
* @date 2020-04-04
*/
public class AlipayConfig {
/**
* 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
*/
public static String APP_ID = "201610xxxxxx";

/**
* 商户私钥,您的PKCS8格式RSA2私钥
*/
public static String MERCHANT_PRIVATE_KEY = "官方密钥工具中生成的私钥";

/**
* 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
*/
public static String ALIPAY_PUBLIC_KEY = "沙箱环境中的支付宝公钥";

/**
* 异步通知
*/
public static String notify_url = "异步通知回调地址";

/**
* 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
*/
public static String return_url = "支付完成后返回地址";

/**
* 签名方式
*/
public static String SIGN_TYPE = "RSA2";

/**
* 字符编码格式
*/
public static String CHARSET = "utf-8";

/**
* 请求参数格式
*/
public static String FORMAT = "json";

/**
* 支付宝网关(此处为沙箱环境网关)
*/
public static String GATEWAY_URL = "https://openapi.alipaydev.com/gateway.do";

/**
* 支付宝网关
*/
public static String LOG_PATH = "C:\\";


//↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

/**
* 写日志,方便测试(看网站需求,也可以改成把记录存入数据库)
*
* @param sWord 要写入日志里的文本内容
*/
public static void logResult(String sWord) {
FileWriter writer = null;
try {
writer = new FileWriter(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();
}
}
}
}
}
  • RSA_PRIVATE_KEY:私钥,这个参数就是通过 支付宝开放平台开放助手 生成的应用公钥应用私钥 中的应用私钥
  • notify_url:异步通知,当你支付成功时,支付宝回调你本地启动项目的接口,必须是外网可以访问的,否则支付宝请求不到,但是又得是你本地的方法「不讨论线上测试」,所以这就需要用到内网映射(内网穿透),推荐使用免费版的 natapp(自行百度)。
  • ALIPAY_PUBLIC_KEY:支付宝公钥,注意这个参数不是你上边生成的应用公钥,而是的支付宝公钥

image-20200410193308133.png

  1. 新建AlipayVO视图对象类:
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
/**
* 阿里支付视图类
*
* @author zxchengb
* @date 2020-04-04
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class AlipayVO {
/**
* 订单号
*/
private String outTradeNo;

/**
* 总金额
*/
private String totalAmount;

/**
* 商品名称
*/
private String subject;
}
  1. 新建AlipayServer支付接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 支付接口
*
* @author zxchengb
* @date 2020-04-04
*/
public interface AlipayService {
/**
* 支付宝支付调用接口
*
* @param response 响应体
* @param request 请求体
* @param alipayVO 参数视图类
* @throws IOException IO异常
* @throws AlipayApiException API异常
*/
void pay(HttpServletResponse response, HttpServletRequest request, AlipayVO alipayVO) throws IOException, AlipayApiException;

}
  1. 新建AlipayServiceImpl支付接口实现类:
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
/**
* 支付接口实现类
*
* @author zxchengb
* @date 2020-04-04
*/
@Service
public class AlipayServiceImpl implements AlipayService {
/**
* 调取支付宝接口 web端支付
*/
DefaultAlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.GATEWAY_URL, AlipayConfig.APP_ID, AlipayConfig.MERCHANT_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.SIGN_TYPE);

@Override
public void pay(HttpServletResponse response, HttpServletRequest request, AlipayVO alipayVO) throws IOException, AlipayApiException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out = response.getWriter();
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
/* 同步通知,支付完成后,支付成功页面*/
alipayRequest.setReturnUrl(AlipayConfig.return_url);
/* 异步通知,支付完成后,需要进行的异步处理*/
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
alipayRequest.setBizContent("{\"out_trade_no\":\"" + alipayVO.getOutTradeNo() + "\","
+ "\"total_amount\":\"" + alipayVO.getTotalAmount() + "\","
+ "\"subject\":\"" + alipayVO.getSubject() + "\","
+ "\"body\":\"商品名称\","
+ "\"timeout_express\":\"90m\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
String body = alipayClient.pageExecute(alipayRequest).getBody();
response.setContentType("text/html;charset=" + AlipayConfig.CHARSET);
response.getWriter().write(body);
response.getWriter().flush();
response.getWriter().close();
}
}
  1. 新建AlipayController支付宝支付控制器:
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
/**
* 支付宝支付控制器
*
* @author zxchengb
* @date 2020-04-04
*/
@RestController
@Slf4j
public class AlipayController {
/**
* 支付服务类
*/
private AlipayService alipayService;

public AlipayController(AlipayService alipayService) {
this.alipayService = alipayService;
}

/**
* web 订单支付
*/
@GetMapping("payment/{TotalAmount}")
private void alipayPay(HttpServletResponse response, HttpServletRequest request, @PathVariable("TotalAmount") String totalAmount) {
AlipayVO vo = new AlipayVO()
.setOutTradeNo(UUID.randomUUID().toString().replace("-", ""))
.setTotalAmount(totalAmount)
.setSubject("捐赠计划");
log.info("发起支付传参:" + vo.toString());
try {
alipayService.pay(response, request, vo);
} catch (IOException | AlipayApiException e) {
log.error(e.getMessage());
}
}
}
  1. HTML页面调用donate方法:
1
2
3
function donate(amount){
window.location.href = "/payment/" + amount
}

上述项目后端采用SpringBoot框架,可根据自身项目需求进行扩展业务。

0x6. 页面视图

当提交支付请求后跳转至此页面(此时用沙箱支付宝扫码支付即可):

UTOOLS1586518949420.png

沙箱支付宝扫描结果:

微信图片_20200410194456.jpg

手机端支付成功后结果:

微信图片_20200410194452.jpg

PC端支付成功后结果:

UTOOLS1586519542611.png

0x7. 注意事项

若出现支付环境异常请检查是否在一个浏览器中登录了多个支付宝账号,或者是否将应用公钥配置给多个应用。

0x8. 结论

至此,即已完成支付宝沙箱环境快速搭建,实现过程并不复杂,线上搭建流程大致也是如此,写下此文以便后续回顾。