# 配送オプションを動的にカスタマイズ 顧客向けにさまざまな配送料金を作成する方法をご紹介します。 決済時に顧客が入力した住所に基づいて配送オプションを動的に更新する方法をご紹介します。 ### ご利用事例 - **住所を検証する**: 貴社に合わせたカスタム検証ルールを使用して、顧客の住所に商品を配送できるかどうかを確認します。また、顧客が希望する住所を確認するためのカスタム UI を作成することもできます。 - **関連する配送オプションを表示する**: 顧客の住所に基づいて、利用可能な配送方法のみを表示します。たとえば、お住まいの国での配送の場合にのみ翌日配送を表示するなどです。 - **配送料を動的に計算する**: 顧客の配送先住所に基づいて配送料を計算して表示します。 - **注文合計額に基づいて配送料を更新する**: 配送先住所または注文合計額に基づいて配送料を提示します (100 USD を超える注文の場合は送料無料など)。数量変更やクロスセルが可能な決済フローについては、[項目を動的に更新する](https://docs.stripe.com/payments/checkout/dynamically-update-line-items.md)をご覧ください。 ### 制限事項 - [payment mode (支払いモード)](https://docs.stripe.com/api/checkout/sessions/object.md#checkout_session_object-mode) でのみサポートされます。[shipping rates (配送料金)](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-shipping_options) はサブスクリプションモードではご利用いただけません。 - [Express Checkout Element](https://docs.stripe.com/elements/express-checkout-element.md) と互換性がありません。Apple Pay や Google Pay などのウォレットは配送先住所を直接収集するため、サーバーのみの更新フローは迂回されます。`permissions.update_shipping_details` が `server_only` に設定されている場合、これらのウォレットは自動的に無効になります。 > #### Payment Intents API > > Payment Intents API を使用する場合は、選択した配送オプションに基づいて手動で配送オプションを更新し、決済金額を変更するか、金額を調整した新しい PaymentIntent を作成する必要があります。 ## Checkout Session の更新権限を設定する [サーバー側] [shipping_address_collection.allowed_countries](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-shipping_address_collection-allowed_countries) を配送先の国のリストに設定します。 Checkout Session を作成するときに、[permissions.update_shipping_details=server_only](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-permissions-update_shipping_details) オプションを渡して、クライアント側の [updateShippingAddress](https://docs.stripe.com/js/custom_checkout/update_shipping_address) メソッドを無効にし、サーバーから配送先住所と配送オプションを更新できるようにします。 ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -d ui_mode=elements \ -d "permissions[update_shipping_details]=server_only" \ -d "shipping_address_collection[allowed_countries][0]=US" \ -d "line_items[0][price]={{PRICE_ID}}" \ -d "line_items[0][quantity]=1" \ -d mode=payment \ --data-urlencode "return_url=https://example.com/return" ``` ## 配送オプションをカスタマイズ [サーバー側] サーバーでエンドポイントを作成し、顧客の配送先住所に基づいて配送オプションを計算します。 1. リクエスト本文から取得した `checkoutSessionId` を使用して、[Checkout Session](https://docs.stripe.com/api/checkout/sessions/object.md) を [取得](https://docs.stripe.com/api/checkout/sessions/retrieve.md) します。 2. リクエスト本文にある顧客の配送先情報を検証します。 3. 顧客の配送先住所と Checkout Session の項目に基づいて、配送オプションを計算します。 4. 顧客の [shipping_details](https://docs.stripe.com/api/checkout/sessions/object.md#checkout_session_object-collected_information-shipping_details) と [shipping_options](https://docs.stripe.com/api/checkout/sessions/update.md#update_checkout_session-shipping_options) で Checkout Session を [更新](https://docs.stripe.com/api/checkout/sessions/update.md) します。 #### Ruby ```ruby require 'sinatra' require 'json' require 'stripe' set :port, 4242 # Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. client = Stripe::StripeClient.new('<>') # Return a boolean indicating whether the shipping details are valid def validate_shipping_details(shipping_details) # TODO: Remove error and implement... raise NotImplementedError.new(<<~MSG) Validate the shipping details the customer has entered. MSG end # Return an array of the updated shipping options or the original options if no update is needed def calculate_shipping_options(shipping_details, session) # TODO: Remove error and implement... raise NotImplementedError.new(<<~MSG) Calculate shipping options based on the customer's shipping details and the Checkout Session's line items. MSG end post '/calculate-shipping-options' do content_type :json request.body.rewind request_data = JSON.parse(request.body.read) checkout_session_id = request_data['checkout_session_id'] shipping_details = request_data['shipping_details'] # 1. Retrieve the Checkout Session session = client.v1.checkout.sessions.retrieve(checkout_session_id) # 2. Validate the shipping details if !validate_shipping_details(shipping_details) return { type: 'error', message: "We can't ship to your address. Please choose a different address." }.to_json end # 3. Calculate the shipping options shipping_options = calculate_shipping_options(shipping_details, session) # 4. Update the Checkout Session with the customer's shipping details and shipping options if shipping_options client.v1.checkout.sessions.update(checkout_session_id, { collected_information: { shipping_details: shipping_details }, shipping_options: shipping_options }) return { type: 'object', value: { succeeded: true } }.to_json else return { type: 'error', message: "We can't find shipping options. Please try again." }.to_json end end ``` ## クライアント SDK を更新する [クライアント側] #### HTML + JS Stripe.js を初期化。 ```javascript const stripe = Stripe('<>'); ``` #### React `stripe` インスタンスを初期化。 ```bash npm install --save @stripe/react-stripe-js@^5.0.0 @stripe/stripe-js@^8.0.0 ``` ```javascript import {loadStripe} from '@stripe/stripe-js'; const stripe = loadStripe("<>"); ``` ## サーバー更新リクエスト [クライアント側] #### HTML + JS サーバーに配送オプションの更新をリクエストする非同期関数を作成し、それを [runServerUpdate](https://docs.stripe.com/js/custom_checkout/run_server_update) でラップします。リクエストが成功すると、[Session](https://docs.stripe.com/js/custom_checkout/session_object) オブジェクトが新しい配送オプションで更新されます。 次のコード例は、`AddressElement` を使用して配送オプションを更新する方法を示しています。 ```html
``` ```javascript // mount the Shipping Address Element const shippingAddressElement = checkout.createShippingAddressElement(); shippingAddressElement.mount('#shipping-address-element'); const toggleViews = (isEditing) => { document.getElementById('shipping-form').style.display = isEditing ? 'block' : 'none'; document.getElementById('shipping-display').style.display = isEditing ? 'none' : 'block'; } const displayAddress = (address) => { const displayDiv = document.getElementById('address-display'); displayDiv.innerHTML = `
${address.name}
${address.address.line1}
${address.address.city}, ${address.address.state} ${address.address.postal_code}
`; } const updateShippingOptions = async (shippingDetails) => { const response = await fetch("/calculate-shipping-options", { method: "POST", headers: { 'Content-type': 'application/json' }, body: JSON.stringify({ checkout_session_id: 'session_id', shipping_details: shippingDetails }) }); const result = await response.json(); if (result.type === 'error') { document.getElementById('error-message').textContent = result.message; toggleViews(true); } else { document.getElementById('error-message').textContent = ''; toggleViews(false); displayAddress(actions.getSession().shippingAddress); } return result; } const handleSave = async () => { const addressElement = await checkout.getShippingAddressElement(); if (!addressElement) { return; } const result = await addressElement.getValue(); if (!result.complete) { return; } try { await checkout.runServerUpdate(() => updateShippingOptions(result.value)); } catch (error) { document.getElementById('error-message').textContent = error?.message; toggleViews(true); } } // Event Listeners document.getElementById('save-button').addEventListener('click', handleSave); document.getElementById('edit-button').addEventListener('click', () => toggleViews(true)); ``` #### React サーバーに配送オプションの更新をリクエストする非同期関数を作成し、それを [runServerUpdate](https://docs.stripe.com/js/custom_checkout/run_server_update) でラップします。リクエストが成功すると、[Session](https://docs.stripe.com/js/custom_checkout/session_object) オブジェクトが新しい配送オプションで更新されます。 次のコード例は、`AddressElement` を使用して配送オプションを更新する方法を示しています。 ```jsx import React from 'react'; import {useCheckoutElements, ShippingAddressElement} from '@stripe/react-stripe-js/checkout'; const Shipping = () => { const [editing, setEditing] = React.useState(true); const [error, setError] = React.useState(null); const checkoutState = useCheckoutElements(); if (checkoutState.type === 'loading') { return (
Loading...
); } else if (checkoutState.type === 'error') { return (
Error: {checkoutState.error.message}
); } const {runServerUpdate, id, shippingAddress, getShippingAddressElement} = checkoutState.checkout; const updateShippingOptions = async (shippingDetails) => { const response = await fetch("/calculate-shipping-options", { method: "POST", headers: { 'Content-type': 'application/json', }, body: JSON.stringify({ checkout_session_id: id, shipping_details: shippingDetails, }) }); if (response.type === 'error') { setError(response.message); setEditing(true); } else { setError(null); setEditing(false); } return result; } const handleEdit = (event) => { event.preventDefault(); setEditing(true); } const handleSave = async () => { const addressElement = getShippingAddressElement(); if (!addressElement) { return; } const aeValue = await addressElement.getValue(); if (!aeValue.complete) { return; } try { await runServerUpdate(() => updateShippingOptions(aeValue.value)); } catch (e) { setError(e?.message); setEditing(true); } } if (!editing && shippingAddress) { const {line1, line2, city, postal_code, state, country} = shippingAddress.address; return (
{shippingAddress.name}
{line1}
{line2}
{city} {state} {postal_code} {country}
); } return (
{error &&
{error}
}
); }; ``` ## 導入をテストする 実装内容をテストして、カスタムの配送オプションが正しく機能することを確認するには、以下のステップに従います。 1. 本番環境を反映したサンドボックス環境を設定します。この環境では、Stripe サンドボックスの API キーを使用してください。 2. さまざまな配送先住所をシミュレーションして、`calculateShippingOptions` 関数がシナリオを正しく処理していることを確認します。 3. ログツールやデバッグツールを使用してサーバーが以下を行っていることを確認し、サーバー側のロジックを検証します。 - [Checkout Session (セッション)](https://docs.stripe.com/api/checkout/sessions/object.md) を取得します。 - 配送の詳細を検証します。 - 配送オプションを計算します。 - 新しい配送の詳細とオプションで [Checkout Session (セッション)](https://docs.stripe.com/api/checkout/sessions/object.md) を更新します。更新のレスポンスに新しい配送の詳細とオプションが含まれていることを確認します。 4. ブラウザーで決済プロセスを複数回実行することで、クライアント側のロジックを検証します。配送の詳細の入力後に UI がどのように更新されるかに注意してください。次のことを確認してください。 - `runServerUpdate` 関数が指定したとおりに呼び出されている。 - 配送オプションが指定された住所に基づいて正しく更新される。 - 配送先が利用できない場合にエラーメッセージが適切に表示される。 5. 無効な配送先住所を入力するか、サーバーエラーをシミュレーションして、サーバー側とクライアント側の両方のエラー処理をテストします。