pythonのstripe sdkでやったことまとめ

最近stripeでやったことを備忘録として適当にまとめる。



# 注意点

:::note warn
dictだったりクラスだったり、指定しているキーの間違いなど、一部ごちゃごちゃになっている部分があるかもなのでコピペ利用したい時は要確認。
:::


# sdk導入
```
pip install stripe
```

# customer


## 作成

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

stripe_customer = stripe.Customer.create(name="hoge")
```

## 取得

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

customer = stripe.Customer.retrieve("stripe_customer_id")
```

## 削除

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

stripe.Customer.delete("stripe_customer_id")
```

# card

## 一覧取得

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

cards = stripe.PaymentMethod.list(
    customer="stripe_customer_id",
    type="card",
)
```

## 一覧取得（ページネーション）


stripeのページネーションの仕組みははカードに限らず他の一覧取得でも使う仕様。


https://stripe.com/docs/api/pagination

一覧取得レスポンスにて、次ページがあれば `has_more == True` となり、starting_afterにリストの末尾のオブジェクトidを指定して再度リストをコールする。

末尾のオブジェクトidというのは以下の例でいうlatest_id。

```python
"""
例
"""
cards = stripe.PaymentMethod.list()
has_more = cards.has_more
latest_id = cards.data[-1].id
```

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

cards = stripe.PaymentMethod.list(
    customer="stripe_customer_id",
    type="card",
    starting_after="latest_id",
)
```

## デフォルト支払いカード変更

オブジェクトのidに応じて処理分け。

:::note warn
コンソールからや旧APIを使った場合は `card_xxxxx` というidでカードが作られ、payment intent apiを使った場合は `pm_xxxxx` というidでカードが作られる。
:::

:::note warn
default_sourceは変更はできるが空にはできないはず。
:::

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

# リクエストボディ等から受け取った値を判定し、オブジェクトのidに応じて処理分け
card_id = "card_xxxxx"

if card_id.startswith("card_"):
    stripe.Customer.modify(
        "stripe_customer_id",
        default_source=card_id,
        invoice_settings={"default_payment_method": ""},
    )
elif card_id.startswith("pm_"):
    stripe.Customer.modify(
        "stripe_customer_id",
        invoice_settings={"default_payment_method": card_id},
    )
```

## カード削除（紐付け解除）

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

stripe.PaymentMethod.detach("card_id")
```

# setup intent

## 作成

setup intentはpayment intentとほぼ同じだが、setup intentは決済が行われない。
カードの登録だけしたい、みたいな時に使う。

returnしている値はフロントでフォームを描画する際に使う。
そこでのカード情報などの入力値をstripe側と通信して保存する。

awsのs3におけるpresignedUrlに近いイメージ。
バックエンドはurlやsecretを発行し、あとはフロント <-> サービスで連携させる感じ。

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

setup_intent = stripe.SetupIntent.create(
    customer="stripe_customer_id",
)

return {
    "setup_intent_id": setup_intent.id,
    "client_secret": setup_intent.client_secret
}
```

# payment intent


## 作成

日本円で1000円のpayment intent作成の例。

旧chargeのような感じ。

指定のカードで決済まで行う。

特にパラメータを指定していない最低限の状態であれば、レスポンスのclient_secret等を用いてフロント側でフォームを描画、カード情報を入力させてstripeと通信/決済。

しかし以下ケースの場合はすでにカードが登録されてる前提のもとpayment intent作成時に決済までされるようにしている例。

ついでにメタデータも使ってる例。

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

# サンプル数値
amount = 1000

pi = stripe.PaymentIntent.create(
    customer="stripe_customer_id",
    amount=amount,
    currency="jpy",
    payment_method="card_id",
    confirm=True,
    automatic_payment_methods={"enabled": True, "allow_redirects": "never"},
    metadata={"project_id": "project_id"},
)
```


## 検索

特定の期間を指定したpayment intentの検索。

```python
from datetime import datetime
import calendar
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

stripe_customer_id = "xxxxx"
amount = 1000

now = datetime.utcnow()
month_end = calendar.monthrange(now.year, now.month)[1]
start = int(datetime(now.year, now.month, 1).timestamp())
end = int(datetime(now.year, now.month, month_end, 23, 59, 59).timestamp())

result = stripe.PaymentIntent.search(
            query=f"status:'succeeded' AND customer:'{stripe_customer_id}' AND amount:{amount} AND created>={start} AND created<={end}",
        )
```

# subscription


## 作成

商品(product)、価格(price)をコンソールで事前に作成する。
price_idをフロントなり環境変数なりベタ書きなり、どこかから受け取って処理したりする。

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

subscription = stripe.Subscription.create(
    customer="stripe_customer_id",
    items=[
        {
            "price": "price_id",
        }
    ],
    default_payment_method="card_id",
)

```


## 一覧取得

ページネーションの場合は `starting_after` を使うようにしている。
subscriptions.dataは、サブスクリプション(dict)の配列。
`expand` にてレスポンスの拡張をしている。

subscriptions.data[0].default_payment_methodは通常payment method idあたりがstringで入ってたはずだが、expandで拡張をすると、payment method idを指定して取得できるpaiment method オブジェクトがincludeされた状態になる。

以下のイメージ

