第一次更新,完成了初版调整
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 企油通 · 上游接口客户端(支付宝企业码通道)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 本次实际对接仅 3 个接口:
|
||||
// | 7.7 运单油费下发 createAndSendQuota —— 本类 createAndSendQuota()
|
||||
// | 7.15 H5 跳转地址 (本地签名生成 URL) —— 本类 h5Url()
|
||||
// | 7.10 运单加油明细推送(异步回调) —— app\api\controller\QytNotify
|
||||
// | 通道固定为支付宝企业码(passageway=1),司机手机号 + 姓名必填。
|
||||
// | 金额:对外「元」入参,7.7 内部换算为「分」。
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types=1);
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use app\common\crypto\Qyt as QytCrypto;
|
||||
|
||||
class QytOil extends BaseService
|
||||
{
|
||||
// 支付宝企业码通道
|
||||
const PASSAGEWAY_ALIPAY = 1;
|
||||
|
||||
/** @var array config('qyt') */
|
||||
protected $conf;
|
||||
|
||||
/** @var QytCrypto */
|
||||
protected $crypto;
|
||||
|
||||
protected function initialize()
|
||||
{
|
||||
$this->conf = (array) config('qyt');
|
||||
$this->crypto = new QytCrypto($this->conf);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 7.7 运单油费下发(一张订单 = 1 batch / 1 waybill / 1 employee)
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* @param array $data ['batch_no','waybill_no','waybill_no_code','mobile','name','amount'(元)]
|
||||
* @return array 归一化 ['code'=>'00'|其它, 'data'=>[], 'message'=>'', 'dup_batch'=>bool]
|
||||
*/
|
||||
public function createAndSendQuota(array $data): array
|
||||
{
|
||||
$params = [
|
||||
'batch_no' => $data['batch_no'],
|
||||
'business_license' => $this->conf['business_license'],
|
||||
'passageway' => (string) self::PASSAGEWAY_ALIPAY,
|
||||
'institution_id' => $this->conf['institution_id'],
|
||||
'waybill_list' => [[
|
||||
'waybill_no' => $data['waybill_no'],
|
||||
'employee_list' => [[
|
||||
'mobile' => $data['mobile'],
|
||||
'name' => $data['name'],
|
||||
'quota_amount' => self::yuanToFen($data['amount']), // 分
|
||||
'voucher_name' => $this->conf['voucher_name'],
|
||||
'effective_start' => '', // 空=当日生效
|
||||
'effective_end' => '', // 空=长期有效
|
||||
'waybill_no_code' => $data['waybill_no_code'],
|
||||
]],
|
||||
]],
|
||||
];
|
||||
|
||||
return $this->post('/enterprise/api/zhyk/createAndSendQuota', $params);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 7.15 H5 免登加油地址(本地签名生成 URL,不发起接口请求)
|
||||
// ================================================================
|
||||
|
||||
public function h5Url(string $mobile): string
|
||||
{
|
||||
$creditcode = !empty($this->conf['h5_with_creditcode']) ? (string) $this->conf['business_license'] : '';
|
||||
return $this->crypto->h5Url($mobile, $creditcode);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 内部:统一请求
|
||||
// ================================================================
|
||||
|
||||
/**
|
||||
* @return array ['code' => '00'成功 | 其它, 'data' => array, 'message' => string, 'dup_batch' => bool, 'raw' => array]
|
||||
*/
|
||||
protected function post(string $path, array $bizParams): array
|
||||
{
|
||||
$url = rtrim((string) $this->conf['base_url'], '/') . $path;
|
||||
$body = $this->crypto->envelope($bizParams);
|
||||
|
||||
$this->saveLog("[请求] {$path} " . json_encode(['url' => $url, 'params' => $bizParams], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$resp = $this->curlPost($url, json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
$this->saveLog("[响应] {$path} " . $resp);
|
||||
|
||||
$json = json_decode($resp, true);
|
||||
if (!is_array($json)) {
|
||||
return ['code' => 'ERR', 'data' => [], 'message' => '响应解析失败', 'dup_batch' => false, 'raw' => ['body' => $resp]];
|
||||
}
|
||||
|
||||
// 通用响应结构:{ status: ok|error, data, message, serialNo, sign }
|
||||
$ok = ($json['status'] ?? '') === 'ok';
|
||||
// 【TODO 待对接人确认】是否所有响应都带 sign、验签串如何构造
|
||||
// if ($ok && !empty($json['sign'])) { $ok = $this->crypto->verify($json['data'] ?? [], $json['sign']); }
|
||||
|
||||
$message = (string) ($json['message'] ?? '');
|
||||
// 幂等:batch_no 已存在 —— 视为「已受理」,供 7.7 重试判定
|
||||
$dupBatch = false;
|
||||
if (!$ok && $message !== '') {
|
||||
foreach ((array) (config('qyt.dup_batch_hints') ?: ['已存在', '重复', 'duplicate', 'exist']) as $hint) {
|
||||
if (mb_stripos($message, $hint) !== false) {
|
||||
$dupBatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => $ok ? '00' : 'ERR',
|
||||
'data' => $json['data'] ?? [],
|
||||
'message' => $message,
|
||||
'dup_batch' => $dupBatch,
|
||||
'raw' => $json,
|
||||
];
|
||||
}
|
||||
|
||||
protected function curlPost(string $url, string $jsonBody): string
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $jsonBody,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => (int) ($this->conf['timeout'] ?? 15),
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Charset: UTF-8'],
|
||||
]);
|
||||
$resp = curl_exec($ch);
|
||||
if ($resp === false) {
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
return json_encode(['status' => 'error', 'message' => 'cURL: ' . $err], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
curl_close($ch);
|
||||
return (string) $resp;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 工具
|
||||
// ================================================================
|
||||
|
||||
/** 元 → 分 */
|
||||
public static function yuanToFen($yuan): int
|
||||
{
|
||||
return (int) bcmul((string) $yuan, '100', 0);
|
||||
}
|
||||
|
||||
/** 分 → 元(字符串,保留 2 位) */
|
||||
public static function fenToYuan($fen): string
|
||||
{
|
||||
return bcdiv((string) (int) $fen, '100', 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成企油通全平台唯一编号(加企业前缀),确定性派生 -> 重试保持一致
|
||||
* @param string $type B=batch / W=waybill / C=waybill_no_code
|
||||
* @param string $seed 订单号 / order_id 等业务种子
|
||||
*/
|
||||
public static function genNo(string $type, string $seed): string
|
||||
{
|
||||
$prefix = (string) (config('qyt.no_prefix') ?: 'HM');
|
||||
return $prefix . $type . $seed;
|
||||
}
|
||||
|
||||
/** 记录日志到 runtime/storelog */
|
||||
public function saveLog($data): void
|
||||
{
|
||||
if (empty($this->conf['log'])) {
|
||||
return;
|
||||
}
|
||||
$dir = runtime_path('storelog/') . date('Ym') . '/' . date('Ymd');
|
||||
is_dir($dir) || @mkdir($dir, 0777, true);
|
||||
@file_put_contents(
|
||||
$dir . '/qyt_' . date('YmdH') . '.txt',
|
||||
date('Y-m-d H:i:s ') . (is_string($data) ? $data : json_encode($data, JSON_UNESCAPED_UNICODE)) . "\r\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user