关于 shipping-method-development
为 Bagisto 开发者提供自定义配送方式的实现指南,介绍承运商类、运费配置和费率计算。适用于增加配送选项、对接承运商或制定自定义计价规则。
主要功能
- 01配置承运商与系统设置
- 02实现运费计算类
- 03支持固定、按件及按重量计价
- 04注册服务提供者与软件包
- 05参考内置配送方式示例
适用场景
- 01为结账添加新的配送方式
- 02按重量或订单门槛计算运费
- 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:注册你的包
- 添加到 composer.json(位于 Bagisto 根目录中):
{
"autoload": {
"psr-4": {
"Webkul\\CustomExpressShipping\\": "packages/Webkul/CustomExpressShipping/src"
}
}
}
- 更新自动加载器:
composer dump-autoload
- 在
bootstrap/providers.php中注册服务提供者:
<?php
return [
App\Providers\AppServiceProvider::class,
// ... other providers ...
Webkul\CustomExpressShipping\Providers\CustomExpressShippingServiceProvider::class,
];
- 清除缓存:
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.php | Shipping 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 规范并使用正确的标点