MENU

Stripe对接

January 5, 2022 • Read: 696 • 学习·笔记

官方文档引导 https://stripe.com/docs/billing/subscriptions/overview

1. 几个核心对象

  • product

产品的描述。

  • plan/price

产品的计划,价格。即计费周期扣费相关定义。

  • payment_method

支付方式,包含卡号,cvc,过期年月、账单邮箱电话地址信息。

  • customer

订阅产品计划和支付产品的顾客。创建时候可以设置payment_method,将支付方式 attach 到顾客里面,并且设置顾客的默认支付方式。
创建时候,会校验支付方式的格式、过期等校验

  • subscription

订阅,包含 plan、customer、payment_method。当创建成功时,会订阅会创建 invoice 及 payment_intent 对象。

  • invoice

发票,订阅每次需要扣款时,都会开一张发票。但发票可能因为支付成功是否,有不同的状态
支付成功和失败会发送对应的webhook事件

  • payment_intent

支付意向,即发票的尝试付款信息。
什么是尝试付款信息?
包含支付的成功是否状态,失败时需要进行哪些步骤才能付款成功。
如有些地区及银行卡,需要3d验证才能支付成功,如信用卡无效需要收集新的支付方式才能支付成功,此类信息会在支付意向中体现

2. 对接流程

2.1 两个参数

# 业务客户端使用,如前端js,可公开暴露,如进行支付时使用
STRIPE_PUBLISHABLE_KEY=pk_test_abc

# 业务服务端使用,如java,不可公开使用
STRIPE_SECRET_KEY=sk_test_edf

2.2 后端sdk的使用

  • 2.2.1 maven依赖,官方sdk
<dependency>
    <groupId>com.stripe</groupId>
    <artifactId>stripe-java</artifactId>
    <version>20.90.0</version>
</dependency>
  • 2.2.1 Java 后端调用样例
Stripe.apiKey = NacosConfig.STRIPE_PUBLISHABLE_KEY;
// 创建产品
ProductCreateParams productCreateParams = ProductCreateParams
    .builder()
    .setActive(true)
    .setName("Product- MyyShoper Basic")
    .build();

Product product = Product.create(productCreateParams);

2.3 前端sdk的使用

  • 2.3.1 依赖
<html>
    <head>
        <script src="https://js.stripe.com/v3/"></script>
    </head>
    <body>
      demo
    </body>
</html>
  • 2.3.2 前端调用样例
// STRIPE_PUBLISHABLE_KEY 是后端返给前端的配置参数
var stripe = Stripe('${STRIPE_PUBLISHABLE_KEY}');
const confirmToPay = () => {
    // PAYMENT_INTEND_CLIENT_SECRET 是后端返给前端订阅时支付意向客户端密钥
    stripe.confirmCardPayment('${PAYMENT_INTEND_CLIENT_SECRET}')
    .then((result) => {
       if(result.error) {
          // 支付失败,失败原因详细信息在此:${result.error.message}
        } else {
          // 支付成功跳转页面
        }
    });
}

2.4 创建订阅

  • 2.4.1 创建支付方式

请求报文:

{
    "payment_method":{
        "type":"card",
        "card":{
            "number":"4242xxxx4242",
            "expMonth":"12",
            "expYear":"2023",
            "cvc":"123"
        },
        "billingDetails":{
            "email":"aaaaa@qq.com",
            "name":"ccccc",
            "phone":"+8618826108955",
            "address":{
                "country":"CN",
                "state":"Guangdong",
                "city":"Shenzhen",
                "postCode":"510006",
                "line1":"",
                "line2":""
            }
        }
    }
}

响应报文,返回支付方式实体,主要是ID,如:pm_xxx

  • 2.4.2 创建客户

请求报文:

{
    "customer":{
        "email":"aaaaa@qq.com",
        "description":"",
        // warn:attach to customer
        "paymentMethod":"pm_xxx",
        "invoiceSettings":{
             // warn:default payment method
            "defaultPaymentMethod":"pm_xxx"
        }
    }
}

