# H5免登接口

# 1.概述

客户平台通过H5免登方式使用美团企业版服务,根据productType等字段传参,跳转对应页面。
注意点:

1.前端不可使用iframe方式接入

2.免登接口返回的重定向链接不可多次使用,每次登录都须调用免登接口

3.免登成功后,将会写入美团相关的 Cookie 信息。请不要手动操作/清理/覆盖 Web 容器中美团域名下的 Cookie,否则将会导致登录状态失效,频繁登录,帐号被风控等异常问题的出现

# 2.接口基本信息

名称 描述
请求方式 POST
调用地址 测试环境:https://waimai-openapi.apigw.test.meituan.com/api/sqt/open/login/h5/loginFree/redirection?test_open_swimlane=test-open
正式环境:https://bep-openapi.meituan.com/api/sqt/open/login/h5/loginFree/redirection
调用方 客户平台
响应方 美团企业版
响应超时时间 3秒
调用限频 每分钟访问不超过100次,每天累计访问不超过100000次

# 2.1.请求体

名称 类型 是否必填 示例 说明
accessKey String 美团企业版分配给客户的接入秘钥
content String UgxoCGPQIzoP 请求体内容,将请求参数JSON序列化后进行加密的结果值,参照:签名实例

# content加密前数据结构

名称 类型 必填 示例 说明
ts Long 1617085650321 13位时间戳。若请求发起时间与平台接受请求时间相差大于10分钟,平台将直接拒绝本次请求
entId Long 2789 固定值,由美团企业版提供
staffInfo Object {
  "staffPhone": "18842600000",
  "staffEmail": "zhangsan@company.com",
  "staffNum": "123123DFSAS23",
  "staffName": "张三",
  "orgName": "部门1",
  "externalOrgId": "organization1",
  "invoiceTitle": "xxxx公司",
  "invoiceNum": "税号"
}
staffPhone 员工手机号
staffEmail 员工邮箱
staffNum 员工工号
staffName 员工姓名
orgName: 部门名称
externalOrgId: 部门唯一标识
invoiceTitle: 发票抬头
invoiceNum: 税号
1. 登录时, staffPhone/staffEmail/staffNum 三者必填一个, 与企业员工唯一识别对应
2. 部门信息, 发票, 税号非必填
3. 自动添加员工时, 对应信息将填充进员工信息中
ssoUser String {"id":"1","name":"a"} 自定义键值对,key、value均可自定义,对接第三方收银台透传用户额外信息时使用,会在调用第三方收银台-下单接口时,通过extInfoMap字段透传给企业
nonce String 3c72e9102112e346c19d9460d1ff15b0a5d2 每次请求时,随机生成36位由字母数字组成的字符串
productType String dp_canyin: 到店商家列表页
mt_waimai: 外卖商家列表页
sqt_center: 工作台
sqt_info: 我的
sqt_home: 美团企业版首页
mt_hotel: 美团企业版酒店预定页
mt_car: 美团打车
mt_maicai: 美团买菜
sqt_peican: 配餐首页
sqt_order_detail: 订单详情
repay_order_detail: 偿还单详情
order_list: 订单列表
audit_detail: 审批详情
mt_maoyan: 猫眼电影
mt_maiyao: 美团买药
pao_tui: 美团跑腿
mt_wmvoucher: 商品券

以下只支持ToB企业
mt_train: 火车票
mt_plane: 飞机票
产品类型
sceneType Integer 1 场景类型
1: 商务宴请
2: 企业用车
3: 商务差旅
4: 工作餐
5: 团建用餐
8: 员工福利
9: 供给分销
bizParam Object 如productType=dp_canyin时, 传递
{
    "channelType": 1,
    "lockCityType: " 1,
    ......
}
根据productType,按照下表进行参数传递

# bizParam参数结构

