Stripe Android 购买集成

1. 准备事项

  • Stripe 账号
  • 域名以及配套的网站
  • Stripe 账号付款信息
  • 公钥和私钥
  • 配置产品以及价格

这些步骤可以看这篇:Stripe Web 购买集成

3. 分析一下流程

客户端集成和 Web 端集成有挺大的区别,客户端一般不会选择 Stripe-hosted page,本质上他是一个页面。如果用户使用卡来支付,那体验还行,如果是需要调用支付宝支付这种,那体验是极差的。他会打开一个页面显示二维码,用手机打开一个二维码,还需要手机来扫码,这操作差点闪到我的老腰。

这就导致用户要付钱,只能截图然后打开支付宝来识别相册中的二维码。用户本来给钱就肉痛了,你给了他一点冷静的时间,他直接就不买了。

所以这次我们使用更适合移动客户端体质的方式来进行集成,也就是自定义支付流程。

Custom payment flow | Stripe 文档

老规矩稍微设计一下,搞个时序图:

和 Web 的方式最主要的区别就是这里不使用 Checkout 对象了

4. 代码集成

4.1 依赖导入

stripe/stripe-java: Java library for the Stripe API. (github.com)

xml 复制代码
<dependency>  
    <groupId>com.stripe</groupId>  
    <artifactId>stripe-java</artifactId>  
    <version>23.3.0</version>  
</dependency>

4.2 配置

properties 复制代码
# 公钥
stripe.key=pk_test_51Nxxxx
# 私钥
stripe.secret=sk_test_51xxxx
# webhook 密钥签名
stripe.endpoint_secret=whsec_Tcxxxx
java 复制代码
@Data  
@Configuration  
@ConfigurationProperties(prefix = "stripe")  
public class StripeConfig {  
    private String key;
    private String secret;
    private String endpointSecret;
    @Bean  
    public StripeClient stripeClient() {
        return new StripeClient(secret);
    }
}

4.3 创建收银

查询公钥太简单了,这里省略

文档:Stripe Mobile Elements | Stripe 文档

Demo: Custom payment flow | Stripe 文档

后端创建 PaymentIntent

java 复制代码
public CheckoutCreateResult create(CheckoutCreateRequest request) {
    // 查询客户
    String customerId = queryCustomerId();

    // 查询价格
    String priceId = queryPriceId();

    try {
        // 查询价格对象
        Price price = stripeClient.prices().retrieve(productPrice.getPriceId());

        // 创建付款对象
        PaymentIntentCreateParams.Builder builder = PaymentIntentCreateParams.builder()
                .setCustomer(customerId)
                .setAmount(price.getUnitAmount())
                .setCurrency(price.getCurrency())
                .putAllMetadata(ImmutableMap.of(
                        MetaDataKey.CHECKOUT_ID, checkoutId,
                        MetaDataKey.UID, request.getUid()

                ));
        PaymentIntent paymentIntent = stripeClient.paymentIntents().create(builder.build());

        return new CheckoutCreateResult()
            .setId(paymentIntent.getId())
            .setTokenThird(paymentIntent.getClientSecret());

    } catch (StripeException e) {
        log.error("failed to create checkout session. {}, {}, {}", request, customerId, priceId, e);
        throw new RuntimeException("failed to create checkout by payment intent: " + e.getMessage());
        return null;
    }
}

4.4 完成收银

4.4.1 前端提交

用户付款完成后,客户端会接收到回调,然后进一步调用后端接口完成收银。

4.4.2 接收 Webhook

用户付款完成后,Stripe 的后台还会将对应的事件通过 WebHook 的方式 POST 我们预先提供的接口。

第一步,先提供一个 Webhook 回调接口

本地测试的方式不是很友好,可以使用内网穿透工具将请求转到本地来进行调试

java 复制代码
@RestController
@Slf4j
public class WebhookController {
    @Resource
    private StripeConfig stripeConfig;
    @Resource
    private List<WebhookHandler> webhookHandlers;

    @PostMapping("/webhook")
    public Object handle(@RequestHeader("Stripe-Signature") String sigHeader, @RequestBody String payload) {
        log.info("stripe webhook payload: {}", payload);
        return webhook(payload, sigHeader, stripeConfig.endpointSecret());
    }

    private Object webhook(String payload, String sigHeader, String endpointSecret) {
        Event event;
        try {
            event = Webhook.constructEvent(payload, sigHeader, endpointSecret);
            StripeEventType stripeEventType = StripeEventType.convert(event.getType());
            webhookHandlers.stream()
                    .filter(webhookHandler -> webhookHandler.supports(stripeEventType))
                    .findFirst()
                    .get().handle(event);
        } catch (Exception e) {
            log.error("failed to handle webhook event. {}, {}", sigHeader, payload, e);
            return ResponseEntity.status(500).body(e.getMessage());
        }
        return ResponseEntity.ok().body("OK");
    }
}

