mcpField
S
spree-fulfillment

spree-fulfillment

github.com/spree開発10104 回インストール4 週間前に更新
リポジトリ

spree-fulfillment について

spree-fulfillment は、Spree のフルフィルメント記録、配送ワークフロー、料金、プロバイダー、追跡、在庫ルーティングを扱う開発者向けスキルです。注文配送の拡張に役立ちます。

主な機能

  1. 01フルフィルメントモデルとステータスの説明
  2. 02出荷、分割、キャンセル、配送ワークフロー
  3. 03配送料金とプロバイダーの拡張ポイント
  4. 04配送ラベルと追跡の連携
  5. 05在庫ロケーションと商品ルーティング

活用シーン

  1. 01部分出荷や手動フルフィルメントの実装
  2. 02配送業者や3PLプロバイダーの追加
  3. 03追跡更新と配送Webhookの処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/spree/agent-skills --skill spree-fulfillment

インストール後、Claude Code で/spree-fulfillmentを入力して読み込まれたか確認します。

同梱ファイルreferences/provider-contracts.md

詳細

Spree フルフィルメント

フルフィルメント(Spree::Fulfillment、接頭辞 ful_、番号 F…)は、1 つの在庫拠点から 1 つの配送方法で顧客に届ける商品のまとまりです。小包、ダウンロード商品、受取カウンターでの受け取り待ちの商品などが該当します。カート/注文には、(在庫拠点 × 配送プロファイル × 分割器の出力)ごとに 1 つのフルフィルメントが割り当てられ、顧客はフルフィルメントごとに配送レートを選択します。

5.x(Shipment、ShippingMethod、StockItem、配送のステートマシン)から移行する場合、定数エイリアスは 6.1 まで存在しますが、spree-upgrade-5-to-6 を参照してください。古い名前を使った新しいコードは書かないでください。

モデルの関連図

Cart / Order
  └── Fulfillment (ful_)                 status: unfulfilled → fulfilled → delivered | canceled
        ├── stock_location (sloc_)        where it ships from
        ├── FulfillmentItem (fi_) × n     line_item + variant + quantity; status on_hand/backordered/shipped/returned
        ├── DeliveryRate (dr_) × n        the options offered; one has selected: true
        │     └── DeliveryMethod (dm_)    what the customer picks ("Standard", "Express", "Collect in store")
        ├── Delivery (dlv_) × n           each consignment/parcel: tracking_number, carrier, status
        ├── ShippingLabel (lbl_) × n      postage bought (purchased) or recorded (uploaded)
        └── TaxLine / Discount / Fee      delivery money rows (see spree-order-totals)

Product ──> DeliveryProfile (fp_)        how a product ships: DeliveryProfiles::Shipping | ::Digital
              ├── DeliveryOriginGroup (og_) × n   a named set of stock locations ("EU warehouse")
              ├── DeliveryZone (dz_) × n          where: DeliveryZoneMember (dzm_) country | state | postal_code
              └── DeliveryMethod × n              belongs_to profile + origin group, optional zone
                    ├── calculator                 how much (when priced internally)
                    ├── rate_provider              where the price comes from (default Internal)
                    ├── fulfillment_provider       how it's dispatched (Manual | Digital | Pickup | PickupPoint | carrier gem)
                    └── DeliveryMethodRule (dmrule_) × n   eligibility, AND-ed

PackageType (pkgtype_)                    box/envelope/carton/pallet/container — tare + dimensions for quotes

把握しておくべきこと:

  • 各フルフィルメントの所有者は必ず 1 つです。チェックアウト中は cart_id、注文確定後は order_id です。fulfillment.owner を参照し、.order だと決めつけないでください。
  • 配送タイプは、文字列カラムではなく配送方法のプロバイダークラスによって決まります。delivery_method.digital?、.pickup?、.pickup_point?、.requires_address? を使います。API では、フルフィルメントの fulfillment_type(shipping | digital | pickup | pickup_point)として公開されます。
  • 商品に適用できる配送方法は、配送プロファイルによって決まります。デジタルプロファイルが受け付けるのはデジタルプロバイダーのみです。配送プロファイルはそれ以外すべてを受け付けます。出荷元グループによって、商品の供給元にできる在庫拠点が限定されます。コーディネーターは、プロファイルで対象とされていない在庫拠点から商品を梱包することはありません。
  • cart.delivery_step_required? は line_items.any? && !digital? です。すべてデジタル商品のカートでは、配送ステップがスキップされます。shipping_address_required? は、選択された配送方法(配送方法の選択前はプロファイル)を確認します。受取のみを提供する配送プロファイルでは、住所は必要ありません。
  • 配送ゾーンは配送専用です。税ゾーンは別にあります(spree-taxes)。

ステータス

Spree::Fulfillment は has_status :unfulfilled, :fulfilled, :delivered, :canceled を使用します。これは文字列の status カラムであり、ステートマシンはありません。ステータスは小包に対して加盟店が行ったことを表し、前にのみ進みます。

ステータス意味
unfulfilledまだ手元にあります
fulfilled引き渡し済み(配送業者、受取カウンターへの引き渡し、またはダウンロード可能な状態にした場合)。fulfilled_at が設定されます
delivered顧客が受け取り済みです。delivered_at が設定されます(Webhook の到着時ではなく、配送業者のスキャンを基準とします)
canceled発送されません。最終状態であり、再開できません

支払い/入荷待ちの準備状況はステータスではありません。発送時に Spree::Fulfillments::Fulfill が確認し、理由(order_not_paid、backordered_units、insufficient_stock_on_hand、order_draft)を返します。スタッフは、未払いの請求がある場合や入荷待ちを超えて発送する場合に force: true を渡します(下書き注文を超えて発送することはできません)。can_fulfill?/can_cancel? は否定形(「すでに発送されていない」)で表現されるため、引き渡し前のカスタムステータスでもフルフィルメント可能な状態を保てます。

配送業者の情報は各**Spree::Delivery**(Spree::Delivery::STATUSES:pending pre_transit in_transit out_for_delivery available_for_pickup delivered return_to_sender failure unknown)に保持され、フルフィルメントのステータスが後戻りすることはありません。すべての貨物が到着すると、フルフィルメントは delivered になります。フルフィルメントの tracking/tracking_url は、最初の配送の情報を要約します。

注文では、この状態が order.fulfillment_status(Spree::Orders::UpdateStatuses のみが書き込みます)に集約されます。値は unfulfilled、partial、fulfilled、delivered、backorder、canceled です。自分で書き込まないでください。

状態遷移はワークフローです

update!(status: 'fulfilled') は絶対に呼び出さないでください。アプリ側で差し替えられるように、DI キーを通じてワークフローを呼び出します。

アクションワークフロー(Spree.<key>)フック
発送(一部または全品)fulfillment_fulfill_workflow → Spree::Fulfillments::Fulfillvalidate、after_fulfill
受領確認fulfillment_mark_delivered_workflow → Fulfillments::MarkDeliveredvalidate、after_mark_delivered
キャンセルfulfillment_cancel_workflow → Fulfillments::Cancelvalidate、after_cancel
注文確定後の手動/3PL フルフィルメントfulfillment_create_workflow → Fulfillments::Createvalidate、get_provider_data、after_create
編集(追跡情報、料金、ロケーション)fulfillment_update_workflow → Fulfillments::Update
出荷前のラベル購入fulfillment_purchase_label_workflow → Fulfillments::PurchaseLabel
配送業者の追跡情報更新delivery_update_tracking_workflow → Deliveries::UpdateTrackingvalidate、after_update_tracking
result = Spree.fulfillment_fulfill_workflow.call(
  fulfillment: fulfillment,
  items: [{ line_item: line_item, quantity: 1 }], # omit to ship everything
  tracking: '1Z999AA10123456784',                # becomes the primary Spree::Delivery
  notify_customer: true,
  force: false
)
result.value # the fulfillment that actually shipped — for a partial ship, the NEW split-off one

Fulfill の処理順序は次のとおりです。validate フック → ガード処理 → 要求された数量を切り分ける(Fulfillments::Create を使用、費用は 0) → 追跡情報を記録 → プロバイダーが generates_labels? を実装し、有効なラベルがない場合はラベルを購入(失敗してもエラーを報告するだけで、小包は発送されます) → provider.create_fulfillment → 割り当て済み数量の在庫を減らす(shipped の移動) → 商品を shipped、フルフィルメントを fulfilled に設定し、fulfillment.fulfilled を発行 → 発送時に請求する決済方法で支払いを回収 → 注文のステータスを集約。