业务类型 名称 类型 必填 示例 说明
餐饮
dp_canyin
location Location Location 经纬度信息
budgetKey String 12345 第三方预算key,接入第三方预算时必传
poiDetailJson String {"mtPoiId":"165548321","ruleId":-1} 门店详情,跳转门店详情时必传,mtPoiId和ruleId参数不可变
channelType Integer 1 当productType=dp_canyin时,跳转页面路由,不传值时默认跳转餐饮列表
0: 餐饮列表-企业允许的全量到店商家列表
1: 团购列表-可以下团购单的商家列表,且仅展示团购支付方式
2: 特许餐饮列表
lockCityType Integer 1 是否限制只能查询当前城市中的餐厅信息
0: 不限制
1: 限制-以Location中地址为准,不允许切换
restaurantType Integer 1 选择餐厅类型-当在申请环节预选餐厅时会需要用到此参数
1: 跳转筛选页面
2: 跳转详情页面-当需要跳转详情页面时,restaurantId必传
restaurantId Long 12345 restaurantType=2时必填
选择餐厅业务id,美团企业版推送的id值(点确认选择提交时)
repastApplyExtraJson RepastApplyExtraJson RepastApplyExtraJson 用餐申请单信息
外卖
mt_waimai
location Location Location 经纬度信息
wmExtraJson WmExtraJson WmExtraJson 外卖收货地址拓展参数,当需要通过免登传外卖收货地址时,此参数必传
budgetKey String 12345 第三方预算key,接入第三方预算时必传
restaurantType Integer 1 选择餐厅类型-当在申请环节预选餐厅时会需要用到此参数
1: 跳转筛选页面
2: 跳转详情页面-当需要跳转详情页面时,restaurantId必传
restaurantId Long 12345 restaurantType=2时必填
选择餐厅业务id,美团企业版推送的id值(点确认选择提交时)
thirdOrderId String 1234dd5 三方订单号,用于外卖订单详情页跳转,功能已停用
新客户订单详情跳转直接使用productType=sqt_order_detail
repastApplyExtraJson RepastApplyExtraJson RepastApplyExtraJson 用餐申请单信息
酒店
mt_hotel
budgetKey String 12345 第三方预算key,接入第三方预算时必传
tmcApplyExtraJson TmcApplyExtraJson TmcApplyExtraJson 出差申请单信息
火车票
mt_train
budgetKey String 12345 第三方预算key,接入第三方预算时必传
tmcApplyExtraJson TmcApplyExtraJson TmcApplyExtraJson 出差申请单信息
飞机票
mt_plane
budgetKey String 12345 第三方预算key,接入第三方预算时必传
tmcApplyExtraJson TmcApplyExtraJson TmcApplyExtraJson 出差申请单信息
订单详情
sqt_order_detail
sqtBizOrderId String - 美团企业版订单ID,跳转订单详情(productType=sqt_order_detail)时必传
偿还单详情
repay_order_detail
sqtBizOrderId String - 美团企业版订单ID,跳转偿还单详情(productType=repay_order_detail)时必传
审批详情
audit_detail
processInstanceId String 123456 审批实例id,productType为audit_detail时必传
猫眼电影
mt_maoyan
location Location Location 经纬度信息
美团买药
mt_maiyao
location Location Location 经纬度信息
budgetKey String 12345 第三方预算key,接入第三方预算时必传
美团跑腿
pao_tui
location Location Location 经纬度信息
budgetKey String 12345 第三方预算key,接入第三方预算时必传

# 参数类型说明

类型 字段 类型 示例 说明
Location longitude String 116.480881 经度
latitude String 39.989410 纬度
geotype String gcj02(火星坐标系)或者wgs84(国际坐标系) 坐标系(与经纬度一起用来定位)
address String 北京市朝阳区阜通东大街6号 经纬度对应的中文地址
WmExtraJson addressUserName String 张三 姓名
addressPhone String 13112345678 手机号码
addressGender Integer 1 性别,字段枚举值为: 0-未知;1-先生;2-女士
addressHouseNumber String 融新3F01室 门牌号
policyKey String 1 policyKey值为多入口模式下的供给规则对应参数,与供给规则中填写的值一致即可匹配
RepastApplyExtraJson applyNo String 5NQZ0NBUDZPD 美团用餐申请单号(原repastApplyNo)
externalApplyNo String 5N9D0AD96RY92 企业用餐申请单号(原outRepastApplyNo)
TmcApplyExtraJson applyNo Long 33006 美团申请单ID(原tripId)
externalApplyNo String xxx-test-4 企业申请单ID(原outTripId)
tripId Long 16758 美团行程ID(原itineraryId)
externalTripId String externalTripId1 企业行程ID(原outItineraryId)

# 2.2.响应参数

Http 302 跳转

如参数正确,则跳转至目标产品页。如果当前用户首次进入,会进入手机号绑定界面

