mcpField
S
shipping-method-development

shipping-method-development

github.com/bagisto开发13270 次安装2 周前更新
代码仓库

关于 shipping-method-development

为 Bagisto 开发者提供自定义配送方式的实现指南,介绍承运商类、运费配置和费率计算。适用于增加配送选项、对接承运商或制定自定义计价规则。

主要功能

  1. 01配置承运商与系统设置
  2. 02实现运费计算类
  3. 03支持固定、按件及按重量计价
  4. 04注册服务提供者与软件包
  5. 05参考内置配送方式示例

适用场景

  1. 01为结账添加新的配送方式
  2. 02按重量或订单门槛计算运费
  3. 03为不同销售渠道设置运费

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bagisto/agent-skills --skill shipping-method-development

安装后在 Claude Code 中输入/shipping-method-development确认已加载。

详细介绍

配送方式开发

概述

在 Bagisto 中创建自定义配送方式,可让你根据具体业务需求定制配送选项。无论你需要为易碎商品提供特殊处理、提供特快配送选项,还是设置特定区域的配送规则,自定义配送方式都能为电商商店提供所需的灵活性。

在本教程中,我们将创建 Custom Express Shipping 配送方式,演示构建任意类型配送方案所需的所有基本概念。

适用场景

在以下情况下启用此技能:

  • 创建新的配送方式
  • 集成配送承运商(FedEx、UPS、DHL、USPS 等)
  • 在结账时添加配送选项
  • 修改现有配送配置
  • 为配送方式创建管理端配置
  • 实现费率计算逻辑

Bagisto 配送架构

Bagisto 的配送系统采用灵活的承运商架构,将配置与业务逻辑分离。

核心组件
组件用途位置
承运商配置定义配送方式属性Config/carriers.php
承运商类包含费率计算逻辑Carriers/ClassName.php
系统配置管理端界面表单Config/system.php
服务提供程序注册配送方式Providers/ServiceProvider.php
配送 Facade收集并管理费率Webkul\Shipping\Shipping
主要功能
  • 灵活的费率计算:支持按单位、按订单、按重量或自定义定价。
  • 配置管理:提供便于管理员使用的设置界面。
  • 多渠道支持:不同销售渠道可使用不同的费率和设置。
  • 支持本地化:全面支持翻译。
  • 可扩展架构:便于与第三方 API 集成。

分步指南

步骤 1:创建软件包目录结构
mkdir -p packages/Webkul/CustomExpressShipping/src/{Carriers,Config,Providers}
步骤 2:创建承运商配置

文件: packages/Webkul/CustomExpressShipping/src/Config/carriers.php

<?php

return [
    'custom_express_shipping' => [
        'code'         => 'custom_express_shipping',
        'title'        => 'Express Delivery (1-2 Days)',
        'description'  => 'Premium express shipping with tracking',
        'active'       => true,
        'default_rate' => '19.99',
        'type'         => 'per_order',
        'class'        => 'Webkul\CustomExpressShipping\Carriers\CustomExpressShipping',
    ],
];
配置属性说明
属性类型用途说明
code字符串唯一标识符必须与数组键和承运商类中的 $code 属性一致。
title字符串默认显示名称结账时向客户显示(可在管理端覆盖)。
description字符串方式说明对配送服务的简要说明。
active布尔值默认状态指示配送方式是否默认启用。
default_rate字符串/浮点数基础配送费用计算前的基础配送费用。
type字符串定价模式per_order(固定费率)或 per_unit(按商品计费)。
class字符串承运商类命名空间承运商类的完整路径。

注意: 数组键(custom_express_shipping)必须与承运商类中的 code 属性和系统配置键路径一致,并且应在各处保持一致。

步骤 3:创建承运商类

文件: packages/Webkul/CustomExpressShipping/src/Carriers/CustomExpressShipping.php

<?php

namespace Webkul\CustomExpressShipping\Carriers;

use Webkul\Shipping\Carriers\AbstractShipping;
use Webkul\Checkout\Models\CartShippingRate;
use Webkul\Checkout\Facades\Cart;

