新手指南

BD申请clientId和clientSecret

要使用美团第三方测试平台任何设施首先要成为美团合作者
请首先联系BD配置商家合同信息和技术商名称
配置成功后请联系BD获取商家clientId和clientSecret用于登陆第三方测试系统

阅读API文档

第三方在进行测试之前,需要完成对接美团系统的接口开发
打开旅游第三方对接标准API ,即可查阅所有接口标准。
API文档用于第三方开发美团, 接入门票产品在美团平台上进行售卖. 交互方式为Rest接口, 编码方式为utf-8.

API文档中提供的接口包括美团调用商家接口和商家调用美团接口
1.商家调用美团接口由美团提供给第三方调用,接口的实现在美团, 第三方需要按照格式开发该接口的调用接口. 接口包括如下几个:

  • 产品变化通知(支持推送新产品) POST /mtp/api/deal/change/notice
  • 消费通知 POST /mtp/api/order/consume/notice
  • 退款通知 POST /mtp/api/order/refund/notice
2.美团调用商家接口指的是第三方提供给美团调用的接口, 第三方负责按照文档实现接口 (接口的url, 第三方可以自由定义,但是参数和返回值的格式要按照文档实现), 美团会根据流程调用第三方提供的这些接口.
另外的接口都为美团调用商家接口

http body和body字段
接口请求的形式是post,请求对象转化成json,然后存放在http body流里面,读取请求数据,可以从http body流里读取数据,然后转化成对象。
接口的请求对象里有body字段,这个字段跟http 的body流是不一样概念的。
注意:数据是放在http body当中。

认证与安全
为了保证双方间的交互安全,采用HTTP Header 参数加密认证的方式。双方需要约定id与密钥,根据加密算法,生成HTTP Header 字段。
HTTP Header部分必须包含如下三个字段,样式如下:
字段 内容
PartnerId 由美团分配
Date Fri, 17 Feb 2012 23:34:53 GMT
Authorization MWS crm:frJIUN8DYpKDtOLCwozzyllqDzg=(具体生成策略见BA认证)
其中Date 字段指的是接口调用的当前时间. 美团服务会检查该时间,如果该时间过早,会拒绝服务.

产品同步流程

订单同步流程

确定需要开发的接口

我司为第三方提供了确定需要开发的接口 ,第三方可以在页面中选择自己的业务需求,系统将会估算出第三方相应的需要开发的接口。
第三方可以根据自己的对接需求进行选择,我方会根据所选项估计出需要开发的接口,如下图所示:

BA认证

在开发接口之前,请首先确保BA认证能够通过。
我方给出了一个BA签名的Demo:

需要签名的字串拼装格式为:
String stringToSign = request.getMethod().toUpperCase() + " " + request.getURI().getPath() + "\n" + date;

签名前:"POST /rhone/lv/deal/change/notice" + "\n" + "Wed, 06 May 2015 10:34:20 GMT";

secret=123456ce8b61f8e608eaf2e9702864a9

签名后:hVQHFpzOXtxzOy5INj2IohJ85Y0=
                        
签名出现错误的原因分析:
  • 不按照签名字串格式生成字串:缺少空格,/n等字符。
  • HttpRequest Head缺少字段。
  • 进行签名的URl不对。是 /rhone/lv/deal/change/notice 而不是http://lvyou.test.sankuai.info/rhone/lv/deal/change/notice
  • 签名的方法不对。表现为同样的字串得到的签名不同。
  • 时间不对。
    • 时间格式不对。正确的时间格式:Tue, 05 May 2015 06:11:34 GMT
    • 时间为US时间,不是中国时间
    • 合作方放在HttpRequest中的时间和我们的在HttpRequest中获取到得时间不同。

例如:同一个时间字串,双方的解析结果不同
某合作方的签名字段是: "POST /rhone/lv/deal/change/notice"+"\n"+"Wed, 06 May 2015 10:22:43 GMT"
得到的签名:P2ROUPE7Rd2hwjDeDV8JidKDVR0=

美团的签名字串:"POST /rhone/lv/deal/change/notice"+"\n"+"Wed, 06 May 2015 10:34:20 GMT”
得到的签名:8tn1DMxvLR0ec4xPSoY3PHn+geo=


BA签名的实现:
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.methods.HttpRequestBase;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/**
* 生成http请求头认证信息
* Author:yangdecheng@meituan.com
* Date:14-7-18
* Time:下午10:35
*/
public class BasicAuthorizationUtils {
    private static final Log log = LogFactory.getLog(BasicAuthorizationUtils.class);

    private static final String ALGORITHM_HMAC_SHA1 = "HmacSHA1";
    public static final String HTTP_HEADER_PARTNERID = "PartnerId";
    public static final String HTTP_HEADER_DATE = "Date";
    public static final String HTTP_HEADER_AUTHORIZATION = "Authorization";
    public static final String MEITUAN_AUTH_METHOD = "MWS";
    private static final String HTTP_HEADER_TIME_ZONE = "GMT";
    private static final String HTTP_HEADER_DATE_FORMAT = "EEE', 'dd' 'MMM' 'yyyy' 'HH:mm:ss' 'z";

