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

发布于:2025-07-12 ⋅ 阅读:(37) ⋅ 点赞:(0)

一、商户注册与配置

  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签名(转大写)

网站公告

今日签到

点亮在社区的每一天
去签到