【开发心得】SpringBoot对接Stripe支付

概述

    应用出海,需要对接国外的支付,之一的选择就是Stripe。这个介绍下Java对接Stripe的实现。

官网: Stripe | Financial Infrastructure to Grow Your Revenue

API文档:
https://docs.stripe.com/apiicon-default.png?t=O83Ahttps://docs.stripe.com/api

Stripe支付方式和概念比较多。

  1. 付款方式: 一次性付款和周期订阅付款。
  2. 支付方式: Paylink、charge、pay intent多种概念。
  3. 对接方式: 前端对接,后端对接两种。

Stripe在认证开通之前,支付属于沙箱模式。支付的时候,卡号填 4242 4242 4242 4242即可(在外网资料中看到的,暂时不知道其他特殊卡号是否支持)。

国内正式环境测试可以通过一些双币卡,比如东方航空与中信银行的联名信用卡。(吐槽下,Stripe有一个最低消费额,换算成人民币,3块左右每次)

对接步骤

1. 引入maven/gradle依赖

这里以maven为例。(2024年5月份对接的时候,我这里选型26.3,网上一些资料基于更老的版本)

<!-- https://mvnrepository.com/artifact/com.stripe/stripe-java -->
<dependency>
	<groupId>com.stripe</groupId>
	<artifactId>stripe-java</artifactId>
	<!--                <version>24.16.0</version>-->
	<version>26.3.0</version>
</dependency>
2. webhook监听

设置webhook监听地址:

(1) 页面方式

入口比较隐蔽,可以在全局搜

地址填写实现webhook的地址。事件根据需要选择。

创建完后点击对应链接,获得密钥签名webHookSecret

(2) 代码方式创建

import com.stripe.Stripe;
import com.stripe.model.Event;
import com.stripe.model.WebhookEndpoint;
import com.stripe.net.WebhookEndpointCollection;
import com.stripe.exception.*;
 
public class CreateWebhookEndpoint {
    public static void main(String[] args) {
        Stripe.apiKey = "your_stripe_api_key";
 
        try {
            WebhookEndpoint endpoint = WebhookEndpoint.create(
                "https://your-webhook-handler-domain.com",
                Arrays.asList("charge.succeeded", "charge.failed")
            );
 
            System.out.println(endpoint.getId());
        } catch (StripeException e) {
            e.printStackTrace();
        }
    }
}

Stripe有个回调机制,在一定时间内,不断重试知道请求成功。它依赖于http的状态码。

关于Stripe的Java服务端监听,官方文档参考: Stripe Login | Sign in to the Stripe DashboardSign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.icon-default.png?t=O83Ahttps://dashboard.stripe.com/webhooks/create?endpoint_location=hosted

  private static final String PAYLOAD_CHARSET = "UTF-8";

  private static final String STRIPE_SIGNATURE_FIELD = "Stripe-Signature";

  private static final String REMOTE_SUCCEED_FIELD = "succeeded";

stripeApiKey与webHookSecret  这里是我从配置文件中读取出来的的,@Value方式,可自行实现。

stripeApiKey 是Stripe的支付sk

webHookSecret是回调页面配置获取的key

webhook 监听的事件包含如下几个常用,其实比较多,可以通过页面设置webhook,或者代码设置webhook的时候指定。