如参数有误或异常情况,则跳转至错误页

# 3.示例

# 3.1.请求示例

# 3.1.1.请求示例

{
  "accessKey":"B3KSWLDSKSKDMJ",
  "content":"UgxoCGPQIzoP"
}

# 3.1.2.请求参数content解析

{
    "ts": 100746,
    "entId": 100837,
    "staffInfo": {
        "staffPhone": "15603309067",
        "invoiceTitle": "测试发票",
        "invoiceNum": "12222222"
    },
    "nonce": "5e8097cd-2344-4ee7-be67-81f541403329",
    "productType": "dp_canyin",
    "sceneType": 1,
    "bizParam": {
        "location": {
            "longitude": "116.480881",
            "latitude": "39.989410",
            "geotype": "gcj02",
            "address": "北京市朝阳区阜通东大街6号"
        },
        "tmcApplyExtraJson": {
            "applyNo": 33006,
            "externalApplyNo": "xxx-test-4",
            "tripId": 16758,
            "externalTripId": "externalTripId1"
        },
        "repastApplyExtraJson": {
            "applyNo": "5NQZ0NBUDZPD",
            "externalApplyNo": "5N9D0AD96RY92"
        }
    }
}

# 3.2.响应示例

# 3.2.1.响应结果

HTTP/1.1 302 Found
M-TraceId: xxxxxxxxxxxxxxxxxxx
Location: <跳转链接>

# 4.错误码

错误代码 描述 解决方案
10100000 参数校验失败 请根据页面报错,确认字段传值是否符合规范标准,如staffPhone字段不符合大陆手机号格式等
10100001 必填参数缺失 请根据页面报错,补充必填字段传值,如productType字段不可为空
10100002 短时间内收到了重复请求 请检查是否重复发送了免登请求
10100003 entId 不合法 请检查entId是否正确,且和调用环境相匹配,例如使用测试环境的entId调用了线上环境免登地址
10100004 员工不存在 请到【美团企业版企业管理后台-员工管理-组织架构】菜单中查询员工是否已同步成功
10100005 部门名称或唯一标识错误 检查orgName和externalOrgId字段传值,当需要同步部门信息时,两个字段必须同时有值
10100006 保存部门失败 重新调用接口,如若仍不成功,请联系客户经理进行排查
10100007 发票抬头或税号错误 检查invoiceTitle和invoiceNum字段传值,当需要同步发票信息时,两个字段必须同时有值
10100008 保存发票失败 重新调用接口,如若仍不成功,请联系客户经理进行排查
40100000 获取跳转链接失败 重新调用接口,如若仍不成功,请联系客户经理进行排查

# 5.代码实例

依赖SDK包地址:SDK下载地址

# 5.1 Java代码实例

import com.google.common.collect.Maps;
import com.meituan.sqt.constant.CommonConstants;
import com.meituan.sqt.model.HttpResponse;
import com.meituan.sqt.request.in.login.LoginRequest.LoginStaffInfo;

import com.meituan.sqt.request.in.login.LoginRequest;
import com.meituan.sqt.response.in.login.LoginApiInfo;
import com.meituan.sqt.utils.EncryptUtil;
import com.meituan.sqt.utils.HttpClientUtil;
import com.meituan.sqt.utils.JsonUtil;
import org.apache.commons.lang3.RandomStringUtils;

import java.util.Map;

/**
 * @author niliwei
 * @description 登录接口使用实例
 * @since 2023/4/3 7:35 PM
 */

public class LoginDemo {

    /**
     * 测试环境免登接口url
     * test_open_swimlane为测试环境特有值,无需关注
     */
    private static String LOGIN_URL_TEST = "https://waimai-openapi.apigw.test.meituan.com/api/sqt/open/login/h5/loginFree/redirection?test_open_swimlane=test-open";
    /**
     * 正式环境免登接口url
     */
    private static String LOGIN_URL_PROD = "https://bep-openapi.meituan.com/api/sqt/open/login/h5/loginFree/redirection";

