Compare commits
merge into: Leadfyy.co:dev
Leadfyy.co:ccb_pay
Leadfyy.co:ccb_pay2
Leadfyy.co:dev
Leadfyy.co:master
Leadfyy.co:sf_express
pull from: Leadfyy.co:ccb_pay
Leadfyy.co:ccb_pay
Leadfyy.co:ccb_pay2
Leadfyy.co:dev
Leadfyy.co:master
Leadfyy.co:sf_express
4 Commits
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
6d1928b1ae |
建行支付(支付的分账参数未完善)
|
3 years ago |
|
|
3aadf8e09d |
修改注释
|
4 years ago |
|
|
bfe89bba95 |
增加.user.ini到忽略文件
|
4 years ago |
|
|
2e7e0cd81e |
增加.user.ini到忽略文件
|
4 years ago |
9 changed files with 693 additions and 3 deletions
-
1.gitignore
-
8MySQL_change.sql
-
287app/Controller/v3/CcbNotifyController.php
-
218app/Service/v3/CcbPay.php
-
152app/Service/v3/Implementations/CcbPayService.php
-
2app/Service/v3/Implementations/OrderOnlineService.php
-
4config/autoload/dependencies.php
-
8config/routes.php
-
16test/Cases/CcbTest.php
@ -0,0 +1,8 @@ |
|||
# TODO 屏蔽提现接口 |
|||
# 2022-05-01 11:57 |
|||
ALTER TABLE `lanzu_order_main` |
|||
ADD COLUMN `py_trn_no` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '建行生成的支付流水号' AFTER `pay_time`; |
|||
|
|||
# 2022-05-01 19:05 |
|||
ALTER TABLE `lanzu_store` |
|||
ADD COLUMN `card_no` VARCHAR(100) NULL DEFAULT '' COMMENT '银行卡号(用于建行接口分账)' AFTER `cash_code_img`; |
|||
@ -0,0 +1,287 @@ |
|||
<?php |
|||
|
|||
namespace App\Controller\v3; |
|||
|
|||
use App\Constants\v3\LogLabel; |
|||
use App\Constants\v3\OrderState; |
|||
use App\Constants\v3\OrderType; |
|||
use App\Controller\BaseController; |
|||
use App\Exception\BusinessException; |
|||
use App\Model\v3\Goods; |
|||
use App\Model\v3\GoodsActivity; |
|||
use App\Model\v3\Order; |
|||
use App\Model\v3\OrderGoods; |
|||
use App\Model\v3\OrderMain; |
|||
use App\Model\v3\User; |
|||
use App\Service\v3\Interfaces\BadgeServiceInterface; |
|||
use App\Service\v3\Interfaces\CouponRebateServiceInterface; |
|||
use App\Service\v3\Interfaces\CouponServiceInterface; |
|||
use App\Service\v3\Interfaces\DeviceServiceInterface; |
|||
use App\Service\v3\Interfaces\FeiePrintServiceInterface; |
|||
use App\Service\v3\Interfaces\FinancialRecordServiceInterface; |
|||
use App\Service\v3\Interfaces\GoodsActivityServiceInterface; |
|||
use App\Service\v3\Interfaces\MiniprogramServiceInterface; |
|||
use App\Service\v3\Interfaces\MqttServiceInterface; |
|||
use App\Service\v3\Interfaces\OrderOnlineServiceInterface; |
|||
use App\Service\v3\Interfaces\OrderStatisticsServiceInterface; |
|||
use App\Service\v3\Interfaces\SeparateAccountsServiceInterface; |
|||
use App\Service\v3\Interfaces\UserInfoServiceInterface; |
|||
use Exception; |
|||
use Hyperf\DbConnection\Db; |
|||
use Hyperf\Di\Annotation\Inject; |
|||
use Psr\Http\Message\ResponseInterface; |
|||
|
|||
/** @var Inject 注解使用 */ |
|||
|
|||
/** |
|||
* 建行结果通知接口 |
|||
*/ |
|||
class CcbNotifyController extends BaseController |
|||
{ |
|||
/** |
|||
* @Inject |
|||
* @var OrderOnlineServiceInterface |
|||
*/ |
|||
protected $orderOnlineService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var SeparateAccountsServiceInterface |
|||
*/ |
|||
protected $separateAccountsService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var OrderStatisticsServiceInterface |
|||
*/ |
|||
protected $orderStatisticsService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var CouponRebateServiceInterface |
|||
*/ |
|||
protected $couponRebateService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var MqttServiceInterface |
|||
*/ |
|||
protected $mqttService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var DeviceServiceInterface |
|||
*/ |
|||
protected $deviceService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var FeiePrintServiceInterface |
|||
*/ |
|||
protected $feiePrintService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var BadgeServiceInterface |
|||
*/ |
|||
protected $badgeService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var MiniprogramServiceInterface |
|||
*/ |
|||
protected $miniprogramService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var UserInfoServiceInterface |
|||
*/ |
|||
protected $userInfoService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var CouponServiceInterface |
|||
*/ |
|||
protected $couponService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var GoodsActivityServiceInterface |
|||
*/ |
|||
protected $goodsActivityService; |
|||
|
|||
/** |
|||
* @Inject |
|||
* @var FinancialRecordServiceInterface |
|||
*/ |
|||
protected $financialService; |
|||
|
|||
/** |
|||
* 支付结果通知 |
|||
*/ |
|||
public function payResult(): ResponseInterface |
|||
{ |
|||
/*$formData = [ |
|||
'Main_Ordr_No' => '主订单编号', |
|||
'Py_Trn_No' => 'Py_Trn_No', |
|||
'Ordr_Amt' => '订单金额', |
|||
'Txnamt' => '支付金额', |
|||
'Pay_Time' => '支付时间', |
|||
'Ordr_Stcd' => '2成功 3失败 4失效', |
|||
'TYPE' => '非必输。Pymd_cd=‘05’返回,支付宝:ALIPAY 微信:WEIXIN 建行:CCB 银联:UNIONPAY(暂不支付)', |
|||
'Sign_Inf' => '签名参数', |
|||
];*/ |
|||
|
|||
$formData = $this->request->all(); |
|||
try { |
|||
/*if (!CcbPay::getInstance()->SHA256WithRSAVerify($formData)) { |
|||
throw new BusinessException(500, '签名验证失败,收到的数据为:' . json_encode($formData)); |
|||
}*/ |
|||
|
|||
if (!isset($formData['Ordr_Stcd']) || $formData['Ordr_Stcd'] != 2) { |
|||
throw new BusinessException(500, 'Ordr_Stcd状态码异常:' . ($formData['Ordr_Stcd'] ?? 'null')); |
|||
} else if (!isset($formData['Main_Ordr_No'])) { |
|||
throw new BusinessException(500, '未获取到订单号Main_Ordr_No'); |
|||
} |
|||
|
|||
$orderMain = OrderMain::where([ |
|||
'global_order_id' => $formData['Main_Ordr_No'], |
|||
'type' => OrderType::ONLINE, |
|||
'state' => OrderState::UNPAID |
|||
])->first(); |
|||
|
|||
if (!$orderMain) { |
|||
throw new BusinessException(500, $formData['Main_Ordr_No'] . '订单不存在!'); |
|||
} |
|||
|
|||
$this->orderOnlineService->doByPaid($orderMain->global_order_id); |
|||
$this->separateAccountsService->orderOnlinePaid($orderMain->global_order_id); |
|||
|
|||
//记录当前市场的当天外卖订单数
|
|||
$this->orderStatisticsService->setForMarket($orderMain->market_id); |
|||
|
|||
// 优惠券返券
|
|||
$this->couponRebateService->couponRebateInTask($orderMain->global_order_id); |
|||
|
|||
// 喇叭通知,兼容旧音响,MQTT+IOT
|
|||
$res = $this->mqttService->speakToStore($orderMain->global_order_id); |
|||
$res = $this->deviceService->pubMsgToStoreByOrderMainId($orderMain->global_order_id); |
|||
|
|||
// 打印订单,自动打印
|
|||
$res = $this->feiePrintService->feiePrint($orderMain->global_order_id); |
|||
|
|||
// 记录badge
|
|||
$orderChildIds = Order::query()->where(['order_main_id' => $orderMain->global_order_id])->pluck('store_id'); |
|||
$this->badgeService->doByOrder($orderMain->user_id, $orderChildIds, $orderMain->global_order_id, OrderState::PAID); |
|||
|
|||
// 公众号模板消息
|
|||
$res = $this->miniprogramService->sendTemMsgForOnlineOrder($orderMain->global_order_id); |
|||
|
|||
co(function () use ($orderMain) { |
|||
$openid = User::query()->where(['id' => $orderMain->user_id])->value('openid'); |
|||
$unionid = $this->userInfoService->getPaidUnionId($openid, ['mch_id' => config('wxpay.mch_id'), 'out_trade_no' => $orderMain->global_order_id]); |
|||
if ($unionid) { |
|||
User::query()->where(['id' => $orderMain->user_id, 'openid' => $openid])->update(['unionid' => $unionid]); |
|||
} |
|||
}); |
|||
|
|||
return $this->response->json(['Svc_Rsp_St' => '00', 'Rsp_Inf' => 'success']); // 00成功;01失败
|
|||
} catch (Exception $exception) { |
|||
$this->log->event( |
|||
LogLabel::ORDER_ONLINE_PAY_NOTIFY_LOG, |
|||
['exception_fail' => $exception->getMessage()] |
|||
); |
|||
return $this->response->json(['Svc_Rsp_St' => '01', 'Rsp_Inf' => $exception->getMessage()]); // 00成功;01失败
|
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 退款通知 |
|||
*/ |
|||
public function refund(): ResponseInterface |
|||
{ |
|||
/*$formData = [ |
|||
'Ittparty_Tms' => '发起方时间戳 yyyymmddhhmmssfff', |
|||
'Ittparty_Jrnl_No' => '该笔直连交易的客户方流水号(不允许重复)', |
|||
'Py_Trn_No' => '支付流水号', |
|||
'Cust_Rfnd_Trcno' => '请求时送入客户方退款流水号,则返回送入的值,否则该字段为空。', |
|||
'Super_Refund_No' => '惠市宝生成,与退款动作唯一匹配', |
|||
'Rfnd_Amt' => '退款金额', |
|||
'Refund_Rsp_St' => '00-退款成功 01-退款失败', |
|||
'Refund_Rsp_Inf' => '退款响应信息', |
|||
'Sign_Inf' => '签名信息', |
|||
];*/ |
|||
|
|||
$formData = $this->request->all(); |
|||
Db::beginTransaction(); |
|||
try { |
|||
/*if (!CcbPay::getInstance()->SHA256WithRSAVerify($formData)) { |
|||
throw new BusinessException(500, '签名验证失败,收到的数据为:' . json_encode($formData)); |
|||
}*/ |
|||
|
|||
if (!isset($formData['Refund_Rsp_St'])) { |
|||
throw new BusinessException(500, 'Refund_Rsp_St状态码异常:' . ($formData['Ordr_Stcd'] ?? 'null')); |
|||
} else if (!isset($formData['Ittparty_Jrnl_No'])) { |
|||
throw new BusinessException(500, '未获取到订单号Ittparty_Jrnl_No'); |
|||
} |
|||
|
|||
$orderMain = OrderMain::whereNotIn('state', [OrderState::UNPAID]) |
|||
->where(['global_order_id' => $formData['Ittparty_Jrnl_No']]) |
|||
->first(); |
|||
|
|||
if (!$orderMain) { |
|||
throw new BusinessException(500, $formData['Ittparty_Jrnl_No'] . '订单不存在!'); |
|||
} else if ($orderMain->state == OrderState::REFUNDED) { |
|||
return $this->response->json(['Svc_Rsp_St' => '00', 'Ittparty_Tms' => date('YmdHis000'), 'Rsp_Inf' => 'success']); |
|||
} else if ($formData['Refund_Rsp_St'] != '00') { // 建行返回退款失败
|
|||
$orderMain->state = OrderState::REFUND_REFUSE; |
|||
$orderMain->refuse_refund_note = '建行拒绝退款'; |
|||
$orderMain->save(); |
|||
Db::commit(); |
|||
|
|||
return $this->response->json(['Svc_Rsp_St' => '00', 'Ittparty_Tms' => date('YmdHis000'), 'Rsp_Inf' => 'success']); // 00成功;01失败
|
|||
} |
|||
|
|||
// 添加退款时间
|
|||
$orderMain->refund_time = time(); |
|||
$orderMain->state = OrderState::REFUNDED; |
|||
$orderMain->save(); |
|||
|
|||
// 退款返还优惠券
|
|||
$this->couponService->orderRefundCoupons($orderMain->global_order_id); |
|||
|
|||
// 处理特价商品缓存
|
|||
$orderChildren = Order::query()->where(['order_main_id' => $orderMain->global_order_id])->get()->toArray(); |
|||
$orderGoods = OrderGoods::query()->whereIn('order_id', array_values(array_column($orderChildren, 'id')))->get()->toArray(); |
|||
foreach ($orderGoods as &$item) { |
|||
if ($item['activity_type'] == 2) { # 活动商品
|
|||
$this->goodsActivityService->clearCacheRecord($item['goods_id'], $item['number'], $orderMain->user_id); |
|||
$goods = GoodsActivity::find($item['goods_id']); |
|||
} else { |
|||
$goods = Goods::find($item['goods_id']); |
|||
} |
|||
$goods->inventory = $goods->inventory + $item['number']; |
|||
$goods->sales = $goods->sales - $item['number']; |
|||
$goods->save(); |
|||
} |
|||
|
|||
// 添加用户的流水
|
|||
$this->financialService->userByOLOrderRefund($orderMain->user_id, $orderMain->global_order_id, $orderMain->money); |
|||
|
|||
Db::commit(); |
|||
|
|||
// 记录badge
|
|||
$orderChildIds = array_values(array_column($orderChildren, 'store_id')); |
|||
$this->badgeService->doByOrder($orderMain->user_id, $orderChildIds, $orderMain->global_order_id, OrderState::REFUNDED); |
|||
|
|||
return $this->response->json(['Svc_Rsp_St' => '00', 'Ittparty_Tms' => date('YmdHis000'), 'Rsp_Inf' => 'success']); // 00成功;01失败
|
|||
} catch (\Exception $exception) { |
|||
Db::rollBack(); |
|||
$this->log->event( |
|||
LogLabel::ORDER_ONLINE_PAY_NOTIFY_LOG, |
|||
['exception_fail' => $exception->getMessage()] |
|||
); |
|||
return $this->response->json(['Svc_Rsp_St' => '01', 'Ittparty_Tms' => date('YmdHis000'), 'Rsp_Inf' => $exception->getMessage()]); // 00成功;01失败
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,218 @@ |
|||
<?php |
|||
|
|||
namespace App\Service\v3; |
|||
|
|||
use App\Exception\BusinessException; |
|||
use Hyperf\Guzzle\ClientFactory; |
|||
use Hyperf\Utils\ApplicationContext; |
|||
|
|||
/** |
|||
* 建行支付相关接口 |
|||
*/ |
|||
class CcbPay |
|||
{ |
|||
private static ?CcbPay $_instance = null; |
|||
private string $privateKey; |
|||
private string $publicKey; |
|||
private string $host = 'http://marketpayktwo.dev.jh:8028'; |
|||
private ClientFactory $clientFactory; |
|||
|
|||
public function __construct() |
|||
{ |
|||
$this->privateKey = env('CCB_SELF_PRIVATE_KEY'); |
|||
$this->privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" . chunk_split($this->privateKey, 64, "\n") . "-----END RSA PRIVATE KEY-----\n"; |
|||
|
|||
$this->publicKey = env('CCB_BANK_PUBLIC_KEY'); |
|||
$this->publicKey = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($this->publicKey, 64, "\n") . "-----END PUBLIC KEY-----\n"; |
|||
|
|||
$this->clientFactory = ApplicationContext::getContainer()->get(ClientFactory::class); |
|||
} |
|||
|
|||
public static function getInstance(): CcbPay |
|||
{ |
|||
if (self::$_instance === null) { |
|||
self::$_instance = new self(); |
|||
} |
|||
return self::$_instance; |
|||
} |
|||
|
|||
/** |
|||
* 发起退款 |
|||
*/ |
|||
public function refundOrder(array $args): array |
|||
{ |
|||
if (!isset($args['Ittparty_Jrnl_No'], $args['Py_Trn_No'])) { |
|||
throw new BusinessException(500, '交易流水号、支付流水号不能为空'); |
|||
} |
|||
|
|||
$params = array_merge([ |
|||
'Ittparty_Stm_Id' => '00000', //固定5个0
|
|||
'Py_Chnl_Cd' => '0000000000000000000000000', // 因定25个0
|
|||
'Ittparty_Tms' => date('YmdHis999'), // 时间yyyymmddhhmmssfff,年月日, 时分秒,毫秒
|
|||
'Ittparty_Jrnl_No' => '', //该笔直连交易的客户方流水号(不允许重复)
|
|||
'Mkt_Id' => env('CCB_MKT_ID'), //14位市场编号,该字段由银行在正式上线前提供,测试阶段有测试数据
|
|||
'Py_Trn_No' => '', // 支付流水号,由建行生成,与该订单的支付动作唯一匹配
|
|||
// 'Rfnd_Amt' => 0, // 订单全额退款时不需要送,订单部分退款时必须送此值,且值等于所有子订单的退款金额之和
|
|||
/*'Sub_Ordr_List' => [ // 子订单列表,主订单全额退款时不需要传该域
|
|||
|
|||
],*/ |
|||
'Vno' => '3', // 非必输
|
|||
], $args); |
|||
|
|||
return $this->send('/online/direct/refundOrder', $params); |
|||
} |
|||
|
|||
/** |
|||
* 创建订单 |
|||
*/ |
|||
public function gatherPlaceOrder(array $args): array |
|||
{ |
|||
if (!isset($args['Ittparty_Jrnl_No'], $args['Main_Ordr_No'], $args['Ordr_Tamt'], $args['Txn_Tamt'], $args['Sub_Openid'], $args['Orderlist'])) { |
|||
throw new BusinessException(500, '流水号、主订单流水号、付款金额、实付总金额、openid、Orderlist不能为空'); |
|||
} |
|||
|
|||
$params = array_merge([ |
|||
'Ittparty_Stm_Id' => '00000', //固定5个0
|
|||
'Py_Chnl_Cd' => '0000000000000000000000000', // 因定25个0
|
|||
'Ittparty_Tms' => date('YmdHis888'), // 时间yyyymmddhhmmssfff,年月日, 时分秒,毫秒
|
|||
'Ittparty_Jrnl_No' => '', //该笔直连交易的客户方流水号(不允许重复)
|
|||
'Mkt_Id' => env('CCB_MKT_ID'), //14位市场编号,该字段由银行在正式上线前提供,测试阶段有测试数据
|
|||
'Main_Ordr_No' => '', // 客户方主订单流水号,不允许重复
|
|||
'Pymd_Cd' => env('CCB_PYMD_CD'), // 03 移动端H5页面 (app) 05 微信小程序(无收银台)
|
|||
'Py_Ordr_Tpcd' => '04', //02 消费券购买订单 03 在途订单(只有是否支持在途模式为“是”时才可以使用)(注:品类管控市场订单类型必须为03) 04普通订单
|
|||
'Py_Rslt_Ntc_Sn' => '1', // 支付结果通知给市场方维护的指定地址序号,对应建行后台设置的地址,1-10之间
|
|||
'Ccy' => '156', // 156人民币
|
|||
'Ordr_Tamt' => 0, // 应付总金额
|
|||
'Txn_Tamt' => $args['Txn_Tamt'] ?? $args['Ordr_Tamt'], // 消费者实付总金额
|
|||
'Sub_Appid' => env('APP_ID'), // “Pymd_Cd(支付方式代码)”为“05-微信小程序”时必输;当前调起支付的小程序APPID
|
|||
'Sub_Openid' => '', // “Pymd_Cd(支付方式代码)”为“05-微信小程序”时必输;用户在小程序appid下的唯一标识
|
|||
'Orderlist' => [ |
|||
'Ordr_Amt' => $args['Ordr_Amt'] ?? $args['Ordr_Tamt'], // 订单商品总金额,即应付金额,所有商品订单金额之和等于主订单金额;
|
|||
'Cmdty_Ordr_No' => '', // 返显输入接口中的客户方子订单编号
|
|||
'Txnamt' => $args['Txnamt'] ?? $args['Ordr_Tamt'], // 消费者实付金额,所有商品订单金额之和等于主交易总金额金额
|
|||
'Mkt_Mrch_Id' => '41060860800469061877', // 商家编号
|
|||
'Clrg_Rule_Id' => 'F410608608004691879', // 分账规则编号,1.“Py_Ordr_Tpcd(订单类型)”为“02-消费券购买订单”时该字段无效,可不送;2.走默认分账策略,可不送;3.多个子订单时不可送
|
|||
'Parlist' => [ |
|||
['Seq_No' => '1', 'Mkt_Mrch_Id' => '41060860800469061878'], // Seq_No:参与方顺序号(默认从1开始);Mkt_Mrch_Id:商家编号
|
|||
['Seq_No' => '2', 'Mkt_Mrch_Id' => '41060860800469000000'], |
|||
] |
|||
], |
|||
'Vno' => '4', |
|||
], $args); |
|||
|
|||
return $this->send('/online/direct/gatherPlaceorder', $params); |
|||
} |
|||
|
|||
/** |
|||
* 发送请求 |
|||
*/ |
|||
private function send(string $url, array &$params): array |
|||
{ |
|||
$this->SHA256WithRSASign($params); // 计算签名,加入签名参数
|
|||
|
|||
$res = json_decode($this->clientFactory->create()->post($this->host . $url, ['json' => $params])->getBody()->getContents(), true); |
|||
if (!$res || !isset($res['Svc_Rsp_St']) || $res['Svc_Rsp_St'] != '00') { |
|||
throw new BusinessException(500, ($res['Rsp_Inf'] ?? '请求异常')); |
|||
} else if (env('APP_ENV') != 'prod' && (!isset($res['Sign_Inf']) || !$this->SHA256WithRSAVerify($res))) { |
|||
throw new BusinessException(500, '返回数据签名验证失败'); |
|||
} |
|||
|
|||
return $res; |
|||
} |
|||
|
|||
/** |
|||
* SHA256WithRSA加密 |
|||
*/ |
|||
private function SHA256WithRSAEncrypt(string $data): string |
|||
{ |
|||
openssl_private_encrypt( |
|||
$data, |
|||
$encrypted_data, |
|||
openssl_get_privatekey($this->privateKey), |
|||
); |
|||
return base64_encode($encrypted_data); |
|||
} |
|||
|
|||
/** |
|||
* SHA256WithRSA解密 |
|||
*/ |
|||
private function SHA256WithRSADecrypt(string $data) |
|||
{ |
|||
openssl_public_decrypt( |
|||
base64_decode($data), |
|||
$decrypted_data, |
|||
openssl_get_publickey($this->publicKey), |
|||
OPENSSL_ALGO_SHA256 |
|||
); |
|||
return $decrypted_data; |
|||
} |
|||
|
|||
/** |
|||
* SHA256WithRSA生成签名 |
|||
* @param array $params 请求的参数 |
|||
*/ |
|||
private function SHA256WithRSASign(array &$params) |
|||
{ |
|||
$this->kSort($params); |
|||
openssl_sign( |
|||
$this->joinStr($params), |
|||
$binary_signature, |
|||
openssl_get_privatekey($this->privateKey), |
|||
OPENSSL_ALGO_SHA256, |
|||
); |
|||
$params['Sign_Inf'] = base64_encode($binary_signature); |
|||
} |
|||
|
|||
/** |
|||
* 字符串验签 |
|||
* openssl_verify => 1:签名正确;0:签名不正确;-1:验签出错error |
|||
* @param array $params 请求接口后返回的数组 |
|||
* @return bool |
|||
*/ |
|||
public function SHA256WithRSAVerify(array $params): bool |
|||
{ |
|||
// 公共参数不参与签名
|
|||
$sign = $params['Sign_Inf']; |
|||
unset($params['Sign_Inf'], $params['Svc_Rsp_St'], $params['Svc_Rsp_Cd'], $params['Rsp_Inf']); |
|||
|
|||
$this->kSort($params); |
|||
return openssl_verify( |
|||
$this->joinStr($params), |
|||
base64_decode($sign), |
|||
openssl_get_publickey($this->publicKey), |
|||
OPENSSL_ALGO_SHA256 |
|||
) === 1; |
|||
} |
|||
|
|||
/** |
|||
* 数据按key排序,包括子元素 |
|||
*/ |
|||
private function kSort(array &$params) |
|||
{ |
|||
ksort($params); |
|||
foreach ($params as &$item) { |
|||
if (is_array($item)) { |
|||
$this->kSort($item); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 字符串拼接 |
|||
*/ |
|||
private function joinStr(array $params): string |
|||
{ |
|||
$str = ''; |
|||
foreach ($params as $key => $item) { |
|||
if ($item === '' || $item === []) { // 如果参数的值为空则不参与签名
|
|||
continue; |
|||
} |
|||
if (is_array($item)) { |
|||
$str .= (empty($str) ? '' : '&') . $this->joinStr($item); |
|||
} else { |
|||
$str .= (empty($str) ? '' : '&') . $key . '=' . $item; |
|||
} |
|||
} |
|||
return $str; |
|||
} |
|||
} |
|||
@ -0,0 +1,152 @@ |
|||
<?php |
|||
|
|||
namespace App\Service\v3\Implementations; |
|||
|
|||
use App\Commons\Log; |
|||
use App\Constants\v3\ErrorCode; |
|||
use App\Constants\v3\LogLabel; |
|||
use App\Constants\v3\OrderState; |
|||
use App\Constants\v3\OrderType; |
|||
use App\Constants\v3\Payment; |
|||
use App\Exception\ErrorCodeException; |
|||
use App\Model\v3\Order; |
|||
use App\Model\v3\OrderMain; |
|||
use App\Model\v3\User; |
|||
use App\Service\v3\CcbPay; |
|||
use App\Service\v3\Interfaces\PaymentServiceInterface; |
|||
use Hyperf\Di\Annotation\Inject; |
|||
|
|||
/** @var Inject 注解使用 */ |
|||
|
|||
/** |
|||
* 建行支付 |
|||
*/ |
|||
class CcbPayService implements PaymentServiceInterface |
|||
{ |
|||
/** |
|||
* @Inject |
|||
* @var Log |
|||
*/ |
|||
protected Log $log; |
|||
|
|||
public function do($globalOrderId, $money, $userId, $notifyUrl) |
|||
{ |
|||
try { |
|||
// 待支付的,未超时(15min,900sec)的订单
|
|||
$orderMain = OrderMain::where('created_at', '>=', (time() - 900)) |
|||
->where(['state' => OrderState::UNPAID, 'global_order_id' => $globalOrderId, 'user_id' => $userId]) |
|||
->first(); |
|||
|
|||
if (empty($orderMain)) { |
|||
throw new ErrorCodeException(ErrorCode::ORDER_NOT_AVAILABLE, '[支付订单号]' . $globalOrderId); |
|||
} |
|||
|
|||
$payMoney = $orderMain->money; // 注:此处单位是元,不是分
|
|||
if (env('APP_ENV') != 'prod' && $orderMain->type == OrderType::ONLINE) { |
|||
$payMoney = 0.01; |
|||
} |
|||
|
|||
$user = User::select('openid')->find($userId); |
|||
|
|||
// 设置分账规则
|
|||
$order = Order::with('store:id,card_no')->where('order_main_id', $globalOrderId)->get(); |
|||
$parList = []; |
|||
$seqNo = 1; |
|||
foreach ($order as $item) { |
|||
if (!empty($item->store->card_no)) { |
|||
$parList[] = [ |
|||
'Seq_No' => $seqNo, |
|||
'Mkt_Mrch_Id' => $item->store->card_no, |
|||
// 'Amt' => 0, // TODO 分账金额
|
|||
]; |
|||
$seqNo++; |
|||
} |
|||
} |
|||
|
|||
$res = CcbPay::getInstance()->gatherPlaceOrder([ |
|||
'Ittparty_Jrnl_No' => $globalOrderId, |
|||
'Main_Ordr_No' => $globalOrderId, |
|||
'Ordr_Tamt' => $payMoney, // 订单总金额
|
|||
'Txn_Tamt' => $payMoney, // 应付金额
|
|||
'Sub_Openid' => $user['openid'], |
|||
'Orderlist' => [ |
|||
'Ordr_Amt' => $payMoney, // 订单商品总金额,即应付金额,所有商品订单金额之和等于主订单金额;
|
|||
'Cmdty_Ordr_No' => $globalOrderId, // 返显输入接口中的客户方子订单编号
|
|||
'Txnamt' => $payMoney, // 消费者实付金额,所有商品订单金额之和等于主交易总金额金额
|
|||
'Mkt_Mrch_Id' => env('CCB_MKT_MRCH_ID'), // 商家编号
|
|||
'Clrg_Rule_Id' => env('CCB_CLRG_RULE_ID'), // 分账规则编号,1.“Py_Ordr_Tpcd(订单类型)”为“02-消费券购买订单”时该字段无效,可不送;2.走默认分账策略,可不送;3.多个子订单时不可送
|
|||
'Parlist' => $parList, |
|||
], |
|||
]); |
|||
|
|||
// 保存一下 $res['Py_Trn_No'] 参数,后续退款等都会用到
|
|||
$orderMain->py_trn_no = $res['Py_Trn_No']; |
|||
$orderMain->save(); |
|||
|
|||
// 返回支付参数给前端
|
|||
return [ |
|||
'appId' => $res['Rtn_Par_Data']['appid'], |
|||
'timeStamp' => $res['Rtn_Par_Data']['timeStamp'], |
|||
'nonceStr' => $res['Rtn_Par_Data']['nonceStr'], |
|||
'package' => $res['Rtn_Par_Data']['package'], |
|||
'signType' => $res['Rtn_Par_Data']['signType'], |
|||
'paySign' => $res['Rtn_Par_Data']['paySign'], |
|||
'order_main_id' => $globalOrderId, |
|||
]; |
|||
} catch (\Exception $e) { |
|||
$this->log->event(LogLabel::ORDER_PAYMENT_LOG, ['payment_do_exception_msg' => $e->getMessage()]); |
|||
throw new ErrorCodeException(ErrorCode::PAYMENT_FAIL, '[稍后重试]'); |
|||
} |
|||
} |
|||
|
|||
public function check() |
|||
{ |
|||
// TODO: Implement check() method.
|
|||
} |
|||
|
|||
/** |
|||
* 退款的整单,用户申请的退款 |
|||
* @param $globalOrderId |
|||
* @param $userId |
|||
* @return bool |
|||
*/ |
|||
public function undo($globalOrderId, $userId) |
|||
{ |
|||
try{ |
|||
// 已支付的,未退款的,使用微信(或建行)支付的订单
|
|||
$orderMain = OrderMain::query() |
|||
->whereIn('state', [OrderState::PAID, OrderState::DELIVERY, OrderState::COMPLETED, OrderState::EVALUATED, OrderState::REFUNDING]) |
|||
->where(['global_order_id' => $globalOrderId, 'user_id' => $userId, 'pay_type' => Payment::WECHAT, 'refund_time' => 0]) |
|||
->first(); |
|||
|
|||
if (empty($orderMain)) { |
|||
throw new ErrorCodeException(ErrorCode::ORDER_NOT_AVAILABLE, '[支付订单号]' . $globalOrderId); |
|||
} |
|||
|
|||
$result = CcbPay::getInstance()->refundOrder([ |
|||
'Ittparty_Jrnl_No' => $globalOrderId, |
|||
'Py_Trn_No' => $orderMain->py_trn_no, |
|||
]); |
|||
|
|||
if (!isset($result['Refund_Rsp_St'])) { // 接口返回异常
|
|||
throw new ErrorCodeException(ErrorCode::REFUND_PAYMENT_FAIL, '退款异常,操作失败'); |
|||
} else if ($result['Refund_Rsp_St'] == '01') { |
|||
throw new ErrorCodeException(ErrorCode::REFUND_PAYMENT_FAIL, '退款失败'); |
|||
} else if ($result['Refund_Rsp_St'] == '02') { |
|||
throw new ErrorCodeException(ErrorCode::REFUND_PAYMENT_FAIL, '退款延迟等待'); |
|||
} else if ($result['Refund_Rsp_St'] == '03') { |
|||
throw new ErrorCodeException(ErrorCode::REFUND_PAYMENT_FAIL, '退款结果不确定,请稍后查看退款结果'); |
|||
} |
|||
|
|||
return true; |
|||
} catch (\Exception $e) { |
|||
$this->log->event(LogLabel::ORDER_REFUND_LOG, ['payment_do_exception_msg' => $e->getMessage()]); |
|||
throw new ErrorCodeException(ErrorCode::REFUND_PAYMENT_FAIL); |
|||
} |
|||
} |
|||
|
|||
public function payToWx($money, $tradeNo, $openId, $userName, $desc = '', $checkName = 'NO_CHECK') |
|||
{ |
|||
throw new ErrorCodeException(500, 'payToWx未实现'); |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
<?php |
|||
|
|||
namespace HyperfTest\Cases; |
|||
|
|||
use App\Service\v3\CcbPay; |
|||
use HyperfTest\HttpTestCase; |
|||
|
|||
class CcbTest extends HttpTestCase |
|||
{ |
|||
public function ccbRsaTest() |
|||
{ |
|||
echo PHP_EOL, '111111111111111', PHP_EOL; |
|||
echo CcbPay::getInstance()->SHA256WithRSAEncrypt('123456'); |
|||
echo PHP_EOL, '222222222222222', PHP_EOL; |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue