LikeShop 第三方插件兼容二开:对接第三方 ERP、财务系统与开票能力

一、LikeShop 扩展机制与钩子原理

1.1 四种扩展机制

LikeShop 支持二次开发,提供插件 API 和钩子机制,方便开发者在不修改核心代码的情况下扩展功能;所有营销玩法、经营应用都能采用插件化、模块化的开发思路,做到业务轻耦合、代码轻耦合;理解 LikeShop 的扩展机制,是对接第三方系统的前提,LikeShop 提供四种扩展机制:

机制一:事件驱动(Event)。LikeShop 内置“无缝事件机制行为扩展”,核心业务节点(订单创建、支付成功、订单完成、售后退款等)均预留了事件触发点;二开代码通过注册事件监听器接入,不与核心逻辑耦合;这是对接第三方系统最主要的方式。

机制二:Service 层扩展。LikeShop 的二开所有业务逻辑都应该写在 Service / Logic 层,但要注意区分“核心 Logic”和“扩展 Logic”;OrderLogic、PayLogic、UserLogic 等核心文件应被视为“只读”的,可以调用它们的方法,但不要修改它们的方法体;对接第三方 ERP 的逻辑应封装在独立的 Service 中,由事件监听器调用。

机制三:数据表扩展。通过新增字段或关联表来存储第三方系统的映射关系(如 ERP 单据号、发票号、财务凭证号),无需修改原生表的核心字段。

机制四:定时任务(Crontab)。LikeShop 的定时任务入口通过 php think crontab 命令调用,所有子任务共用一条服务器 Crontab;定时任务适合处理异步同步场景(如订单批量同步到 ERP、财务流水批量推送)。

1.2 第三方对接的整体架构

对接第三方系统的整体架构尊重“事件驱动 + 适配器模式”:

php
// 事件监听器注册(app/event.php)
return [
   'bind' => [
       'OrderPaySuccess'   => [
           'app\common\listener\ErpOrderSyncListener',    // 同步订单到ERP
           'app\common\listener\FinanceVoucherListener',  // 生成财务凭证
          'app\common\listener\InvoiceApplyListener',    // 触发自动开票
       ],
       'OrderComplete'     => [
          'app\common\listener\ErpOrderCompleteListener',
       ],
       'AfterSaleRefund'   => [
          'app\common\listener\ErpRefundSyncListener',   // 同步退款到ERP
          'app\common\listener\InvoiceRedListener',      // 触发发票冲红
       ],
    ],
];

这种架构下,新增第三方系统对接时只需注册新的事件监听器,不改动任何核心逻辑,保证系统可升级性。

二、数据库扩展设计

2.1 第三方系统配置表

