```
feat(shopro_wechat_trade): 新增微信发货一律自提功能 - 添加 shipping_force_self_pickup 配置项,默认开启,用于控制 upload_shipping_info 时固定使用 logistics_type=4(用户自提) - 修改 WxShippingPayload 类,新增 resolveLogisticsType 和 shouldUseExpressTracking 方法处理自提逻辑 - 更新配置页面添加自提开关组件及说明 - 修改数据库操作逻辑,支持配置项不存在时的插入操作 - 在 README.md 中补充相关命令行工具使用说明和配置说明 - 更新安装脚本添加默认配置值 ```
这个提交包含在:
@@ -60,6 +60,25 @@ php think shopro_wechat_trade:retry-failed
|
||||
php think shopro_wechat_trade:confirm-orders --sn=202608500438103091004500,202606584688620091002100
|
||||
# 商城已是「已收货」、只补微信:
|
||||
php think shopro_wechat_trade:confirm-orders --sn=订单号 --skip-mall
|
||||
# 自提/到店:未发货会自动先触发自提发货(logistics_type=4),再商城确认+微信同步
|
||||
php think shopro_wechat_trade:confirm-orders --sn=202606584688620091002100
|
||||
```
|
||||
|
||||
## 微信发货一律按自提
|
||||
|
||||
后台 **微信交易合规 → 微信发货一律自提**(`shipping_force_self_pickup`,默认开启):
|
||||
|
||||
- 调用 `upload_shipping_info` 时固定 **`logistics_type = 4`(用户自提)**
|
||||
- 不再要求快递单号/快递公司,用订单号作 `tracking_no`
|
||||
- **不改变**商城订单的配送方式(快递单仍可在后台照常发货)
|
||||
|
||||
已同步过「快递」的旧单若需按自提重传,在同步日志里对该单重新触发发货同步,或 `php think shopro_wechat_trade:retry-failed`。
|
||||
|
||||
已有库可执行:
|
||||
|
||||
```sql
|
||||
INSERT IGNORE INTO fa_shopro_wechat_trade_config (`name`, `value`, `group`, `description`) VALUES
|
||||
('shipping_force_self_pickup', '1', 'basic', '微信 upload_shipping_info 一律按用户自提(logistics_type=4)');
|
||||
```
|
||||
|
||||
## 触发点
|
||||
|
||||
@@ -32,7 +32,20 @@ class Config extends Backend
|
||||
$this->error('参数错误');
|
||||
}
|
||||
foreach ($params as $name => $value) {
|
||||
Db::name('shopro_wechat_trade_config')->where('name', $name)->update(['value' => $value]);
|
||||
if ($name === 'mini_program_app_id') {
|
||||
continue;
|
||||
}
|
||||
$exists = Db::name('shopro_wechat_trade_config')->where('name', $name)->find();
|
||||
if ($exists) {
|
||||
Db::name('shopro_wechat_trade_config')->where('name', $name)->update(['value' => $value]);
|
||||
} else {
|
||||
Db::name('shopro_wechat_trade_config')->insert([
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
'group' => 'basic',
|
||||
'description' => '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
WxTradeConfig::reload();
|
||||
$this->success('保存成功');
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
<el-form-item label="重试间隔(秒)">
|
||||
<el-input v-model="form.retry_interval"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="微信发货一律自提">
|
||||
<el-switch v-model="form.shipping_force_self_pickup" active-value="1" inactive-value="0"></el-switch>
|
||||
<div style="color:#999;font-size:12px;margin-top:6px;">
|
||||
开启后 upload_shipping_info 固定 logistics_type=4(用户自提),不再传快递单号;商城配送方式不变。
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="小程序AppID">
|
||||
<el-input v-model="form.mini_program_app_id" readonly></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -36,4 +36,5 @@ INSERT IGNORE INTO `__PREFIX__shopro_wechat_trade_config` (`name`, `value`, `gro
|
||||
('wechat_trade_enable', '1', 'basic', '微信交易合规总开关'),
|
||||
('order_detail_path', 'pages/order/detail?id=${商品订单号}', 'basic', '购物订单跳转路径'),
|
||||
('max_retry', '5', 'basic', '失败最大重试次数'),
|
||||
('retry_interval', '300', 'basic', '重试间隔秒');
|
||||
('retry_interval', '300', 'basic', '重试间隔秒'),
|
||||
('shipping_force_self_pickup', '1', 'basic', '微信 upload_shipping_info 一律按用户自提(logistics_type=4)');
|
||||
|
||||
@@ -18,6 +18,24 @@ class WxShippingPayload
|
||||
return self::$logisticsMap[$dispatchType] ?? 1;
|
||||
}
|
||||
|
||||
/** 同步微信发货时使用的 logistics_type(1 快递 2 同城 3 虚拟 4 自提) */
|
||||
public static function resolveLogisticsType($dispatchType)
|
||||
{
|
||||
if (WxTradeConfig::getInstance()->shippingForceSelfPickup()) {
|
||||
return 4;
|
||||
}
|
||||
return self::mapLogisticsType($dispatchType);
|
||||
}
|
||||
|
||||
public static function shouldUseExpressTracking(array $item)
|
||||
{
|
||||
if (WxTradeConfig::getInstance()->shippingForceSelfPickup()) {
|
||||
return false;
|
||||
}
|
||||
$dispatchType = $item['dispatch_type'] ?? 'express';
|
||||
return in_array($dispatchType, ['express', 'store'], true);
|
||||
}
|
||||
|
||||
public static function buildUploadPayload($order, array $items, $triggerItem = null)
|
||||
{
|
||||
$transactionId = WxOrderContext::resolveTransactionId($order);
|
||||
@@ -37,7 +55,8 @@ class WxShippingPayload
|
||||
$allItems = self::filterAllActiveItems($items);
|
||||
$deliveryMode = count($shippableItems) >= count($allItems) ? 1 : 2;
|
||||
|
||||
$logisticsType = self::mapLogisticsType($triggerItem['dispatch_type'] ?? ($shippableItems[0]['dispatch_type'] ?? 'express'));
|
||||
$dispatchHint = $triggerItem['dispatch_type'] ?? ($shippableItems[0]['dispatch_type'] ?? 'express');
|
||||
$logisticsType = self::resolveLogisticsType($dispatchHint);
|
||||
$shippingList = [];
|
||||
foreach ($shippableItems as $item) {
|
||||
$shippingList[] = self::buildShippingListItem($item, $order);
|
||||
@@ -93,11 +112,10 @@ class WxShippingPayload
|
||||
|
||||
protected static function buildShippingListItem(array $item, $order)
|
||||
{
|
||||
$dispatchType = $item['dispatch_type'] ?? 'express';
|
||||
$desc = mb_substr($item['goods_title'] ?? '商城商品', 0, 120);
|
||||
$entry = ['item_desc' => $desc];
|
||||
|
||||
if (in_array($dispatchType, ['express', 'store'], true)) {
|
||||
if (self::shouldUseExpressTracking($item)) {
|
||||
$trackingNo = trim((string) ($item['express_no'] ?? ''));
|
||||
$expressCode = WxExpressMapper::toWechatCode($item['express_code'] ?? '');
|
||||
if ($trackingNo === '') {
|
||||
|
||||
@@ -65,4 +65,12 @@ class WxTradeConfig
|
||||
$v = intval($this->get('retry_interval', 300));
|
||||
return $v > 0 ? $v : 300;
|
||||
}
|
||||
|
||||
/**
|
||||
* upload_shipping_info 是否一律按「用户自提」(logistics_type=4),与商城 dispatch_type 无关
|
||||
*/
|
||||
public function shippingForceSelfPickup()
|
||||
{
|
||||
return $this->get('shipping_force_self_pickup', '1') === '1';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ define(['jquery', 'bootstrap', 'backend', 'form'], function ($, undefined, Backe
|
||||
order_detail_path: 'pages/order/detail?id=${商品订单号}',
|
||||
max_retry: '5',
|
||||
retry_interval: '300',
|
||||
shipping_force_self_pickup: '1',
|
||||
mini_program_app_id: ''
|
||||
},
|
||||
checkResult: ''
|
||||
|
||||
@@ -32,7 +32,20 @@ class Config extends Backend
|
||||
$this->error('参数错误');
|
||||
}
|
||||
foreach ($params as $name => $value) {
|
||||
Db::name('shopro_wechat_trade_config')->where('name', $name)->update(['value' => $value]);
|
||||
if ($name === 'mini_program_app_id') {
|
||||
continue;
|
||||
}
|
||||
$exists = Db::name('shopro_wechat_trade_config')->where('name', $name)->find();
|
||||
if ($exists) {
|
||||
Db::name('shopro_wechat_trade_config')->where('name', $name)->update(['value' => $value]);
|
||||
} else {
|
||||
Db::name('shopro_wechat_trade_config')->insert([
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
'group' => 'basic',
|
||||
'description' => '',
|
||||
]);
|
||||
}
|
||||
}
|
||||
WxTradeConfig::reload();
|
||||
$this->success('保存成功');
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
<el-form-item label="重试间隔(秒)">
|
||||
<el-input v-model="form.retry_interval"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="微信发货一律自提">
|
||||
<el-switch v-model="form.shipping_force_self_pickup" active-value="1" inactive-value="0"></el-switch>
|
||||
<div style="color:#999;font-size:12px;margin-top:6px;">
|
||||
开启后 upload_shipping_info 固定 logistics_type=4(用户自提),不再传快递单号;商城配送方式不变。
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="小程序AppID">
|
||||
<el-input v-model="form.mini_program_app_id" readonly></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -9,6 +9,7 @@ define(['jquery', 'bootstrap', 'backend', 'form'], function ($, undefined, Backe
|
||||
order_detail_path: 'pages/order/detail?order_sn=${商品订单号}',
|
||||
max_retry: '5',
|
||||
retry_interval: '300',
|
||||
shipping_force_self_pickup: '1',
|
||||
mini_program_app_id: ''
|
||||
},
|
||||
checkResult: ''
|
||||
|
||||
在新工单中引用
屏蔽一个用户