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签名(转大写)
相关推荐
Jtti11 小时前
如何准确查看服务器网络的利用率?
开发语言·php
rufeii12 小时前
php的原生类
php·原生类
wkj00112 小时前
php 如何通过mysqli操作数据库?
android·数据库·php
難釋懷15 小时前
微信小程序全局配置
微信小程序·小程序
Enti7c15 小时前
微信小程序核心知识点速览
微信小程序·小程序
饶了我吧,放了我吧18 小时前
计算机网络实验——以太网安全实验
计算机网络·安全·php
wkj00119 小时前
php中调用对象的方法可以使用array($object, ‘methodName‘)?
android·开发语言·php
wkj00119 小时前
php use 命名空间与 spl_autoload_register的关系
android·php·android studio
weixin_4432906919 小时前
【云服务器安全相关】堡垒机、WAF、防火墙、IDS 有什么区别?
服务器·安全·php