using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Security.Cryptography; using System.Text; using System.Web; namespace SFAPITest.Services { public class MafengwoHelper { // 马蜂窝API调用: //1. 加密请求数据(data) //2. 拼接请求基本字段内容 //3. 生成签名 //4. 发送HTTP请求 //5. 得到HTTP响应 //6. 解密数据 //7. 解析JSON结果 public static string clientId = ""; public static string clientSecret = ""; public static string key = ""; /// <summary> /// 马蜂窝api请求封装 /// </summary> /// <param name="actionName">API接口的动作名称</param> /// <param name="requesParams">业务接口请求数据</param> /// <returns></returns> private static string MFWApiRequest(string actionName, object requesParams) { //商家ID int partnerId = int.Parse(clientId); //API接口的动作名称 string action = actionName; //请求发起的时间戳 string timestamp = Convert.ToInt32((DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).ToString(); //随机串 string nonce = GetRandomStr(); //业务请求数据 object obj = requesParams; string reqData = Newtonsoft.Json.JsonConvert.SerializeObject(obj); //加密请求数据data string data = encrypt(reqData, key); //根据基础参数生成的数据签名 //对 partnerId、action、timestamp、key、nonce 和 data 字段进行合并 string paramsValueStr = partnerId + action + timestamp + key + nonce + data; string sign = SignRequest(paramsValueStr); //OAuth token string access_token = GetAccessToekn(clientId, clientSecret, "client_credentials"); //加密后的字符串有可能会包含“+”或者“=”字符,Java 或 C# 在某些处理字符串的步骤上会做特殊的处理【对 data 加密后的数据进行 urlencode】 //data 字段只有在请求的最后一步之前再做 urlencode,请不要在生成签名之前做,这样会导致加密的数据无法被反解成正常的数据 data = HttpUtility.UrlEncode(data); //http请求 var str = string.Format("partnerId={0}&action={1}×tamp={2}&nonce={3}&data={4}&sign={5}&access_token={6}", partnerId, action, timestamp, nonce, data, sign, access_token); var responseData = Post("https://openapi.mafengwo.cn/deals/rest", str); var response = decrypt(responseData, key); return DecodeString(response); } /// <summary> /// 生成签名 /// 在数据已经加密完成之后,需要对 partnerId、action、timestamp、key、nonce 和 data 字段进行合并,并对汇总后的字符串进行 MD5 加密。 /// 加密后的结果为本次请求的签名。 /// md5 加密后的英文字符均应为小写字符,否则验证会不通过 /// MD5(partnerId + action + timestamp + key + nonce + data) /// </summary> /// <returns></returns> private static string SignRequest(string str) { MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider(); byte[] hashedDataBytes; hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(str)); //hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("utf-8").GetBytes(str)); StringBuilder tmp = new StringBuilder(); foreach (byte i in hashedDataBytes) { tmp.Append(i.ToString("x2")); } return tmp.ToString().ToLower(); } /// <summary> /// 获取授权令牌 (默认有效期为120分钟(7200秒)) /// </summary> /// <param name="client_id">商户ID</param> /// <param name="client_secret">商户Secret</param> /// <param name="grant_type">固定值client_credentials</param> /// <returns>token</returns> private static string GetAccessToekn(string client_id, string client_secret, string grant_type = "client_credentials") { string url = "https://openapi.mafengwo.cn/oauth2/token?client_id={0}&client_secret={1}&grant_type={2}"; string reqUrl = string.Format(url, client_id, client_secret, grant_type); var resStr = Get(reqUrl); //"{\"access_token\":\"aab19fa3942ed9456945fc23b88a6baa\",\"token_type\":\"GET\",\"expires_in\":7200,\"scope\":null}" var str = ""; JObject resultObj = JObject.Parse(resStr); if (!string.IsNullOrEmpty(resultObj["expires_in"].ToString()) && int.Parse(resultObj["expires_in"].ToString()) > 0) { str = resultObj["access_token"].ToString(); } return str; } /// <summary> /// 生成随机串 /// 随机串的生成规则为: /// 1. 长度为16为字符串 /// 2. 字符串仅能包括 大小写英文字符、数字 /// </summary> /// <param name="maxLength">随机串长度</param> /// <returns>生成的随机串</returns> private static string GetRandomStr(int maxLength = 16) { string str = ""; string strPool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; Random rand = new Random(); for (var i = 0; i < maxLength; i++) { str += strPool[rand.Next(0, 61)]; } return str; } public static string Post(string url, string postData) { //请求 WebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded;charset=utf-8"; //request.ContentType = "multipart/form-data;charset=utf-8"; request.ContentLength = Encoding.UTF8.GetByteCount(postData); byte[] postByte = Encoding.UTF8.GetBytes(postData); Stream reqStream = request.GetRequestStream(); reqStream.Write(postByte, 0, postByte.Length); reqStream.Close(); //响应 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream myResponseStream = response.GetResponseStream(); StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.GetEncoding("utf-8")); string retString = myStreamReader.ReadToEnd(); myStreamReader.Close(); myResponseStream.Close(); return retString; } public static string Get(string url) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.Method = "GET"; request.ContentType = "text/html;charset=UTF-8"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream myResponseStream = response.GetResponseStream(); StreamReader myStreamReader = new StreamReader(myResponseStream, Encoding.UTF8); string retString = myStreamReader.ReadToEnd(); myStreamReader.Close(); myResponseStream.Close(); return retString; } /// <summary> /// unicode转中文 /// </summary> /// <param name="unicode"></param> /// <returns></returns> public static string DecodeString(string unicode) { if (string.IsNullOrEmpty(unicode)) { return string.Empty; } return System.Text.RegularExpressions.Regex.Unescape(unicode); } #region 加密解密 static int BLOCK_SIZE = 32; /** * 获得对明文进行补位填充的字节. * * @param count 需要进行填充补位操作的明文字节个数 * @return 补齐用的字节数组 */ public static byte[] fillByte(int count) { // 计算需要填充的位数 int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); if (amountToPad == 0) { amountToPad = BLOCK_SIZE; } // 获得补位所用的字符 char padChr = chr(amountToPad); String tmp = string.Empty; for (int index = 0; index < amountToPad; index++) { tmp += padChr; } return Encoding.UTF8.GetBytes(tmp); } /** * 删除解密后明文的补位字符 * * @param decrypted 解密后的明文 * @return 删除补位字符后的明文 */ public static byte[] removeByte(byte[] decrypted) { int pad = (int)decrypted[decrypted.Length - 1]; if (pad < 1 || pad > 32) { pad = 0; } byte[] res = new byte[decrypted.Length - pad]; Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad); return res; } /** * 将数字转化成ASCII码对应的字符,用于对明文进行补码 * * @param a 需要转化的数字 * @return 转化得到的字符 */ static char chr(int a) { byte target = (byte)(a & 0xFF); return (char)target; } /// <summary> /// 解密 /// </summary> /// <param name="text"></param> /// <param name="sKey"></param> /// <returns></returns> public static string decrypt(string text, string sKey) { byte[] Key = Encoding.UTF8.GetBytes(sKey); byte[] Iv = new byte[16]; Array.Copy(Key, Iv, 16); byte[] Text = Encoding.UTF8.GetBytes(text); return AES_decrypt(text, Iv, Key); } private static string AES_decrypt(String Input, byte[] Iv, byte[] Key) { RijndaelManaged aes = new RijndaelManaged(); aes.KeySize = 256; aes.BlockSize = 128; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; //aes.Padding = PaddingMode.PKCS7; aes.Key = Key; aes.IV = Iv; var decrypt = aes.CreateDecryptor(aes.Key, aes.IV); byte[] xBuff = null; using (var ms = new MemoryStream()) { using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write)) { byte[] xXml = Convert.FromBase64String(Input); byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32]; Array.Copy(xXml, msg, xXml.Length); cs.Write(xXml, 0, xXml.Length); } xBuff = removeByte(ms.ToArray()); } return System.Text.Encoding.Default.GetString(xBuff); } /// <summary> /// 加密 /// </summary> /// <param name="Input"></param> /// <param name="Iv"></param> /// <param name="Key"></param> /// <returns></returns> public static string encrypt(string text, string sKey) { byte[] Key = Encoding.UTF8.GetBytes(sKey); byte[] Iv = new byte[16]; Array.Copy(Key, Iv, 16); byte[] Text = Encoding.UTF8.GetBytes(text); return AES_encrypt(Text, Iv, Key); } private static String AES_encrypt(byte[] Input, byte[] Iv, byte[] Key) { var aes = new System.Security.Cryptography.RijndaelManaged(); //秘钥的大小,以位为单位 aes.KeySize = 256; //支持的块大小 aes.BlockSize = 128; //填充模式 //aes.Padding = PaddingMode.PKCS7; aes.Padding = System.Security.Cryptography.PaddingMode.None; aes.Mode = System.Security.Cryptography.CipherMode.CBC; aes.Key = Key; aes.IV = Iv; var encrypt = aes.CreateEncryptor(aes.Key, aes.IV); byte[] xBuff = null; #region 自己进行PKCS7补位,用系统自己带的不行 byte[] msg = new byte[Input.Length + 32 - Input.Length % 32]; Array.Copy(Input, msg, Input.Length); byte[] pad = fillByte(Input.Length); Array.Copy(pad, 0, msg, Input.Length, pad.Length); #endregion #region 注释的也是一种方法,效果一样 //ICryptoTransform transform = aes.CreateEncryptor(); //byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length); #endregion using (var ms = new MemoryStream()) { using (var cs = new System.Security.Cryptography.CryptoStream(ms, encrypt, System.Security.Cryptography.CryptoStreamMode.Write)) { cs.Write(msg, 0, msg.Length); } xBuff = ms.ToArray(); } String Output = Convert.ToBase64String(xBuff); return Output; } #endregion #region 接口调用 /// <summary> /// 获取马蜂窝订单详情 /// </summary> /// <param name="orderId"></param> /// <returns></returns> public static MFWResponse<MFWOrderDetail> GetMFWOrderDetail(string orderId) { //API接口的动作名称 string action = "sales.order.detail.get"; //业务请求数据 object obj = new { order_id = orderId }; var response = MFWApiRequest(action, obj); var result = Newtonsoft.Json.JsonConvert.DeserializeObject<MFWResponse<MFWOrderDetail>>(response); return result; } /// <summary> /// 获取订单备注 /// </summary> /// <param name="orderId"></param> /// <returns></returns> public static List<MFWOrderMemo> GetMFWOrderMemo(string orderId) { string action = "sales.order.memo.get"; object obj = new { order_id = orderId }; var response = MFWApiRequest(action, obj); var result = Newtonsoft.Json.JsonConvert.DeserializeObject<MFWResponse<List<MFWOrderMemo>>>(response); return result.data; } public static string GetMFWOrderMemoStr(string orderId) { var memoList = GetMFWOrderMemo(orderId); var memo = string.Join("|", memoList.Select(x => x.content)); return memo; } /// <summary> /// 获取出行人信息 /// </summary> /// <returns></returns> public static MFWTravelInfo GetTraveler(string orderId) { string action = "sales.order.traveler.get"; object obj = new { order_id = orderId }; var response = MFWApiRequest(action, obj); var result = Newtonsoft.Json.JsonConvert.DeserializeObject<MFWResponse<MFWTravelInfo>>(response); return result.data; } /// <summary> /// 获取补款单列表 /// </summary> /// <param name="orderId"></param> /// <returns></returns> public static MFWReplenishResponse GetReplenishList(string orderId) { string action = "sales.replenish.list.get"; object obj = new { order_id = orderId }; var response = MFWApiRequest(action, obj); var result = Newtonsoft.Json.JsonConvert.DeserializeObject<MFWReplenishResponse>(response); return result; } #endregion } public class MFWResponse<T> { public int errno { get; set; } public string message { get; set; } public T data { get; set; } } #region 订单详情实体 /// <summary> /// 订单详情 /// </summary> public class MFWOrderDetail { //旅行商城业务订单号 public string orderId { get; set; } public Status status { get; set; } //旅行出行时间 public string goDate { get; set; } //旅行结束日期 public string endDate { get; set; } //订单支付时间 public string paytime { get; set; } //订单创建时间 public string ctime { get; set; } //预订人信息 public BookingPeople bookingPeople { get; set; } //马蜂窝产品id,产品唯一标识 public string salesId { get; set; } //产品名称 public string salesName { get; set; } //商家设置的产品外部编码 public string otaSalesName { get; set; } //订单关联产品品类 public int salesType { get; set; } //目的地 public string mdd { get; set; } //订单关联产品出发地 public string from { get; set; } //马蜂窝SKU ID,SKU唯一标识 public string skuId { get; set; } //商家设置的SKU外部编码 public string otaSkuId { get; set; } //SKU名称 public string skuName { get; set; } //订单原始金额 public decimal totalPrice { get; set; } //用户实际支付金额 public decimal paymentFee { get; set; } //订单购买项详细信息 public List<Items> items { get; set; } //订单优惠信息 public PromotionDetail promotionDetail { get; set; } //库存信息 public List<Skus> skus { get; set; } } /// <summary> /// 库存信息 /// </summary> public class Skus { //库存名称 public string stockName { get; set; } //商家设置的SKU外部编码 public string otaSkuId { get; set; } //库存ID public int skuId { get; set; } } /// <summary> /// 订单优惠信息 /// </summary> public class PromotionDetail { //马蜂窝补贴金额 public decimal reduce_mfw { get; set; } //商家补贴金额 public decimal reduce_ota { get; set; } } /// <summary> /// 订单购买项详情信息 /// </summary> public class Items { //剩余可退金额 public decimal remain_payment_fee { get; set; } //剩余可退数量 public int remain_num { get; set; } //库存ID public int skuId { get; set; } //费用项 public int price_type { get; set; } //本项总金额 public decimal total_price { get; set; } //本项应支付金额 public decimal payment_fee { get; set; } //购买项描述 public string name { get; set; } //本项单价金额 public decimal price { get; set; } //本项购买个数 public int num { get; set; } //购买项ID public int id { get; set; } } /// <summary> /// 预订人信息 /// </summary> public class BookingPeople { //预定人马蜂窝UID public int uid { get; set; } public string name { get; set; } public string email { get; set; } public string phone { get; set; } public string phone_area { get; set; } public string wechat { get; set; } public string remark { get; set; } } /// <summary> /// 状态信息 /// </summary> public class Status { //订单状态 public int orderStatus { get; set; } //全退标识 public int allRefundFlag { get; set; } //退款状态 public int refundStatus { get; set; } } /// <summary> /// 订单备注实体 /// </summary> public class MFWOrderMemo { public int id { get; set; } public string order_id { get; set; } public int admin_uid { get; set; } public string content { get; set; } public DateTime dateTime { get; set; } } #region 出行人信息实体 public class MFWTravelInfo { public string order_id { get; set; } public MFWTravelPeople travel_people { get; set; } } public class MFWTravelPeople { public MFWTraveler[] traveler { get; set; } public MFWTrip trip { get; set; } public MFWTsAddress ts_address { get; set; } public MFWAddress address { get; set; } } /// <summary> /// 出行人信息 /// </summary> public class MFWTraveler { public string name { get; set; } public string id_type { get; set; } public string birthday { get; set; } public string gender { get; set; } public string nationality { get; set; } public string height { get; set; } public string weight { get; set; } public string shoe_size { get; set; } public string left_eye_sight { get; set; } public string right_eye_sight { get; set; } public string date_of_expiry { get; set; } public string cellphone { get; set; } public string family_name { get; set; } public string mainland_phone { get; set; } public string traveler_id { get; set; } public string laissez_passer_tw { get; set; } public string laissez_passer { get; set; } public string passport { get; set; } public string id_card { get; set; } public string first_name { get; set; } } public class MFWTrip { public string pick_up_time { get; set; } public string pick_up_place { get; set; } public string send_to { get; set; } public string pick_up_place_en { get; set; } public string send_to_en { get; set; } public string hotel_name_pick_up { get; set; } public string hotel_address_pick_up { get; set; } public string hotel_name_en_pick_up { get; set; } public string hotel_address_en_pick_up { get; set; } public string hotel_name_send_to { get; set; } public string hotel_address_send_to { get; set; } public string hotel_name_en_send_to { get; set; } public string hotel_address_en_send_to { get; set; } public string hotel_name_over_night { get; set; } public string flight_no_arrival { get; set; } public string flight_time_arrival { get; set; } public string flight_no_departure { get; set; } public string flight_time_departure { get; set; } public string luggage { get; set; } public string hotel_name_en { get; set; } public string hotel_address_en { get; set; } public string hotel_phone { get; set; } public string hotel_telephone { get; set; } public string using_time { get; set; } public string using_place { get; set; } public string flight_number { get; set; } public string flight_time { get; set; } public string place { get; set; } public string return_hotel { get; set; } public string hotel_adress { get; set; } public string return_hotel_phone { get; set; } public string pick_and_send_hotel_phone { get; set; } public string schedule { get; set; } public string back_hotel { get; set; } public string back_adress { get; set; } public string back_phone { get; set; } public string hotel_name { get; set; } public string hotel_address { get; set; } public string hotel_phone_number { get; set; } public string check_in_date { get; set; } public string check_out_date { get; set; } public string return_flight_number { get; set; } public string return_flight_time { get; set; } public string arrival_date { get; set; } public string departure_date { get; set; } public string departure_hotel_name { get; set; } public string departure_hotel_adress { get; set; } public string departure_hotel_number { get; set; } public string back_date { get; set; } public string over_night_hotel_address { get; set; } public string get_device_adress { get; set; } public string departure_hotel_name_cn { get; set; } public string time { get; set; } public string number { get; set; } public string phone { get; set; } public string wechat { get; set; } public string estimated_travel_date { get; set; } public string train_number { get; set; } public string train_station { get; set; } public string departure_time { get; set; } public string departure_frequency { get; set; } public string departure_hotel_area { get; set; } public string meal_time { get; set; } //出行人数 public string tourists_number { get; set; } } public class MFWTsAddress { public string pick_up_address { get; set; } public string return_address { get; set; } } public class MFWAddress { public string adress { get; set; } public string receiver_name { get; set; } public string receiver_phone { get; set; } } #endregion #region 补款单 public class MFWReplenishResponse { public int total { get; set; } public MFWReplenish[] list { get; set; } } public class MFWReplenish { public string order_id { get; set; } //补款单号 public string replenish_id { get; set; } //补款单状态 public int status { get; set; } //补款单创建时间 public string ctime { get; set; } //创建补款单原因 public int reason { get; set; } // 补款单具体金额 public decimal fee { get; set; } // 补款单备注 public string remark { get; set; } } /// <summary> /// 补款单状态 /// </summary> public enum ReplenishStatus { 待支付 = 0, 已支付 = 1, 申请退款中 = 2, 部分退款成功 = 3, 全部退款成功 = 4, 已关闭 = 5 } #endregion #endregion }