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签名(转大写)
相关推荐
CRMEB定制开发7 小时前
CRMEB私域电商系统后台开发实战:小程序配置全流程解析
小程序·开源软件·小程序商城·商城源码·微信商城·crmeb
2501_9151063210 小时前
iOS混淆工具实战 金融支付类 App 的安全防护与合规落地
android·ios·小程序·https·uni-app·iphone·webview
從南走到北13 小时前
JAVA国际版东郊到家同城按摩服务美容美发私教到店服务系统源码支持Android+IOS+H5
android·java·开发语言·ios·微信·微信小程序·小程序
Summer不秃13 小时前
uniapp 手写签名组件开发全攻略
前端·javascript·vue.js·微信小程序·小程序·html
林深时见鹿74914 小时前
使用k8s k3s kuboard 部署 php hyperf 框架
php
长城202414 小时前
从词源和输出生成等角度详细解析PHP中常用文件操作类函数
php·文件·函数·文件操作函数
长城202414 小时前
PHP如何使用JpGraph生成3D饼形图?
开发语言·php·jpgraph·3d饼形图
熬夜苦读学习21 小时前
Reactor 反应堆模式
运维·服务器·网络·网络协议·http·智能路由器·php
小森林821 小时前
分享一次Guzzlehttp上传批量图片优化的经历
后端·php
THMAIL1 天前
大模型0基础开发入门与实践:第11章 进阶:LangChain与外部工具调用
开发语言·langchain·php