美餐支付 - 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,一经查实,立即删除!

相关文章

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

铸铁检验平台可应用于以下行业&#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(

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设备…

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; 出现如图表示私…

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

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

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服务资质升级…

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

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

QUV紫外光老化加速试验机

1.1 IEC61215标准背景 IEC61215Crystallinesiliconterrestrialphotovoltaic(PV)modules—Designqualificationandtypeapproval》是国际电工委员会的一个产品测试方法。目前太阳能行业正在广泛引用这个标准&#xff0c;对材料或产品进行测试。 2 材料耐候性老化测试原理 在介…

杨中科 ASP.NET Core 中的依赖注入的使用

ASP.NET CORE中服务注入的地方 1、在ASP.NET Core项目中一般不需要自己创建ServiceCollection、IServiceProvider。在Program.cs的builder.Build()之前向builderServices中注入 2、在Controller中可以通过构造方法注入服 务。 3、演示 新建一个calculator类 注入 新建TestC…

网页内容任君采撷-右键无法复制

CSDN一年一度的博客之星评选活动已经结束&#xff0c;刚好点击来看看学习一下大佬们的博客。 发现绝大部分的博主对于知识的公开度都是非常高的&#xff0c;当然除了收费的专栏外。 其中少部分博主对自己的博文设定了一定的操作&#xff0c;无法直接使用博文中的内容。 现在大…

Photoshop Express一款出色的照片编辑器

​【应用名称】&#xff1a;Photoshop Express ​【适用平台】&#xff1a;#Android ​【软件标签】&#xff1a;#Photoshop ​【应用版本】&#xff1a;12.1.2 ​【应用大小】&#xff1a;223MB ​【软件说明】&#xff1a;软件升级更新。一款出色的照片编辑器&#xff0c…

生成模型 | 2024年新年新论文:audio2photoreal[正在更新中]

本博客主要包含了20240103新出的论文From Audio to Photoreal Embodiment: Synthesizing Humans in Conversations论文解释及项目实现~ 论文题目&#xff1a;20240103_From Audio to Photoreal Embodiment: Synthesizing Humans in Conversations 论文地址&#xff1a;2401.018…

8年测试总结,正确的自动化测试实施-单元/接口/Web自动化...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 今天给大家分享自…

作业--day42

界面设计 MyProWin::MyProWin(QWidget *parent): QMainWindow(parent) {/**********窗口主体**********///窗口大小this->setFixedSize(644, 493);this->setWindowTitle("QQ");this->setWindowIcon(QIcon("C:/Users/10988/Downloads/pictrue/pictrue/…