手動フルフィルメント(3PL または宅配業者の処理結果を Spree に反映する場合):POST /api/v3/admin/orders/:order_id/fulfillments に stock_location_id、items: [{ item_id: 'li_…', quantity }]、任意の tracking、delivery_method_id、cost、status、metadata を指定します。数量は現在のフルフィルメントから移動します(ロケーションが異なる場合は在庫を戻してから減らします)。空になった元のフルフィルメントは削除され、その費用は引き継がれます。注意点として、受け付けられる status の値はレガシー値の 'shipped' のみです(登録時点ですでにフルフィルメント済みとして扱われます)。それ以外の値では invalid_status が発生します。

発送せずに分割する場合:PATCH …/fulfillments/:id/split に variant_id、quantity、任意の stock_location_id を指定します(数量を新しいフルフィルメントに移動します。別のロケーションへの移動も可能です)。注文上のすべてのフルフィルメントが返されます。

条件設定とカスタムステージ

検証フックで引き渡しを制御します。ハンドラーはワークフローを受け取り、reject! を呼び出します。

# config/initializers/spree.rb
Spree.hooks.register('fulfillments.fulfill.validate') do |flow|
  work_order = MyApp::WorkOrder.find_by(fulfillment_id: flow.fulfillment.id)
  flow.reject!('Still in production') unless work_order&.completed?
end

フックキーは <workflow key>.<hook> です。ワークフローキーはクラス名から Spree:: を除き、スネークケースに変換してドットで区切ります(Spree::Fulfillments::Fulfill → fulfillments.fulfill)。不明なキーがあると、イーガーロード時に起動に失敗します。

スタッフがそのステータスで絞り込む場合、レポートでそのステータスごとに集計する場合、またはそのステータスでウェブフックを発火させる場合に限り、実際のステータスを追加してください。

# config/initializers/spree.rb — to_prepare so it survives dev reloads
Rails.application.config.to_prepare do
  Spree::Fulfillment.add_status('in_production', after: 'unfulfilled')
end

これにより in_production?、.in_production と有効な値が追加されます。そのステータスへの移行は、独自の Spree::Workflow で実装してください(spree-workflows を参照)。コアの fulfill/cancel は引き続き機能します。ステータスは追加のみ可能です。コアのステータスは削除しないでください。

料金:計算機と料金プロバイダー

すべての DeliveryMethod には、rate_provider(クラス名の文字列で、Spree.delivery_rate_providers に照らして検証されます)と計算機があります。

必要なこと使用するもの
荷物の内容に基づく計算(定額、商品ごと、商品合計の割合、価格帯、フレキシ料金、デジタル商品の無料配送)計算機 — 組み込みのものは Spree::Calculator::Shipping::* にあります
Spree の外部から取得する料金(配送業者 API、アグリゲーター、契約料金表サービス)料金プロバイダー — Spree::DeliveryRateProvider::Base のサブクラス
「審査後に見積もり」(パレット、コンテナ)Spree::DeliveryRateProvider::Freight — 運賃の概要を含む unpriced 料金を返します
「…の場合のみ提供」(注文金額、重量、容積、法人購入者、チャネル、対象外商品)メソッドに設定する DeliveryMethodRule。計算機のしきい値(minimum_*/maximum_* の設定値)は非推奨です

デフォルトのプロバイダーは Spree::DeliveryRateProvider::Internal です(計算機を使用して料金を算出します)。金額ベースの計算機には currency の設定値があり、通貨が注文と一致しない計算機は料金に反映されません(換算は行われません)。販売する通貨ごとに、メソッドまたは計算機を作成してください。同じ理由により、別の通貨での料金プロバイダーの見積もりは Spree::Stock::Estimator によって除外されます。

カスタム計算機:

class MyApp::Calculator::DimensionalWeight < Spree::ShippingCalculator
  preference :rate_per_kg, :decimal, default: 0
  preference :currency, :string, default: -> { Spree::Store.default&.default_currency }

  def self.description = 'Dimensional weight'

  def supports_currency?(currency) = currency.casecmp?(preferred_currency)

  def compute_package(package)
    package.weight * preferred_rate_per_kg # nil hides the method
  end
end

# config/initializers/spree.rb — core ASSIGNS this list in after_initialize, so append there
Rails.application.config.after_initialize do
  Spree.calculators.shipping_methods << MyApp::Calculator::DimensionalWeight
end