php
CREATE TABLE `ls_third_party_config` (
 `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
 `system_type` varchar(20) NOT NULL COMMENT '系统类型:erp/finance/invoice',
 `provider` varchar(30) NOT NULL COMMENT '服务商:kingdee/yonyou/wechat_invoice/piaozone',
 `config_name` varchar(50) NOT NULL DEFAULT '' COMMENT '配置名称',
 `config_data` text COMMENT '配置数据,JSON格式(含API地址、密钥、账套等)',
 `is_enable` tinyint NOT NULL DEFAULT 1 COMMENT '是否启用',
 `last_sync_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '上次同步时间',
 `create_time` int UNSIGNED NOT NULL DEFAULT 0,
 `update_time` int UNSIGNED NOT NULL DEFAULT 0,
 PRIMARY KEY (`id`),
 KEY `idx_type_provider` (`system_type`, `provider`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方系统对接配置表';

config_data 字段存储各系统的差异化配置;以金蝶云星空为例,需要存储数据中心ID(账套ID)、应用ID、授权用户、请求地址前缀、应用秘钥等参数;以用友优普为例,需要存储 from_account、to_account、app_key、token 等参数。

2.2 同步日志表

sql
CREATE TABLE `ls_third_party_sync_log` (
  `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
  `system_type` varchar(20) NOT NULL COMMENT '系统类型',
  `provider` varchar(30) NOT NULL COMMENT '服务商',
  `sync_type` varchar(30) NOT NULL COMMENT '同步类型:order_create/order_complete/refund/voucher/invoice',
  `business_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '业务ID(订单ID/退款ID等)',
  `business_sn` varchar(32) NOT NULL DEFAULT '' COMMENT '业务单号',
  `third_party_sn` varchar(64) NOT NULL DEFAULT '' COMMENT '第三方系统返回的单据号/发票号',
  `request_data` text COMMENT '请求数据快照',
  `response_data` text COMMENT '响应数据快照',
  `status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0-待同步,1-同步成功,2-同步失败',
  `retry_count` tinyint NOT NULL DEFAULT 0 COMMENT '重试次数',
  `error_msg` varchar(500) NOT NULL DEFAULT '' COMMENT '错误信息',
  `create_time` int UNSIGNED NOT NULL DEFAULT 0,
  `update_time` int UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  KEY `idx_business` (`business_id`, `sync_type`),
  KEY `idx_status` (`status`, `system_type`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='第三方系统同步日志表';

2.3 订单映射表

sql
CREATE TABLE `ls_order_third_mapping` (
   `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
   `order_id` int UNSIGNED NOT NULL COMMENT 'LikeShop订单ID',
   `order_sn` varchar(32) NOT NULL COMMENT '订单号',
   `erp_order_sn` varchar(64) NOT NULL DEFAULT '' COMMENT 'ERP侧单据号',
   `finance_voucher_sn` varchar(64) NOT NULL DEFAULT '' COMMENT '财务凭证号',
   `invoice_sn` varchar(64) NOT NULL DEFAULT '' COMMENT '发票号',
   `invoice_url` varchar(500) NOT NULL DEFAULT '' COMMENT '发票PDF下载地址',
   `invoice_status` tinyint NOT NULL DEFAULT 0 COMMENT '开票状态:0-未申请,1-开票中,2-已开票,3-已冲红',
   `create_time` int UNSIGNED NOT NULL DEFAULT 0,
   `update_time` int UNSIGNED NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_order_id` (`order_id`),
  KEY `idx_invoice_status` (`invoice_status`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单第三方系统映射表';
三、订单同步至 ERP 实现

3.1 事件监听器

订单支付成功后,通过事件监听器触发 ERP 同步,同步逻辑异步执行,不阻塞支付流程:

 php
 namespace app\common\listener;

 use app\common\model\ThirdPartyConfigModel;
 use app\common\model\ThirdPartySyncLogModel;
 use app\common\service\ErpAdapterFactory;
 use think\facade\Queue;

 class ErpOrderSyncListener
 {
     public function handle($orderId)
     {
        // 检查 ERP 同步是否启用
        $config = ThirdPartyConfigModel::where('system_type', 'erp')
           ->where('is_enable', 1)
           ->find();
        if (!$config) return;

        // 推入队列异步执行,不阻塞支付回调
        Queue::push(ErpSyncJob::class, [
             'order_id'  => $orderId,
             'sync_type' => 'order_create',
        ], 'erp_sync');
      }
   }

3.2 适配器工厂

不同 ERP 系统的接口规范差异巨大,需要适配器模式统一封装:

php
namespace app\common\service;

interface ErpAdapterInterface
{
       /**
       * 创建销售订单
       */
       public function createOrder($orderId): array;

       /**
       * 同步订单发货状态
       */
       public function syncOrderDelivery($orderId, $expressName, $expressNo): array;

      /**
      * 同步退款单
      */
      public function syncRefund($orderId, $refundAmount): array;

      /**
      * 查询订单状态(用于反向同步)
      */
      public function queryOrderStatus($erpOrderSn): array;
    }
class ErpAdapterFactory
{
public static function create($provider)
{
    switch ($provider) {
        case 'kingdee':
            return new KingdeeErpAdapter();
        case 'yonyou':
            return new YonyouErpAdapter();
        default:
            throw new \Exception("不支持的ERP服务商:{$provider}");
    }
  }
}

3.3 金蝶云星空适配器

金蝶云星空提供 Web API 接口,支持生成销售出库单、销售订单和发货通知单等单据;对接前需完成三方授权配置,获取数据中心ID、应用ID、授权用户和请求地址前缀。

php
class KingdeeErpAdapter implements ErpAdapterInterface
{
private $config;
private $apiUrl;
private $headers;

public function __construct()
{
    $this->config = ThirdPartyConfigModel::where('system_type', 'erp')
        ->where('provider', 'kingdee')
        ->where('is_enable', 1)
        ->value('config_data');
    $this->config = json_decode($this->config, true);
    $this->apiUrl = rtrim($this->config['server_url'], '/') . '/k3cloud/';
}

/**
 * 创建销售订单
 */
public function createOrder($orderId): array
{
    $order = OrderModel::with(['order_goods', 'user'])->find($orderId);
    if (!$order) {
        throw new \Exception('订单不存在');
    }

    // 构建金蝶销售订单数据结构
    $billData = [
        'FBillTypeID'  => ['FNumber' => 'XSDD01_SYS'], // 单据类型
        'FDate'        => date('Y-m-d', $order['create_time']),
        'FSaleOrgId'   => ['FNumber' => $this->config['sale_org']],
        'FCustId'      => $this->mapCustomer($order['user']),
        'FMaterialId'  => $this->mapMaterial($order['order_goods']),
        'FQty'         => $order['order_goods'][0]['goods_num'],
        'FTaxPrice'    => $order['order_amount'],
        'FNote'        => 'LikeShop订单:' . $order['order_sn'],
    ];

    // 调用金蝶保存接口
    $result = $this->callApi('Kingdee.BOS.WebApi.ServicesStub.
        DynamicFormService.Save.common.kdsvc', [
        'formid'  => 'SAL_SaleOrder',
        'data'    => ['Model' => $billData],
    ]);

    if (!$result['success']) {
        throw new \Exception('金蝶销售订单创建失败:' . ($result['message'] ?? ''));
    }

    // 记录映射关系
    OrderThirdMappingModel::updateOrCreate(
        ['order_id' => $orderId],
        ['erp_order_sn' => $result['bill_no'], 'update_time' => time()]
    );

    return [
        'erp_order_sn' => $result['bill_no'],
        'status'       => 'success',
    ];
}

/**
 * 调用金蝶 WebAPI
 */
private function callApi($servicePath, $params)
{
    $url = $this->apiUrl . $servicePath;
    $requestData = [
        'parameters' => [json_encode($params)],
        'format'     => 1,
    ];

    $response = HttpClient::post($url, json_encode($requestData), [
        'Content-Type: application/json',
        'Authorization: Bearer ' . $this->getAccessToken(),
    ]);

    return json_decode($response, true);
}

private function getAccessToken()
{
    // 使用应用密钥获取 AccessToken,缓存至有效期结束
    $cacheKey = 'kingdee_access_token';
    $token = Cache::get($cacheKey);
    if ($token) return $token;

    $result = HttpClient::post($this->apiUrl . 'Kingdee.BOS.WebApi.
        ServicesStub.AuthService.ValidateUser.common.kdsvc', [
        'acctID'   => $this->config['acct_id'],
        'username' => $this->config['username'],
        'password' => $this->config['password'],
        'lcid'     => 2052,
    ]);

    $data = json_decode($result, true);
    $token = $data['Context']['UserToken'];
    Cache::set($cacheKey, $token, 3600);
    return $token;
  }
}

3.4 用友 U8 适配器

用友优普开放平台提供电商订单列表查询接口 eb_tradelist/batch_get,支持 U8V12.0 及更高版本。该接口通过 app_key 和 token 鉴权,支持按店铺编码、交易时间范围等条件查询订单。

php
class YonyouErpAdapter implements ErpAdapterInterface
{
private $config;

public function __construct()
{
    $this->config = json_decode(
        ThirdPartyConfigModel::where('system_type', 'erp')
            ->where('provider', 'yonyou')
            ->value('config_data'), true
    );
}

public function createOrder($orderId): array
{
    $order = OrderModel::with(['order_goods', 'user'])->find($orderId);

    // 构建用友电商订单数据结构
    $tradeData = [
        'cshopcode'    => $this->config['shop_code'],
        'tid'          => $order['order_sn'],
        'buyer_nick'   => $order['user']['nickname'] ?? '',
        'receiver_name'=> $order['consignee'],
        'receiver_mobile' => $order['mobile'],
        'receiver_state'  => $order['province'],
        'receiver_city'   => $order['city'],
        'receiver_district' => $order['district'],
        'receiver_address'  => $order['address'],
        'pay_time_begin'    => date('Y-m-d H:i:s', $order['pay_time']),
        'trade_memo'        => 'LikeShop订单',
        'goods_list'        => $this->buildGoodsList($order['order_goods']),
    ];

    // 调用用友 API
    $result = $this->callApi('eb_tradelist/add', $tradeData);

    if ($result['errcode'] != '0') {
        throw new \Exception('用友订单创建失败:' . $result['errmsg']);
    }

    OrderThirdMappingModel::updateOrCreate(
        ['order_id' => $orderId],
        ['erp_order_sn' => $result['tid'], 'update_time' => time()]
    );

    return ['erp_order_sn' => $result['tid'], 'status' => 'success'];
}

private function callApi($method, $data)
{
    $params = array_merge([
        'from_account' => $this->config['from_account'],
        'to_account'   => $this->config['to_account'],
        'app_key'      => $this->config['app_key'],
        'token'        => $this->getToken(),
    ], $data);

    $url = 'https://api.yonyouup.com/api/' . $method
        . '?' . http_build_query($params);

    return json_decode(HttpClient::get($url), true);
}

private function getToken()
{
    $cacheKey = 'yonyou_token';
    $token = Cache::get($cacheKey);
    if ($token) return $token;

    $result = HttpClient::get('https://api.yonyouup.com/api/token/get', [
        'from_account' => $this->config['from_account'],
        'app_key'      => $this->config['app_key'],
        'app_secret'   => $this->config['app_secret'],
    ]);
    $data = json_decode($result, true);
    $token = $data['token'];
    Cache::set($cacheKey, $token, $data['expires_in'] ?? 7200);
    return $token;
  }
}

3.5 同步失败的补偿机制

第三方系统不可用时,同步操作需要支持自动重试;在 ls_third_party_sync_log 中记录同步状态,通过定时任务扫描失败记录进行补偿:

php
class ThirdPartySyncRetryCrontab
{
public function retryFailedSyncs()
{
    $failedLogs = ThirdPartySyncLogModel::where('status', 2)
        ->where('retry_count', '<', 5)
        ->where('update_time', '<=', time() - 300) // 5分钟前失败
        ->limit(20)
        ->select();

    foreach ($failedLogs as $log) {
        try {
            $adapter = ErpAdapterFactory::create(
                ThirdPartyConfigModel::where('system_type', $log['system_type'])
                    ->value('provider')
            );

            switch ($log['sync_type']) {
                case 'order_create':
                    $adapter->createOrder($log['business_id']);
                    break;
                case 'order_delivery':
                    $adapter->syncOrderDelivery($log['business_id'],
                        $log['request_data']['express_name'] ?? '',
                        $log['request_data']['express_no'] ?? '');
                    break;
            }

            ThirdPartySyncLogModel::where('id', $log['id'])->update([
                'status'      => 1,
                'update_time' => time(),
            ]);
        } catch (\Exception $e) {
            ThirdPartySyncLogModel::where('id', $log['id'])->update([
                'retry_count' => $log['retry_count'] + 1,
                'error_msg'   => mb_substr($e->getMessage(), 0, 500),
                'update_time' => time(),
            ]);
        }
     }
  }
}
四、财务流水对接金蝶/用友

4.1 业务数据到财务凭证的映射

金蝶用友和电商系统打通的核心,是让电商业务数据(订单、成本、平台费用)自动生成财务系统能用的凭证;对接不是目的,解决业务问题是目的——要先想清楚是为了自动生成凭证、业财一体核算利润,还是平台对账目自动化。

财务对接的核心是建立“电商业务数据 → 财务凭证”的映射规则;需要先完成三张档案对照表:商品/科目对照表、客户/往来单位对照表、店铺/核算项目对照表;凭证生成的核心映射关系为:

电商业务财务凭证分录金蝶科目用友科目
订单确认收入借:应收账款11221122
贷:主营业务收入60016001
贷:应交税费-销项税22212221
平台佣金借:销售费用-平台佣金66016601
贷:应收账款11221122
退款借:主营业务收入(红字)60016001
贷:应收账款(红字)11221122

对接前必须先想清楚三件事:第一,对接目的是什么;第二,数据流向怎么走;第三,凭证规则怎么定。

4.2 凭证生成服务

php
class FinanceVoucherService
{
/**
 * 订单支付成功后生成财务凭证
 */
public function generateVoucher($orderId)
{
    $order = OrderModel::find($orderId);
    if (!$order) return;

    // 幂等校验:检查是否已生成凭证
    $exists = OrderThirdMappingModel::where('order_id', $orderId)
        ->where('finance_voucher_sn', '<>', '')
        ->count();
    if ($exists > 0) return;

    // 1. 构建收入凭证
    $voucherData = [
        'voucher_date' => date('Y-m-d', $order['pay_time']),
        'voucher_type' => '记',
        'entries'      => [
            [
                'abstract'   => '销售商品收入',
                'account_no' => '1122', // 应收账款
                'debit'      => $order['order_amount'],
                'credit'     => 0,
            ],
            [
                'abstract'   => '销售商品收入',
                'account_no' => '6001', // 主营业务收入
                'debit'      => 0,
                'credit'     => round($order['order_amount'] / 1.13, 2),
            ],
            [
                'abstract'   => '销项税额',
                'account_no' => '2221',
                'debit'      => 0,
                'credit'     => round($order['order_amount'] - $order['order_amount'] / 1.13, 2),
            ],
        ],
    ];

    // 2. 根据配置的财务系统推送凭证
    $config = ThirdPartyConfigModel::where('system_type', 'finance')
        ->where('is_enable', 1)
        ->find();
    if (!$config) return;

    $adapter = FinanceAdapterFactory::create($config['provider']);
    $result = $adapter->pushVoucher($voucherData);

    // 3. 记录凭证映射
    OrderThirdMappingModel::updateOrCreate(
        ['order_id' => $orderId],
        ['finance_voucher_sn' => $result['voucher_sn'], 'update_time' => time()]
    );
  }
}

4.3 对接方式的选择

金蝶用友和电商系统的对接,主要有开放 API 接口对接和中间表/文件对接两种方式;开放 API 接口对接实时性好、自动化程度高,但开发工作量较大,适合单量大的企业;中间表和文件对接实时性差但实现简单,适合单量不大或过渡阶段的企业。

对于 LikeShop 这种电商系统,建议采用 API 接口对接为主、中间表兜底的混合方案:

  1. 实时场景(订单确认收入、退款):通过 API 实时推送凭证
  2. 批量场景(月度成本归集、平台费用分摊):通过定时任务批量生成凭证后通过中间表同步
  3. 降级方案:当 API 不可用时,自动切换为中间表模式,将凭证数据写入中间表,由财务系统定时拉取
五、订单自动申请电子发票

5.1 微信支付电子发票对接

微信支付提供完整的电子发票 API 体系,支持用户通过支付账单页申请开票,商户匹配订单信息后调用接口为用户开具电子发票并插入微信卡包。

对接流程分为六个步骤:

第一步:配置开发选型。调用 《配置开发选项》接口设置回调地址、支付开票入口标识等,用于接收获取用户抬头等信息通知。

第二步:创建电子发票卡劵模版。调用 《创建电子发票卡券模版》接口创建电子发票的卡劵模版。

第三步:查询开票基础信息。调用 《获取商品和服务税收分类对照表》查询设置的开票商品税率信息,调用 《获取商户开票基础信息》查询商户配置的电子发票开票基础信息。

第四步:用户申请开票。用户在支付账单页填写发票抬头并提交申请,或通过 《获取抬头填写链接》生成开票二维码在订单小程序展示。

第五步:接收用户抬头并开票。用户填写抬头后,商户会收到 《用户发票抬头填写完成通知》,通过 《获取用户填写的抬头》获取信息后,匹配相关订单信息,调用 《开具电子发票》为用户开具电子发票。

第六步:售后退款冲红。如商户需对已开具的电子发票进行冲红,可调用 《冲红电子发票》进行冲红,并将其从用户的微信卡包中删除。

5.2 电子发票服务核心实现

php
class InvoiceService
{
/**
 * 处理用户发票抬头填写完成通知
 */
public function handleTitleSubmit($notifyData)
{
    $outTradeNo = $notifyData['out_trade_no'];
    $order = OrderModel::where('order_sn', $outTradeNo)->find();
    if (!$order) return;

    // 获取用户填写的抬头
    $titleInfo = WxInvoiceApiService::getUserTitle($outTradeNo);

    // 保存抬头信息
    OrderInvoiceTitleModel::updateOrCreate(
        ['order_id' => $order['id']],
        [
            'title_type'   => $titleInfo['title_type'], // 1-个人,2-企业
            'title'        => $titleInfo['title'],
            'tax_no'       => $titleInfo['tax_no'] ?? '',
            'bank_name'    => $titleInfo['bank_name'] ?? '',
            'bank_account' => $titleInfo['bank_account'] ?? '',
            'address'      => $titleInfo['address'] ?? '',
            'phone'        => $titleInfo['phone'] ?? '',
            'email'        => $titleInfo['email'] ?? '',
            'update_time'  => time(),
        ]
    );

    // 判断是否自动开票(根据后台配置)
    if (ConfigModel::get('invoice_auto_apply', 1)) {
        Queue::push(InvoiceApplyJob::class, ['order_id' => $order['id']]);
    }
}

/**
 * 执行开票
 */
public function applyInvoice($orderId)
{
    $order = OrderModel::with(['order_goods', 'invoice_title'])->find($orderId);
    if (!$order) throw new \Exception('订单不存在');

    // 幂等校验
    $mapping = OrderThirdMappingModel::where('order_id', $orderId)->find();
    if ($mapping && $mapping['invoice_status'] >= 2) {
        return ['status' => 'already_invoiced'];
    }

    // 构建开票请求
    $invoiceData = [
        'out_trade_no'   => $order['order_sn'],
        'total_amount'   => (int)($order['order_amount'] * 100), // 单位:分
        'title_type'     => $order['invoice_title']['title_type'],
        'title'          => $order['invoice_title']['title'],
        'tax_no'         => $order['invoice_title']['tax_no'],
        'items'          => $this->buildInvoiceItems($order['order_goods']),
    ];

    // 调用微信开票接口
    $result = WxInvoiceApiService::applyInvoice($invoiceData);

    if ($result['code'] != 'SUCCESS') {
        throw new \Exception('开票失败:' . ($result['message'] ?? ''));
    }

    // 更新映射状态
    OrderThirdMappingModel::updateOrCreate(
        ['order_id' => $orderId],
        [
            'invoice_sn'     => $result['invoice_no'] ?? '',
            'invoice_url'    => $result['download_url'] ?? '',
            'invoice_status' => 2, // 已开票
            'update_time'    => time(),
        ]
    );

    return ['status' => 'success', 'invoice_no' => $result['invoice_no']];
}

/**
 * 退款时触发发票冲红
 */
public function redInvoice($orderId)
{
    $mapping = OrderThirdMappingModel::where('order_id', $orderId)->find();
    if (!$mapping || $mapping['invoice_status'] != 2) return;

    $order = OrderModel::find($orderId);

    // 调用微信冲红接口
    $result = WxInvoiceApiService::redInvoice([
        'out_trade_no' => $order['order_sn'],
        'invoice_no'   => $mapping['invoice_sn'],
        'reason'       => '订单退款',
    ]);

    OrderThirdMappingModel::where('order_id', $orderId)->update([
        'invoice_status' => 3, // 已冲红
        'update_time'    => time(),
      ]);
   }
}

5.3 对接第三方开票服务商

除了微信支付电子发票,还可以对接第三方开票服务商(航天信息/百望股份);航天信息金税盘或百望税控盘设备均可通过统一接口对接;电子发票系统需对接税务局(全电发票/数电发票平台)或第三方服务商(航信/百望),通过全电发票平台 API 直接开具,或由第三方服务商提供开票 SDK 封装税务局接口。

服务商选择的具体方案:航天信息可直接使用航信电子发票的账号和密码登录授权即可完成绑定;百望股份则向服务商索取或由服务商帮忙填写百望云账号及密码即可完成绑定。

php
class PiaozoneInvoiceAdapter
{
private $config;

public function __construct()
{
    $this->config = json_decode(
        ThirdPartyConfigModel::where('system_type', 'invoice')
            ->where('provider', 'piaozone')
            ->value('config_data'), true
    );
}

/**
 * 通过票通接口开具发票
 */
public function issueInvoice($invoiceData)
{
    // 票通接口地址统一为 WSS 推送模式
    $url = 'https://api.piaozone.com/bill-websocket/invoicewebsocket/push'
        . '?name=' . $this->config['client_secret'] . $this->config['pan_no'];

    $requestBody = [
        'request_path' => '/invoice/issue',
        'client_secret'=> $this->config['client_secret'],
        'pan_no'       => $this->config['pan_no'],
        'data'         => $invoiceData,
    ];

    $response = HttpClient::post($url, json_encode($requestBody));

    return json_decode($response, true);
   }
} 
六、完整开发步骤总结

6.1 数据库变更清单

操作表名说明
CREATEls_third_party_config第三方系统对接配置表
CREATEls_third_party_sync_log同步日志表
CREATEls_order_third_mapping订单第三方系统映射表
CREATEls_order_invoice_title发票抬头信息表

6.2 后端开发清单

模块文件/类工作内容
数据层ThirdPartyConfigModel配置表 Model
数据层ThirdPartySyncLogModel同步日志 Model
数据层OrderThirdMappingModel订单映射 Model
适配器ErpAdapterInterfaceERP 适配器接口
适配器KingdeeErpAdapter金蝶云星空适配器
适配器YonyouErpAdapter用友 U8 适配器
适配器FinanceAdapterFactory财务系统适配器工厂
适配器PiaozoneInvoiceAdapter票通开票适配器
业务层FinanceVoucherService凭证生成服务
业务层InvoiceService电子发票服务
事件ErpOrderSyncListener订单同步 ERP 监听器
事件FinanceVoucherListener财务凭证生成监听器
事件InvoiceApplyListener自动开票监听器
命令ErpSyncJobERP 同步队列任务
命令ThirdPartySyncRetryCrontab失败重试定时任务

6.3 后台前端开发清单

页面工作内容
系统设置 → 第三方对接 → ERP 配置金蝶/用友的连接参数配置、连接测试
系统设置 → 第三方对接 → 财务配置科目映射规则配置、凭证推送方式选择
系统设置 → 第三方对接 → 开票配置微信/航信/百望的开票参数配置、自动开票开关
系统设置 → 第三方对接 → 同步日志同步记录列表、失败重试、错误详情查看
订单管理 → 订单详情增加 ERP 单据号、财务凭证号、发票状态展示

6.4 开发注意事项

稳定内核原则:OrderLogic、PayLogic、UserLogic 等核心文件应被视为“只读”的,所有扩展逻辑通过事件监听器和独立的 Service 实现,保证系统可升级性;对接第三方系统的代码必须封装在独立的 Service 和适配器中,不修改核心逻辑。

幂等与重试:第三方系统对接的最大风险是重复同步;每次同步操作前必须做幂等校验(查询 ls_third_party_sync_log 是否已有成功记录),失败后通过定时任务自动重试,重试次数上限为 5 次,超过上限后标记为“需人工处理”。

凭证口径统一:财务对接的口径要统一,比如电商系统的“订单收入”对应财务系统的哪个科目,“平台佣金”记到哪个费用科目;口径不一致,凭证就是错的。

发票合规:电子发票的开具、冲红均需符合税务规范;开票请求中的金额单位(微信为“分”)、税率、商品税收分类编码必须准确;退款时必须同步冲红发票,避免税务风险。

配置隔离:第三方系统的密钥、Token 等敏感信息建立加密存储,不在日志中明文输出;多商户场景下,每个商户的 ERP/财务配置需要按 shop_id 隔离。

0
0
0
0
评论
未登录
暂无评论