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
}