class CustomExpressShipping extends AbstractShipping
{
    /**
     * Shipping method code - must match carriers.php key.
     *
     * @var string
     */
    protected $code = 'custom_express_shipping';

    /**
     * Shipping method code.
     *
     * @var string
     */
    protected $method = 'custom_express_shipping_custom_express_shipping';

    /**
     * Calculate shipping rate for the current cart.
     *
     * @return \Webkul\Checkout\Models\CartShippingRate|false
     */
    public function calculate()
    {
        if (! $this->isAvailable()) {
            return false;
        }

        return $this->getRate();
    }

    /**
     * Get shipping rate.
     *
     * @return \Webkul\Checkout\Models\CartShippingRate
     */
    public function getRate(): CartShippingRate
    {
        $cart = Cart::getCart();

        $cartShippingRate = new CartShippingRate;

        $cartShippingRate->carrier = $this->getCode();
        $cartShippingRate->carrier_title = $this->getConfigData('title');
        $cartShippingRate->method = $this->getMethod();
        $cartShippingRate->method_title = $this->getConfigData('title');
        $cartShippingRate->method_description = $this->getConfigData('description');
        $cartShippingRate->price = 0;
        $cartShippingRate->base_price = 0;

        $baseRate = (float) $this->getConfigData('default_rate');

        if ($this->getConfigData('type') == 'per_unit') {
            foreach ($cart->items as $item) {
                if ($item->getTypeInstance()->isStockable()) {
                    $cartShippingRate->price += core()->convertPrice($baseRate) * $item->quantity;
                    $cartShippingRate->base_price += $baseRate * $item->quantity;
                }
            }
        } else {
            $cartShippingRate->price = core()->convertPrice($baseRate);
            $cartShippingRate->base_price = $baseRate;
        }

        return $cartShippingRate;
    }
}
步骤 4:创建系统配置

文件: packages/Webkul/CustomExpressShipping/src/Config/system.php

<?php

return [
    [
        'key'    => 'sales.carriers.custom_express_shipping',
        'name'   => 'Custom Express Shipping',
        'info'   => 'Configure the Custom Express Shipping method settings.',
        'sort'   => 1,
        'fields' => [
            [
                'name'          => 'title',
                'title'         => 'Method Title',
                'type'          => 'text',
                'validation'    => 'required',
                'channel_based' => true,
                'locale_based'  => true,
            ],
            [
                'name'          => 'description',
                'title'         => 'Description',
                'type'          => 'textarea',
                'channel_based' => true,
                'locale_based'  => false,
            ],
            [
                'name'          => 'default_rate',
                'title'         => 'Base Rate',
                'type'          => 'text',
                'validation'    => 'required|numeric|min:0',
                'channel_based' => true,
                'locale_based'  => false,
            ],
            [
                'name'          => 'type',
                'title'         => 'Pricing Type',
                'type'          => 'select',
                'options'       => [
                    [
                        'title' => 'Per Order (Flat Rate)',
                        'value' => 'per_order',
                    ],
                    [
                        'title' => 'Per Item',
                        'value' => 'per_unit',
                    ],
                ],
                'channel_based' => true,
                'locale_based'  => false,
            ],
            [
                'name'          => 'active',
                'title'         => 'Enabled',
                'type'          => 'boolean',
                'validation'    => 'required',
                'channel_based' => true,
                'locale_based'  => false,
            ],
        ],
    ],
];
系统配置字段属性
属性用途描述
name字段标识符用于存储和获取配置值。
title字段标签在管理表单中显示的标签。
type输入类型text、textarea、boolean、select、password 等。
default_value默认设置首次配置时的初始值。
channel_based多店铺支持为不同销售渠道设置不同的值。
locale_based多语言支持支持按语言翻译内容。
validation字段验证如 required、numeric、email 等规则。
步骤 5:创建服务提供者

文件: packages/Webkul/CustomExpressShipping/src/Providers/CustomExpressShippingServiceProvider.php

<?php

namespace Webkul\CustomExpressShipping\Providers;

