1、准备
注册账号和文档
直接百度搜索Stripe官网—>管理平台—>注册
这里的国家并没有中国大陆可以选,但是选中国香港进行注册也是可以的
Stripe的相关文档也不少,本人是结合两个文档开发的:API文档及Docs大纲
环境和配置
登录后点击开发人员获取到公钥和私钥
另外就是Webhook这个大家都很熟悉的东西,自然也要配置上去
这里有很多种事件选择侦听,一般只要successes、failed、refund就好
配置完后加到我们的配置文件上去就好了,大概就是这些
2、开始
创建订单和支付
参考API文档,创建订单可以通过以下两个API
这两个方式都可以成功创建订单并且返回支付链接,以Create Session为例:
public String pay(CreateOrderEntity createOrderEntity) { //这个entity是自己定义的,跟API无关
Stripe.apiKey = apiKey;
try {
//创建产品
Map<String, Object> params = new HashMap<>();
params.put("name", createOrderEntity.getProductName());
Product product = Product.create(params);
//创建价格
Map<String, Object> priceParams = new HashMap<>();
BigDecimal actualAmount = createOrderEntity.getAmount().multiply(BigDecimal.valueOf(100)); //stripe的默认单位是分,即传入的amount实际上小数点会被左移两位
//给price绑定元数据并更新price用于检索
Map<String, Object> metadata = new HashMap<>();
metadata.put("orderId", createOrderEntity.getOrderId());
priceParams.put("metadata", metadata); //通过订单号关联用于检索price信息(可选)
priceParams.put("unit_amount", actualAmount.intValue());
priceParams.put("currency", createOrderEntity.getCurrency());
priceParams.put("product", product.getId());
Price price = Price.create(priceParams);
//创建支付信息得到url
SessionCreateParams params3 = SessionCreateParams.builder()
.setMode(SessionCreateParams.Mode.PAYMENT)
.setSuccessUrl(successUrl) //支付成功回调,可自定义成功页面
.setCancelUrl(cancelUrl)
.addLineItem(
SessionCreateParams.LineItem.builder()
.setQuantity(createOrderEntity.getQuantity())
.setPrice(price.getId())
.build()).build();
Session session = Session.create(params3);
System.out.println("sessionId:" +session.getId());
String sessionId = session.getId(); //退款方式1:拿到sessionId入库,退款的时候根据这个id找到PaymentIntent的id然后发起退款
return session.getUrl();
}catch (Exception e){
log.error("创建支付会话出现异常:",e);
}
return "";
}
产品:
所谓产品就是你的商品信息,通常来说你的商品只要有个名称和类型,或者加个图片什么的就够了
价格:
就是支付的数据来源以及订单追踪,包括订单号、商品(上面的id)、币种、数量、单价(支付金额就是总价=单价*数量),另外就是支付的时候是默认分为单位
Session:
successUrl:支付成功的回调页面
cancelUrl:取消支付的回调页面
Metadata:
这个参数呢在这里顺带一提就行,就是有些API是支持传这个参数的,为的就是追踪你那个API对应的参数和行为,说白了就是可以自定义一个map,支持Metadata的API可以通过Search API根据map中的参数去检索对应的对象
这里面使用银行卡支付4242***4242的号码是测试付款专用的,每个测试用户下面都会有这个卡号,也可以自己创建一个,无所谓,邮箱也无所谓(只是我习惯填自己的才打码了)。
支付成功后会回调,回调有两个,一个是Create Session的时候传的successUrl,这个作为页面重定向到自己自定义支付成功的页面即可;另一个是Webhook,这个需要多做处理,里面有一些信息需要获取到,后面会解释。
接着你就可以在开发者页面看到订单信息啦
退款
Stripe的退款方式有两种:根据支付意图编号和根据退款编号。
一种就是当你以Create Session支付订单后通过Webhook回调你可以捕获到一个object,里面有个json串可以解析出一个latestCharge参数,这个参数就是你支付订单后生成的支付意图即PaymentIntent唯一编号,以pi_开头;
还有一种是直接拿到你订单生成后给你默认生成的退款id(开发者页面找到订单可以看到一个以ch_开头的字符串就是)
public void refund(CreateRefundEntity createRefund) {
try{
Stripe.apiKey = apiKey;
if (StringUtils.isNotEmpty(createRefund.getSessionId())){ //根据会话编号退款
Session session =
Session.retrieve(
createRefund.getSessionId()
);
RefundCreateParams params;
if (createRefund.getAmount() != null && createRefund.getAmount().compareTo(BigDecimal.ZERO) != 0){ //指定退款金额
BigDecimal actualAmount = createRefund.getAmount().multiply(BigDecimal.valueOf(100)); //api默认单位分
params = RefundCreateParams.builder()
.setPaymentIntent(session.getPaymentIntent())
.setAmount(actualAmount.longValue())
.build();
}else { //全额退款
params = RefundCreateParams.builder()
.setPaymentIntent(session.getPaymentIntent())
.build();
}
Refund.create(params);
log.info("根据会话编号退款成功");
return;
}
if (StringUtils.isNotEmpty(createRefund.getChargeId())){ //根据退款编号退款
Map<String, Object> params = new HashMap<>();
params.put("charge", createRefund.getChargeId());
if (createRefund.getAmount() != null && createRefund.getAmount().compareTo(BigDecimal.ZERO) != 0){ //指定退款金额,否则全额退款
BigDecimal actualAmount = createRefund.getAmount().multiply(BigDecimal.valueOf(100)); //api默认单位分
params.put("amount", actualAmount.longValue());
}
Refund.create(params);
log.info("根据退款编号退款成功");
return;
}
}catch (Exception e){
//e.getMessage.contain("charge_already_refunded") 已退款
//e.getMessage.contain("resource_missing") 退款编号错误
//e.getMessage.contain("amount on charge ($n)") 金额应小于n
log.error("退款异常:",e);
}
}
退款可以部分退款,传入的金额也是以分为单位,关于退款异常的处理我没有写的很详细,如果需要,可以解析退款异常的message信息,返回不同的自定义提示信息给用户。
题外话:
看到这里你可能会有疑问,上面提到的Metadata可以检索到对应的API行为和参数的话,为什么不在创建订单的时候加入它,然后需要退款的时候用Metadata检索到订单信息,从订单信息里面获取退款编号直接退款呢?
这个我本人调用了好几个API以及看了好几个API Response都没有看到有订单绑定了退款id,随即我就看了一下订单的处理事件
圈起来的是重点,这些事件是按时间倒序的,并且是在支付成功后均由后台处理的,也就是说,只有Session是我们自己创建并定义参数的,而PaymentIntent和Charge都是由后台根据我们的订单信息生成的。因此我们在一开始的Session里面传入了Metadata也没用,我们只能找到session这个东西,没法找到PaymentIntent和Charge。PaymentIntent还好,可以通过Session获取到,也可以在Webhook捕获到,但是Charge这玩意就只有通过chargeId调用Retireve和通过Metadata去检索到它,但是上面又说了,Charge都是后台自己创建的,咱哪有机会传个Metadata进去。
综上所述,退款这个操作只能在创建订单的时候拿到它返回给你的sessionID入库,或者在支付成功后的Webhook中捕获到PaymentIntent再分别调用对应的API才能实现。
Webhook
在开发者页面配置的Webhook的URL必须是能够被外界访问的
public Object webhook(HttpServletRequest request, HttpServletResponse response) {
InputStream inputStream = null;
ByteArrayOutputStream output = null;
try {
//获取请求体的参数
inputStream = request.getInputStream();
output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024*4];
int n;
while (-1 != (n = inputStream.read(buffer))) {
output.write(buffer, 0, n);
}
byte[] bytes = output.toByteArray();
String eventPayload = new String(bytes, "UTF-8");
System.out.println("获取请求体的参数:" + eventPayload);
//获取请求头签名
String sigHeader = request.getHeader("Stripe-Signature");
System.out.println("获取请求头的签名:" + sigHeader);
Event event;
try {
event = Webhook.constructEvent(eventPayload,sigHeader,webhookSecret); //调用webhook进行验签
} catch (JsonSyntaxException e) {
log.error("参数格式有误,解析失败:",e);
response.setStatus(400);
return "";
}catch (SignatureVerificationException e) {
log.error("参数被篡改,验签失败:",e);
response.setStatus(400);
return "";
}
EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();
StripeObject stripeObject;
if (dataObjectDeserializer.getObject().isPresent()) { //如何数据为空
stripeObject = dataObjectDeserializer.getObject().get();
} else {
//一般是网络或者Stripe引起的数据丢失
response.setStatus(400);
return "";
}
// 获取支付状态
// Stripe的Webhooks中配置回调的时候同时指定了多种事件侦听,这些事件将被记录下来
switch (event.getType()) {
case StripeConstant.PAY_STRIPE_STATUS_CALL_BACK_SUCCESSED:
//支付成功
PaymentIntent paymentIntent = (PaymentIntent) stripeObject;
System.out.println("chargeId:" + paymentIntent.getLatestCharge());
String chargeId = paymentIntent.getLatestCharge(); //退款方式2:回调保存该退款编号用于退款,
break;
case StripeConstant.PAY_STRIPE_STATUS_CALL_BACK_ATTACHED:
PaymentMethod paymentMethod = (PaymentMethod) stripeObject;
break;
case StripeConstant.PAY_STRIPE_STATUS_CALL_BACK_CANCELED:
log.warn("取消订单");
break;
case StripeConstant.PAY_STRIPE_STATUS_CALL_BACK_FAILED:
log.warn("支付失败");
break;
default:
System.out.println("Unhandled event type: " + event.getType());
}
response.setStatus(200); //处理无异常,返回
}catch (Exception e){
log.error("webhook异步通知解析异常:",e);
response.setStatus(400);
return "";
}finally {
try {
if (inputStream != null){
inputStream.close();
}
if (output != null){
output.close();
}
}catch (Exception e){
log.error("流关闭异常");
}
}
return "";
}
上面使用到了接口验签的API,如果不需要接口验签可以直接调用API里面的原始方法
最后附上自己的代码,如有需要可在gitee仓库上自取:https://gitee.com/faker20/tutu-pub-stripe.git