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 完成收银
步骤流程:
- 判断对应的订单是否存在
- 订单所有者
- 对应的Stripe Payment Intent 状态是否正常
- 订单完成
- 发送订单完成事件
- 事件订阅者处理后续流程
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
Custom payment flow | Stripe 文档
stripe/stripe-java: Java library for the Stripe API. (github.com)