Java对接Stripe支付


在这里插入图片描述

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥肥肥柯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值