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

相关推荐
阿华的代码王国5 分钟前
【Android】适配器与外部事件的交互
android·xml·java·前端·后端·交互
写bug写bug6 分钟前
分布式锁的使用场景和常见实现(下)
分布式·后端·面试
Postkarte不想说话6 分钟前
Debian13编译安装FreeSWITCH
后端
SimonKing10 分钟前
Mybatis批量插入,形式不同性能也不同
数据库·后端·程序员
MacroZheng15 分钟前
还在用WebSocket实现即时通讯?试试MQTT吧,真香!
java·spring boot·后端
midsummer_woo38 分钟前
基于springboot的IT技术交流和分享平台的设计与实现(源码+论文)
java·spring boot·后端
这里有鱼汤1 小时前
miniQMT+Qlib才是AI量化的正确打开方式
后端
无奈何杨1 小时前
风控系统事件分析中心,关联关系、排行、时间分布
前端·后端
Moment1 小时前
nginx 如何配置防止慢速攻击 🤔🤔🤔
前端·后端·nginx
rannn_1111 小时前
【MySQL学习|黑马笔记|Day7】触发器和锁(全局锁、表级锁、行级锁、)
笔记·后端·学习·mysql