    public static void main(String[] args) throws Exception {
        // 免登接口正常需要客户端/前端发起调用 基于此有两种登录
        //第一种登录方式(推荐,需客户端/前端+服务端介入):服务端提供给客户端/前端免登接口url及请求体,客户端/前端以post方式调用免登接口,免登接口会通过302(重定向)方式跳转到对应的页面
        clientInvokeLogin();
        // 第二种登录方式(需服务端介入):服务端以post方式调用免登接口,获取接口返回的302(重定向)链接,然后服务端可重定向至该链接完成登录
        // !!!重要:接口返回的302链接不可重复使用,必须每次登录时调用接口重新生成
        serverInvokeLogin();
    }

    private static void clientInvokeLogin() throws Exception {
        LoginApiInfo loginApiInfo = new LoginApiInfo();
        // 判断环境决定用那个参数,这里以测试环境示例
        loginApiInfo.setUrl(LOGIN_URL_TEST);
        loginApiInfo.setAccessKey(CommonConstants.accessKey);
        loginApiInfo.setContent(buildLoginRequestBody());
        System.out.println(JsonUtil.object2Json(loginApiInfo));
    }

    private static void serverInvokeLogin() throws Exception {
        Map<String, String> headerMap = Maps.newHashMap();
        headerMap.put("Accept", "application/json");
        headerMap.put("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
        Map<String, String> requestParamMap = Maps.newHashMap();
        requestParamMap.put("accessKey", CommonConstants.accessKey);
        requestParamMap.put("content", buildLoginRequestBody());
        HttpResponse httpResponse = null;
        try {
            httpResponse = HttpClientUtil.invokePost(LOGIN_URL_TEST, headerMap, requestParamMap, 2000, 3000);
            // 如需json方式调用接口,参考以下注释代码
//            httpResponse = HttpClientUtil.invokePost2Json(LOGIN_URL_TEST, JsonUtil.object2Json(requestParamMap), 2000,3000);
            // 成功
            if (httpResponse.getStatus() == 302) {
                System.out.println("url = " + httpResponse.getHeader().get("Location").get(0));
                return;
            }
            // 失败场景,根据返回值具体参考:https://h5.dianping.com/app/bep-docs/open-platform-doc/login/login_free.html#_4-%E9%94%99%E8%AF%AF%E7%A0%81
            System.out.println("httpResponse = " + JsonUtil.object2Json(httpResponse));
        } catch (Exception e) {
            // 调用接口超时或其他场景 可以根据需要增加重试
            System.out.println("e = " + e);
        }
    }

    private static String buildLoginRequestBody() throws Exception {
        LoginRequest loginRequest = new LoginRequest();
        loginRequest.setTs(System.currentTimeMillis());
        loginRequest.setEntId(CommonConstants.entId);
        loginRequest.setStaffInfo(buildLoginStaffInfo());
        loginRequest.setNonce(RandomStringUtils.random(36, true, true));
        loginRequest.setProductType("mt_waimai");
//        这里如果要使用bizParam,并且map的value不是String和包装类型(Integer等),需自行定义java类
//        loginRequest.setBizParam(null);
        String rawContent = JsonUtil.object2Json(loginRequest, false);
        String content = EncryptUtil.aesEncrypt(rawContent, CommonConstants.secretKey);
        return content;
    }

    private static LoginStaffInfo buildLoginStaffInfo() {
        LoginStaffInfo loginStaffInfo = new LoginStaffInfo();
        // 传企业的唯一标识,这里以手机号为例
        loginStaffInfo.setStaffPhone("19987888888");
        return loginStaffInfo;
    }
}

# 5.2 C#代码实例

try
{
    var task = Task.Run(async () => await MeiTuanPost.MTWaimaiLogin(para.UserID)).Result;
    retunr task.ToString();
}
catch (Exception ex)
{
    return null;
}


MeiTuanPost类中的方法:

public static async Task<string> MTWaimaiLogin(int p_intPersonID)
{
    string strTime = GetTimeStamp();
    string strJson = "{ " +
        "\"ts\": " + strTime + "," +
        "\"entId\": " + m_tokenMeituan.EntId + "," +
        "\"staffInfo\": {" +
        "   \"staffNum\": \"" + p_intPersonID.ToString().Trim() + "\"" + //人员唯一标识
        "}," +
        " \"nonce\": \"5e8097cd-2344-4ee7-be67-81f541403329\"," +
        " \"productType\": \"mt_waimai\"" +
        "}";
    string strReurn = await PostToMTAsync(m_tokenMeituan, strJson);

    return strReurn;
}