カスタム配送業者料金プロバイダー(完全なコントラクト、サービスカタログ、テストについては references/provider-contracts.md を参照してください):

module SpreeAcme
  class DeliveryRateProvider < Spree::DeliveryRateProvider::Base
    def self.integration_class = 'SpreeAcme::Integration' # hidden/refused until connected
    def self.requires_address? = true                        # it quotes real shipments

    def estimates(package)
      integration.client.rates(from: package.stock_location, to: package.order.ship_address, weight: package.weight).map do |q|
        Spree::DeliveryRateProvider::Estimate.new(
          cost: q.amount, currency: q.currency, carrier: q.carrier,
          service_level: q.service, estimated_delivery_date: q.eta, metadata: { quote_id: q.id }
        )
      end
    rescue SpreeAcme::Error => e
      Rails.error.report(e, source: 'acme.rating')
      [] # never raise into checkout — an empty array hides the method
    end
  end
end

Rails.application.config.after_initialize do
  Spree.integrations << 'SpreeAcme::Integration'
  Spree.delivery_rate_providers << SpreeAcme::DeliveryRateProvider
end

cost は税抜きです。Spree はその後に税金を適用します。参考実装は spree_easypost gem(SpreeEasyPost::DeliveryRateProvider + SpreeEasyPost::FulfillmentProvider + SpreeEasyPost::Integration)です。

フルフィルメントプロバイダー(発送、ラベル、3PL/WMS)

delivery_method.fulfillment_provider(デフォルトは Spree::FulfillmentProvider::Manual。ほかに Digital、Pickup、PickupPoint があります)が出荷処理を担います。WMS/3PL/配送業者との連携では、Spree::FulfillmentProvider::Base をサブクラス化して create_fulfillment(fulfillment)(任意で { tracking_number:, tracking_url: } を返します)と cancel_fulfillment(fulfillment) を実装します。ラベルを生成する配送業者は、さらに self.generates_labels?、purchase_label(owner)(owner は Fulfillment または Return)、refund_label(label) と、任意で tracking_url(delivery)、documents(owner)、can_fulfill?(fulfillment) をオーバーライドします。after_initialize で登録します:Spree.fulfillment_providers << MyProvider(このリストはコア側でも設定されます)。

配送業者の追跡 Webhook:Spree は POST /api/v3/webhooks/fulfillments/:integration_id(プレフィックス付きの Integration ID。API キーは不要)を管理します。Integration は ID のみで検索され、リクエストはその Integration 自身のストアで実行されます(Spree::Current.store = integration.store)。そのため、どのストアでも Integration ごとに 1 つの URL で機能します。Spree::Integration のサブクラスで parse_webhook_event(raw_post, headers) を実装します。署名を検証し(Spree::Integration::WebhookSignatureError を発生させると 401 になります。シークレットが設定されていない場合も同様です)、{ tracking_code:, tracking_status:, estimated_delivery_at:, delivered_at:, details: } を返すか、無視する場合は nil を返します(→ 200)。コントローラーは Integration のストア内で追跡番号を使って Spree::Delivery を検索し、Deliveries::UpdateTracking を実行します。ステータスが delivered の場合、フルフィルメントで受領を確定します。不明な配送業者のステータスは 'unknown' にマッピングしてください。

注文ルーティングと在庫分割

商品をどのようにフルフィルメントにするかは、次の2つのレイヤーで決まります。

  1. どの在庫ロケーションを使うか — 注文ルーティングです。Spree::OrderRouting::Strategy::Rules(デフォルト)は、チャネルの Spree::OrderRoutingRule レコード(PreferredLocation、MinimizeSplits、DefaultLocation)を順に評価します(ランクが低いものを優先し、nil は評価対象外です)。カスタムルールを作成するには、Spree::OrderRoutingRule をサブクラス化して、ロケーションごとに1つの LocationRanking を返す rank(order, locations) を実装し、Spree.order_routing.rules << MyRule で登録して、チャネルごとにレコードを作成します。カスタムストラテジーを作成するには、Spree::OrderRouting::Strategy::Base をサブクラス化し、for_allocation(Spree::Stock::Packages を返します)と for_sale(fulfillment:)、for_release、for_cancellation を実装します。Spree.order_routing.strategies << MyStrategy で登録し、store.preferred_order_routing_strategy / channel.preferred_order_routing_strategy で選択します(未登録の値は Rules にフォールバックします)。
  2. 1つのロケーションでの割り当てをどのようにパッケージに分割するか — 分割器です。デフォルトのチェーンは Spree.stock_splitters = [Splitter::DeliveryProfile, Splitter::Backordered] です。Splitter::Weight(クラスレベルの threshold、デフォルトは 150)は、オプトインで使用します。カスタム分割器を作成するには、Spree::Stock::Splitter::Base をサブクラス化し、split(packages) を実装して、必ず最後に return_next(packages) を呼び出します。