创建时候,会校验支付方式的格式、过期等校验

响应报文,返回顾客实体,主要是ID,如:cus_xxx

  • 2.4.3 创建商品及计划

请求报文:

// product
{
    "product":{
        "name":"Myshipping - MyyShoper Basic"
    }
}
// plan
{
    "plan":{
        "product":"prod_xxx",
        "amount":900,
        "currency":"USD",
        "interval":"MONTH"
    }
}

响应报文,返回商品及价格的实体,主要是ID,如:商品ID=prod_xxx及计划ID=plan_xxx

  • 2.4.4 创建订阅

请求报文:

{
    "subscription":{
        "customer":"cus_xxx", // 哪个顾客
        "item":{
            "price":"plan_xxx" // 哪个商品价格
        },
        "defaultPaymentMethod":"pm_xxx" // 支付方式
    }
}

2.5 创建订阅后响应处理

  • 2.5.1 订阅成功及成功扣费
{
    // @subscription
    "object": "subscription",
    "id": "sub_1KBCdfAQe605kX9nQXAmTXzg",
    "status": "active", // ********* subscription status = active *********
    "latest_invoice": {
        "id": "in_1KBCdfAQe605kX9nSKDY3MEB",
        "expandedObject":{
            // @invoice
            "object": "invoice",
            "id": "in_1KBCdfAQe605kX9nSKDY3MEB",
            "status": "paid",  // ********* latest_invoice status = paid *********
            "paid": true,
            "hosted_invoice_url": "https://invoice.stripe.com/i/acct_1K945EAQe605kX9n/test_YWNjdF8xSzk0NUVBUWU2MDVrWDluLF9LcXVTUjZyb3dBY0xvUGRteVRBaVdyRWRmOHlyQVdG01003h9J8Klc",
            "invoice_pdf": "https://pay.stripe.com/invoice/acct_1K945EAQe605kX9n/test_YWNjdF8xSzk0NUVBUWU2MDVrWDluLF9LcXVTUjZyb3dBY0xvUGRteVRBaVdyRWRmOHlyQVdG01003h9J8Klc/pdf",
            "payment_intent": {
                "id": "pi_3KBCdgAQe605kX9n1QEtEle3",
                "expandedObject": {
                    // @payment_intent
                    "object": "payment_intent",
                    "id": "pi_3KBCdgAQe605kX9n1QEtEle3",
                    "status": "succeeded",  // ********* payment_intent status = succeeded *********
                    // ********* next_action not existed *********
                    "client_secret": "pi_3KBCdgAQe605kX9n1QEtEle3_secret_EHrdDAbfcSg7b4g4GeyaCwkyD",  // ********* client_secret existed *********
                    "payment_method": {
                        "id": "pm_1KBCdSAQe605kX9n33fS7Xmk"
                    },
                    "invoice": {
                        "id": "in_1KBCdfAQe605kX9nSKDY3MEB"
                    },
                }
            }
        }
    }
}

参数小结:

subscription.status = active
latest_invoice.status = paid
latest_invoice.paid = true
latest_invoice.payment_intent status = succeeded
latest_invoice.payment_intent.next_action not existed
latest_invoice.payment_intent.client_secret = 'pi_xxxxx'

