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签名(转大写)
相关推荐
AI浩13 小时前
深入级联不稳定性:从 Lipschitz 连续性视角探讨图像恢复与目标检测的协同作用
人工智能·目标检测·php
2501_9159184113 小时前
App 上架苹果商店全流程详解 从开发者账号申请到开心上架(Appuploader)跨平台免 Mac 上传实战指南
macos·ios·小程序·uni-app·objective-c·cocoa·iphone
说私域14 小时前
定制开发开源AI智能名片S2B2C商城小程序中的羊群效应应用研究
人工智能·小程序
一匹电信狗14 小时前
【C++】红黑树详解(2w字详解)
服务器·c++·算法·leetcode·小程序·stl·visual studio
CsharpDev-奶豆哥15 小时前
微信小程序通过主键ID修改json数据的技术分享
微信小程序·小程序·json
汤姆yu15 小时前
基于微信小程序的防诈骗管理系统
微信小程序·小程序·防诈骗管理
2501_9160074715 小时前
从零开始学习iOS App开发:Xcode、Swift和发布到App Store完整教程
android·学习·ios·小程序·uni-app·iphone·xcode
2501_9160088916 小时前
前端工具全景实战指南,从开发到调试的效率闭环
android·前端·小程序·https·uni-app·iphone·webview
杜子不疼.17 小时前
Linux】 性能调优实战:内核参数优化技巧
linux·运维·php
立早正文17 小时前
Docker从零到一部署DNMP+Redis《全程干货》
docker·容器·php