一、商戶注冊與配置
- ??注冊支付平臺賬號??:在拉卡拉開放平臺注冊商戶賬號(私信聯系注冊)
- ??創建應用??:獲取小程序應用ID(AppID)
- ??配置支付參數??:
- 商戶號(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.php<?php
namespace app\services\pay;use think\facade\Config;
use app\services\BaseServices;
use app\services\order\StoreOrderServices;class LakalaPayService extends BaseServices
{protected $apiUrl = 'https://api.lakala.com/payment/gateway'; // 正式環境// protected $apiUrl = 'https://test.api.lakala.com/payment/gateway'; // 測試環境// 小程序支付下單public function miniPay($order){$config = $this->getConfig();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('<xml><return_code>SUCCESS</return_code></xml>', 200, [], 'xml');}} catch (\Throwable $e) {Log::error('拉卡拉回調異常:' . $e->getMessage());}return response('<xml><return_code>FAIL</return_code></xml>', 200, [], 'xml');
}
配置注意事項:
- ??拉卡拉參數??:在后臺系統中配置商戶號、終端號、API密鑰和小程序AppID
- ??商戶證書??:如需雙向驗證,需在CURL請求中添加證書配置
- ??多門店處理??:
- 支付請求中附加
store_id
參數 - 回調中解析門店ID并更新對應門店訂單
- 支付請求中附加
- ??跨域問題??:確保API路由支持小程序跨域請求
簽名驗證流程:
- 所有參數按參數名ASCII碼升序排序
- 使用URL鍵值對格式拼接參數
- 拼接API密鑰(
&key=XXX
) - 對結果進行MD5簽名(轉大寫)