mirror of
https://gitee.com/ShopeX/OMS
synced 2026-03-23 10:55:34 +08:00
420 lines
22 KiB
PHP
420 lines
22 KiB
PHP
<?php
|
||
/**
|
||
* Copyright 2012-2026 ShopeX (https://www.shopex.cn)
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
|
||
/**
|
||
* 电子发票类
|
||
* @author wangjianjun<wangjianjun@shopex.cn>
|
||
* 20160627
|
||
* @version 0.1
|
||
*/
|
||
class invoice_electronic{
|
||
|
||
//组电子发票获取url的参数
|
||
function getEinvoiceGetUrlRequestParams($sdf){
|
||
//获取platform node_id
|
||
$shop_info = kernel::single('ome_shop')->getRowByShopId($sdf['shop_id']);
|
||
$einvoice_shop_type = kernel::single('invoice_common')->returnEinvoiceShopType($shop_info);
|
||
$platform = kernel::single('invoice_common')->getPlatformByShopType($einvoice_shop_type);
|
||
|
||
//根据红/蓝票的参数 电子发票开票信息明细表里获取invoice_no
|
||
$billing_type = 1; //默认蓝票
|
||
if($sdf["einvoice_type"] == "red"){
|
||
$billing_type = 2;
|
||
}
|
||
$mdlInOrderElIt = app::get('invoice')->model('order_electronic_items');
|
||
$rs_invoice_item = $mdlInOrderElIt->dump(array("id"=>$sdf["id"],"billing_type"=>$billing_type));
|
||
|
||
$params = array(
|
||
'node_id' => $shop_info["node_id"],
|
||
'platform' => $platform,
|
||
'invoice_no' => $rs_invoice_item["invoice_no"],
|
||
'tid' => $sdf["order_bn"],
|
||
'order_bn' => $sdf["order_bn"],
|
||
'expire' => $sdf["expire_time"],
|
||
);
|
||
|
||
return $params;
|
||
}
|
||
|
||
//组电子发票回流天猫的参数
|
||
function getEinvoiceUploadTmallRequestParams($sdf){
|
||
//获取invoice_item
|
||
$invoice_items = $this->getEinvoiceInvoiceItems($sdf,$sdf["einvoice_type"]);
|
||
|
||
$mdlInOrderElIt = app::get('invoice')->model('order_electronic_items');
|
||
if($sdf["einvoice_type"] == "blue"){
|
||
//蓝票
|
||
$invoice_type = "1";
|
||
$invoice_amount = $sdf["amount"]; //开票金额(价税合计)
|
||
//获取当前蓝票发票代码、发票号码
|
||
$rs_el_item = $mdlInOrderElIt->dump(array("id"=>$sdf["id"],"billing_type"=>"1"));
|
||
$invoice_code = $rs_el_item["invoice_code"];
|
||
$invoice_no = $rs_el_item["invoice_no"];
|
||
}
|
||
if($sdf["einvoice_type"] == "red"){
|
||
//冲红成功
|
||
$invoice_type = "2";
|
||
$invoice_amount = -$sdf["amount"]; //开票金额(价税合计)
|
||
//获取当前红票和原蓝票的发票代码、发票号码
|
||
$rs_el_items = $mdlInOrderElIt->getList("*",array("id"=>$sdf["id"]));
|
||
foreach ($rs_el_items as $var_item){
|
||
if(intval($var_item["billing_type"]) == 1){
|
||
//原蓝票
|
||
$arr_old_blue_info = array(
|
||
"normal_invoice_code" => $var_item["invoice_code"], //回流红票时,对应的原蓝票发票代码
|
||
"normal_invoice_no" => $var_item["invoice_no"], //回流红票时,对应的原蓝票发票号码
|
||
);
|
||
}
|
||
if(intval($var_item["billing_type"]) == 2){
|
||
//当前红票
|
||
$invoice_code = $var_item["invoice_code"];
|
||
$invoice_no = $var_item["invoice_no"];
|
||
}
|
||
}
|
||
}
|
||
|
||
//获取invoice_file_data 和 electronic_invoice_no
|
||
$einvoice_url = $this->getApiEinvoiceUrl($sdf, $sdf["einvoice_type"]);
|
||
$invoice_file_data = file_get_contents($einvoice_url);
|
||
$electronic_invoice_no = $invoice_code.$invoice_no;
|
||
|
||
$params = array(
|
||
"invoice_code" => $invoice_code, //发票代码
|
||
"invoice_no" => $invoice_no, //发票号码
|
||
"invoice_file_data" => base64_encode($invoice_file_data), //发票文件内容,目前只支持jpg,png,bmp,pdf格式
|
||
"invoice_type" => $invoice_type, //1 蓝票 2 红票
|
||
"invoice_items" => json_encode($invoice_items), //电子发票明细
|
||
"electronic_invoice_no" => $electronic_invoice_no, //电子发票号(一般是:发票代码+发票号码)
|
||
"serial_no" => $sdf["serial_no"], //开票流水号,唯一标志开票请求。如果两次请求流水号相同,则表示重复请求, 由于ERP根据自己的业务请求确定。可采用订单id+操作代码,比如:订单号转成十六进制 + 操作代码(表示红票还是蓝票)+ 操作序号
|
||
"payee_register_no" => $sdf["tax_no"], //收款方税务登记号
|
||
"invoice_amount" => number_format($invoice_amount,2,".",""), //开票金额(价税合计金额),格式:100.00, 冲红时格式为"-100.00"
|
||
"invoice_time" => date("Y-m-d H:i:s",$sdf["dateline"]), //开票日期, 格式"YYYY-MM-DD HH:SS:MM"
|
||
"payee_name" => $sdf["payee_name"], //开票方名称,xx商城
|
||
"tid" => $sdf["order_bn"], //淘宝的主订单id
|
||
"invoice_title" => $sdf["title"]?$sdf["title"]:$sdf["tax_company"], //发票抬头,付款方名称
|
||
'image_type' => 'pdf',
|
||
//可选
|
||
"qr_code" => "", //二维码,二维码扫码的结果。
|
||
"anti_fake_code" => "", //防伪码
|
||
);
|
||
|
||
if($arr_old_blue_info){
|
||
$params = array_merge($params,$arr_old_blue_info);
|
||
}
|
||
|
||
return $params;
|
||
}
|
||
|
||
//打电子发票接口获取电子发票url
|
||
function getApiEinvoiceUrl($sdf,$type){
|
||
$einvoice_url_cache_name = $type.$sdf["id"].$sdf["sync"];
|
||
$einvoice_url = cachecore::fetch($einvoice_url_cache_name);
|
||
if($einvoice_url){
|
||
return $einvoice_url;
|
||
}
|
||
$expire_time = 3600;
|
||
$sdf["expire_time"] = $expire_time; //过期时间
|
||
$sdf["einvoice_type"] = $type; //看电子发票(蓝票blue或红票red)
|
||
$einvoice_return_rs = kernel::single('invoice_event_trigger_einvoice')->getInvoiceUrl($sdf['shop_id'],$sdf);
|
||
if($einvoice_return_rs["rsp"] == "succ"){
|
||
$einvoice_url_data = json_decode($einvoice_return_rs["data"],true);
|
||
$einvoice_url = $einvoice_url_data["url"];
|
||
cachecore::store($einvoice_url_cache_name, $einvoice_url, $expire_time);
|
||
return $einvoice_url;
|
||
}else{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
//组打电子发票接口开蓝票/冲红的参数
|
||
function getEinvoiceCreateRequestParams($sdf,$type="blue"){
|
||
//获取invoice_item
|
||
$invoice_items = $this->getEinvoiceInvoiceItems($sdf,$type);
|
||
|
||
//获取platform invoice_amount provider_appkey proxy_appkey
|
||
$shop_info = kernel::single('ome_shop')->getRowByShopId($sdf['shop_id']);
|
||
$einvoice_shop_type = kernel::single('invoice_common')->returnEinvoiceShopType($shop_info);
|
||
$platform = kernel::single('invoice_common')->getPlatformByShopType($einvoice_shop_type);
|
||
$invoice_amount = $sdf["amount"]; //开票金额(价税合计)
|
||
$mdlInOrderSet = app::get('invoice')->model('order_setting');
|
||
$rs_invoice_setting = $mdlInOrderSet->dump(array("shop_id"=>$sdf["shop_id"]));
|
||
$provider_appkey = '60028257';//$rs_invoice_setting["provider_appkey"];
|
||
$proxy_appkey = $provider_appkey;//目前和开票服务商的APPKEY一样
|
||
|
||
//获取订单主表信息
|
||
$mdlOmeOrders = app::get('ome')->model('orders');
|
||
$rs_order = $mdlOmeOrders->dump(array("order_bn"=>$sdf["order_bn"],"shop_id"=>$sdf["shop_id"]));
|
||
|
||
if($type == "red"){
|
||
$invoice_amount = -$sdf["amount"];
|
||
$rs_order["total_amount"] = -$rs_order["total_amount"];
|
||
$sdf["cost_tax"] = -$sdf["cost_tax"];
|
||
}
|
||
|
||
$params = array(
|
||
"business_type" => "0", //默认:0。对于商家对个人开具,为0;对于商家对企业开具,为1;
|
||
"platform" => $platform, //电商平台代码
|
||
"tid" => $sdf["order_bn"], //电商平台对应的订单号
|
||
"serial_no" => $sdf["serial_no"], //开票流水号 例子: 20141234123412341
|
||
"payee_address" => $sdf["address"], //开票方地址(新版中为必传)
|
||
"payee_name" => $sdf["payee_name"], //开票方名称,公司名(如:XX商城)
|
||
"payee_operator" => $sdf["payee_operator"], //开票人
|
||
"invoice_amount" => number_format($invoice_amount,2,".",""), //开票金额
|
||
"invoice_time" => date("Y-m-d H:i:s",time()), //开票日期
|
||
"invoice_type" => $type, //发票(开票)类型,蓝票blue,红票red,默认blue
|
||
"payee_register_no" => $sdf["tax_no"], //收款方税务登记证号
|
||
"payer_name" => $sdf["tax_company"]?$sdf["tax_company"]:$sdf["title"], //付款方名称, 对应发票台头
|
||
"sum_price" => number_format($rs_order["total_amount"],2,".",""), //合计金额(新版中为必传) 订单总金额
|
||
"sum_tax" => number_format($sdf["cost_tax"],2,".",""), //合计税额
|
||
"invoice_items" => json_encode($invoice_items), //电子发票明细
|
||
//沙箱测试用 写死都是60028257
|
||
//"provider_appkey" => $provider_appkey, //开票服务商的APPKEY
|
||
// "proxy_appkey" => $proxy_appkey, //商家自己申请的放在开票代理客户端的appkey
|
||
//可选
|
||
"erp_tid" => "", //erp中唯一单据
|
||
"payee_bankaccount" => $sdf["bank_no"], //开票方银行及 帐号
|
||
"payer_register_no" => $sdf["ship_tax"], //付款方税务登记证号。对企业开具电子发票时必填
|
||
"invoice_memo" => $sdf["remarks"], //发票备注
|
||
"payer_address" => $sdf["ship_addr"], //消费者地址
|
||
"payer_bankaccount" => $sdf["ship_bank_no"], //付款方开票开户银行及账号
|
||
"payer_email" => "", //消费者电子邮箱
|
||
"payer_phone" => $sdf["ship_tel"], //消费者联系电话
|
||
"payee_checker" => "", //复核人
|
||
"payee_receiver" => "", //收款人
|
||
"payee_phone" => $sdf["telephone"], //收款方电话
|
||
);
|
||
|
||
if($type == "red"){
|
||
//获取原始蓝票记录
|
||
$rs_old_blue = $this->getOldBlueEinvoiceInfo($sdf["id"]);
|
||
$params["normal_invoice_code"] = $rs_old_blue["invoice_code"]; //原发票代码(开红票时传入)
|
||
$params["normal_invoice_no"] = $rs_old_blue["invoice_no"]; //原发票号码(开红票时传入)
|
||
}
|
||
|
||
return $params;
|
||
}
|
||
|
||
//组invoice_item行数据
|
||
private function getEinvoiceInvoiceItemArr($type="blue",$item_name,$row_type,$sum_price,$tax,$tax_rate,$amount,$unit,$item_no="",$price="",$quantity=""){
|
||
if($type == "red"){
|
||
$amount = -$amount;
|
||
$sum_price = -$sum_price;
|
||
$tax = -$tax;
|
||
$quantity = -$quantity;
|
||
}
|
||
$return_arr = array(
|
||
"item_name" => $item_name, //发票项目名称(或商品名称)
|
||
"row_type" => $row_type, //发票行性质。0表示正常行,1表示折扣行,2表示被折扣行。比如充电器单价100元,折扣10元,则明细为2行,充电器行性质为2,折扣行性质为1。如果充电器没有折扣,则值应为0
|
||
"sum_price" => number_format($sum_price,2,".",""), //总价,格式:100.00
|
||
"tax" => number_format($tax,2,".",""), //税额
|
||
"tax_rate" => $tax_rate, //税率
|
||
"amount" => number_format($amount,2,".",""), //价税合计
|
||
"unit" => $unit, //单位。新版电子发票,折扣行不能传,非折扣行必传 如之后必要可从ome_products表中获取
|
||
"item_no" => $item_no, //可选参数 发票项目编号(或商品编号)
|
||
"specification" => "", //可选参数 规格型号 目前给空
|
||
);
|
||
//$price和$quantity 正常行和被折扣行 必有
|
||
if($price){
|
||
$return_arr["price"] = number_format($price,2,".",""); //单价,格式:100.00。新版电子发票,折扣行此参数不能传,非折扣行必传
|
||
}
|
||
if($quantity){
|
||
$return_arr["quantity"] = $quantity; //数量。新版电子发票,折扣行此参数不能传,非折扣行必传
|
||
}
|
||
return $return_arr;
|
||
}
|
||
|
||
//组invoice_items参数数组
|
||
private function getEinvoiceInvoiceItems($sdf,$type="blue"){
|
||
//获取订单的order_object
|
||
$mdlOmeOrderObjects = app::get('ome')->model('order_objects');
|
||
$rs_objects= $mdlOmeOrderObjects->getList("*",array("order_id"=>$sdf["order_id"]));
|
||
|
||
//商品的unit为必要参数统一获取
|
||
$rl_bn_unit = $this->getEinvoiceBnUnitRlArr($rs_objects);
|
||
|
||
$invoice_items = array();
|
||
$count_line = 0; //如是折扣行需要计算行数
|
||
$tax_rate = number_format($sdf["tax_rate"]/100,2,".","");
|
||
foreach ($rs_objects as $var_object){
|
||
if($var_object["pmt_price"] > 0){
|
||
//有优惠金额 获取被折扣行和折扣行
|
||
//先被折扣行 row_type为2
|
||
$format_amount = number_format($var_object["amount"],2,".","");
|
||
$tax = number_format($format_amount*$tax_rate,2,".","");
|
||
$amount_plus_tax = $format_amount+$tax;
|
||
$invoice_items[] = $this->getEinvoiceInvoiceItemArr($type,$var_object["name"],"2",$format_amount,$tax,$tax_rate,$amount_plus_tax,$rl_bn_unit[$var_object["bn"]],$var_object["bn"],$var_object["price"],$var_object["quantity"]);
|
||
$count_line++;
|
||
//后折扣行 row_type为1
|
||
$format_pmt_price = number_format($var_object["pmt_price"],2,".",""); //优惠金额
|
||
$item_name = "折扣行数".$count_line."()";
|
||
$pmt_price = -$format_pmt_price; //优惠金额
|
||
$tax = number_format($format_pmt_price*$tax_rate,2,".","");
|
||
$tax = -$tax;
|
||
$amount_plus_tax = $pmt_price+$tax;
|
||
$invoice_items[] = $this->getEinvoiceInvoiceItemArr($type,$item_name,"1",$pmt_price,$tax,$tax_rate,$amount_plus_tax,$rl_bn_unit[$var_object["bn"]],$var_object["bn"]);
|
||
$count_line++;
|
||
}else{
|
||
//row_type为0正常行
|
||
$format_amount = number_format($var_object["amount"],2,".",""); //单商品总价
|
||
$tax = number_format($format_amount*$tax_rate,2,".","");
|
||
$amount_plus_tax = $format_amount+$tax;
|
||
$invoice_items[] = $this->getEinvoiceInvoiceItemArr($type,$var_object["name"],"0",$format_amount,$tax,$tax_rate,$amount_plus_tax,$rl_bn_unit[$var_object["bn"]],$var_object["bn"],$var_object["price"],$var_object["quantity"]);
|
||
$count_line++;
|
||
}
|
||
}
|
||
//判断是否有配送费用
|
||
$mdlOmeOrders = app::get('ome')->model('orders');
|
||
$rs_order = $mdlOmeOrders->dump(array("order_bn"=>$sdf["order_bn"],"shop_id"=>$sdf["shop_id"]),"*");
|
||
if($rs_order["shipping"]["cost_shipping"]>0){
|
||
$shipping_tax = $rs_order["shipping"]["cost_shipping"]*$tax_rate;
|
||
$amount_plus_tax = $rs_order["shipping"]["cost_shipping"]+$shipping_tax;
|
||
$invoice_items[] = $this->getEinvoiceInvoiceItemArr($type,"运费","0",$rs_order["shipping"]["cost_shipping"],$shipping_tax,$tax_rate,$amount_plus_tax,"笔","",$rs_order["shipping"]["cost_shipping"],"1");
|
||
}
|
||
return $invoice_items;
|
||
}
|
||
|
||
//获取bn统一对应unit数组
|
||
private function getEinvoiceBnUnitRlArr($rs_objects){
|
||
$products_bns = array();
|
||
foreach ($rs_objects as $var_obj){
|
||
$products_bns[] = $var_obj["bn"];
|
||
}
|
||
$mdlOmeProducts = app::get('ome')->model('products');
|
||
$rs_products = $mdlOmeProducts->getList("bn,unit",array("bn|in"=>$products_bns));
|
||
$rl_bn_unit = array();
|
||
foreach ($rs_products as $var_product){
|
||
$rl_bn_unit[$var_product["bn"]] = $var_product["unit"];
|
||
}
|
||
return $rl_bn_unit;
|
||
}
|
||
|
||
public function getEinvoiceSerialNo(&$sdf, $billing_type = 1)
|
||
{
|
||
//获取开票信息明细表中的开票流水号
|
||
$mdlInOrderElIt = app::get('invoice')->model('order_electronic_items');
|
||
$filter = array ("id" => $sdf["id"], "billing_type" => $billing_type);
|
||
$rs_item = $mdlInOrderElIt->dump($filter);
|
||
if (empty($rs_item)) {
|
||
//第一次点击开蓝票或者冲红 相应明细为空则insert一条有新的开票流水号的记录
|
||
$serial_no = $this->genEinvoiceSerialNo();
|
||
$insert_arr = array (
|
||
'id' => $sdf["id"],
|
||
'serial_no' => $serial_no,
|
||
'billing_type' => $billing_type,
|
||
'create_time' => time(),
|
||
'invoice_status' => $billing_type == 1 ? '10' : '20',
|
||
);
|
||
|
||
if ($sdf['invoice_action_type']) {
|
||
$insert_arr['invoice_action_type'] = $sdf['invoice_action_type'];
|
||
}
|
||
|
||
$result = $mdlInOrderElIt->insert($insert_arr);
|
||
if ($result) {
|
||
// 再次读取, 因需获取全量数据
|
||
$rs_item = $mdlInOrderElIt->dump($filter);
|
||
$sdf["serial_no"] = $rs_item["serial_no"];
|
||
unset($rs_item['content']);
|
||
// 金4全量电票,补充电票明细表内全部数据
|
||
$sdf['order_electronic_items'] = $rs_item;
|
||
return $sdf;
|
||
} else {
|
||
//没有生成明细记录 直接返回false 停止开票
|
||
return false;
|
||
}
|
||
} else {
|
||
//开票中或者开票失败或者冲红中或者冲红失败 不是首次点击 直接获取获取serial_no
|
||
$sdf["serial_no"] = $rs_item["serial_no"];
|
||
// 金4全量电票,补充电票明细表内全部数据
|
||
unset($rs_item['content']);
|
||
$sdf['order_electronic_items'] = $rs_item;
|
||
return $sdf;
|
||
}
|
||
}
|
||
|
||
//做当前电子发票开票或者冲红的按钮显示缓存 限制5分钟内不显示刚刚点击过的开票或者冲红link 防止重复点击打开票接口(蓝票、红票)
|
||
public function do_einvoice_create_limit($id,$type="blue"){
|
||
$einvoice_create_name = $id."_".$type;
|
||
$current_time = time();
|
||
$expire_time = 300;
|
||
cachecore::store($einvoice_create_name,$current_time,$expire_time);
|
||
}
|
||
|
||
//生成电子发票唯一开票流水号20位
|
||
private function genEinvoiceSerialNo(){
|
||
//当前时间戳9位十六进制
|
||
list($usec, $sec) = explode(" ", microtime());
|
||
$time = $sec.$usec*100000000;
|
||
$time_str = substr($time,7,9);
|
||
$time_hex = dechex($time_str); //转16进制
|
||
$time_hex = substr($time_hex,0,6);//截6位
|
||
|
||
//请求ip十六进制
|
||
if(!isset($GLOBALS['_REMOTE_ADDR_'])){
|
||
$addrs = array();
|
||
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
|
||
foreach( array_reverse( explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) as $x_f ) {
|
||
$x_f = trim($x_f);
|
||
if ( preg_match( '/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/', $x_f ) ) {
|
||
$addrs[] = $x_f;
|
||
}
|
||
}
|
||
}
|
||
$GLOBALS['_REMOTE_ADDR_'] = isset($addrs[0])?$addrs[0]:$_SERVER['REMOTE_ADDR'];
|
||
}
|
||
$remote_ip = $GLOBALS['_REMOTE_ADDR_'];
|
||
$remote_ip = str_replace('.','',$remote_ip);
|
||
$ip_hex = dechex((int)$remote_ip); //转16进制
|
||
$ip_hex = substr($ip_hex,0,5);//截5位
|
||
|
||
//随机数md5
|
||
$rand_num = mt_rand(0,999999);
|
||
$rand_md5 = md5($time.$rand_num);
|
||
$rand_str = substr($rand_md5,0,9); //截9位
|
||
|
||
return $time_hex.$ip_hex.$rand_str;
|
||
}
|
||
|
||
//获取冲红时原始蓝票的记录
|
||
private function getOldBlueEinvoiceInfo($id){
|
||
//电子发票开票信息明细表
|
||
$mdlInOrderElIt = app::get('invoice')->model('order_electronic_items');
|
||
//一个主表id只会有一个蓝票和一个红票
|
||
$arr_filter = array("id"=>$id,"billing_type"=>"1");
|
||
$rs_info = $mdlInOrderElIt->dump($arr_filter);
|
||
return $rs_info;
|
||
}
|
||
|
||
//组开票结果接口请求参数
|
||
public function getEinvoiceCreateResultParams($sdf){
|
||
//获取platform node_id
|
||
$shop_info = kernel::single('ome_shop')->getRowByShopId($sdf['shop_id']);
|
||
$einvoice_shop_type = kernel::single('invoice_common')->returnEinvoiceShopType($shop_info);
|
||
$platform = kernel::single('invoice_common')->getPlatformByShopType($einvoice_shop_type);
|
||
//获取存在的开票流水号
|
||
$mdlInOrderElIt = app::get('invoice')->model('order_electronic_items');
|
||
$rs_item = $mdlInOrderElIt->dump(array("id"=>$sdf["id"],"billing_type"=>$sdf["billing_type"]));
|
||
return array(
|
||
"platform" => $platform, //电商平台代码
|
||
"serial_no" => $rs_item["serial_no"], //开票流水号
|
||
"tid" => $sdf["order_bn"], //电商平台对应的订单号
|
||
"payee_register_no" => $sdf["tax_no"], //收款方税务登记证号
|
||
);
|
||
}
|
||
|
||
}
|