```sql
-- 拡張してないやつ
SELECT * FROM subscriptions;

-- 拡張したやつ
SELECT * FROM subscriptions
LEFT JOIN payment_methods ON subscriptions.payment_method_id = payment_methods.id;

```


https://stripe.com/docs/expand?locale=ja-JP


```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

# クエリパラメータ等で受け取る
latest_id = "xxxxx"

if latest_id:
    subscriptions = stripe.Subscription.list(
        customer="stripe_customer_id",
        expand=["data.default_payment_method"],
        starting_after=latest_id,
    )
else:
    subscriptions = stripe.Subscription.list(
        customer="stripe_customer_id",
        expand=["data.default_payment_method"],
    )
```

## 取得

:::note warn
一覧では `expand=["data.default_payment_method"]` となっていたが、単一の場合はレスポンスの構造が異なるのでそれに合わせて `expand=["default_payment_method"]` となっている。
:::

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

subscription = stripe.Subscription.retrieve(
    "subscription_id",
    expand=["default_payment_method"],
)
```

## 更新

### サブスクリプションに紐づく支払いカードを変更する

https://stripe.com/docs/api/subscriptions/object#subscription_object-default_payment_method

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

# リクエストボディなどで受け取る
card_id = "xxxxx"

subscription = stripe.Subscription.modify(
    "subscription_id",
    default_payment_method=card_id
)
```

#### 参考

`default_payment_method` 以上に柔軟にいろいろできる

https://stripe.com/docs/billing/subscriptions/payment-methods-setting?locale=ja-JP

### トライアル終了時またはサブスクリプション期間終了時に自動でサブスクリプションがキャンセルされるよう設定する

サブスク期間にキャンセルを行っても期間の終了までは利用できるようにしたい、といった場合に使う。

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

stripe.Subscription.modify(
    "stripe_subscription_id",
    cancel_at_period_end=True,
)
```

#### 参考

任意に指定したタイミングでキャンセルしたいなら `cancel_at` を使う
https://stripe.com/docs/api/subscriptions/update#update_subscription-cancel_at

# invoice

## 検索

特定の条件で絞り込みを行いたかったため、 `.list()` ではなく `.search()` を使っている。
以下ではトライアル設定を行った際に発行される0円のinvoiceを除外した一覧を取得。

:::note info
search apiでのページネーションは `page` パラメータを使う。
レスポンスの `next_page` を `page` に指定する。
:::


```python
"""
例
"""
invoices = stripe.Invoice.search()
next_page = invoices.next_page
```

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

# クエリパラメータ等で受け取った値
next_page = "xxxxx"

stripe_customer_id = "xxxxx"

# サブスクの更新（トライアル設定）によって作成される0円のinvoiceを除外
if next_page:
    invoices = stripe.Invoice.search(
        query=f"customer:'{stripe_customer_id}' AND -total:0",
        page=next_page,
    )
else:
    invoices = stripe.Invoice.search(
        query=f"customer:'{stripe_customer_id}' AND -total:0",
    )
```

## 取得

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

invoice = stripe.Invoice.retrieve("invoice_id")
```


# webhook

必要に応じて設定する。
コンソール > 開発者 あたりから設定できる。

https://stripe.com/docs/payments/handling-payment-events?locale=ja-JP

## デフォルトカードを設定する

`payment_method.attached` イベントが発生した場合にコールされるapiを用意しており、カード登録時にデフォルトがなければ設定する。

```python
import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

pm_id = body.data.object.id
customer_id = body.data.object.customer
customer = stripe.Customer.retrieve(customer_id)

has_default_pm = False
if customer.invoice_settings.default_payment_method:
    has_default_pm = True

if customer.default_source:
    has_default_pm = True

if not has_default_pm:
    if pm_id.startswith("card_"):
        stripe.Customer.modify(
            customer_id,
            default_source=pm_id,
            invoice_settings={"default_payment_method": ""},
        )
    elif pm_id.startswith("pm_"):
        stripe.Customer.modify(
            customer_id,
            invoice_settings={"default_payment_method": pm_id},
        )
```

## トライアル終了日を設定する

`invoice.paid` イベントが発生した場合にコールされるapiを用意しており、サブスクリプションに関連する支払いの場合にトライアルを設定している。

初回は当月 + 1年を期間とし、次回以降は年次で請求サイクルを回したかった。
stripeに質問したところ、初回決済完了後にトライアルを設定し、トライアル終了日を初回のサブスク期限にすることで実現可能と助言いただいた。

:::note info
パラメータの指定で比例配分の無効化
= 日割計算はしない
= 有効なサブスクリプションに対する請求サイクルの変更時に、請求額の調整を行わない
= 次回請求から勝手に日割りで割引される等がなくなる
:::

```python

# ---webhookのリクエストから、サブスクであるか/初回支払い完了時かなどを確認するためのフィルタリング処理など---

# トライアル終了日計算
now = datetime.utcnow()
month_end = calendar.monthrange(now.year, now.month)[1]
next_year_month_end = datetime(now.year + 1, now.month, month_end)
next_year_month_end_unixtime = int(next_year_month_end.timestamp())

# サブスク更新
subscription = stripe.Subscription.modify(
    subscription_id,
    trial_end=trial_end,
    proration_behavior="none",
)
```

# オートページネーション（2023/11/16追記）

自動でページネーションする仕組みがあると教えてもらった

 https://stripe.com/docs/api/pagination/auto