ルーティングが実際に適用される箇所を把握してください。 ストアフロントのチェックアウトでは、Cart#rebuild_fulfillments! がフルフィルメントを作成し、Spree::Stock::Coordinator を直接呼び出します。これは配送プロファイルのオリジングループ、チャネルの対応ロケーション、分割器チェーンを考慮しますが、ルーティングストラテジーやルーティングルールは参照しません。Carts::Complete はカートのフルフィルメントを注文にコピーします。ルーティングストラテジーは Order#rebuild_fulfillments! で実行されます。これは、管理画面での下書き注文の作成・更新(注文確定後は何もしない Spree::Orders::BuildFulfillments)と、注文確定後の OrderInventory の変更で呼び出されます。注文確定後に商品の編集をしても、フルフィルメントは再構築されません。代わりに Spree::OrderInventory がその場でフルフィルメントを調整するため、注文確定時の割り当ては、キャンセルや出荷時にリリースできるレコードに保持されます。ストラテジーの for_sale / for_release / for_cancellation は契約の一部ですが、現在コアからは呼び出されません(Rules では何もしません)。割り当て後の副作用は、フルフィルメントのワークフローフックまたは fulfillment.* サブスクライバーで処理してください。クレーム/交換の代替商品にも Coordinator が使われます。ストアフロントのカートで出荷元となる倉庫を変更するには、ルーティングルールだけを使うのではなく、配送プロファイル/オリジングループ、チャネルの在庫ロケーション、または Coordinator のデコレーターを使用してください。

# Splitters are assigned by core in after_initialize — append there, not in to_prepare
Rails.application.config.after_initialize do
  Spree.stock_splitters << MyApp::Stock::Splitter::Refrigerated
end

店頭受け取り、デジタル、貨物輸送

  • 店頭受け取り:FulfillmentProvider::Pickup を使う配送方法は、店頭受け取り対応の在庫ロケーションを提供します(GET /api/v3/store/delivery_methods/:id/pickup_locations)。PickupPoint は店頭受け取り場所プロバイダーに問い合わせます(…/pickup_points?latitude=&longitude=)。店頭受け取り注文は、配達済みとしてマークして完了します。
  • デジタル:商品にストアのデジタル配送プロファイルを割り当てます。Digital フルフィルメントプロバイダーは注文確定時に自動でフルフィルメントを行い、Spree::DigitalLinks を付与します(イベント digital_link.downloaded)。アップロード済みファイル以外を配信するには、Spree::DigitalAssetProvider::Base のサブクラス(deliver(digital_link, expires_in:))を作成し、Spree.digital_asset_providers に登録します。
  • 貨物輸送:Freight レートプロバイダーの配送方法は、volume_rule / company_rule で制限されます。レートは unpriced: true として返され、「確認後に見積もり」と表示され、有料レートの後に並びますが、チェックアウトは引き続き完了します。

API サーフェス

  • Store:PATCH /api/v3/store/carts/:cart_id/fulfillments/:id で selected_delivery_rate_id を指定します。料金は cart.fulfillments[].delivery_rates にあります。
  • 選択内容は商品を変更しても保持されます。再計算のたびに候補が再構築され(rebuild_fulfillments!)、再構築では各在庫ロケーションの選択済み料金を引き継ぎます(同じ配送業者のサービス、そうでなければ同じ方法、どちらにも該当しなければ見積もり機能のデフォルト)。その後、新しい内容に基づいて料金が計算されます。宛先の変更(Address::DESTINATION_FIELDS:住所欄、市区町村、州、郵便番号、国)のみ、デフォルト料金にリセットされます。電話番号や名前を変更しても選択内容は保持されます。注文確定後の配送料金は、再構築によって再計算されません。
  • 管理画面 — 権限キー:フルフィルメントのエンドポイントには read_/write_fulfillments(ネストされたラベル/配送は *_orders 配下)、配送方法/ルールには *_delivery_methods、*_package_types、ゾーンとプロファイルには *_settings を使用します。エンドポイント:…/orders/:order_id/fulfillments(一覧/表示/作成/更新)とメンバーアクションの fulfill(items: [{ item_id, quantity }]、tracking、tracking_carrier、notify_customer、force)、mark_delivered、cancel、split。ネストされた labels(作成/削除、download、refund)と deliveries(CRUD、mark_delivered)。セットアップ:delivery_methods(calculators、rate_providers、fulfillment_providers、ネストされた rules を含む)、delivery_method_rules/types、delivery_zones、delivery_profiles(kinds、ネストされた origin_groups を含む)、package_types、tracking_carriers。
  • Admin SDK:adminClient.orders.fulfillments.{list,fulfill,markDelivered,cancel,split}、.labels.*、.deliveries.*。