后端响应前端订阅成功,前端跳转到订阅成功页面。此后异步等待扣费成功事件,激活会员套餐

  • 2.5.2 订阅失败,需要3d验证
{
    // @subscription
    "object": "subscription",
    "id": "in_1KBC5mAQe605kX9nHGSPFOU3",
    "status": "incomplete", // ********* subscription status = incomplete *********
    "latest_invoice": {
        "id": "in_1KBC5mAQe605kX9nHGSPFOU3",
        "expandedObject":{
            // @invoice
            "object": "invoice",
            "id": "in_1KBC5mAQe605kX9nHGSPFOU3",
            "status": "open",  // ********* latest_invoice status = open *********
            "paid": false,
            "hosted_invoice_url": "https://invoice.stripe.com/i/acct_1K945EAQe605kX9n/test_YWNjdF8xSzk0NUVBUWU2MDVrWDluLF9LcXR0TG1HR3E0b3c2cWZ2NTJFVjZ3YzdRYnlPZDZZ0100CALfGIiN",
            "invoice_pdf": "https://pay.stripe.com/invoice/acct_1K945EAQe605kX9n/test_YWNjdF8xSzk0NUVBUWU2MDVrWDluLF9LcXR0TG1HR3E0b3c2cWZ2NTJFVjZ3YzdRYnlPZDZZ0100CALfGIiN/pdf",
            "payment_intent": {
                "id": "pi_3KBC5mAQe605kX9n0y0Zkhvo",
                "expandedObject": {
                    // @payment_intent
                    "object": "payment_intent",
                    "id": "pi_3KBC5mAQe605kX9n0y0Zkhvo",
                    "status": "requires_action",  // ********* payment_intent status = requires_action *********
                    "next_action": {              // ********* next_action existed *********
                        // redirect_to_url
                        // ********* use_stripe_sdk *********
                        // alipay_handle_redirect
                        // oxxo_display_details
                        // verify_with_microdeposits
                        "type": "use_stripe_sdk",
                        "use_stripe_sdk": {
                            "type": "three_d_secure_redirect",
                            "stripe_js": "https://hooks.stripe.com/redirect/authenticate/src_1KBC5mAQe605kX9nhsX97k6W?client_secret=src_client_secret_mMxXglz7tm3C204yVDSUWEwy&source_redirect_slug=test_YWNjdF8xSzk0NUVBUWU2MDVrWDluLF9LcXR0QTIyMDd6NjZFeE82TmRNdGhhanZLeWExQUhR0100NYxq9Xrr",
                            "source": "src_1KBC5mAQe605kX9nhsX97k6W"
                        }
                    },
                    "client_secret": "pi_3KBC5mAQe605kX9n0y0Zkhvo_secret_za8UgrcOaqHKLfAzDqRiPdAF6",  // ********* client_secret existed *********
                    "payment_method": {
                        "id": "pm_1KBC5gAQe605kX9nrXLIFnE6"
                    },
                    "invoice": {
                        "id": "in_1KBC5mAQe605kX9nHGSPFOU3"
                    },
                }
            }
        },
    }
}

参数小结:

subscription.status = incomplete
latest_invoice.status = open
latest_invoice.paid = false
latest_invoice.payment_intent status = requires_action
latest_invoice.payment_intent.next_action.type = use_stripe_sdk
latest_invoice.payment_intent.next_action.use_stripe_sdk={"type": "three_d_secure_redirect"}
latest_invoice.payment_intent.client_secret = 'pi_xxxxx'

后端响应前端订阅失败,包含3d验证所需要的参数。3d验证后,扣费是否成功等是stripe的sdk提示。如果验证成功,前端跳转到订阅成功页面。此后异步等待扣费成功事件,激活会员套餐

后端返回前端核心参数:

{
    "client_secret" : "pi_xxxxx",
    "stripe_publishable_key" : "pk_test_abc"
}