Stripe 事件枚举

java 复制代码
public enum StripeEventType implements EnumBase {  
    // 收银完成  
    CHECKOUT_SESSION_COMPLETED("checkout.session.completed"),  
    // 退款  
    CHARGE_REFUNDED("charge.refunded"),
    // 付款完成,一般是客户端集成。注意:CHECKOUT_SESSION_COMPLETED 也会同时收到这个 
    PAYMENT_INTENT_SUCCEEDED("payment_intent.succeeded"),
  
    IGNORED("");  
  
    private final String message;  
  
    StripeEventType(String message) {  
        this.message = message;  
    }  
  
    public static StripeEventType convert(String message) {  
        for (StripeEventType value : StripeEventType.values()) {  
            if (StringUtils.equals(value.message(), message)) {  
                return value;  
            }  
        }  
        return IGNORED;  
    }  
  
    @Override  
    public String message() {  
        return this.message;  
    }  
  
    @Override  
    public Number value() {  
        return null;  
    }  
}

Webhook 处理器

java 复制代码
public interface WebhookHandler {  
    boolean supports(StripeEventType stripeEventType);  
    void handle(Event event) throws EventDataObjectDeserializationException;  
}

public abstract class WebhookHandlerBase<T> implements WebhookHandler {  
    @SuppressWarnings("unchecked")  
    @Override  
    public void handle(Event event) throws EventDataObjectDeserializationException {  
        EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer();  
        StripeObject stripeObject = dataObjectDeserializer.deserializeUnsafe();  
        handle((T) stripeObject);  
    }  
  
    public abstract void handle(T stripeObject);  
}

@Component  
@Slf4j  
public class WebhookHandlerDefaultImpl implements WebhookHandler {  
    @Override  
    public boolean supports(StripeEventType stripeEventType) {  
        return stripeEventType.equals(StripeEventType.IGNORED);  
    }  
    @Override  
    public void handle(Event event) {  
        log.info("ignored event: {} {}", event.getType(), event.toJson());  
    }  
}

@Component
@Slf4j
public class WebhookHandlerPaymentIntentSucceededImpl extends WebhookHandlerBase<PaymentIntent> {
    @Resource
    private CheckoutFactory checkoutFactory;
    @Override
    public boolean supports(StripeEventType stripeEventType) {
        return stripeEventType.equals(StripeEventType.PAYMENT_INTENT_SUCCEEDED);
    }
    @Override
    public void handle(PaymentIntent paymentIntent) {
        // 付款完成
    }
}

@Component
@Slf4j
public class WebhookHandlerChargeRefundImpl extends WebhookHandlerBase<Charge> {
    @Override
    public boolean supports(StripeEventType stripeEventType) {
        return stripeEventType.equals(StripeEventType.CHARGE_REFUNDED);
    }
    @Override
    public void handle(Charge charge) {
       // 订单退款
    }
}

配置 Stripe Webhook

管理平台 -- FeloTranslator -- Stripe [Test]

4.4.3 完成收银

步骤流程:

  1. 判断对应的订单是否存在
  2. 订单所有者
  3. 对应的Stripe Payment Intent 状态是否正常
  4. 订单完成
  5. 发送订单完成事件
  6. 事件订阅者处理后续流程
java 复制代码
protected PaymentIntent checkPaymentIntent(String receipt) {

    PaymentIntent paymentIntent = stripeClient.paymentIntents().retrieve(receipt);

    //https://docs.stripe.com/api/payment_intents/object#payment_intent_object-status
    String status = paymentIntent.getStatus();
    if (StringUtils.notEquals(status, "succeeded")) {
        throw new RuntimeException("Checkout has no completed: " + status);
    }
    
    return paymentIntent;
}

Ref

Documentation | Stripe 文档

收款 | Stripe 文档

Custom payment flow | Stripe 文档

stripe/stripe-java: Java library for the Stripe API. (github.com)

Stripe API Reference

相关推荐
瓜牛_gn3 分钟前
依赖注入注解
java·后端·spring
Estar.Lee20 分钟前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
喜欢猪猪22 分钟前
Django:从入门到精通
后端·python·django
一个小坑货22 分钟前
Cargo Rust 的包管理器
开发语言·后端·rust
bluebonnet2726 分钟前
【Rust练习】22.HashMap
开发语言·后端·rust
uhakadotcom1 小时前
如何实现一个基于CLI终端的AI 聊天机器人?
后端
Iced_Sheep1 小时前
干掉 if else 之策略模式
后端·设计模式
XINGTECODE2 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶2 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺2 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端