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

一、商户注册与配置

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

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

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

复制代码
// 文件路径:app/admin/controller/system/config/PayConfig.php

public 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.php

public 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.php

Route::post('api/pay/lakala/notify', 'api/pay.Pay/lakalaNotify');

七、回调控制器

复制代码
// 文件路径:app/api/controller/pay/Pay.php

public 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');
}

配置注意事项:

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

签名验证流程:

  1. 所有参数按参数名ASCII码升序排序
  2. 使用URL键值对格式拼接参数
  3. 拼接API密钥(&key=XXX
  4. 对结果进行MD5签名(转大写)
相关推荐
BingoGo2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php
JaguarJack2 天前
当你的 PHP 应用的 API 没有限流时会发生什么?
后端·php·服务端
BingoGo3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack3 天前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo4 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack5 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理5 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082855 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe5 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5