前端使用这两个参数,配合前端sdk进行3d验证支付,例子在前面章节
(前端sdk文档 https://stripe.com/docs/js 前端官方example https://stripe.dev/elements-examples/

  • 2.5.3 其他订阅失败

后端抛出异常,内容为stripe提示的信息或订阅失败请联系管理员

2.6 几个重要事件回调

  • invoice.payment_failed
  • invoice.payment_succeeded
  • customer.subscription.deleted

2.6.1 invoice.payment_failed

{
  "api_version": "2020-08-27",
  "created": 1641279084,
  "data": {
    "object": {
      "id": "in_1KE5ukAQe605kX9nqoJjSMcp",
      "object": "invoice",
      // 首次失败 subscription_create
      // 循环扣费失败 subscription_cycle
      // 其他 upcoming subscription_threshold manual
      "billing_reason": "subscription_cycle",
      "created": 1641275430,
      "customer": "cus_KqttrF9wjePEcp",
      "paid": false,
      "payment_intent": "pi_3KE6reAQe605kX9n3H9Iqcv8",
      "status": "open",
      "subscription": "sub_1KBC5lAQe605kX9nYaJ1uQk9"
    }
  },
  "id": "evt_1KE6rgAQe605kX9ndcD1QB1Y",
  "object": "event",
  "type": "invoice.payment_failed"
}

后端处理:billing_reason=subscription_cycle,暂停订阅,其他情况不管

2.6.2 invoice.payment_succeeded

{
  "api_version": "2020-08-27",
  "created": 1641281392,
  "data": {
    "object": {
      "id": "in_1KE6ViAQe605kX9nlQATpkHX",
      "object": "invoice",
      // 首次成功 subscription_create
      // 循环扣费成功 subscription_cycle
      // 其他 upcoming subscription_threshold manual
      "billing_reason": "subscription_cycle",
      "created": 1641277722,
      "customer": "cus_KquSV42aaf47ct",
      "paid": true,
      "status": "paid",
      "subscription": "sub_1KBCdfAQe605kX9nQXAmTXzg",
    }
  },
  "id": "evt_1KE7SuAQe605kX9nDWz3uXhd",
  "object": "event",
  "type": "invoice.payment_succeeded"
}

后端处理:billing_reason=subscription_create,进行激活及增加权益套餐时长。其他情况,如 subscription_cycle 等,全部增加权益套餐时长

2.6.3 customer.subscription.deleted

{
  "api_version": "2020-08-27",
  "created": 1641367955,
  "data": {
    "object": {
      "id": "sub_1KByy7AQe605kX9nMfEnfecD",
      "object": "subscription",
      "canceled_at": 1641367955,
      "created": 1640772075,
      "customer": "cus_KriO4LMmXY3VZV",
      "default_payment_method": "pm_1KByy5AQe605kX9ndTyC3n3h",
      "latest_invoice": "in_1KE9sWAQe605kX9n0aSNY3FJ",
      "plan": {
        "id": "plan_KriO4rMF7AzYiy",
        "object": "plan",
        "active": true,
        "product": "prod_KriOmXENCXm9qL"
      },
      "status": "canceled"
    }
  },
  "id": "evt_1KETz6AQe605kX9nL7suqsNK",
  "object": "event",
  "type": "customer.subscription.deleted"
}

后端处理:暂停套餐

3. 重要实体在状态变化情况

3.1 首次订阅支付

PAYMENT OUTCOMEPAYMENTINTENT STATUSINVOICE STATUSSUBSCRIPTION STATUS
Successsucceededpaidactive
Fails because of a card errorrequires_payment_methodopenincomplete
Fails because of authenticationrequires_actionopenincomplete

创建订阅支付状态

23 小时后没有收集新的支付方式或通过3d验证进行成功支付
这个订阅的状态被更新为: subscription.status = incomplete_expired
这个订阅的发票状态更新为 invoice.status = void
这个订阅的发票的支付意向状态更新为 payment_intent.status = canceled

3.2 循环扣费失败

循环扣费情况

webhook回调PAYMENTINTENT STATUSINVOICE STATUSSUBSCRIPTION STATUS
invoice.payment_failedrequires_payment_methodopenpast_due
invoice.payment_action_requiredrequires_actionopenpast_due

本文由 ONE 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
如有版权疑问交流,请给我留言:oneisall8955@gmail.com
本文永久链接:https://liuzhicong.cn/index.php/study/45.html

Last Modified: June 21, 2023
Archives QR Code Tip
QR Code for this page
Tipping QR Code