一、聚水潭ERP开放平台操作
入驻流程文档:https://openweb.jushuitan.com/doc?docId=20
根据流程注册开放平台,申请成为开发者,创建自有商城应用(保存APP Key和APP Secret)。
根据需要申请api接口
使用新版消息通知时,需要在开放平台订阅相关消息
二、聚水潭ERP后台操作
在聚水潭后台(www.erp321.com)添加店铺,店铺平台选择自有商城(保存店铺编号)。
如果店铺列表没有显示店铺编号,在列表右上角点击列设置,勾选上店铺编号。
配置消息推送回调地址(库存同步、订单取消、物流同步)
售后同步使用新版消息推送,因为返回数据更全面(开放平台->消息管理->订阅管理->订阅消息)
三、自有商城对接聚水潭ERP接口
0.聚水潭SDK
聚水潭SDK-PHPhttps://gitee.com/out-of-town/jushuitan-sdk-php
把SDK部署到server/extend目录下。
1.公共PHP文件
JstErpService.php
//JstErpService.php
<?php
namespace app\common\service;
use jushuitan\Client;
use jushuitan\Route;
use think\facade\Cache;
class JstErpService
{
// 授权地址
private $authUrl = 'https://openweb.jushuitan.com/auth';
// 接口地址
// private $baseUrl = 'https://dev-api.jushuitan.com/';//测试环境地址
private $baseUrl = 'https://openapi.jushuitan.com/';//正式环境地址
// 授权接口地址
// private $apiUrl = 'https://dev-api.jushuitan.com/openWebIsv';//测试环境地址
private $apiUrl = 'https://openapi.jushuitan.com/openWeb';//正式环境地址
// access_token
private $accessToken = '';
// 应用APP Key
private $appKey;
// 应用APP Secret
private $appSecret;
// 版本号
private $version = '2';
// UNIX时间戳,单位秒
private $timestamp;
// 字符编码(固定值:utf-8)
private $charset = 'utf-8';
// 是否验证证书
private $verify = false;
// 超时时间
private $timeout = 0;
// 配置
private $config;
private $client;
private $route;
/**
* 构造方法
*/
public function __construct()
{
$this->appKey = ConfigService::get('jst_erp_config', 'app_key');
$this->appSecret = ConfigService::get('jst_erp_config', 'app_secret');
$this->timestamp = time();
//获取access_token
$this->getAccessToken();
$this->client = Client::getInstance($this->config);
$this->route = Route::getInstance();
}
/**
* @notes 获取access_token
*/
public function getAccessToken()
{
$this->config = [
'authUrl' => $this->authUrl,
'baseUrl' => $this->baseUrl,
'apiUrl' => $this->apiUrl,
'accessToken' => $this->accessToken,
'appKey' => $this->appKey,
'appSecret' => $this->appSecret,
'version' => $this->version,
'timestamp' => $this->timestamp,
'charset' => $this->charset,
'verify' => $this->verify,
'timeout' => $this->timeout,
];
// 获取缓存access_token
$accessTokenCache = Cache::get('JST_ERP_ACCESS_TOKEN');
if (!$accessTokenCache) {
$this->client = Client::getInstance($this->config);
$getAccessToken = $this->client->getInitToken(generate_code(6));
$getAccessToken = json_decode($getAccessToken, true);
if ($getAccessToken['code'] !== 0 || empty($getAccessToken['data']['access_token'])) {
throw new \Exception($getAccessToken['msg'] ?? '获取access_token失败');
}
$this->accessToken = $getAccessToken['data']['access_token'];
$ttl = $getAccessToken['data']['expires_in'];
$cacheValue = $getAccessToken['data'];
$cacheValue['expires_in'] = time() + $cacheValue['expires_in'];
Cache::set('JST_ERP_ACCESS_TOKEN', json_encode($cacheValue), $ttl);
} else {
$accessTokenArr = json_decode($accessTokenCache, true);
$this->accessToken = $accessTokenArr['access_token'];
//access_token有效期剩余小于15天时执行刷新操作
if ((time() + (15 * 86400)) > $accessTokenArr['expires_in']) {
//删除缓存
Cache::delete('JST_ERP_ACCESS_TOKEN');
$this->client = Client::getInstance($this->config);
$getAccessToken = $this->client->refreshToken($accessTokenArr['refresh_token']);
$getAccessToken = json_decode($getAccessToken, true);
//无效的access_token或access_token已过期,重新获取access_token
if ($getAccessToken['code'] === 100) {
$getAccessToken = $this->client->getInitToken(generate_code(6));
$getAccessToken = json_decode($getAccessToken, true);
if ($getAccessToken['code'] !== 0 || empty($getAccessToken['data']['access_token'])) {
throw new \Exception($getAccessToken['msg'] ?? '获取access_token失败');
}
} elseif ($getAccessToken['code'] !== 0 || empty($getAccessToken['data']['access_token'])) {
throw new \Exception($getAccessToken['msg'] ?? '刷新access_token失败');
}
$this->accessToken = $getAccessToken['data']['access_token'];
$ttl = $getAccessToken['data']['expires_in'];
$cacheValue = $getAccessToken['data'];
$cacheValue['expires_in'] = time() + $cacheValue['expires_in'];
Cache::set('JST_ERP_ACCESS_TOKEN', json_encode($cacheValue), $ttl);
}
}
$this->config['accessToken'] = $this->accessToken;
}
/**
* @notes 同步商品
*/
public function uploadGoods($goodsData)
{
$response = $this->client->request($this->route->getRoute('UPLOAD_ITEMSKU'), [
'items' => $goodsData
]);
$response = json_decode($response, true);
if ($response['code'] !== 0) {
throw new \Exception($response['msg'] ?? '同步商品失败');
}
return $response;
}
/**
* @notes 同步店铺商品
*/
public function uploadShopGoods($shopGoodsData)
{
$response = $this->client->request($this->route->getRoute('UPLOAD_SKUMAP'), [
'items' => $shopGoodsData
]);
$response = json_decode($response, true);
if ($response['code'] !== 0) {
throw new \Exception($response['msg'] ?? '同步店铺商品失败');
}
return $response;
}
/**
* @notes 同步库存
*/
public function uploadStock($soId,$shopGoodsData)
{
$response = $this->client->request($this->route->getRoute('UPLOAD_INVENTORY_V2'), [
// 'wms_co_id' => '',//仓库编码,不填写默认主仓
'type' => 'check',//盘点类型 :非精细化盘点 ,全量:check (覆盖,传哪个商品覆盖哪个商品库存,没有传的商品库存不变);增量:adjust(默认adjust,在原基础数量上增加) ;精细化仓位盘点,type=check,为全量盘点,仓位上未传的商品库存盘为0;type=adjust,为增量盘点,未传的商品库存不盘点,传的数量做增量盘点,如盘前库存=10,传参数量=2,则盘点后库存为10+2=12
'is_confirm' => true,//是否自动确认单据
'so_id' => $soId,//外部单号(自定义传唯一值不可重复传,以第一次传成功的结果为准)
'warehouse' => '1',//仓库;主仓=1,销退仓=2, 进货仓=3,次品仓 = 4,自定义1仓=6,自定义2仓=7,自定义3仓=8
// 'remark' => '',//备注
// 'bin' => '',//仓位(开启精细化管理有效且必填)
// 'default_type' => '',//暂存位类型(开启精细化管理有效且必填可传值Default(暂存位),Pick(拣货暂存位),None)
'items' => $shopGoodsData
]);
$response = json_decode($response, true);
if ($response['code'] !== 0) {
throw new \Exception($response['msg'] ?? '同步库存失败');
}
return $response;
}
/**
* @notes 同步订单
*/
public function uploadOrder($orderData)
{
$response = $this->client->request($this->route->getRoute('UPLOAD_ORDERS'), [
$orderData
]);
$response = json_decode($response, true);
if ($response['code'] !== 0) {
throw new \Exception($response['msg'] ?? '同步订单失败');
}
return $response;
}
/**
* @notes 同步售后单
*/
public function uploadAfterSale($uploadData)
{
$response = $this->client->request($this->route->getRoute('UPLOAD_AFTERSALE'), [
$uploadData
]);
$response = json_decode($response, true);
if ($response['code'] !== 0) {
throw new \Exception($response['msg'] ?? '同步售后单失败');
}
return $response;
}
}
JstCommonLogic.php
//JstCommonLogic.php
<?php
namespace app\common\logic;
use app\common\enum\AfterSaleEnum;
use app\common\enum\OrderEnum;
use app\common\enum\PayEnum;
use app\common\service\JstErpService;
use app\common\service\RegionService;
/**
* 聚水潭公共逻辑接口
*/
class JstCommonLogic extends BaseLogic
{
/**
* @notes 同步商品到聚水潭
*/
public static function uploadGoods($goodsId = true)
{
//聚水潭店铺编码
$shopid = '';
//获取商品数据
$goodsList = [];
$goodsSyncData = [];//普通商品数据
$shopGoodsSyncData = [];//店铺商品数据
foreach ($goodsList as $goods) {
$goodsSyncData[] = [
'sku_id' => $goods['item_code'],//商品编码
'i_id' => $goods['item_code'],//款式编码
'brand' => $goods['brand_name'],//品牌
// 'vc_name' => '',//虚拟分类
'c_name' => $goods['category_text'],//商品分类
's_price' => $goods['sell_price'],//基本售价
'item_type' => '成品',//商品属性,可选值["成品","半成品","原材料","包材"]
// 'l' => '',//长
// 'w' => '',//宽
// 'h' => '',//高
'pic' => $goods['item_image'],//图片地址(款图片)
'pic_big' => $goods['item_image'],//大图地址
'sku_pic' => $goods['item_image'],//商品图片(sku图片)
'name' => $goods['name'],//名称
// 'remark' => '',//备注
'properties_value' => $goods['spec_value_str'],//颜色及规格
// 'short_name' => '',//简称
'weight' => $goods['weight'],//重量
'enabled' => $goods['status'] == 1 ? 1 : -1,//是否启用,默认值1,可选值:-1=禁用,0=备用,1=启用
'supplier_name' => $goods['supplier_name'],//供应商名称
// 'sku_code' => '',//国标码
// 'supplier_sku_id' => '',//供应商商品编码
// 'supplier_i_id' => '',//供应商款式编码
// 'other_price_1' => '',//其它价格1
// 'other_price_2' => '',//其它价格2
// 'other_price_3' => '',//其它价格3
// 'other_price_4' => '',//其它价格4
// 'other_price_5' => '',//其它价格5
// 'other_1' => '',//其它属性1
// 'other_2' => '',//其它属性2
// 'other_3' => '',//其它属性3
// 'other_4' => '',//其它属性4
// 'other_5' => '',//其它属性5
'stock_disabled' => false,//库存同步,true=禁止,false=允许
'c_price' => $goods['cost_price'],//成本价
'market_price' => $goods['lineation_price'],//市场|吊牌价
'unit' => $goods['unit_name'],//单位
// 'labels' => [],//增加标签
// 'batch_enabled' => '',//是否启用生产批次
// 'is_series_number' => '',//是否启用序列号
// 'other_code' => '',//辅助码;长度不超过500
// 'shelf_life' => '',//保质期天数;必须大于0
// 'hand_day' => '',//临期天数
// 'rejectLifecycle' => '',//保质期禁收天数
// 'lockupLifecycle' => '',//保质期禁售天数
// 'adventLifecycle' => '',//保质期临期预警天数
// 'CategoryPropertys' => [],//按款显示时商品列表里的属性
// 'deletedlabels' => '',//移除标签
// 'production_licence' => '',//生产许可证
// 'purchase_price' => '',//采购价
];
$shopGoodsSyncData[] = [
'sku_id' => $goods['item_code'],//商品编码
'i_id' => $goods['item_code'],//款式编码
// 'sku_code' => '',//国标码
'shop_i_id' => $goods['item_code'],//店铺商品款式
'shop_sku_id' => $goods['item_code'],//店铺商品编码
'original_sku_id' => $goods['item_code'],//原始商品编码
'name' => $goods['name'],//商品名称
'shop_properties_value' => $goods['spec_value_str'],//店铺颜色规格
// 'sku_sign' => $goods['stock_lock_warning'],//商品标识(线上商品的特殊属性,比如零售,阶梯)
'shop_id' => $shopid,//店铺编号
'shop_sku_url' => $goods['item_image'],//线上链接
];
}
//同步商品数据到聚水潭
while (!empty($goodsSyncData)) {
$jstErp = new JstErpService();
//同步普通商品 接口每次请求最大200
$uploadData = array_splice($goodsSyncData,0,200);
$jstErp->uploadGoods($uploadData);
//同步店铺商品 接口每次请求最大50
$uploadShopData = array_splice($shopGoodsSyncData,0,50);
$jstErp->uploadShopGoods($uploadShopData);
}
}
/**
* @notes 同步库存到聚水潭
*/
public static function uploadStock($goodsId = true)
{
//获取商品数据
$goodsList = [];
//处理库存数据
$stockSyncData = [];
foreach ($goodsList as $goods) {
$stockSyncData[] = [
'qty' => $goods['stock'],//数量
// 'sku_sns' => [],//唯一码集合,如果type = adjust 不支持传sn会报错增量盘点不支持传唯一码(开启统一功能:stockchange.use.reconfig.service)
'sku_id' => $goods['item_code'],//商品编码
// 'supplier_id' => '',//供应商编码
// 'batch_id' => $goods['sell_price'],//批次号;如果开启商品批次开关,批次信息必填
// 'produced_date' => '',//生产日期(时间范围1970-01-01 00:00:00-3000-01-01 00:00:00)
// 'expiration_date' => '',//有效期(时间范围1970-01-01 00:00:00-3000-01-01 00:00:00)
];
}
//同步库存数据到聚水潭
while (!empty($stockSyncData)) {
// 最大500
$uploadData = array_splice($stockSyncData,0,500);
$soId = date('YmdHis');
(new JstErpService())->uploadStock($soId,$uploadData);
}
}
/**
* @notes 同步订单到聚水潭
* 此接口一次最大上传50个订单,订单发货前,重复上传可更新订单(仅支持文档说明中发货前可更新字段)
*/
public static function uploadOrder($orderSn,$isHandle = false)
{
try {
//聚水潭店铺编号
$shopid = '';
//获取订单信息
$order = [];
//获取订单发货信息
$delivery = [];
//快递公司编码
$expressCode = '';
//获取erp订单状态
$orderStatus = '';
switch ($order['order_status']) {
case OrderEnum::STATUS_WAIT_PAY://等待买家付款
$orderStatus = 'WAIT_BUYER_PAY';
break;
case OrderEnum::STATUS_WAIT_DELIVERY://等待卖家发货(传此状态时实际支付金额即pay节点支付金额=应付金额ERP才会显示已付款待审核)
$orderStatus = 'WAIT_SELLER_SEND_GOODS';
break;
case OrderEnum::STATUS_WAIT_RECEIVE://等待买家确认收货
$orderStatus = 'WAIT_BUYER_CONFIRM_GOODS';
break;
case OrderEnum::STATUS_FINISH://交易成功
$orderStatus = 'TRADE_FINISHED';
break;
case OrderEnum::STATUS_CLOSE://付款后交易关闭=TRADE_CLOSED,付款前交易关闭=TRADE_CLOSED_BY_TAOBAO
$orderStatus = $order['pay_status'] == 1 ? 'TRADE_CLOSED' :'TRADE_CLOSED_BY_TAOBAO';
break;
}
//处理订单商品信息
$orderGoodsData = [];
foreach ($order['order_goods'] as $goods) {
$itemData = [
'sku_id' => $goodsCode,//商品编号
'shop_sku_id' => $goodsCode,//店铺商品编码
'shop_i_id' => $goodsCode,//店铺商品款式编码
'i_id' => $goodsCode,//商品款式编码
'pic' => $goods['goods_image'],//图片地址
// 'properties_value' => '',//商品属性
'amount' => (float)$goods['total_pay_price'],//成交总额单位(元)
'price' => (float)$goods['goods_price'],//单价,单位(元)
'base_price' => (float)$goods['total_price'],//原价
'qty' => (int)$goods['goods_num'],//数量
// 'refund_qty' => 0,//退货数量
'name' => $goods['goods_name'],//商品名称
// 'refund_status' => '',//退货状态
'outer_oi_id' => $goodsCode.'_'.$goods['goods_id'],//商家系统订单商品明细主键,为了拆单合单时溯源
// 'remark' => '',//订单明细备注
// 'batch_id' => '',//生产批次号
// 'produced_date' => '',//生产日期
// 'is_gift' => '',//是否赠品
'bu_id' => 0,//虚拟仓编码
];
//订单商品售后
$orderGoodsAftersale = [];
$afterSaleStatus = [1=>'waiting',2=>'success',3=>'closed'];
if (!empty($orderGoodsAftersale)) {
$itemData['refund_qty'] = $orderGoodsAftersale['goods_num'];
$itemData['refund_status'] = $afterSaleStatus[$orderGoodsAftersale['status']] ?? 'waiting';
}
$orderGoodsData[] = $itemData;
}
$uploadData = [
'shop_id' => $shopid,//店铺编号
// 'oaid' => '',//oaid(从淘宝平台拿到的的密文串),同时订单标签要打上“淘宝订单”full_receive_en_json的OuterSoId要传线上单号
// 'wms_co_id' => '',//分仓编号;订单指定分仓,主仓发货无需填写
// 'plan_delivery_date' => '',//最晚发货时间;发货前可更新
// 'outer_so_id' => $handleOrder['order_info']['transaction_id'],//外部交易单号
// 'full_receive_en_json' => '',//收件人密文串json(支持从淘宝抖音快手拼多多京东平台拿到的密文信息)同时订单多标签要打上对应密文站点标签如快手的标记“快手订单”(抖音订单,快手订单,拼多多订单,京东订单)
// 'receiver_mobile_en' => '',//收件人密文手机号
// 'receiver_name_en' => '',//收件人密文姓名
// 'buyer_paid_amount' => $order['pay_status'] == 1 ? $order['order_amount'] : 0,//总买家实付
// 'seller_income_amount' => $order['pay_status'] == 1 ? $order['order_amount'] : 0,//总卖家实收
'so_id' => $order['sn'],//自研商城系统订单号
'order_date' => $order['create_time'],//订单日期
'shop_status' => $orderStatus,//自研商城系统订单状态
'shop_buyer_id' => $order['user']['nickname'],//买家帐号
'receiver_state' => RegionService::getAddress($order['address']->province),//收货省份
'receiver_city' => RegionService::getAddress($order['address']->city),//收货市
'receiver_district' => RegionService::getAddress($order['address']->district),//收货区/街道
'receiver_address' => $order['address']->address,//收货地址
'receiver_name' => $order['address']->contact,//收件人
'receiver_phone' => $order['address']->mobile,//联系电话
// 'receiver_country' => 'CN',//收货国家
// 'receiver_zip' => '',//邮政编码
'pay_amount' => (float)$order['order_amount'],//应付金额
'freight' => (float)$order['express_price'],//运费
'buyer_message' => $order['user_remark'],//买家留言
'remark' => $order['order_remarks'],//卖家备注
// 'shipment' => '',//跨境买家指定物流
'is_cod' => false,//是否货到付款
'shop_modified' => $order['update_time'],//订单修改日期
'l_id' => $delivery['invoice_no'] ?? '',//快递单号
'logistics_company' => $delivery['express_name'] ?? '',//快递公司名称
// 'question_desc' => '',//订单异常描述
// 'seller_flag' => '',//卖家标签(旗帜),可选1:red,2:yellow,3:green,4:blue,5:purple
'items' => $orderGoodsData,//商品明细
'send_date' => $delivery['create_time'] ?? '',//发货日期
// 'labels' => '',//标签
'lc_id' => $expressCode,//快递公司编码
// 'currency' => '',//货币代码,跨境线下订单必填,不填写默认人民币
// 'invoice_type' => '',//发票类型:个人电子普通;单位电子普通;个人纸质普通;单位纸质普通;个人电子增值税;单位电子增值税;个人纸质增值税;单位纸质增值税
// 'invoice_title' => '',//发票抬头
// 'buyer_tax_no' => '',//发票税号
// 'node' => '',//线下备注
// 'creator' => '',//业务员id
// 'creator_name' => '',//业务员名称
];
//处理支付明细
if ($order['pay_status'] == 1) {
$uploadData['pay'] = [
'outer_pay_id' => $order['transaction_id'],//外部支付单号
'pay_date' => $order['pay_time'],//支付日期
'payment' => PayEnum::getPayDesc($order['pay_way']),//支付方式
'seller_account' => 'sellerAccount',//卖家支付账号
'buyer_account' => 'buyerAccount',//买家支付账号
'amount' => (float)$order['order_amount'],//支付总额
];
}
(new JstErpService())->uploadOrder($uploadData);
} catch (\Exception $e) {
return $e->getMessage();
}
}
/**
* @notes 同步售后订单到聚水潭
* 售后上传不会改变订单状态
*/
public static function uploadAfterSale($aftersaleSn,$isHandle = false)
{
try {
//聚水潭店铺编号
$shopid = '';
//获取售后信息
$aftersale = [];
//获取订单信息
$order = [];
//退款方式
$refundMethod = $aftersale['refund_method'] == 1 ? '仅退款' : '普通退货';
//获取erp售后商品状态
if (!empty($order['confirm_take_time'])) {
$goodStatus = 'BUYER_RECEIVED';//买家已收到货
} else {
$goodStatus = 'BUYER_NOT_RECEIVED';//买家未收到货
}
//获取erp售后状态
$shopStatus = 'wait';
switch ($aftersale['sub_status']) {
case AfterSaleEnum::SUB_STATUS_WAIT_SELLER_AGREE://买家已经申请,等待卖家同意
$shopStatus = 'WAIT_SELLER_AGREE';
break;
case AfterSaleEnum::SUB_STATUS_WAIT_BUYER_RETURN://卖家已经同意,等待买家退货
$shopStatus = 'WAIT_BUYER_RETURN_GOODS';
break;
case AfterSaleEnum::SUB_STATUS_WAIT_SELLER_RECEIPT://买家已经退货,等待卖家确认收货
case AfterSaleEnum::SUB_STATUS_WAIT_SELLER_HANDLE:
case AfterSaleEnum::SUB_STATUS_WAIT_SELLER_REFUND:
case AfterSaleEnum::SUB_STATUS_SELLER_REFUND_ING:
case AfterSaleEnum::SUB_STATUS_SELLER_REFUND_FAIL:
$shopStatus = 'WAIT_SELLER_CONFIRM_GOODS';
$goodStatus = 'BUYER_RETURNED_GOODS';
break;
case AfterSaleEnum::SUB_STATUS_SELLER_REFUSE_AFTER_SALE://卖家拒绝售后
$shopStatus = 'SELLER_REFUSE_BUYER';
break;
// case AfterSaleEnum::SUB_STATUS_WAIT_SELLER_AGREE://等待卖家发货
// $shopStatus = 'WAIT_SELLER_DELIVER_GOODS';
// break;
// case AfterSaleEnum::SUB_STATUS_WAIT_SELLER_AGREE://补发卖家发货
// $shopStatus = 'REISSUE_SELLER_DELIVERY';
// break;
case AfterSaleEnum::SUB_STATUS_BUYER_CANCEL_AFTER_SALE://售后关闭(售后单未确认前填写该状态erp的售后单自动作废)
case AfterSaleEnum::SUB_STATUS_SELLER_REFUSE_RECEIPT:
case AfterSaleEnum::SUB_STATUS_SELLER_REFUSE_REFUND:
$shopStatus = 'CLOSED';
break;
case AfterSaleEnum::SUB_STATUS_SELLER_REFUND_SUCCESS://退款成功
$shopStatus = 'SUCCESS';
break;
}
if ($aftersale['sub_status'] == AfterSaleEnum::SUB_STATUS_WAIT_SELLER_HANDLE) {
$goodStatus = 'SELLER_RECEIVED';//卖家已收到退货
}
//处理售后商品信息
$orderGoodsData = [];
foreach ($aftersale['after_sale_goods'] as $goods) {
$orderGoodsData[] = [
'outer_oi_id' => $goodsCode.'_'.$goods['goods_id'],//平台订单明细编号(开启售后单下载拦截订单,不传会整单退款,订单转取消)
'sku_id' => $goodsCode,//商家商品编码
'qty' => (int)$goods['goods_num'],//退货数量
'amount' => (float)$goods['refund_amount'],//SKU退款金额
'type' => $aftersale['refund_method'] == 1 ? '其它' : '退货',//可选:退货,换货,其它,补发
// 'name' => '',//商品名称 outer_oi_id存在,此处可以不填
// 'pic' => '',//图片地址 outer_oi_id存在,此处可以不填
// 'properties_value' => '',//属性规格 outer_oi_id存在,此处可以不填
// 'des' => '',//备注
];
}
$uploadData = [
'shop_id' => $shopid,//店铺编号
'outer_as_id' => $aftersale['sn'],//退货退款单号
'so_id' => $order['sn'],//平台订单号
'type' => $refundMethod,//售后类型,普通退货,其它,拒收退货,仅退款,投诉,补发,维修,换货
'logistics_company' => $aftersale['express_name'],//快递公司(货物状态为卖家已收到货物之后不可更新)
'l_id' => $aftersale['invoice_no'],//物流单号(货物状态为卖家已收到货物之后不可更新)
// 'receiver_name_en' => '',//收货人:仅针对换货补发类型的售后单有效
// 'receiver_mobile_en' => '',//联系手机:仅针对换货补发类型的售后单有效
'shop_status' => $shopStatus,//平台单据状态
'remark' => $aftersale['refund_remark'],//备注
'good_status' => $goodStatus,//商品状态
'question_type' => $aftersale['refund_reason'],//问题类型
'total_amount' => (float)$order['order_amount'],//原单据总金额
'refund' => (float)$aftersale['refund_total_amount'],//卖家应退金额
// 'payment' => 0,//买家应补偿金额
'warehouse_type' => 2,//仓库;主仓 = 1, 销退仓 = 2, 进货仓 = 3, 次品仓 = 4, 自定义1仓=6,自定义2仓=7, 自定义3仓=8(只能选择已启用的仓)
// 'wms_co_id' => 0,//收货仓编码
// 'receiver_state' => '',//省份
// 'receiver_city' => '',//城市
// 'receiver_district' => '',//县市
// 'receiver_address' => '',//收货地址
// 'send_lc_id' => '',//寄出快递编码
// 'send_lc_name' => '',//寄出快递名称
'items' => $orderGoodsData,//商品列表
// 'freight' => 0,//卖家应退运费
];
(new JstErpService())->uploadAfterSale($uploadData);
} catch (\Exception $e) {
return $e->getMessage();
}
}
}
2.自有商城操作逻辑(仅供参考)
- 商品同步
JstCommonLogic::uploadGoods(true);
-
库存同步
JstCommonLogic::uploadStock(true);
-
订单同步
JstCommonLogic::uploadOrder($orderSn);
-
售后同步(售后上传不会更新订单,需要调用订单上传接口更新)
//同步售后信息到聚水潭 JstCommonLogic::uploadAfterSale($afterSaleSn); //同步订单信息到聚水潭 JstCommonLogic::uploadOrder($orderSn);
四、消息推送
1、旧版消息推送文档:https://openweb.jushuitan.com/doc?docId=300
支持:库存同步、订单发货、订单取消、售后收货
2、新版消息推送文档:https://openweb.jushuitan.com/doc?docId=28
订阅消息和订阅规则的文档:https://openweb.jushuitan.com/qaCenter?groupId=31&cataId=32&postId=79
使用新版售后同步订阅消息时发现一个问题,只在订阅消息订阅售后消息推送时,不能正常触发消息推送,还得在订阅规则里创建一个店铺的售后订阅消息并且订阅后才能正常触发消息推送(不确定是否是推送延迟)。
注意:
1.新版和旧版都支持对接 ,根据业务需求选择。
2.旧版的库存同步区别在于需要维护店铺商品资料,可以在erp菜单“同步规则”中设置同步规则自定义可用数同步,并且可以指定对应的分仓进行库存同步。3.旧版的售后收货只支持售后单在“确认收到货物/取消收到货物”操作时,才会进行消息推送。
4.新版库存同步返回数据更详细。
5.新版售后同步比旧版功能强大,只要涉及(确认,反确认,确认收到货物,取消收到货物,作废,反作废)售后状态更新就会触发同步,进行消息推送。
6.库存同步有3层设置:1店铺设置 2普通商品管理 3店铺商品管理,均开启才可自动库存同步
逻辑代码(确保回调地址是外网可正常访问的)
<?php
namespace app\adminapi\controller\jst_erp;
use app\adminapi\controller\BaseAdminController;
use app\adminapi\logic\jst_erp\ConfigLogic;
class ConfigController extends BaseAdminController
{
/**
* @notes 取消订单回调地址
* 使用旧版消息推送
*/
public function cancelOrderCallbackUrl()
{
$params = $this->request->post();
$result = ConfigLogic::cancelOrderCallbackUrl($params);
if (true === $result) {
echo json_encode(['code'=>'0','msg'=>'执行成功']);
} else {
echo json_encode(['code'=>'-1','msg'=>'执行失败']);
}
}
/**
* @notes 物流同步回调地址
* 使用旧版消息推送
*/
public function orderLogisticsCallbackUrl()
{
$params = $this->request->post();
$result = ConfigLogic::orderLogisticsCallbackUrl($params);
if (true === $result) {
echo json_encode(['code'=>'0','msg'=>'执行成功']);
} else {
echo json_encode(['code'=>'-1','msg'=>'执行失败']);
}
}
/**
* @notes 库存同步回调地址
* 使用旧版消息推送
*/
public function stockCallbackUrl()
{
$params = $this->request->post();
$result = ConfigLogic::stockCallbackUrl($params);
if (true === $result) {
echo json_encode(['code'=>'0','msg'=>'执行成功']);
} else {
echo json_encode(['code'=>'-1','msg'=>'执行失败']);
}
}
/**
* @notes 售后同步回调地址
* 使用新版消息推送-售后同步
*/
public function afterSaleCallbackUrl()
{
$params = $this->request->post();
$result = ConfigLogic::afterSaleCallbackUrl($params);
if (true === $result) {
echo json_encode(['code'=>'0','msg'=>'执行成功']);
} else {
echo json_encode(['code'=>'-1','msg'=>'执行失败']);
}
}
}
<?php
namespace app\adminapi\logic\jst_erp;
use app\common\enum\AfterSaleEnum;
use app\common\enum\OrderEnum;
use app\common\logic\BaseLogic;
use app\common\logic\JstCommonLogic;
use app\common\model\AfterSale;
use app\common\model\Order;
use think\facade\Db;
use think\facade\Log;
class ConfigLogic extends BaseLogic
{
/**
* @notes 库存同步回调
*/
public static function stockCallbackUrl($params)
{
foreach ($params['datas'] as $datas) {
Db::startTrans();
try {
if (empty($datas['qty']) || empty($datas['sku_id'])) {
return throw new \Exception('参数错误');
}
//TODO 同步聚水潭商品库存到自有商城
Db::commit();
} catch (\Exception $e) {
Db::rollback();
Log::write('库存同步回调错误:'.$e->getMessage().' / 参数:'.(json_encode($params) ?? $params));
}
}
return true;
}
/**
* @notes 取消订单回调
*/
public static function cancelOrderCallbackUrl($params)
{
Db::startTrans();
try {
if (empty($params['so_id'])) {
return throw new \Exception('参数错误');
}
//获取订单数据
$order = Order::where(['sn'=>$params['so_id']])->findOrEmpty();
//TODO 取消订单操作
Db::commit();
} catch (\Exception $e) {
Db::rollback();
Log::write('取消订单回调错误:'.$e->getMessage().' / 参数:'.(json_encode($params) ?? $params));
}
return true;
}
/**
* @notes 物流同步回调地址
*/
public static function orderLogisticsCallbackUrl($params)
{
Db::startTrans();
try {
if (empty($params['so_id']) || empty($params['l_id']) || empty($params['lc_id']) || empty($params['logistics_company']) || empty($params['send_date']) || empty($params['items'])) {
return throw new \Exception('参数错误');
}
//获取订单数据
$order = Order::where(['sn'=>$params['so_id']])->with(['order_goods'])->findOrEmpty();
if (!$order->isEmpty() && $order->order_status != OrderEnum::STATUS_WAIT_DELIVERY) {
return throw new \Exception('非待发货订单');
}
//TODO 订单发货操作
Db::commit();
} catch (\Exception $e) {
Db::rollback();
Log::write('物流同步回调错误:'.$e->getMessage().' / 参数:'.(json_encode($params) ?? $params));
}
return true;
}
/**
* @notes 售后同步回调
*/
public static function afterSaleCallbackUrl($params)
{
Db::startTrans();
try {
if (empty($params['action_code']) || $params['action_code'] != 'trade_after_sale' || empty($params['biz'])) {
return throw new \Exception('参数错误');
}
$paramsData = json_decode($params['biz'], true);
$afterSale = AfterSale::where(['sn'=>$paramsData['outerAsId']])->findOrEmpty();
if($afterSale->isEmpty()) {
throw new \think\Exception('售后订单不存在');
}
if($afterSale->status != AfterSaleEnum::STATUS_ING) {
throw new \think\Exception('不在售后中状态,不能进行售后操作');
}
//操作动作(识别操作节点):确认 = 1, 反确认 = 2, 确认收到货物 = 3, 取消收到货物 = 4, 作废 = 5, 反作废 = 6
switch ($paramsData['action'] ?? 0) {
case 1://商家确认退款成功
//TODO 售后同步操作
//同步订单信息到聚水潭
JstCommonLogic::uploadOrder($orderSn);
break;
case 3://商家确认收到货物
//TODO 售后同步操作
break;
case 4://商家取消收到货物
//TODO 售后同步操作
break;
case 5://退款关闭
//TODO 售后同步操作
break;
}
//同步售后信息到聚水潭
JstCommonLogic::uploadAfterSale($afterSale->sn);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
Log::write('售后同步回调错误:'.$e->getMessage().' / 参数:'.(json_encode($params) ?? $params));
}
return true;
}
}
五、测试
- 测试环境说明文档(可以直接看场景二的已有正式环境应用说明):https://openweb.jushuitan.com/doc?docId=110#cc6cb98d9a0e4a3685ebb849d7e6fa34
- 根据说明文档打开聚水潭erp测试开放平台,并APP Key和APP Secret、店铺编号替换为测试环境应用对应参数。申请对应需要的api。
- 登录聚水潭测试后台,根据是否精细化库存管理选择企业版或专业版后台
ERP企业版(有精细化库存管理) 登录地址:https://dev-c.erp321.cn 帐号: kfcs@jst.com 密码: !kfptqy0414@ 企业名称:开发测试 ERP专业版(无精细化库存管理) 登录地址:https://dev-c.erp321.cn 帐号: kfcszy@jst.com 密码: !kfptzy0414@ 企业名称:开发测试专业版
- 在测试后台查询数据是否同步正常。
六、注意事项
1.先同步商品资料再同步库存。
2.如果使用ERP的库存推送(inventory.upload),需要同时维护普通商品资料和店铺商品资料。上面的逻辑是默认使用ERP库存推送的,已同步处理普通商品和店铺商品资料。
3.测试环境上的消息推送均不可实现,请在正式环境测试消息推送模块。
4.订单发货前,重复调用订单上传接口可以更新订单信息,发货后不可更新订单,并且没有完成状态。
5.售后单上传不会改变订单状态,需要调用订单上传接口更新。
6.调用商品上传接口需要注意使用的商品类目,必须是[商品类目管理]中的叶子节点,即最小一级分类,不允许多选。