> 技术文档 > crmeb多门店对接拉卡拉支付小程序聚合收银台集成全流程详解_crmeb如何对接第三方支付

crmeb多门店对接拉卡拉支付小程序聚合收银台集成全流程详解_crmeb如何对接第三方支付


一、商户注册与配置

  1. ​注册支付平台账号​​:在拉卡拉开放平台注册商户账号(私信联系注册)
  2. ​创建应用​​:获取小程序应用ID(AppID)
  3. ​配置支付参数​​:
    • 商户号(MID)
    • 终端号(TID)
    • API密钥
    • 支付回调地址

二、配置拉卡拉参数(后台)

app/admin/controller/system/config/PayConfig.php中添加:

// 文件路径:app/admin/controller/system/config/PayConfig.phppublic function index(){ //...已有代码... $list = [ // 添加拉卡拉支付配置 [ \'menu_name\' => \'拉卡拉支付\', \'config\' => [ // 商户编号 [  \'type\' => \'text\',  \'name\' => \'lakala_merchant_id\',  \'title\' => \'商户号(MID)\', ], // 终端号 [  \'type\' => \'text\',  \'name\' => \'lakala_terminal_id\',  \'title\' => \'终端号(TID)\', ], // API密钥 [  \'type\' => \'text\',  \'name\' => \'lakala_api_key\',  \'title\' => \'API密钥\', ], // 应用ID(小程序) [  \'type\' => \'text\',  \'name\' => \'lakala_app_id\',  \'title\' => \'小程序AppID\', ], // 是否启用 [  \'type\' => \'radio\',  \'name\' => \'lakala_status\',  \'title\' => \'启用状态\',  \'value\' => 0,  \'options\' => [ [\'label\' => \'关闭\', \'value\' => 0], [\'label\' => \'开启\', \'value\' => 1]  ] ] ] ] ]; //...后续代码...}

三、支付服务层(核心)

// 文件路径:app/services/pay/LakalaPayService.phpgetConfig(); if (!$config[\'status\']) throw new \\Exception(\'拉卡拉支付未开启\'); $params = [ \'version\' => \'1.0\', \'merchant_id\' => $config[\'merchant_id\'], \'terminal_id\' => $config[\'terminal_id\'], \'biz_type\' => \'MINIPRO\', \'trade_type\' => \'JSAPI\', \'notify_url\' => sys_config(\'site_url\') . \'/api/pay/lakala/notify\', \'out_trade_no\' => $order[\'order_id\'], \'total_fee\' => bcmul($order[\'pay_price\'], 100), // 转为分 \'body\' => \'订单支付\', \'sub_appid\' => $config[\'app_id\'], \'sub_openid\' => $order[\'openid\'], // 小程序用户openid \'attach\' => \'store_id:\' . $order[\'store_id\'] // 多门店标识 ]; // 生成签名 $params[\'sign\'] = $this->generateSign($params, $config[\'api_key\']); // 请求拉卡拉接口 $result = $this->curlPost($this->apiUrl, $params); if ($result[\'return_code\'] != \'SUCCESS\') { throw new \\Exception(\'拉卡拉支付请求失败: \' . $result[\'return_msg\']); } // 返回小程序支付参数 return [ \'appId\' => $config[\'app_id\'], \'package\' => \'prepay_id=\' . $result[\'prepay_id\'], \'timeStamp\' => (string) time(), \'nonceStr\' => get_nonce(16), \'signType\' => \'MD5\', \'paySign\' => $this->generateJsSign($result, $config[\'api_key\']) ]; } // 生成支付签名 private function generateSign($data, $key) { ksort($data); $string = \'\'; foreach ($data as $k => $v) { if ($v === \'\' || $k == \'sign\') continue; $string .= $k . \'=\' . $v . \'&\'; } $string .= \'key=\' . $key; return strtoupper(md5($string)); } // 生成JS支付签名 private function generateJsSign($result, $key) { $data = [ \'appId\' => $result[\'appid\'], \'timeStamp\' => (string) time(), \'nonceStr\' => get_nonce(16), \'package\' => \'prepay_id=\' . $result[\'prepay_id\'], \'signType\' => \'MD5\' ]; ksort($data); $string = implode(\'&\', array_map(function($k, $v) { return \"$k=$v\"; }, array_keys($data), $data)); $string .= \'&key=\' . $key; return strtoupper(md5($string)); } // 处理支付回调 public function handleNotify() { $xml = file_get_contents(\'php://input\'); $data = json_decode(json_encode(simplexml_load_string($xml, \'SimpleXMLElement\', LIBXML_NOCDATA)), true); // 验证签名 $sign = $data[\'sign\']; unset($data[\'sign\']); if ($sign != $this->generateSign($data, config(\'pay.lakala_api_key\'))) { return false; } // 获取门店ID $attach = explode(\':\', $data[\'attach\']); $storeId = isset($attach[1]) ? intval($attach[1]) : 0; /** @var StoreOrderServices $orderService */ $orderService = app()->make(StoreOrderServices::class); return $orderService->successPay($data[\'out_trade_no\'], [ \'pay_type\' => \'lakala\', \'store_id\' => $storeId ]); } // 获取配置 private function getConfig() { return [ \'merchant_id\' => sys_config(\'lakala_merchant_id\'), \'terminal_id\' => sys_config(\'lakala_terminal_id\'), \'api_key\' => sys_config(\'lakala_api_key\'), \'app_id\' => sys_config(\'lakala_app_id\'), \'status\' => sys_config(\'lakala_status\') ]; } // HTTP POST请求 private function curlPost($url, $data) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); $response = curl_exec($ch); curl_close($ch); return json_decode(json_encode(simplexml_load_string($response)), true); }}

