美餐支付 - PHP代碼实现

前言

  • 背景
    前段时间,因接手的项目需要实现 美餐支付 的功能对接
    在此记录一下鄙人的实现步骤,方便有需要的道友参考借鉴

  • 场景描述
    我们的 “现代膳食” 售卖机,可以在屏幕上显示可配送的餐食
    用户选中商品后,点击购买
    选择 “美餐支付” 后,提示用户刷卡或扫描 美餐APP支付码
    我们的设备端,会将读取到的 卡号/⼆维码 Code 传到服务接口,随后开发人员处理支付逻辑

  • 美餐
    听客户描述,当地使用美餐卡的用户群比较普遍 …

实现步骤

以下为鄙人整理的开发过程,可根据自己的实际业务优化处理

①. 前期准备

  • 开发前,首先要沟通获取到 官方提供的 开发文档、开发者 ID、商户ID、店铺ID、开发者私钥/公钥 等信息
  • 以下为鄙人业务接口,所需要的参数要求:

②. 快速支付

  • 美餐-快速支付,核心方法如下:
    /*** @Notes: 快速支付* @param array $post_data* @return array* @User: zhanghj* @DateTime: 2023-08-09 19:34* 要求 : 参数需在请求JSON传参*/public function payQuick($post_data = []){$opFlag = false;$opMsg = '';$curr_time = time();$merchant_id = self::MERCHANT_ID;$url = "/meican-pay-quick/v1/merchants/{$merchant_id}/mode-s/pay";$order_sn = $post_data['order_sn']??'';$payer_code = $post_data['payer_code']??'';$quick_type = $post_data['quick_type']??1;$orderInfo = Order::getOrderInfoByOrderSn($order_sn,'order_id,order_amount');$order_id = $orderInfo['order_id']??0;//检验当前订单id,是否符合快速支付条件$check_msg = (new OrderService())->checkMeicanOrderToPay($order_id,'PAY');if ($check_msg){$opMsg = $check_msg;}else{if (!in_array($quick_type,[1,2])){$opMsg = '请确认美餐支付参数';}else{$sum_order_amount = Order::find()->where(['order_sn' => $order_sn])->sum('order_amount');//1:刷卡支付,2:美餐APP反扫码$type_identifier = ($quick_type==1)?'MEICAN_PHYSICAL_CARD':'MEICAN_ELECTRIC_CARD';$request_body = [//可以考虑原订单号加随机数,避免无法付款'order_id' => $order_id.'M'.$order_sn,'store_id' => self::STORE_ID,//TODO 店铺ID'expire_time' => $curr_time+(6*3600),'description' => 'MEICAN_PAY',//⽀付单描述 售货机订单-美餐⽀付'payer' => ['payer_type' => 'CARD', //用户RN支付类型'id_card' => ['type_identifier' => $type_identifier ,//物理卡类型、美餐付款码类型'code' => $payer_code,//卡内码],],'total' => $sum_order_amount * 100,//⽀付⾦额(实付⾦额)分'currency' => 'CNY','notification_url' => $this->curr_domain.'/meican-pay/pay_notify'];list($opFlag,$opMsg,$opData) = $this->httpToMeicanServer('POST',$url,$curr_time,$request_body);}}if ($opFlag){$this->logInfoToRuntime('actionPayQuick','订单ID【'.$order_id.'】'.json_encode($opData??[],JSON_UNESCAPED_UNICODE));// 判断是否支付成功$result_code = $opData['result_code']??'';$result_description = $opData['result_description']??'';if ($result_code == 'OK'){$save_data = ['pay_type' => ($quick_type==1)?4:5,'pay_time' => time()];$saveTag = Order::updateOrderByOrderSn($order_sn,$save_data);if ($saveTag){$opMsg = '支付成功';}else{$opFlag = false;$opMsg = '支付更新失败';}}else{$opFlag = false;$opMsg = '支付接口,调用失败:'.$result_description;}}return [$opFlag,$opMsg,$opData??''];}

③. 支付回调处理

对于回调接口,需要联系商家,添加到白名单

  • 根据前面配置的支付回调参数 notification_url , 回调处理如下:
    /*** @Notes: 快速支付,回调逻辑处理* 白名单接口:http://clientapi.xxxxxxxxxxxxxxxx.com/meican-pay/pay_notify* @User: zhanghj* @DateTime: 2023-08-09 11:29*/public function actionPayNotify(){$request = new Request();if ($request->isPost){$raw_json = $request->getRawBody();$op_flag = (new MeicanPayService())->dealToPayNotify($raw_json);$data = ['result_code' => $op_flag?'OK':'NO','result_description' => $op_flag?'Success':'Failure',];}else{$data = ['result_code' => 'NO','result_description' => 'GET请求方式不合法',];}return json_encode($data,JSON_UNESCAPED_UNICODE);}

④. 退款申请、退款回调

具体实现,可参考文件后面的 附录代码

  • 发起退款请求,处理如下:
$order_id = $request->post('order_id',0);list($op_flag,$op_msg) = (new MeicanPayService)->payRefund($order_id);
  • 退款回调,处理如下:
    /*** @Notes: 退款申请,回调逻辑处理* http://clientapi.xxxxxxxxxxx.com/meican-pay/refund_notify* @User: zhanghj* @DateTime: 2023-08-09 11:29*/public function actionRefundNotify(){$request = new Request();if ($request->isPost){$raw_json = $request->getRawBody();$op_flag = (new MeicanPayService())->dealToRefundNotify($raw_json);$data = ['result_code' => $op_flag?'OK':'NO','result_description' => $op_flag?'Success':'Failure',];}else{$data = ['result_code' => 'NO','result_description' => 'GET请求方式不合法',];}return json_encode($data,JSON_UNESCAPED_UNICODE);}
  • 美餐支付 退款查询
    /*** @Notes:美餐支付 退款查询* @return false|string* @User: zhanghj* @DateTime: 2023-11-06 11:27*/public function actionQueryPayRefund(){$request = new Request();if ($request->isGet){$order_id = $request->get('order_id',0);list($op_flag,$op_msg,$op_data) = (new MeicanPayService)->queryPayRefund($order_id);}else{$op_flag = false;$op_msg = '请求方式不合法';}$op_res = ['code' => $op_flag?200:405,'msg' => $op_msg,'data' => $op_data??[]];return json_encode($op_res,JSON_UNESCAPED_UNICODE);}

附录

①. 注意事项

    1. 注意开发私钥、公钥的存储,以我的代码实现为例,存放的私钥位置、形式如下:
    1. 注意,支付回调接口,一定要联系商家,添加到接口白名单

②. 美餐支付服务类(封装)

  • 整理 美餐支付服务类 ,源代码提供如下:
<?phpnamespace clientapi\services;
use common\helper\Helper;
use common\models\Device;
use common\models\MealOrder;
use common\models\Order;
use GuzzleHttp\Client;/*** Meican Pay 支付服务类* Class MeicanPayService* @package api\services*/
class MeicanPayService
{const DEVELOPER_ID = '7103xxxxxxxxxxxxxxxxxxxxxxxx';         //开发者 ID(由 Meican Pay 分配)const MERCHANT_ID = '1013xxxxxxxxxxxx';           //商户IDconst STORE_ID = '1011xxxxxxxxxx';              //店铺IDconst BASE_URL = 'https://developer-api.meican.com';     //Meican Pay 接口域名const KEY_FILE_DIR = __DIR__.'/../web/meican_key/'; //公钥、私钥存储路径private $private_key;           //开发者私钥private $public_key;            //开发者公钥private $platform_public_key;   //美餐平台公钥(接收来⾃ Meican Pay 的请求应答及回调通知)protected $httpClient = null;private $curr_domain;           //当前服务器域名private $developerApi_domain;public function __construct(){$this->curr_domain = 'http://clientapi.welfare.kairende.com';$this->developerApi_domain = 'https://developer-api.meican.com';$this->httpClient = new Client(['base_uri' => self::BASE_URL,'verify' => false,'http_errors' => false]);// 加载私钥文件$this->private_key = openssl_pkey_get_private(file_get_contents(self::KEY_FILE_DIR.'rsa_private.pem'));// 加载公钥文件$this->public_key = openssl_pkey_get_public(file_get_contents(self::KEY_FILE_DIR.'rsa_public.pem'));}/*** @Notes: 获取 Header 头部配置信息* @param string $request_method 请求方法* @param string $url 请求URL* @param int $time_stamp 时间戳* @param array $request_body 请求主体* @return bool|array* @User: zhanghj* @DateTime: 2023-08-09 16:22*/public function getHeaderConf($request_method = 'GET',$url = '',$time_stamp = 0,$request_body = []){$nonce_str = self::createNonceStr(); //32位的随机字符串list($opFlag,$opMsg,$signature_str) = $this->createSignStr($request_method,$url,$time_stamp,$nonce_str,$request_body);if ($opFlag){$header = ['Meican-Developer-Id' => self::DEVELOPER_ID,'Timestamp' => $time_stamp,'Nonce' => $nonce_str,'Sign' => $signature_str,//平台要求,需要 json 格式请求"Content-Type" => 'application/json'];}else{$header = [];$opFlag = false;}return [$opFlag,$opMsg,$header];}/*** @Notes: 生成 32位的随机字符串* @User: zhanghj* @DateTime: 2023-08-09 15:11* @param int $length 字符串位数* @return string*/public static function createNonceStr($length = 32){$nonce_str='';$rand_str= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';$max = strlen($rand_str)-1;for($i = 0;$i < $length;$i++) {$nonce_str .= $rand_str[mt_rand(0,$max)];}return $nonce_str;}/*** @Notes:生成 sha256WithRSA 签名* 提示:SPKI(subject public key identifier,主题公钥标识符)* @param null $signContent     待签名内容* @param string $privateKey    私钥数据(如果为单行,内容需要去掉RSA的标识符)* @param bool $singleRow       是否为单行私钥-标识* @return string               签名串* @User: zhanghj* @DateTime: 2023-09-27 9:41*/public function getSHA256SignWithRSA($signContent = null, $privateKey = '', $singleRow = false){if ($singleRow){//如果传入的私钥是单行数据,且没有RSA的标识符,需做格式转化$privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" .wordwrap($privateKey, 64, "\n", true) ."\n-----END RSA PRIVATE KEY-----";}$key = openssl_get_privatekey($privateKey);//开始加密openssl_sign($signContent, $signature, $key, OPENSSL_ALGO_SHA256);//进行 base64编码 加密后内容$encryptedData = base64_encode($signature);openssl_free_key($key);return $encryptedData;}/*** @Notes:验证 sha256WithRSA 签名* @param null $signContent     待签名内容* @param string $signatureStr  签名串* @param string $public_key    公钥数据(如果为单行,内容需要去掉RSA的标识符)* @param bool $singleRow       是否为单行私钥-标识* @return int                  1:签名成功,0:签名失败* @User: zhanghj* @DateTime: 2023-09-27 10:38*/public static function verifySha256SignWithRSA($signContent = null, $signatureStr = '', $public_key = '',$singleRow = false){if ($singleRow){$public_key = "-----BEGIN PUBLIC KEY-----\n" .wordwrap($public_key, 64, "\n", true) ."\n-----END PUBLIC KEY-----";}$key = openssl_get_publickey($public_key);$ok = openssl_verify($signContent, base64_decode($signatureStr), $key, OPENSSL_ALGO_SHA256);openssl_free_key($key);return $ok;}/*** @Notes: 签名生成* @param string $request_method 请求方法* @param string $url 请求URL* @param int $time_stamp 时间戳* @param string $nonce_str 32位随机字符串* @param array $request_body 请求主体* @return []* @User: zhanghj* @DateTime: 2023-08-09 15:45*/public function  createSignStr($request_method = 'GET',$url = '',$time_stamp = 0,$nonce_str = '',$request_body = []){$op_flag = false;//签名串⼀共有五⾏,每⼀⾏为⼀个参数if ($request_body){$request_body_json = json_encode($request_body);}else{$request_body_json = '';}$sign_str =$request_method."\n".$url."\n".$time_stamp."\n".$nonce_str."\n".$request_body_json."\n";//使⽤开发者私钥对待签名串进⾏ SHA256 with RSA 签名,并对签名结果进⾏ Base64编码 得到签名值$signature_res = self::getSHA256SignWithRSA($sign_str,$this->private_key);// 验证签名是否正确//$result = self::verifySha256SignWithRSA($sign_str,$signature_res,$this->public_key);$result = 1;if ($result == 1) {$op_flag = true;$op_msg = '签名成功';} elseif ($result == 0) {$op_msg = 'Signature is invalid';} else {$op_msg = 'Verification error: ' . openssl_error_string();}return [$op_flag,$op_msg,$signature_res??''];}/*** @Notes: 查询退款 逻辑代码* @param int $order_id* @return array* @User: zhanghj* @DateTime: 2023-08-10 21:02*/public function queryPayRefund($order_id = 0){$curr_time = time();$merchant_id = self::MERCHANT_ID;$currOrderInfo = Order::getOrderInfoByOrderId($order_id,'order_id,order_sn,money_paid');$order_sn = $currOrderInfo['order_sn']??0;$refund_order_id = $order_id.'M'.$order_sn.'F';$url = "/meican-pay-quick/v1/merchants/{$merchant_id}/refund-orders/{$refund_order_id}";$request_body = [];list($opFlag,$opMsg,$opData) = $this->httpToMeicanServer('GET',$url,$curr_time,$request_body);return [$opFlag,$opMsg,$opData??''];}/*** @Notes:全额退款* @param int $order_id* @return array* @User: zhanghj* @DateTime: 2023-11-06 13:03*/public function payFullRefund($order_id = 0){$curr_time = time();$merchant_id = self::MERCHANT_ID;$currOrderInfo = Order::getOrderInfoByOrderId($order_id,'order_id,order_sn,money_paid');$order_sn = $currOrderInfo['order_sn']??0;//检验当前订单id,是否符合快速支付条件$check_msg = (new OrderService())->checkMeicanOrderToPay($order_id,'REFUND');if ($check_msg){$opFlag = false;$opMsg = $check_msg;}else{//查询 美餐支付时的 【order_id】$meicanMasterOrderInfo = Order::getMeicanPayMasterOrderInfoByOrderSn($order_sn,'order_id');$master_order_id = $meicanMasterOrderInfo['order_id']??0;$pay_order_id =  $master_order_id.'M'.$order_sn;$url = "/meican-pay-quick/v1/merchants/{$merchant_id}/orders/{$pay_order_id}/mode-s/refund";$refund_order_id = $order_id.'M'.$order_sn.'F';$request_body = ['refund_order_id' => $refund_order_id,'full_refund' => true,'reason' => 'FULL_REFUND',//退款原因 售货机订单-全额退款'notification_url' => $this->curr_domain.'/meican-pay/refund_notify'];list($opFlag,$opMsg,$opData) = $this->httpToMeicanServer('POST',$url,$curr_time,$request_body);}if ($opFlag){// 判断是否退款申请成功$result_code = $opData['result_code']??'';$refund_json_str = json_encode($opData??[],JSON_UNESCAPED_UNICODE);$save_data = ['refund_json_str' => $refund_json_str,'order_status' => 6];Order::updateOrderByOrderID($order_id,$save_data);if ($result_code == 'OK'){$opMsg = '退款申请成功';}else{$opMsg = '退款接口,调用失败';}}return [$opFlag,$opMsg,$opData??''];}/*** @Notes: 发起退款 逻辑代码* @param int $order_id* @return array* @User: zhanghj* @DateTime: 2023-08-10 21:02*/public function payRefund($order_id = 0){$curr_time = time();$merchant_id = self::MERCHANT_ID;$currOrderInfo = Order::getOrderInfoByOrderId($order_id,'order_id,order_sn,money_paid');$order_sn = $currOrderInfo['order_sn']??0;$money_paid = $currOrderInfo['money_paid']??0;//检验当前订单id,是否符合快速支付条件$check_msg = (new OrderService())->checkMeicanOrderToPay($order_id,'REFUND');if ($check_msg){$opFlag = false;$opMsg = $check_msg;}else{//查询 美餐支付时的 【order_id】$meicanMasterOrderInfo = Order::getMeicanPayMasterOrderInfoByOrderSn($order_sn,'order_id');$master_order_id = $meicanMasterOrderInfo['order_id']??0;$pay_order_id =  $master_order_id.'M'.$order_sn;$url = "/meican-pay-quick/v1/merchants/{$merchant_id}/orders/{$pay_order_id}/mode-s/refund";$refund_order_id = $order_id.'M'.$order_sn.'F';$request_body = ['refund_order_id' => $refund_order_id,'full_refund' => false,'amount' => $money_paid*100,//⽀付⾦额(实付⾦额)分'reason' => '售货机订单-退款',//退款原因'notification_url' => $this->curr_domain.'/meican-pay/refund_notify'];list($opFlag,$opMsg,$opData) = $this->httpToMeicanServer('POST',$url,$curr_time,$request_body);}if ($opFlag){// 判断是否退款申请成功$result_code = $opData['result_code']??'';$refund_json_str = json_encode($opData??[],JSON_UNESCAPED_UNICODE);if ($result_code == 'OK'){$opMsg = '退款申请成功';}else{$opMsg = '退款接口,调用失败';}$save_data = ['refund_json_str' => $refund_json_str,'order_status' => 6];Order::updateOrderByOrderID($order_id,$save_data);}return [$opFlag,$opMsg,$opData??''];}/*** @Notes: 发起退款 逻辑代码 (单商户版本)* @param int $meal_order_id* @param string $order_sn* @param int $money_paid* @return array* @User: zhanghj* @DateTime: 2023-08-10 21:02*/public function payRefundForDealer($meal_order_id = 0,$order_sn = '',$money_paid = 0){$curr_time = time();$merchant_id = self::MERCHANT_ID;//查询 美餐支付时的 【order_id】$pay_order_id =  $meal_order_id.'D'.$order_sn;$url = "/meican-pay-quick/v1/merchants/{$merchant_id}/orders/{$pay_order_id}/mode-s/refund";$refund_order_id = $meal_order_id.'D'.$order_sn.'F';$request_body = ['refund_order_id' => $refund_order_id,'full_refund' => false,'amount' => $money_paid*100,//⽀付⾦额(实付⾦额)分'reason' => '售货机订单-退款',//退款原因'notification_url' => $this->curr_domain.'/meican-pay/refund_notify'];list($opFlag,$opMsg,$opData) = $this->httpToMeicanServer('POST',$url,$curr_time,$request_body);if ($opFlag){// 判断是否退款申请成功$result_code = $opData['result_code']??'';$refund_json_str = json_encode($opData??[],JSON_UNESCAPED_UNICODE);if ($result_code == 'OK'){$opMsg = '退款申请成功';}else{$opMsg = '退款接口,调用失败';}$save_data = ['refund_sn' => $refund_order_id,'order_status' => MealOrder::ORDER_REFUND_IN_PROGRESS,'refund_confirm_at' => time(),'update_at' => time()];MealOrder::updateOrderInfoByOrderId($meal_order_id,$save_data);}return [$opFlag,$opMsg,$opData??''];}/*** @Notes:光眼检测,失败进行退款* @param int $order_id* @param int $refund_fee* @return array* @User: zhanghj* @DateTime: 2023-11-04 18:24*/public function payRefundForLighteyeFailed($order_id = 0,$refund_fee = 0){$curr_time = time();$merchant_id = self::MERCHANT_ID;$orderInfo = Order::getOrderInfoByOrderId($order_id,'order_sn,order_id,order_amount,money_paid,device_id');$device_id = $orderInfo['device_id']??0;$order_sn = $orderInfo['order_sn']??'';$money_paid = $orderInfo['money_paid']??0;//检验当前订单id,是否符合快速支付条件$check_msg = (new OrderService())->checkMeicanOrderToPay($order_id,'REFUND');if ($check_msg){$opFlag = false;$opMsg = $check_msg;}else{$meicanMasterOrder = Order::getMeicanPayMasterOrderInfoByOrderSn($order_sn,'order_id');$master_order_id = $meicanMasterOrder['order_id']??0;$pay_order_id =  $master_order_id.'M'.$order_sn;$url = "/meican-pay-quick/v1/merchants/{$merchant_id}/orders/{$pay_order_id}/mode-s/refund";$refund_order_id = $order_id.'M'.$order_sn.'F';$request_body = ['refund_order_id' => $refund_order_id,'full_refund' => false,'amount' => $money_paid*100,//⽀付⾦额(实付⾦额)分'reason' => '售货机订单-退款',//退款原因'notification_url' => $this->curr_domain.'/meican-pay/refund_notify'];list($opFlag,$opMsg,$opData) = $this->httpToMeicanServer('POST',$url,$curr_time,$request_body);}if ($opFlag){// 判断是否退款申请成功$result_code = $opData['result_code']??'';$refund_json_str = json_encode($opData??[],JSON_UNESCAPED_UNICODE);if ($result_code == 'OK'){$save_data = ['order_status' => 6,'refund_confirm_at' => time(),'light_eye_need_refund' => 2,'refund_amount' => $refund_fee,'refund_json_str' => $refund_json_str];$saveTag = Order::updateOrderByOrderID($order_id,$save_data);if ($saveTag){if(true){$device = Device::find()->where(['device_id'=>$device_id])->one();$device->sale_amount = $device->sale_amount - $refund_fee;$device->order_amount = $device->order_amount - 1;$device->save();}$opMsg = '退款申请成功';}else{$opMsg = '退款更新失败';}}else{$save_data = ['order_status' => 6,'refund_json_str' => $refund_json_str,'light_eye_need_refund' => 3,'refund_amount' => $refund_fee];$saveTag = Order::updateOrderByOrderID($order_id,$save_data);if ($saveTag){$opMsg = '退款申请成功';}else{$opMsg = '退款接口,调用失败:order_id='.$order_id;}}}return [$opFlag,$opMsg,$opData??''];}/*** @Notes: 快速支付* @param array $post_data* @return array* @User: zhanghj* @DateTime: 2023-08-09 19:34* 要求 : 参数需在请求JSON传参*/public function payQuick($post_data = []){$opFlag = false;$opMsg = '';$curr_time = time();$merchant_id = self::MERCHANT_ID;$url = "/meican-pay-quick/v1/merchants/{$merchant_id}/mode-s/pay";$order_sn = $post_data['order_sn']??'';$payer_code = $post_data['payer_code']??'';$quick_type = $post_data['quick_type']??1;$orderInfo = Order::getOrderInfoByOrderSn($order_sn,'order_id,order_amount');$order_id = $orderInfo['order_id']??0;//检验当前订单id,是否符合快速支付条件$check_msg = (new OrderService())->checkMeicanOrderToPay($order_id,'PAY');if ($check_msg){$opMsg = $check_msg;}else{if (!in_array($quick_type,[1,2])){$opMsg = '请确认美餐支付参数';}else{$sum_order_amount = Order::find()->where(['order_sn' => $order_sn])->sum('order_amount');//1:刷卡支付,2:美餐APP反扫码$type_identifier = ($quick_type==1)?'MEICAN_PHYSICAL_CARD':'MEICAN_ELECTRIC_CARD';$request_body = [//可以考虑原订单号加随机数,避免无法付款'order_id' => $order_id.'M'.$order_sn,'store_id' => self::STORE_ID,//TODO 店铺ID'expire_time' => $curr_time+(6*3600),'description' => 'MEICAN_PAY',//⽀付单描述 售货机订单-美餐⽀付'payer' => ['payer_type' => 'CARD', //用户RN支付类型'id_card' => ['type_identifier' => $type_identifier ,//物理卡类型、美餐付款码类型'code' => $payer_code,//卡内码],],'total' => $sum_order_amount * 100,//⽀付⾦额(实付⾦额)分'currency' => 'CNY','notification_url' => $this->curr_domain.'/meican-pay/pay_notify'];list($opFlag,$opMsg,$opData) = $this->httpToMeicanServer('POST',$url,$curr_time,$request_body);}}if ($opFlag){$this->logInfoToRuntime('actionPayQuick','订单ID【'.$order_id.'】'.json_encode($opData??[],JSON_UNESCAPED_UNICODE));// 判断是否支付成功$result_code = $opData['result_code']??'';$result_description = $opData['result_description']??'';if ($result_code == 'OK'){$save_data = ['pay_type' => ($quick_type==1)?4:5,'pay_time' => time()];$saveTag = Order::updateOrderByOrderSn($order_sn,$save_data);if ($saveTag){$opMsg = '支付成功';}else{$opFlag = false;$opMsg = '支付更新失败';}}else{$opFlag = false;$opMsg = '支付接口,调用失败:'.$result_description;}}return [$opFlag,$opMsg,$opData??''];}/*** @Notes: 快速支付* @param int $order_id* @param string $order_sn* @param int $sum_order_amount* @param string $payer_code* @return array* @User: zhanghj* @DateTime: 2023-08-09 19:34* 要求 : 参数需在请求JSON传参*/public function payQuickForDealer($order_id = 0,$order_sn = '',$sum_order_amount = 0,$payer_code = ''){$opFlag = false;$opMsg = '';$curr_time = time();$merchant_id = self::MERCHANT_ID;$url = "/meican-pay-quick/v1/merchants/{$merchant_id}/mode-s/pay";$quick_type = 1;if (!in_array($quick_type,[1,2])){$opMsg = '请确认美餐支付参数';}else{//1:刷卡支付,2:美餐APP反扫码$type_identifier = ($quick_type==1)?'MEICAN_PHYSICAL_CARD':'MEICAN_ELECTRIC_CARD';$request_body = [//可以考虑原订单号加随机数,避免无法付款'order_id' => $order_id.'D'.$order_sn,'store_id' => self::STORE_ID,//TODO 店铺ID'expire_time' => $curr_time+(6*3600),'description' => 'MEICAN_PAY',//⽀付单描述 售货机订单-美餐⽀付'payer' => ['payer_type' => 'CARD', //用户RN支付类型'id_card' => ['type_identifier' => $type_identifier ,//物理卡类型、美餐付款码类型'code' => $payer_code,//卡内码],],'total' => $sum_order_amount * 100,//⽀付⾦额(实付⾦额)分'currency' => 'CNY','notification_url' => $this->curr_domain.'/meican-pay/pay_notify'];list($opFlag,$opMsg,$opData) = $this->httpToMeicanServer('POST',$url,$curr_time,$request_body);}if ($opFlag){$this->logInfoToRuntime('actionDealerMeicanImmediatePayment','订单ID【'.$order_id.'】'.json_encode($opData??[],JSON_UNESCAPED_UNICODE));// 判断是否支付成功$result_code = $opData['result_code']??'';$result_description = $opData['result_description']??'';if ($result_code == 'OK'){$save_data = ['pay_type' => 4];$saveTag = MealOrder::updateOrderInfoByOrderId($order_id,$save_data);if ($saveTag){$opMsg = '支付成功';}else{$opFlag = false;$opMsg = '支付更新失败';}}else{$opFlag = false;$opMsg = '支付接口,调用失败:'.$result_description;}}return [$opFlag,$opMsg,$opData??''];}/*** @Notes: 封装请求方法* @param string $request_method* @param string $url* @param int $curr_time* @param array $request_body* @return array* @User: zhanghj* @DateTime: 2023-08-09 19:46*/public function httpToMeicanServer( $request_method = 'GET',$url = '',$curr_time = 0,$request_body = []){list($opFlag,$opMsg,$header_data) = self::getHeaderConf($request_method,$url,$curr_time,$request_body);if ($opFlag){$options = ['headers' => $header_data,];if ($request_method == 'GET'){$options['query'] = $request_body;}else{//参数需在请求JSON传参//$options['form_params'] = $request_body;$options['json'] = $request_body;}try {$response  = $this->httpClient->request($request_method,$url,$options);$contents = $response->getBody().'';$opData = json_decode($contents,true);$opMsg = '请求成功';}catch (\Exception $exception){$opFlag = false;$opMsg = $exception->getMessage();}}return [$opFlag,$opMsg,$opData??''];}/*** @Notes: 处理支付回调逻辑* @param string $raw_json* @return bool* @User: zhanghj* @DateTime: 2023-08-10 15:46*/public function dealToPayNotify($raw_json = ''){$op_flag = false;if ($raw_json){//进行日志记录$this->logInfoToRuntime('actionPayNotify',$raw_json);$raw_arr = json_decode($raw_json,true);if (is_array($raw_arr)){$return_order_id = $raw_arr['order_id']??'';//订单ID$isClientOrder = strrpos($return_order_id,'M');$isMealOrder = strrpos($return_order_id,'D');if ($isClientOrder){//此为 设备订单,美餐支付回调$orderSn = explode('M',$return_order_id)[1]??'';$orderList = Order::find()->where(['order_sn'=>$orderSn])->select('order_id,order_sn,pay_type,order_status,order_amount')->asArray()->all();if ($orderList){foreach ($orderList as $key => $currOrder){//检查是否已支付if ($currOrder){$pay_type = $currOrder['pay_type'];$order_id = $currOrder['order_id']??0;if (in_array($pay_type,[4,5]) && $currOrder['order_status']==1){$money_paid = $currOrder['order_amount']??0;$save_data = ['pay_time' => time(),'order_status' => 2,'money_paid' => $money_paid,'payment_json_str' => $raw_json];//进行订单表更新$saveFlag = Order::updateOrderByOrderID($order_id,$save_data);if ($saveFlag){$op_flag = true;}}else{//订单已不是待支付状态,无需再次请求$this->logInfoToRuntime('actionPayNotify','订单ID【'.$order_id.'】非待支付状态,无需再次请求');$op_flag = true;}}}}}elseif ($isMealOrder){//此为 单商户外卖订单 美餐支付回调$orderSn = explode('D',$return_order_id)[1]??'';$order = MealOrder::findInfoByOrderSn($orderSn);if ($order->order_status == MealOrder::ORDER_UNPAID) {$money_paid = $raw_arr['transaction']['total']??0;//支付⾦额 (⼈⺠币 - 分)$order->order_status = MealOrder::ORDER_PAID;$order->money_paid   = bcdiv($money_paid, 100, 2);$order->pay_time     = time();$order->update_at    = time();if ($order->save()) {$op_flag = true;}}}}}return $op_flag;}/*** @Notes: 处理退款回调逻辑* @param string $raw_json* @return bool* @User: zhanghj* @DateTime: 2023-08-10 15:46*/public function dealToRefundNotify($raw_json = ''){$op_flag = false;if ($raw_json){$this->logInfoToRuntime('actionRefundNotify',$raw_json);$raw_arr = json_decode($raw_json,true);if (is_array($raw_arr)){$refund_order_id = $raw_arr['refund_order_id']??'';//订单ID$isClientOrder = strrpos($refund_order_id,'M');$isMealOrder = strrpos($refund_order_id,'D');$refund_amount = $raw_arr['transaction']['amount']??0;//退款⾦额 (⼈⺠币 - 分)if ($isClientOrder){//此为设备订单,美餐支付退款$order_id = explode('M',$refund_order_id)[0]??'';$save_data = ['order_status' => 8,'refund_json_str' => $raw_json,'refund_amount' => $refund_amount/100];//进行订单表更新$saveFlag = Order::updateOrderByOrderID($order_id,$save_data);if ($saveFlag){$op_flag = true;}}elseif ($isMealOrder){//此为单商户 外卖订单美餐支付退款$order_id = explode('D',$refund_order_id)[0]??'';$save_data = ['order_status' => MealOrder::ORDER_REFUNDED,'update_at' => time(),];//进行订单表更新$saveFlag = MealOrder::updateOrderInfoByOrderId($order_id,$save_data);if ($saveFlag){$op_flag = true;}}}}return $op_flag;}/*** @Notes: 日志整理记录* @param string $title* @param string $log_message* @User: zhanghj* @DateTime: 2023-08-11 14:49*/public function logInfoToRuntime($title = '',$log_message = ''){$raw_arr = json_decode($log_message,true);if (is_array($raw_arr)){$log_content = json_encode($raw_arr,JSON_UNESCAPED_UNICODE);}else{$log_content = $log_message;}//进行日志记录$project_dir = 'clientapi';$file_name = 'meican_pay_'.date('Ym').'_log.txt';Helper::addLog($project_dir, $log_content, $title,$file_name);//\Yii::info("{$title}: ".$log_content,'meican_pay');}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/608042.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis-Plus Generator代码生成器

1.加入mybatis-plus相关依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version> </dependency><dependency><groupId>com.baomidou<…

Git 忽略提交 .gitignore

Git 忽略提交 .gitignore 在使用Git的过程中&#xff0c;我们喜欢有的文件比如日志&#xff0c;临时文件&#xff0c;编译的中间文件等不要提交到代码仓库&#xff0c;这时就要设置相应的忽略规则&#xff0c;来忽略这些文件的提交。 Git 忽略文件提交的方法 有三种方法可以实…

铸铁检验平台主要应用在哪些行业中——河北北重

铸铁检验平台可应用于以下行业&#xff1a; 汽车制造业&#xff1a;用于检验汽车零部件的铸铁材质和质量&#xff0c;以确保零部件的可靠性和耐用性。 机械制造业&#xff1a;用于检验铸铁机械零部件的质量和性能&#xff0c;以确保机械设备的稳定运行。 建筑工程&#xff1a…

《罗素论教育》笔记

目录 全书架构 书简介 经典摘录 一、教育的理想 教育的基本原理 教育的目的 二、品性的教育 一岁前的教育 主要是2岁到6岁的教育 三、智力教育 14岁前的课程安排 最后的学年 大学教育 四、结束语 全书架构 书简介 经典摘录 一、教育的理想 教育的基本原理 1、我…

ROS学习笔记(二):话题通信、服务通信的了解和对应节点的搭建(C++)

ROS学习笔记&#xff08;二&#xff09;&#xff1a;话题通信、服务通信的了解和对应节点的搭建&#xff08;C和Python&#xff09; 前言一、Topics话题通信&#xff08;C&#xff09;0、自定义msg消息类型文件1、发布者&#xff08;Publisher&#xff09;2、订阅者&#xff08…

thinkphp递归实现无限级子分类合并上级children

//设别分类列表public function getCategoryList(){$list = Db::name(categorys)->select(

【Python机器学习】决策树的优缺点

控制决策树模型复杂度的参数是预剪枝参数&#xff0c;它在树完全展开之前停止树的构造。 决策树的优点&#xff1a; 1、得到的模型很容易可视化 2、算法完全不受数据缩放的影响 决策树算法不需要特征预处理&#xff0c;比如归一化或标准化。特别是特征的尺度完全不一样时或…

MODBUS转PROFINET网关与全数字交流伺服配置案例

MODBUS转PROFINET网关连接与全数字交流伺服驱动系统的配置案例&#xff0c;这一通信方式极大地简化了工业自动化系统中的数据传输和控制过程。变频器和伺服电机可以实现数据交流和控制指令的实时传输&#xff0c;从而实现更精确更高效的生产过程。 案例简介&#xff1a;本案例是…

跟我学java|Stream流式编程——Stream 基础

一、流式编程的概念和作用 Java 流(Stream)是一连串的元素序列&#xff0c;可以进行各种操作以实现数据的转换和处理。流式编程的概念基于函数式编程的思想&#xff0c;旨在简化代码&#xff0c;提高可读性和可维护性。 Java Stream 的主要作用有以下几个方面&#xff1a; 简化…

苹果Find My查找芯片-伦茨科技ST17H6x支持苹果Find My认证

Apple「查找」Find My可通过庞大的“Apple Find My Network” 实现全球查找功能。无数iOS、iPadOS、macOS、watchOS激活设备与Find My 设备结合在一起&#xff0c;无需连接到Wi-Fi或者蜂窝网络&#xff0c;用户也可以给遗失的设备定位。对于任何iOS、iPadOS、macOS、watchOS设备…

LeetCode 49. 字母异位词分组

49. 字母异位词分组 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs ["eat", "tea", "tan", "ate"…

el-dialog的modal-class

今天发现个事 <el-dialogv-model"bindDialogVisible":title"bindDialogTitle"append-to-bodyclose-on-press-escapedraggablemodal-class"bindNdevice-dialog"width"500px"></el-dialog> 这个样式这样写生效 <style …

【Docker】私有仓库

目录 1.搭建 2. 上传镜像 3.拉取镜像 1.搭建 1.拉取私有仓库的镜像 docker pull registry 2.创建私有仓库容器 docker run -id --nameregistry -p 5000:5000 registry 3.打开浏览器,输入地址&#xff08;http:私有仓库服务器ip:5000/v2/_catalog&#xff09; 出现如图表示私…

什么是diffusion模型?

Diffusion模型是一种生成模型&#xff0c;用于在深度学习和人工智能领域生成高质量、逼真的数据&#xff0c;尤其是在图像生成方面表现出色。这种模型的基本思想是首先生成一个随机的噪声数据&#xff0c;然后逐步将这个噪声转化为有意义的数据&#xff08;如图像&#xff09;&…

【数据结构】栈的基本知识详解

栈的基本概念与基本操作 导言一、栈的基本概念1.1 栈的定义1.2 栈的重要术语1.3 栈的数学性质 二、栈的基本操作结语 导言 大家好&#xff0c;很高兴又和大家见面了&#xff01;&#xff01;&#xff01; 今天开始&#xff0c;咱们将正式进入【数据结构】第三章的内容介绍。在…

MySql02:增删改查

1.外键索引(外键约束)2.DML - 添加 、修改 、删除2.1添加 insert2.2修改 update2.3删除 delete2.4删除的三种方式 3.DQL - 查询关键字3.1 普通查询3.2 as 关键字3.3 distinct 去除重复的内容3.4 where 条件3.5 between and 关键字3.6 like 实现模糊查询3.7 in 范围查询3.8 null…

vue3用户权限管理(路由控制等)1

在前端开发的过程中&#xff0c;我们需要做前端的权限管理&#xff0c;我们需要根据后端提供的信息来控制权限&#xff0c;这时候就需要根据用户的操作来进行权限控制了。逻辑稍微有一点绕&#xff0c;多理解就好了。 用户路由权限管理 大致的实现原理&#xff1a; 一般将路由…

解析IT运维领域ITSS和ITIL证书

&#x1f33b;IT运维领域ITSS和ITIL证书是两种广泛认可的专业认证。 &#x1f4d7;ITSS认证证书 ITSS是中国电子技术标准化研究院推出的&#xff0c;&#x1f449;包含“IT 服务工程师”和“IT 服务经理”的系列培训。有效满足GB/T 28827.1 的符合性评估要求和ITSS服务资质升级…

JavaScript音视频,JavaScript简单获取电脑摄像头画面并播放

前言 本章实现JavaScript简单获取电脑摄像头画面并播放的功能 兼容性(不支持Node.js) 需要注意的是,由于涉及到用户的隐私和安全,获取用户媒体设备需要用户的明确同意,并且可能需要在用户的浏览器中启用相关的权限。在某些浏览器中,可能需要用户手动开启摄像头权限。 …

中国建设银行 关于解决微软升级导致插入网银盾无法自动打开企业网银的通知

关于解决微软升级导致插入网银盾无法自动打开企业网银的通知 发布时间&#xff1a;2023-10-18 尊敬的客户&#xff1a; 近期Windows操作系统升级会禁止使用IE浏览器&#xff0c;可能会导致您在插入网银盾后无法自动弹出企业网银登录页面&#xff0c;您可以通过以下方式解决&…