* charge.succeed
* charge.canceled
* payment_intent.succeeded
* payment_intent.failed
* payment_intent.canceled
    @ResponseBody
    @PostMapping(value = "/callback")
    public void postEventsWebhook(HttpServletRequest request, HttpServletResponse response) {
        Stripe.apiKey = stripeApiKey;
        try {
//            String payload = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
            InputStream inputStream = request.getInputStream();
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024 * 4];
            int n = 0;
            while (-1 != (n = inputStream.read(buffer))) {
                output.write(buffer, 0, n);
            }
            byte[] bytes = output.toByteArray();
            String payload = new String(bytes, PAYLOAD_CHARSET);
            if (!StringUtils.isEmpty(payload)) {
                String sigHeader = request.getHeader(STRIPE_SIGNATURE_FIELD);
                String endpointSecret = webHookSecret;
                Event event = Webhook.constructEvent(payload, sigHeader, endpointSecret);
                Optional<StripeObject> stripeObject = event.getDataObjectDeserializer().getObject();
                String remoteStatus = null;
                Map<String, String> metaData = null;
                if (stripeObject.isPresent()) {
                    PaymentIntent intent = (PaymentIntent) stripeObject.get();
                    remoteStatus = intent.getStatus();
                    metaData = intent.getMetadata();
                } else {
                    StripeObject sobj = event.getData().getObject();
                    if (sobj instanceof Charge) {
                        Charge object = (Charge) sobj;
                        remoteStatus = object.getStatus();
                        metaData = object.getMetadata();
                    } else {
                        PaymentIntent intent = (PaymentIntent) sobj;
                        remoteStatus = intent.getStatus();
                        metaData = intent.getMetadata();
                    }
                }

                ProcessStatus processStatus = null;
                StripePayResultEnum payResultEnum = StripePayResultEnum.getByName(event.getType());
                switch (payResultEnum) {
                    case PI_CREATED: //创建订单
                        // 这种先不处理
                        break;
                    case PI_CANCELED: // 取消订单
                        processStatus = ProcessStatus.CANCELLED;
                        break;
                    case PI_SUCCEED: // 支付成功
                    case CH_SUCCEED: // 结算成功
                        if (REMOTE_SUCCEED_FIELD.equals(remoteStatus)) {
                            processStatus = ProcessStatus.COMPLETED;
                        }
                        break;
                    case PI_FAILED: // 支付失败
                    case CH_FAILED: // 结算失败
                        processStatus = ProcessStatus.ERROR;
                        break;
                    default:
                        break;
                }
                if (processStatus != null) {
                    //自定义传入的参数
                    String orderNo = metaData.get(GatewayConstant.PAY_ORDER_NO);
                    log.info("finished, orderNo:{}, webhook event:{}, status:{}", orderNo, payResultEnum, processStatus);
                    Boolean finish = orderService.finish(orderNo, processStatus);
                    log.info("orderNo:{} deal finished, result:{}", orderNo, finish);
                }
                response.setStatus(200);
            }

        } catch (Exception e) {
            response.setStatus(500);
            log.error(e.getMessage(), e);
        }
    }

可以通过内网穿透,或者把应用放到云服务器上测试。

也可以通过本地webhook模拟测试,参考:

Stripe Login | Sign in to the Stripe DashboardSign in to the Stripe Dashboard to manage business payments and operations in your account. Manage payments and refunds, respond to disputes and more.icon-default.png?t=O83Ahttps://dashboard.stripe.com/webhooks/create?endpoint_location=local

3. 业务支付

(1) 创建PriceId

XxxPrice(自己命名):

    private String name;     // 名称
    private Long amount;     // 金额
    private String currency; // 货币单位(如usd)

checkPrice是简单判断了一下,param参数的货币,金额,调用该代码之后,我们会得到一个price_开头的价格id。将其记录下来,与自定义的商品关联。不用每次创建,这个在商品创建与价格更新的时候处理一次即可。

    public String createPrice(XxxPrice param) {
        this.checkPrice(param);
        Stripe.apiKey = privateKey;
        try {
            PriceCreateParams params =
                    PriceCreateParams.builder()
                            .setCurrency(param.getCurrency())
                            .setUnitAmount(param.getAmount())
                            .setProductData(
                                    PriceCreateParams.ProductData.builder().setName(param.getName()).build()
                            )
                            .build();
            Price price = Price.create(params);
            if (price != null) {
                return price.getId();
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        return null;
    }

 

(2) 创建PayIntent(支付意图,这是Stripe较新的概念)

这里SuccessUrl和CancelUrl自定设置,作用是支付页面支付完成后,重定向到对应的地址。

Quantity是数量,PriceId是上一步获得的id

/**
     * @param priceId
     * @return
     * @throws StripeException
     * @description: 获取支付链接
     */
    public Session createPayment(String priceId, String orderNo) throws StripeException {
        String payStatusUrl = this.getPayStatusUrl(orderNo);
        Stripe.apiKey = privateKey;
        SessionCreateParams.Builder builder = SessionCreateParams.builder();

        builder.setMode(SessionCreateParams.Mode.PAYMENT);
        builder.setPaymentIntentData(SessionCreateParams.PaymentIntentData.builder()
                .putMetadata(GatewayConstant.PAY_ORDER_NO, orderNo)
                .build());
        SessionCreateParams params =
                builder
                        // 支付成功跳转
                        .setSuccessUrl(payStatusUrl)
                        // 支付取消跳转
                        .setCancelUrl(payStatusUrl)
                        .addLineItem(
                                SessionCreateParams.LineItem.builder()
                                        .setQuantity(1L)
                                        // Provide the exact Price ID (for example, pr_1234) of the product you want to sell
                                        // 传入价格ID
                                        .setPrice(priceId)
                                        .build())
                        .build();
        Session session = Session.create(params);
        return session;
    }

这个会得到一个 Session,id为一个pi_开头的字符串,代表付款唯一标识,url是支付界面的url,这个需要前端/客户端展示的。页面类似如下,其中商品名称,描述,货币单位,价格等都可以代码控制。(截图是正式的样例,测试的会有提示测试环境的字样,测试环境可以使用4242 4242 4242 4242来测试)

关于签名: SDK可以自动验证签名:

 String sigHeader = request.getHeader(STRIPE_SIGNATURE_FIELD);
 String endpointSecret = webHookSecret;
 Event event = Webhook.constructEvent(payload, sigHeader, endpointSecret);

关于携带自定义字段:

SessionCreateParams的putMetadata方法实际是一个map,可以设置自定义的键值对来传递。比如我这里传递的orderNo。

结语:

虽然概念多,但实际Stripe的文档也足够详细了。多看文档,多看sdk的源码,对接本身还是简单的。

### JavaStripe API集成 对于希望在其应用程序中处理支付流程的开发者来说,Stripe提供了一个强大而灵活的API来简化这一过程。为了实现Java应用与Stripe API的成功集成,可以遵循以下方法。 #### 添加依赖项 首先,在项目中引入Stripe库作为依赖项。如果使用Maven构建工具,则可以在`pom.xml`文件里加入如下配置: ```xml <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>20.97.0</version> </dependency> ``` 这一步骤确保了所有必要的类和接口都可用于后续开发工作[^1]。 #### 初始化客户端 接着初始化一个用于调用Stripe服务端口的新实例。通常情况下只需要设置一次即可在整个程序生命周期内重复利用该对象。下面是一个简单的例子展示如何完成这项操作: ```java import com.stripe.Stripe; public class PaymentService { public static void init() { Stripe.apiKey = "sk_test_..."; } } ``` 这里需要注意的是替换掉字符串中的占位符部分为实际有效的密钥值,这些信息可以从个人账户下的仪表板获取到[^4]。 #### 创建Charge请求 当准备发起一笔新的交易时,可以通过构造相应的参数并提交给指定路径的方法来进行。例如要创建一个新的收费记录可参照下述代码片段: ```java import com.stripe.model.Charge; import java.util.HashMap; import java.util.Map; // ... Map<String, Object> chargeParams = new HashMap<>(); chargeParams.put("amount", 2000); chargeParams.put("currency", "usd"); chargeParams.put("source", "tok_visa"); // obtained with Stripe.js chargeParams.put("description", "Charge for service"); Charge.create(chargeParams); ``` 上述示例展示了怎样通过传递金额、货币种类以及付款方式等必要字段至服务器从而触发一次完整的在线支付行为。 #### 处理Webhook事件通知 除了主动发送指令外,监听来自平台的通知也是不可或缺的一环。每当发生特定类型的活动(比如退款成功),系统会自动向预先注册过的URL推送消息告知最新状态变化情况。因此建议按照官方文档指导搭建好接收器以便及时响应各种异步反馈机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值