四、支付控制器

// 文件路径:app/api/controller/v1/pay/PayController.phppublic function lakalaPay(){ $orderId = $this->request->param(\'order_id\'); $openid = $this->request->param(\'openid\'); // 小程序获取的openid // 验证订单 $order = $this->validateOrder($orderId, $openid); try { /** @var LakalaPayService $lakala */ $lakala = app()->make(LakalaPayService::class); $payment = $lakala->miniPay([ \'order_id\' => $orderId, \'pay_price\' => $order[\'pay_price\'], \'openid\' => $openid, \'store_id\' => $order[\'store_id\'] ]); return $this->success(compact(\'payment\')); } catch (\\Throwable $e) { return $this->fail($e->getMessage()); }}

五、小程序端调用

// 小程序端支付调用wx.request({ url: \'/api/pay/lakala\', method: \'POST\', data: { order_id: \'订单ID\', openid: \'用户openid\' }, success: (res) => { const payment = res.data.payment; wx.requestPayment({ appId: payment.appId, timeStamp: payment.timeStamp, nonceStr: payment.nonceStr, package: payment.package, signType: payment.signType, paySign: payment.paySign, success: () => { wx.showToast({ title: \'支付成功\' }); }, fail: (err) => { wx.showToast({ title: \'支付失败\', icon: \'error\' }); } }); }});

六、回调路由设置

// 文件路径:route/app.phpRoute::post(\'api/pay/lakala/notify\', \'api/pay.Pay/lakalaNotify\');

七、回调控制器

// 文件路径:app/api/controller/pay/Pay.phppublic function lakalaNotify(){ /** @var LakalaPayService $lakala */ $lakala = app()->make(LakalaPayService::class); try { $result = $lakala->handleNotify(); if ($result) { return response(\'SUCCESS\', 200, [], \'xml\'); } } catch (\\Throwable $e) { Log::error(\'拉卡拉回调异常:\' . $e->getMessage()); } return response(\'FAIL\', 200, [], \'xml\');}

配置注意事项:

  1. ​拉卡拉参数​​:在后台系统中配置商户号、终端号、API密钥和小程序AppID
  2. ​商户证书​​:如需双向验证,需在CURL请求中添加证书配置
  3. ​多门店处理​​:
    • 支付请求中附加store_id参数
    • 回调中解析门店ID并更新对应门店订单
  4. ​跨域问题​​:确保API路由支持小程序跨域请求

签名验证流程:

  1. 所有参数按参数名ASCII码升序排序
  2. 使用URL键值对格式拼接参数
  3. 拼接API密钥(&key=XXX
  4. 对结果进行MD5签名(转大写)