イベント

fulfillment.created/updated/deleted、fulfillment.fulfilled(メタデータ notify_customer)、fulfillment.delivered、fulfillment.canceled、delivery.created/updated/deleted、shipping_label.purchased/.refunded(およびライフサイクルイベント)、注文単位の order.fulfilled、order.delivered があります。fulfillment.resumed はありません。WMS 同期やメール送信にはサブスクライバーを使用してください(spree-events-webhooks)。

トラブルシューティング

チェックアウト時に配送料金が表示されない(カートに delivery_unavailable 警告が表示され、フルフィルメントが削除される):

  • 商品の配送プロファイルに、住所または発送元ロケーションを対象とするゾーン(または発送元グループ)の配送方法がありません。
  • DeliveryMethodRule によってパッケージが除外されています(商品合計、重量、容積、会社、チャネル)。
  • 計算機の通貨がカートの通貨と異なるか、計算機が nil を返しました。
  • 料金プロバイダーの連携が接続されていない、エラーが発生した(エラーレポートを確認してください)、または外貨での見積もりしか返しませんでした。
  • コンソール:Spree::Stock::Coordinator.new(cart).packages.map { |p| [p.stock_location.name, p.delivery_rates.map(&:name)] }、または管理画面専用の方法を含める場合は Spree::Stock::Estimator.new(cart).delivery_rates(package, Spree::DeliveryMethod::BACKOFFICE)。

Fulfill が 422 を返す:エラーを確認してください。未払い(force を指定するか、先に決済を確定)、入荷待ちの商品、ロケーションの count_on_hand 不足(在庫を修正するか、force を指定)、下書き注文、または独自の validate フックが原因です。

誤った倉庫から発送される:ストアフロントのカートはルーティングルールを無視します(上記を参照)。商品の配送プロファイルにある発送元グループ、チャネルの在庫ロケーション、および各ロケーションでの StockLevel の有無を確認してください。

追跡ウェブフックが何もしない:仕様により、不明な追跡コードと nil イベントに対してエンドポイントは 200 を返します。401 は署名検証に失敗したことを示します。連携は現在のストアで有効になっている必要があります。

テスト

ファクトリー(spree_dev_tools / spree/testing_support)::fulfillment、:fulfillment_item、:delivery_method、:free_delivery_method、:digital_delivery_method、:pickup_delivery_method、:delivery_zone、:delivery_zone_with_country、:delivery_profile、:digital_delivery_profile、:delivery_rate、:delivery、:stock_package、:cart_ready_for_delivery、:completed_order_with_totals。配送業者クライアントはスタブ化してください。料金の見積もりは、チェックアウトのたびに実行されます。リクエスト外で料金を作成するテストでは、Spree::Current.store を設定してください。

さらに詳しく読む

  • node_modules/@spree/docs/dist/developer/core-concepts/fulfillments.md、delivery-setup.md、freight.md、calculators.md
  • node_modules/@spree/docs/dist/developer/how-to/custom-delivery-rate-provider.md、custom-order-routing.md、custom-stock-splitter.md、sell-digital-products.md、custom-digital-asset-provider.md
  • node_modules/@spree/docs/dist/developer/providers/fulfillment.md;EasyPost の設定:node_modules/@spree/docs/dist/integrations/shipping/easypost.md
  • より詳細な仕様:references/provider-contracts.md
  • 関連スキル:spree-inventory(在庫数、引当、在庫移動)、spree-returns、spree-workflows(フック、カスタムワークフロー)、spree-providers、spree-checkout、spree-order-totals。

コメント