LoginSignup
0
1

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

Last updated at Posted at 2023-11-06

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

注意点

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

sdk導入

pip install stripe

customer

作成

import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

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

取得

import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

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

削除

import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

stripe.Customer.delete("stripe_customer_id")

card

一覧取得

import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

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

一覧取得(ページネーション)

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

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

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

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

stripe.api_key = "STRIPE_SECRET_KEY"

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

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

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

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

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

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},
    )

カード削除(紐付け解除)

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を発行し、あとはフロント <-> サービスで連携させる感じ。

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作成時に決済までされるようにしている例。

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

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の検索。

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をフロントなり環境変数なりベタ書きなり、どこかから受け取って処理したりする。

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された状態になる。

以下のイメージ

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

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

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"],
    )

取得

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

import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

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

更新

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

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 以上に柔軟にいろいろできる

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

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

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を除外した一覧を取得。

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

""""""
invoices = stripe.Invoice.search()
next_page = invoices.next_page
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",
    )

取得

import stripe

stripe.api_key = "STRIPE_SECRET_KEY"

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

webhook

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

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

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

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に質問したところ、初回決済完了後にトライアルを設定し、トライアル終了日を初回のサブスク期限にすることで実現可能と助言いただいた。

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


# ---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追記)

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1