本文主要以前后端分离下单为内容对airwallex进行介绍
空中云汇api地址
https://www.airwallex.com/docs/api
接口解析
接口名称 | 接口 | 解析 |
---|---|---|
商户token认证 | /api/v1/authentication/login | 根据 x-api-key 以及x-client-id 获取后续请求使用的商户token |
获取支付方式 | /api/v1/pa/config/payment_method_types | 根据token,获取当前商户支持的支付方法 |
获取银行logo | /api/v1/pa/config/banks | 获取银行logo |
创建支付用户 | /api/v1/pa/customers/create | 这个用户是你要做的系统和空中云汇做一个关联,后续绑定银行卡等信息都会用到 |
获取支付用户临时token | /api/v1/pa/customers/{id}/generate_client_secret | 后续下单后确认支付等需要支付用户的token |
获取全部同意书 | /api/v1/pa/payment_consents | 支付同意书里包含支付方式以及绑定的银行卡信息 |
删除同意书 | /api/v1/pa/payment_consents/{id}/disable | 删除之前的同意书,删除之前绑定的银行卡信息 |
支付下单 | /api/v1/pa/payment_intents/create | 拉起后端支付,返回前端支付信息 |
查询支付 | /api/v1/pa/payment_intents/{id} | 查询支付信息 |
确认支付 | /api/v1/pa/payment_intents/{id}/confirm | 一般这个接口是需要购买native api才能提供,这个接口可以代替前端拉起支付接口 |
取消支付 | /api/v1/pa/payment_intents/{id}/cancel | 取消支付 |
发起退款 | /api/v1/pa/refunds/create | 根据之前下单接口发起退款 |
查询退款 | /api/v1/pa/refunds/{id} | 查询退款 |
查询所有退款 | /api/v1/pa/refunds | 查询所有退款 |
获取配置信息
1、登录空中云汇
地址
https://www.airwallex.com/app/login?region=cn
2、在账户-开发者中找到配置的API秘钥,获取到 CLIENT ID 以及 API 密钥 ,这两个信息可以token登录接口中使用
例如:
## 请求
curl --request POST \
--url 'https://api-demo.airwallex.com/api/v1/authentication/login' \
--header 'Content-Type: application/json' \
--header 'x-api-key: 72c0a50d840626050145ccf72b733e32f31bb21323d493f9d00290b37676a138bcdf1d6415d9f08aad117450e23a4f4f' \
--header 'x-client-id: 5f1vUssbSnq45temBX6CnB'
## 响应
{
"expires_at": "2021-10-26T06:38:13+0000",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0b20iLCJyb2xlcyI6WyJ1c2VyIl0sImlhdCI6MTQ4ODQxNTI1NywiZXhwIjoxNDg4NDE1MjY3fQ.UHqau03y5kEk5lFbTp7J4a-U6LXsfxIVNEsux85hj-Q"
}
3、配置webhook
根据自己情况配置需要的事件,建议至少将 交易订单 的 已取消 和 已成功 勾选
(1)按照空中云汇文档信息提示,webhook可以配置两个,而且必须要是 https 格式请求
(2)空中云汇webhook对请求响应时间有要求,业务逻辑最好是异步执行,先给返回响应状态
(3)webhook发送失败后间隔 5分钟,1小时,4小时,8小时,12小时,12小时 共重发6次,发送成功则终止重发
在Java中集成空中云汇并拉起支付,仅供参考,根据自己业务实际来进行
1、封装springboot 对象缓存类
@Component
public class ContextHolder implements ApplicationContextAware{
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
if(ContextHolder.applicationContext == null){
ContextHolder.applicationContext = arg0;
}
System.out.println("========ApplicationContext配置成功,ContextHolder.getAppContext()获取applicationContext对象,applicationContext="+ ContextHolder.applicationContext+"========");
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
2、创建网络请求工具
public class HttpClientUtil {
protected static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
/**
* 空中云汇支付请求
* @param url 请求url
* @param obj 请求body
* @param operatorId 商户id
* @return
* @throws Exception
*/
public static String sendAirwallexHttpPost(String url, Object obj,String operatorId) throws Exception {
HttpPost post = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(30000).setConnectionRequestTimeout(30000)
.setSocketTimeout(30000).build();
post.setConfig(requestConfig);
post.addHeader("Content-Type", "application/json;charset=UTF-8");
post.setHeader("Accept", "application/json");
if (StringUtils.isNotBlank(operatorId)) {
AirwallexPaySourceServiceImpl paySourceService = ContextHolder.getBean(AirwallexPaySourceServiceImpl.class);
String airwallexToken = paySourceService.getAirwallexToken(operatorId);
post.setHeader("Authorization",airwallexToken);
}
CloseableHttpAsyncClient httpClient = null;
try {
String json = GsonUtil.toJson(obj);
logger.info("sendAirwallexHttpPost before operatorId :{} url :{} body :{}",operatorId,url,json);
StringEntity entity = new StringEntity(json,"UTF-8");//解决中文乱码问题
entity.setContentEncoding("UTF-8");
entity.setContentType("application/json");
post.setEntity(entity);
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] xcs, String string) {
}
public void checkServerTrusted(X509Certificate[] xcs, String string) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLIOSessionStrategy sslSessionStrategy = new SSLIOSessionStrategy(ctx, new String[] { "TLSv1","TLSv1.1","TLSv1.2" }, null,
SSLIOSessionStrategy.getDefaultHostnameVerifier());
httpClient = HttpAsyncClients.custom().setSSLStrategy(sslSessionStrategy).build();
httpClient.start();
Future<HttpResponse> future = httpClient.execute(post, null);
HttpResponse response = future.get();
StringBuffer sb = new StringBuffer();
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent(),"UTF-8"));
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
String body = sb.toString();
logger.info("sendAirwallexHttpPost after operatorId :{} url :{} response :{}",operatorId,url,body);
return body;
} catch (Exception e) {
logger.error(e.getMessage(),e.getStackTrace());
throw e;
} finally {
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
}
/**
* 空中云汇支付请求
* @param url 请求url
* @param obj 请求body
* @param operatorId 商户id
* @return
* @throws Exception
*/
public static String sendAirwallexHttpPostLogin(String url,Map<String,String> header) throws Exception {
HttpPost post = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(30000).setConnectionRequestTimeout(30000)
.setSocketTimeout(30000).build();
post.setConfig(requestConfig);
post.addHeader("Content-Type", "application/json;charset=UTF-8");
post.setHeader("Accept", "application/json");
if (!CollectionUtils.isEmpty(header)) {
for (Entry<String, String> stringStringEntry : header.entrySet()) {
post.setHeader(stringStringEntry.getKey(), stringStringEntry.getValue());
}
}
CloseableHttpAsyncClient httpClient = null;
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] xcs, String string) {
}
public void checkServerTrusted(X509Certificate[] xcs, String string) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLIOSessionStrategy sslSessionStrategy = new SSLIOSessionStrategy(ctx, new String[] { "TLSv1","TLSv1.1","TLSv1.2" }, null,
SSLIOSessionStrategy.getDefaultHostnameVerifier());
httpClient = HttpAsyncClients.custom().setSSLStrategy(sslSessionStrategy).build();
httpClient.start();
Future<HttpResponse> future = httpClient.execute(post, null);
HttpResponse response = future.get();
StringBuffer sb = new StringBuffer();
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent(),"UTF-8"));
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
String body = sb.toString();
logger.info("sendAirwallexHttpPostLogin url :{} header :{}",url,GsonUtil.toJson(header));
return body;
} catch (Exception e) {
logger.error(e.getMessage(),e.getStackTrace());
throw e;
} finally {
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
}
/**
* 空中云汇支付请求
* 发送信息到服务端 GET方式
* @param url
* @return
* @throws Exception
*/
public static String sendAirwallexHttpGet(String url,String operatorId) throws Exception {
logger.info("sendAirwallexHttpGet before operatorId :{} url :{} ",operatorId,url);
HttpGet httpGet = new HttpGet(url);
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(30000).setConnectionRequestTimeout(30000)
.setSocketTimeout(30000).build();
httpGet.setConfig(requestConfig);
httpGet.addHeader("Content-Type", "application/json;charset=UTF-8");
httpGet.setHeader("Accept", "application/json");
if (StringUtils.isNotBlank(operatorId)) {
AirwallexPaySourceServiceImpl paySourceService = ContextHolder.getBean(AirwallexPaySourceServiceImpl.class);
String airwallexToken = paySourceService.getAirwallexToken(operatorId);
httpGet.setHeader("Authorization",airwallexToken);
}
CloseableHttpAsyncClient httpClient = null;
try {
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] xcs, String string) {
}
public void checkServerTrusted(X509Certificate[] xcs, String string) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[] { tm }, null);
SSLIOSessionStrategy sslSessionStrategy = new SSLIOSessionStrategy(ctx, new String[] { "TLSv1","TLSv1.1","TLSv1.2" }, null,
SSLIOSessionStrategy.getDefaultHostnameVerifier());
httpClient = HttpAsyncClients.custom().setSSLStrategy(sslSessionStrategy).build();
httpClient.start();
Future<HttpResponse> future = httpClient.execute(httpGet, null);
HttpResponse response = future.get();
StringBuffer sb = new StringBuffer();
BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent(),"UTF-8"));
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
String body = sb.toString();
logger.info("sendAirwallexHttpGet after operatorId :{} response :{} ",operatorId,body);
return body;
} catch (Exception e) {
logger.error(e.getMessage(),e.getStackTrace());
throw e;
} finally {
if (httpClient != null) {
try {
httpClient.close();
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
}
}
3、 拉起各个接口请求,以下接口简写,请根据自己实际情况进行调整
(1)使用商户的 CLIENT ID 和 API 密钥 获取token
public String getAirwallexToken(String operatorId) {
//从redis中获取token ,token默认设置有效期20分钟过期
String token = redisTemplate.opsForValue()....省略;
if (StringUtils.isNotBlank(token)) {
return token;
}
String clientId = "";
String apiKey = "";
Map<String,String> headerMap = new HashMap<>();
headerMap.put("x-client-id",clientId);
headerMap.put("x-api-key",apiKey);
String result = null;
try {
//获取token信息
result = HttpClientUtil.sendAirwallexHttpPostLogin(String.format("%s%s", "https://api-demo.airwallex.com", "/api/v1/authentication/login"), headerMap);
}catch (Exception e) {
log.error("sendAirwallexHttpPost getToken error : "+operatorId+e.getMessage(),e);
}
if (StringUtils.isNotBlank(result)) {
//省略。。。。。
//加入到redis中,过期时间20分钟
}
return token;
}
(2) 创建用户
Map<String,Object> params = new HashMap<>();
params.put("request_id",request_id);
params.put("merchant_customer_id",merchant_customer_id);
params.put("first_name",first_name);
params.put("last_name",last_name);
params.put("email",email);
String result = null;
try {
result = HttpClientUtil.sendAirwallexHttpPost(String.format("%s%s", "https://api-demo.airwallex.com", "/api/v1/pa/customers/create"), params,operatorId);
}catch (Exception e) {
log.error("sendAirwallexHttpPost error : "+operatorId+e.getMessage(),e);
}
(3)下单支付接口
Map<String,Object> params = new HashMap<>();
params.put("request_id",request_id);
params.put("merchant_order_id",merchant_order_id);
params.put("amount",amount);
params.put("currency",currency);
params.put("customer_id",customer_id);
//构建额外配置参数
Map<String,Object> metadata = new HashMap<>();
metadata.put("accountId",accountId);
params.put("metadata",metadata);
String result = null;
try {
result = HttpClientUtil.sendAirwallexHttpPost(String.format("%s%s", "https://api-demo.airwallex.com", "/api/v1/pa/payment_intents/create"), params,operatorId);
}catch (Exception e) {
log.error("sendAirwallexHttpPost error : "+operatorId+e.getMessage(),e);
}
前端拉起空中云汇支付
参考网址
https://www.airwallex.com/docs/payments__embedded-elements
git地址
https://github.com/airwallex/airwallex-payment-demo/tree/master/docs