use Illuminate\Support\ServiceProvider;

class CustomExpressShippingServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register(): void
    {
        $this->mergeConfigFrom(
            dirname(__DIR__) . '/Config/carriers.php',
            'carriers'
        );

        $this->mergeConfigFrom(
            dirname(__DIR__) . '/Config/system.php',
            'core'
        );
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot(): void
    {
        //
    }
}
步骤 6:注册你的包
  1. 添加到 composer.json(位于 Bagisto 根目录中):
{
    "autoload": {
        "psr-4": {
            "Webkul\\CustomExpressShipping\\": "packages/Webkul/CustomExpressShipping/src"
        }
    }
}
  1. 更新自动加载器:
composer dump-autoload
  1. 在 bootstrap/providers.php 中注册服务提供者:
<?php

return [
    App\Providers\AppServiceProvider::class,

    // ... other providers ...

    Webkul\CustomExpressShipping\Providers\CustomExpressShippingServiceProvider::class,
];
  1. 清除缓存:
php artisan optimize:clear

基础承运商类参考

位置: packages/Webkul/Shipping/src/Carriers/AbstractShipping.php

所有配送方式都继承自 Webkul\Shipping\Carriers\AbstractShipping:

<?php

namespace Webkul\Shipping\Carriers;

use Webkul\Shipping\Exceptions\CarrierCodeException;

abstract class AbstractShipping
{
    /**
     * Shipping method carrier code.
     *
     * @var string
     */
    protected $code;

    /**
     * Shipping method code.
     *
     * @var string
     */
    protected $method;

    abstract public function calculate();

    /**
     * Checks if shipping method is available.
     *
     * @return array
     */
    public function isAvailable()
    {
        return $this->getConfigData('active');
    }

    /**
     * Returns shipping method carrier code.
     *
     * @return string
     */
    public function getCode()
    {
        if (empty($this->code)) {
            throw new CarrierCodeException('Carrier code should be initialized.');
        }

        return $this->code;
    }

    /**
     * Return shipping method code.
     *
     * @return string
     */
    public function getMethod()
    {
        if (empty($this->method)) {
            $code = $this->getCode();

            return $code . '_' . $code;
        }

        return $this->method;
    }

    /**
     * Returns shipping method title.
     *
     * @return array
     */
    public function getTitle()
    {
        return $this->getConfigData('title');
    }

    /**
     * Returns shipping method description.
     *
     * @return array
     */
    public function getDescription()
    {
        return $this->getConfigData('description');
    }

    /**
     * Retrieve information from shipping configuration.
     *
     * @param  string  $field
     * @return mixed
     */
    public function getConfigData($field)
    {
        return core()->getConfigData('sales.carriers.' . $this->getCode() . '.' . $field);
    }
}

CartShippingRate 模型

位置: packages/Webkul/Checkout/src/Models/CartShippingRate.php

<?php

namespace Webkul\Checkout\Models;

use Illuminate\Database\Eloquent\Model;
use Webkul\Checkout\Contracts\CartShippingRate as CartShippingRateContract;

class CartShippingRate extends Model implements CartShippingRateContract
{
    protected $fillable = [
        'carrier',
        'carrier_title',
        'method',
        'method_title',
        'method_description',
        'price',
        'base_price',
        'discount_amount',
        'base_discount_amount',
        'tax_percent',
        'tax_amount',
        'base_tax_amount',
        'price_incl_tax',
        'base_price_incl_tax',
        'applied_tax_rate',
    ];
}

需要实现的关键方法

方法用途必需
calculate()计算并返回运费是(抽象方法)
getRate()构建 CartShippingRate 对象否(可内联实现)
isAvailable()覆盖默认实现以自定义可用性否(使用默认实现)

内置配送方式

  • FlatRate:packages/Webkul/Shipping/src/Carriers/FlatRate.php
  • Free:packages/Webkul/Shipping/src/Carriers/Free.php

定价示例