 public static string GetTimeStamp()
{
    DateTime dteStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1, 0, 0, 0));
    DateTime dteNow = DateTime.Now;
    long t = (dteNow.Ticks - dteStart.Ticks) / 10000;
    return t.ToString().Trim();
}

public static async Task<string> PostToMTAsync(TokenMeituan p_tokenMeituan, string p_strContent)
{
    string jsonResponse;
    try {
        HttpClientHandler handler = new HttpClientHandler();
        handler.AllowAutoRedirect = false;
        HttpClient httpClient = new HttpClient(handler);
        string strcontent = EncryptUtil.AESEncode(p_strContent, p_tokenMeituan.SecretKey);
        List<KeyValuePair<string, string>> dict = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string, string>("accessKey", p_tokenMeituan.AccessKey),//ApiConfigManager.MeiTuanTakeoutAccessKey
            new KeyValuePair<string, string>("content", strcontent)//loginRequestBody
        };
        FormUrlEncodedContent formUrlEncodedContent = new FormUrlEncodedContent(dict);
        formUrlEncodedContent.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
        formUrlEncodedContent.Headers.ContentType.CharSet = "utf-8";
    
        var response = await httpClient.PostAsync(p_tokenMeituan.Url, formUrlEncodedContent);
        
        if (response.StatusCode == HttpStatusCode.Redirect)
        {
            return response.Headers.Location.ToString();
        } else {
            /// 当返回不是302的时候,可以抛出一个异常或者返回一个错误码,进行处理错误情况
        }
        jsonResponse = await response.Content.ReadAsStringAsync();
    } catch (Exception e) {
        /// 处理异常信息
    }
    return jsonResponse;
}

public class EncryptUtil
{
    #region AES加密
    /// <summary>
    /// AES加密
    /// </summary>
    /// <param name="text">明文</param>
    /// <param name="key">密钥,长度为16的字符串</param>
    /// <returns>密文</returns>
    public static string AESEncode(string text, string key)
    {
        byte[] keys = Convert.FromBase64String(key);
        RijndaelManaged rijndaelCipher = new RijndaelManaged();
        rijndaelCipher.Mode = CipherMode.ECB;
        rijndaelCipher.Padding = PaddingMode.PKCS7;
        rijndaelCipher.KeySize = 128;
        rijndaelCipher.BlockSize = 128;
        byte[] pwdBytes = keys;
        byte[] keyBytes = new byte[16];
        int len = pwdBytes.Length;
        if (len > keyBytes.Length)
            len = keyBytes.Length;
        Array.Copy(pwdBytes, keyBytes, len);
        rijndaelCipher.Key = keyBytes;
        ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
        byte[] plainText = Encoding.UTF8.GetBytes(text);
        byte[] cipherBytes = transform.TransformFinalBlock(plainText, 0, plainText.Length);
        return ConvertHelper.ToBase64StringURLSafe(cipherBytes);//输出为Base64
    }
    #endregion

    #region AES解密
    /// <summary>
    /// AES解密
    /// </summary>
    /// <param name="text">密文</param>
    /// <param name="key">密钥,长度为16的字符串</param>
    /// <returns>明文</returns>
    public static string AESDecode(string text, string key)
    {
        RijndaelManaged rijndaelCipher = new RijndaelManaged();
        rijndaelCipher.Mode = CipherMode.ECB;
        rijndaelCipher.Padding = PaddingMode.PKCS7;
        rijndaelCipher.KeySize = 128;
        rijndaelCipher.BlockSize = 128;
        byte[] encryptedData = ConvertHelper.FromBase64StringURLSafe(text);
        byte[] pwdBytes = Convert.FromBase64String(key);
        byte[] keyBytes = new byte[16];
        int len = pwdBytes.Length;
        if (len > keyBytes.Length)
            len = keyBytes.Length;
        Array.Copy(pwdBytes, keyBytes, len);
        rijndaelCipher.Key = keyBytes;
        ICryptoTransform transform = rijndaelCipher.CreateDecryptor();
        byte[] plainText = transform.TransformFinalBlock(encryptedData, 0, encryptedData.Length);
        return Encoding.UTF8.GetString(plainText);
    }
    #endregion
}

# 6.版本记录

版本号 版本日期 更新内容
v1.0 2022-11-21 新增H5免登接口
上次更新: 6/29/2026, 7:56:38 PM