    /**
    * 生成请求头认证信息
    * @param request
    * @param clientId
    * @param clientSecret
    */
    public static void generateAuthAndDateHeader(HttpRequestBase request, String clientId,
                                                String clientSecret) {
        Date sysdate = new Date();
        SimpleDateFormat df = new SimpleDateFormat(HTTP_HEADER_DATE_FORMAT, Locale.US);
        df.setTimeZone(TimeZone.getTimeZone(HTTP_HEADER_TIME_ZONE));
        String date = df.format(sysdate);
        String stringToSign = request.getMethod().toUpperCase() + " " +
                                            request.getURI().getPath() +
                                            "\n" + date;
        String encoding;
        try {
            encoding = getSignature(stringToSign.getBytes(), clientSecret.getBytes());
        } catch (Exception e1) {
            log.warn("Signature Exception occured:", e1);
            return;
        }
        String authorization = MEITUAN_AUTH_METHOD + " " + clientId + ":" + encoding;
        request.addHeader(HTTP_HEADER_PARTNERID, partnerId); // 请将partnerId替换为商家自身的partnerId
        request.addHeader(HTTP_HEADER_AUTHORIZATION, authorization);
        request.addHeader(HTTP_HEADER_DATE, date);
    }

    /**
    * 生产签名
    * @param data
    * @param key
    * @return
    * @throws InvalidKeyException
    * @throws NoSuchAlgorithmException
    */
    public static String getSignature(byte[] data, byte[] key)
                                            throws InvalidKeyException,NoSuchAlgorithmException {
        SecretKeySpec signingKey = new SecretKeySpec(key, ALGORITHM_HMAC_SHA1);
        Mac mac = Mac.getInstance(ALGORITHM_HMAC_SHA1);
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(data);
        return new String(Base64.encodeBase64(rawHmac));
    }
}
                                        
class Meituan_Service {

    public static $partnerId = '1111';
    private static $clientId = '2222';
    private static $clientSecret = '3333';
    private static $url = 'http://lvyou.test.sankuai.info';

    private static $instance = null;

    /**
     * @return Meituan_Service|null
     */
    public static function create(){
        if(!isset(self::$instance)){
            self::validSign();
            self::$instance = new Meituan_Service();
        }
        return self::$instance;
    }

    public function request($data, $uri){
        date_default_timezone_set('GMT');
        $date = date('D, d M Y H:i:s e', time());
        date_default_timezone_set('PRC');
        $authorization = $this->buildSign('POST', $uri, $date);

        $header = array(
            "Content-Type: application/json; charset=utf-8",
            "Date: " . $date,
            "PartnerId: " . self::$partnerId,
            "Authorization: " . $authorization,
        );
        $data = array_merge(array(
            'code'      => 200,
            'describe'  => 'success',
            'partnerId' => self::$partnerId,
        ), $data);

        $data = json_encode($data);
        //请使用自己的curl工具
        $res_json = Tools::curl(self::$url . $uri, 'POST', $data, $header);
        $res = json_decode($res_json, true);

        return $res;
    }

    public function outputError($res = ''){
        if(is_array($res)){
            $data = array_merge(array(
                'code'      => 300,
                'partnerId' => self::$partnerId,
            ),$res);
        }else{
            $data = array(
                'code'      => 300,
                'describe'  => $res,
                'partnerId' => self::$partnerId,
            );
        }

        exit(json_encode($data, JSON_UNESCAPED_UNICODE));
    }
    public function outputSucc($res = array(), $desc = 'success'){
        $data = array_merge(array(
            'code'      => 200,
            'describe'  => $desc,
            'partnerId' => self::$partnerId,
        ),$res);

        exit(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    }

    private function validSign(){
        //todo sign 验证

        if($_SERVER['HTTP_AUTHORIZATION'] != self::buildSign( $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['HTTP_DATE'])){
            self::outputError('BA验证错误');
        }
        return true;
    }

    private function buildSign($method, $uri, $date){

//        $string_to_sign = "POST /rhone/lv/deal/change/notice" . "\n" . "Wed, 06 May 2015 10:34:20 GMT";
        $string_to_sign = $method . ' ' . $uri . "\n" . $date;

        $client_secret = self::$clientSecret;
        $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $client_secret, true));
        $authorization = 'MWS ' . self::$clientId . ':' . $signature;

        return $authorization;
    }

}

                                        
import base64
import hashlib
import hmac
signature = base64.encodestring(hmac.new(client_secret, string_to_sign, hashlib.sha1).digest()).replace("\n", "")

                                        
然后请务必使用我方提供的BA认证测试工具 来校验BA认证是否通过。

对接测试沙箱

第三方在开发完所有接口后,即可以登入 测试沙箱进行测试
在进行测试之前请选择系统功能和填写你方请求URL地址
选择系统功能页面如下:

请根据接口开发情况进行选择,然后需要填写URL请求地址,如下图所示:
选择完毕后即可进入测试页面进行测试,测试如下如下图所示:

  • 左侧为测试进度
    测试通过后会显示绿色,当前测试任务用紫色标记。
  • 点击开始测试按钮
    只需点击开始测试按钮即可开始测试
  • 点击下一步按钮进行下一个测试
    只有当前测试通过才能进行下一步测试
  • 点击对测试流程有疑问按钮了解测试流程
    请尽量保证至少阅读一次
  • 点击重新填写URL信息按钮重新填写URL
    若合作方发现URL信息有误,可以进行修改
  • 点击重新测试按钮重新开始测试
    若合作方发现在系统功能页面选择出错,即可重新测试,注意,重新测试会覆盖当前测试进度
  • 参看请求信息和日志
    在测试之后会在会分别在下方文本框中显示美团日志信息,数据库入库信息和美团请求参数信息

测试验收

测试完毕后在最后的测试页面点击查询订单按钮
后端系统检测到分别有一单核销和退款成功即会标记测试通过

对接上线

测试通过后我方会发送邮件给PM和BD
PM或BD会主动联系合作方进行对接上线

联系我们

sunsichu@meituan.com
gaoxinghua@meituan.com
ask.api@meituan.com
15901398056