固定运费
public function calculate()
{
    if (! $this->isAvailable()) {
        return false;
    }

    $cartShippingRate = new CartShippingRate;
    $cartShippingRate->carrier = $this->getCode();
    $cartShippingRate->carrier_title = $this->getConfigData('title');
    $cartShippingRate->method = $this->getMethod();
    $cartShippingRate->method_title = $this->getConfigData('title');
    $cartShippingRate->method_description = $this->getConfigData('description');
    $cartShippingRate->price = 15.99;
    $cartShippingRate->base_price = 15.99;

    return $cartShippingRate;
}
按重量计价
public function calculate()
{
    if (! $this->isAvailable()) {
        return false;
    }

    $cart = Cart::getCart();
    $baseRate = 5.00;
    $perKg = 2.50;

    $price = $baseRate + ($cart->weight * $perKg);

    $cartShippingRate = new CartShippingRate;
    $cartShippingRate->carrier = $this->getCode();
    $cartShippingRate->carrier_title = $this->getConfigData('title');
    $cartShippingRate->method = $this->getMethod();
    $cartShippingRate->method_title = $this->getConfigData('title');
    $cartShippingRate->price = core()->convertPrice($price);
    $cartShippingRate->base_price = $price;

    return $cartShippingRate;
}
超过门槛后免运费
public function calculate()
{
    if (! $this->isAvailable()) {
        return false;
    }

    $cart = Cart::getCart();
    $threshold = (float) $this->getConfigData('free_shipping_threshold');
    $price = $cart->sub_total >= $threshold ? 0 : (float) $this->getConfigData('default_rate');

    $cartShippingRate = new CartShippingRate;
    $cartShippingRate->carrier = $this->getCode();
    $cartShippingRate->carrier_title = $this->getConfigData('title');
    $cartShippingRate->method = $this->getMethod();
    $cartShippingRate->method_title = $this->getConfigData('title');
    $cartShippingRate->price = core()->convertPrice($price);
    $cartShippingRate->base_price = $price;

    return $cartShippingRate;
}

Shipping Facade

位置: packages/Webkul/Shipping/src/Shipping.php

Shipping facade 负责管理运费收集和处理:

class Shipping
{
    public function collectRates()
    {
        // Iterates through all carriers and calls calculate()
        // Returns grouped shipping methods with rates
    }

    public function getGroupedAllShippingRates()
    {
        // Returns rates grouped by carrier
    }

    public function getShippingMethods()
    {
        // Returns available shipping methods
    }
}

包结构

packages
└── Webkul
    └── CustomExpressShipping
        └── src
            ├── Carriers
            │   └── CustomExpressShipping.php         # Rate calculation logic
            ├── Config
            │   ├── carriers.php                     # Shipping method definition
            │   └── system.php                        # Admin configuration
            └── Providers
                └── CustomExpressShippingServiceProvider.php  # Registration

测试

可通过结账流程测试配送方式。请测试:

  • 启用后,该方式会显示在结账页面中
  • 运费计算正确
  • 管理后台配置可正常保存
  • 该方式遵循启用/禁用状态

关键文件参考

文件用途
packages/Webkul/Shipping/src/Carriers/AbstractShipping.php基础抽象类
packages/Webkul/Shipping/src/Carriers/FlatRate.php固定运费示例
packages/Webkul/Shipping/src/Carriers/Free.php免运费示例
packages/Webkul/Shipping/src/Config/carriers.php默认承运商配置
packages/Webkul/Shipping/src/Shipping.phpShipping facade
packages/Webkul/Checkout/src/Models/CartShippingRate.php运费模型
packages/Webkul/Admin/src/Config/system.php管理后台配置(承运商部分)

常见问题

  • 忘记在服务提供者中合并配置
  • $code 属性与配置数组键不匹配
  • 未在 bootstrap/providers.php 中注册服务提供者
  • 添加包后忘记运行 composer dump-autoload
  • 修改配置后未清除缓存
  • 多币种支持未使用 core()->convertPrice()
  • 按商品计算时未检查 isStockable()
  • 未遵循 PHPDoc 规范并使用正确的标点

评论

支持 Markdown