# 割引を動的に更新する 決済中に割引コードを適用および変更する方法をご紹介します。 > #### プライベートプレビュー > > 動的割引はプライベートプレビューです。 [Checkout Session](https://docs.stripe.com/api/Checkout/Sessions/object.md) で割引を動的に追加または削除する方法をご紹介します。 ### ご利用事例 このガイドでは、社内の割引システムを導入して動的な金額割引を作成する方法を説明します。合わせて、次についても理解できます。 - **ロイヤルティ割引の適用**: 顧客ロイヤルティ段階または購入履歴に基づき、割引を自動的に適用します。 - **カート金額のプロモーション**: 注文合計が特定の基準値を超えた場合に割引を追加します (たとえば、100 USD を超える注文に対して10 USD の割引を適用)。 - **時間的制約のあるオファー**: 期間限定のプロモーション割引を適用したり、期限切れの割引コードを削除したりできます。 - **店舗ベースの割引**: 顧客の配送先住所に基づいて、地域別に割引を適用します。 - **顧客固有のオファー**: 顧客セグメントまたは以前の購買行動に基づき、個人に合わせた割引を作成します。 > #### Payment Intents API > > Payment Intents API を使用する場合は、決済金額を手動で計算して変更するか、金額を調整した新しい PaymentIntent を作成することで割引を適用できます。 ## SDK を設定する [サーバー側] Stripe の公式ライブラリを使用して、アプリケーションから Stripe API にアクセスします。 #### Ruby ```bash gem install stripe -v 15.1.0-beta.2 ``` ## サーバー SDK を更新する [サーバー側] このプレビュー機能を使用するには、まず SDK を更新して、`checkout_server_update_beta=v1` ベータ版ヘッダーを使用します。 #### Ruby ```ruby # Don't put any keys in code. Use a secrets vault or environment # variable to supply keys to your integration. This example # shows how to set a secret key for illustration purposes only. # # See https://docs.stripe.com/keys-best-practices and find your # keys at https://dashboard.stripe.com/apikeys. Stripe.api_key = '<>' Stripe.api_version = '2025-03-31.basil; checkout_server_update_beta=v1;' ``` ## Checkout Session の更新権限を設定する [サーバー側] Checkout Session を作成する際に、[permissions.update_discounts=server_only](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-permissions-update_discounts) オプションを渡して、クライアント側の割引の適用を無効にし、サーバーからの割引の更新を有効にします。 ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -H "Stripe-Version: 2025-03-31.basil; checkout_server_update_beta=v1;" \ -d ui_mode=custom \ -d "permissions[update_discounts]"=server_only \ -d "line_items[0][price]"="{{PRICE_ID}}" \ -d "line_items[0][quantity]"=1 \ -d mode=payment \ --data-urlencode return_url="https://example.com/return" ``` ## 割引を動的に更新する [サーバー側] サーバーでエンドポイントを作成し、Checkout Session に割引を適用します。これは、後のステップでフロントエンドから呼び出します。 > クライアント側のコードは、ユーザーによって制御された環境で実行されます。悪意のあるユーザーは、クライアント側の検証を迂回し、リクエストを傍受して変更したり、サーバーへの新しいリクエストを作成したりすることができます。 エンドポイントを作成する際には、次のことを推奨します。 - 汎用的にするのではなく、特定の顧客とのやり取りのためにエンドポイントを作成します。たとえば、一般的な「更新」アクションの代わりに「ロイヤルティ割引を適用」します。特定のエンドポイントは、検証ロジックの作成と維持に役立ちます。 - クライアントからエンドポイントに [セッションデータ](https://docs.stripe.com/js/custom_checkout/session_object) を直接渡さないでください。悪意のあるクライアントはリクエストデータを変更し、Checkout Session の状態を判別するための信頼できないソースにする可能性があります。代わりに、[セッション ID](https://docs.stripe.com/js/custom_checkout/session_object#custom_checkout_session_object-id) をサーバーに渡して、それを使用して Stripe API からデータを安全に取得してください。 #### Ruby ```ruby require 'sinatra' require 'json' require 'stripe' set :port, 4242 # Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/apikeys Stripe.api_key = "<>" # Return a boolean indicating whether the discounts are valid. def validate_discounts(discounts, session # Basic validation - ensure we only have one discount if any return true if discounts.empty? || discounts == "" # Ensure only one discount is being applied return false if discounts.is_a?(Array) && discounts.length > 1 # Add your own validation logic here # For example, validate promo codes against your internal system true end # Return an array of the updated discounts or the original ones if no update is needed. def recompute_discounts(discounts, session) # If removing discounts, return empty return [] if discounts.empty? || discounts == "" # Example: Access your internal discounts system # This could be based on customer ID, promo codes, cart value, and so on customer_id = session.customer || session.client_reference_id cart_total = session.amount_total # Example internal discount calculation discount_amount = calculate_customer_discount(customer_id, cart_total) if discount_amount > 0 # Create a dynamic discount using coupon_data [{ coupon_data: { name: "Customer Discount", amount_off: discount_amount, currency: session.currency || 'usd' } }] else # No discount applicable [] end end # Example function to integrate with your internal discounts system def calculate_customer_discount(customer_id, cart_total) # Example logic - replace with your actual discount system # This could check: # - Customer loyalty tier # - Active promotions # - Cart value thresholds # - Seasonal discounts # Example: 10% off for carts over 100 USD, max 20 USD discount if cart_total > 10000 # 100 USD in cents discount = [cart_total * 0.1, 2000].min # Max 20 USD discount discount.to_i else 0 end end post '/update-discounts' do content_type :json request.body.rewind request_data = JSON.parse(request.body.read) checkout_session_id = request_data['checkout_session_id'] discounts = request_data['discounts'] # 1. Retrieve the Checkout Session session = Stripe::Checkout::Session.retrieve(checkout_session_id) # 2. Validate the discounts if !validate_discounts(discounts, session) return { type: 'error', message: 'Your discounts are invalid. Please refresh your session.' }.to_json end # 3. Recompute the discounts with your custom logic discounts = recompute_discounts(discounts, session) # 4. Update the Checkout Session with the new discounts if discounts Stripe::Checkout::Session.update(checkout_session_id, { discounts: discounts, }) return { type: 'object', value: { succeeded: true } }.to_json else return { type: 'error', message: "We could not update your discounts. Please try again." }.to_json end end ``` ## クライアント SDK を更新する [クライアント側] #### HTML + JS `custom_checkout_server_updates_1` ベータヘッダーを使用して Stripe.js を初期化します。 ```javascript const stripe = Stripe('<>', { betas: ['custom_checkout_server_updates_1'], }); ``` #### React `stripe` インスタンスを初期化するときに `custom_checkout_server_updates_1` ベータヘッダーを渡します。 ```javascript import {loadStripe} from '@stripe/stripe-js'; const stripe = loadStripe("<>", { betas: ['custom_checkout_server_updates_1'], }); ``` ## サーバー更新リクエスト [クライアント側] #### HTML + JS フロントエンドからサーバーに更新リクエストを送信し、[runServerUpdate](https://docs.stripe.com/js/custom_checkout/run_server_update) でラップします。リクエストが成功すると、[Session](https://docs.stripe.com/js/custom_checkout/session_object) オブジェクトが新しい割引で更新されます。 ```html ``` ```javascript document.getElementById('apply-customer-discount') .addEventListener("click", async (event) => { const updateCheckout = () => { return fetch("/apply-customer-discount", { method: "POST", headers: { "Content-type": "application/json", }, body: JSON.stringify({ checkout_session_id: actions.getSession().id, }) }); }; const response = await checkout.runServerUpdate(updateCheckout); if (!response.ok) { // Handle error state return; } // Update UI to reflect the applied discount event.target.textContent = "Discount Applied!"; event.target.disabled = true; }); ``` #### React フロントエンドからサーバーに更新リクエストを送信し、[runServerUpdate](https://docs.stripe.com/js/custom_checkout/run_server_update) でラップします。リクエストが成功すると、[Session](https://docs.stripe.com/js/custom_checkout/session_object) オブジェクトが新しい割引で更新されます。 ```jsx import React from 'react'; import {useCheckout} from '@stripe/react-stripe-js/checkout'; const ApplyDiscountButton = () => { const [isDiscountApplied, setIsDiscountApplied] = React.useState(false); const checkoutState = useCheckout(); if (checkoutState.type === 'loading') { return (
Loading...
); } else if (checkoutState.type === 'error') { return (
Error: {checkoutState.error.message}
); } const {runServerUpdate, id} = checkoutState.checkout; const updateCheckout = () => fetch("/apply-customer-discount", { method: "POST", headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ checkout_session_id: id, }) }); const handleClick = async () => {const response = await runServerUpdate(updateCheckout); if (!response.ok) { // set error state return; } // Update UI to reflect the applied discount setIsDiscountApplied(true); }; return ( ); }; ``` ## 導入をテストする 導入内容をテストして、動的な割引が正しく機能することを確認するには、以下のステップに従います。 1. 本番環境を反映したサンドボックス環境を設定します。この環境では、Stripe サンドボックスの API キーを使用してください。 1. さまざまな割引シナリオをシミュレーションして、`recomputeDiscounts` 関数がさまざまなシナリオを正しく処理することを確認できます。 1. ログツールやデバッグツールを使用してサーバーが以下を行っていることを確認し、サーバー側のロジックを検証します。 - [Checkout Session (セッション)](https://docs.stripe.com/api/checkout/sessions/object.md) を取得します。 - 割引リクエストを検証します。 - ご自身の事業のロジックに基づき、更新された割引を再計算します。 - カスタム条件が満たされた場合に [Checkout Session](https://docs.stripe.com/api/checkout/sessions/object.md)を新しい割引で更新します。更新のレスポンスに新しい割引が示されていることを確認します。デフォルトでは、リクエストがオブジェクトを[拡張](https://docs.stripe.com/api/expanding_objects.md)しない限り、レスポンスには割引のフィールドは示されません。 1. ブラウザーで決済プロセスを複数回実行し、クライアント側のロジックを検証します。割引の適用後に UI がどのように更新されるかに注意してください。以下を確認してください。 - [runServerUpdate](https://docs.stripe.com/js/custom_checkout/run_server_update) 関数が想定どおりに呼び出される。 - 割引は事業のロジックに基づき正しく適用されます。 - Checkout の合計が更新され、適用される割引が反映されます。 - 割引の申し込みが失敗した場合は、エラーメッセージが正しく表示されます。 1. 無効な割引リクエストなど、さまざまな割引シナリオをテストするか、サーバーエラーをシミュレーションして、サーバー側とクライアント側の両方でエラー処理をテストします。