C# .NET对接抖音团购验券核销
这里 RPC 使用原生的HTTP Client ,下期写对接饿了么的文章时,再使用Flurl.http进行示例
抖音开发文档链接如下:
https://developer.open-douyin.com/docs/resource/zh-CN/local-life/develop/OpenAPI/life.capacity.fulfilment/certificate.prepare/
验券总结来说就是三步:
1.获取client token
2.验券准备,获取团购券的券码和验券token
3.验券
所有示例代码基于CoreShop
先上基类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CoreCms.Net.Model.Entities.Tiktok
{
/// <summary>
/// 抖音配置类
/// </summary>
public class TiktokConfig
{
public string APPID { get; set; }
public string AppSecret { get; set; }
public bool IsSandBox { get; set; }
public string CallBackUrl { get; set; }
/// <summary>
/// 抖音配置类
/// </summary>
/// <param name="aPPID"></param>
/// <param name="appSecret"></param>
/// <param name="isSandBox"></param>
/// <exception cref="ArgumentNullException"></exception>
public TiktokConfig(string aPPID, string appSecret, string callBackUrl, bool isSandBox = false )
{
APPID = aPPID ?? throw new ArgumentNullException(nameof(APPID));
AppSecret = appSecret ?? throw new ArgumentNullException(nameof(AppSecret));
IsSandBox = isSandBox;
CallBackUrl = callBackUrl;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CoreCms.Net.Model.Entities
{
/// <summary>
/// 抖音常量工具类
/// </summary>
public class TiktokConst
{
/// <summary>
/// 获取接口调用的凭证client_access_token
/// </summary>
public static string CLIENT_TOKEN = "https://open.douyin.com/oauth/client_token/";
/// <summary>
/// 抖音团购token
/// </summary>
public static string DOUYIN_TOKEN_KEY = "douyin_live_token";
//抖音生活服务
/// <summary>
/// 券详情
/// </summary>
public static string CERTIFICATE_GET = "https://open.douyin.com/goodlife/v1/fulfilment/certificate/get/";
/// <summary>
/// 券状态批量查询
/// </summary>
public static string CERTIFICATE_BATCH_GET = "https://open.douyin.com/goodlife/v1/fulfilment/certificate/query/";
/// <summary>
/// 验券准备
/// </summary>
public static string CERTIFICATE_PREPARE = "https://open.douyin.com/goodlife/v1/fulfilment/certificate/prepare/";
/// <summary>
/// 验券
/// </summary>
public static string CERTIFICATE_VERIFY = "https://open.douyin.com/goodlife/v1/fulfilment/certificate/verify/";
/// <summary>
/// 撤销核销
/// </summary>
public static string CERTIFICATE_CANCEL = "https://open.douyin.com/goodlife/v1/fulfilment/certificate/cancel/";
/// <summary>
/// 门店信息查询
/// </summary>
public static string SHOP_POI_QUERY = "https://open.douyin.com/goodlife/v1/shop/poi/query/";
/// <summary>
/// 门店信息更新
/// </summary>
public static string SHOP_POI_UPDATE = "https://open.douyin.com/goodlife/v1/poi/poi/update/";
/// <summary>
/// 验券记录查询
/// </summary>
public static string VERIFY_RECORD_QUERY = "https://open.douyin.com/goodlife/v1/fulfilment/certificate/verify_record/query/";
/// <summary>
/// 账单详细查询
/// </summary>
public static string DETAILED_QUERY = "https://open.douyin.com/goodlife/v1/settle/ledger/detailed_query/";
//*************************** 会员 ***************************
/// <summary>
/// 账单详细查询
/// </summary>
public static string MEMBER_UPDATE = "https://open.douyin.com/goodlife/v1/member/user/update/";
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CoreCms.Net.Model.Entities
{
/// <summary>
/// token响应
/// </summary>
public class ClientTokenResp
{
public Data data { get; set; }
public string message { get; set; }
public class Data
{
public string access_token { get; set; }
public string captcha { get; set; }
public string desc_url { get; set; }
public string description { get; set; }
public int error_code { get; set; }
public int ExpiresIn { get; set; }
public string LogId { get; set; }
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CoreCms.Net.Model.Entities
{
public class TiktokBaseResp<T>
{
public T data { get; set; }
public Extra extra { get; set; }
public string message { get; set; }
public class Extra
{
public int error_code { get; set; }
public string description { get; set; }
public int sub_error_code { get; set; }
public string sub_description { get; set; }
public long now { get; set; }
public string logid { get; set; }
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static SKIT.FlurlHttpClient.Wechat.Api.Models.CardCreateRequest.Types.GrouponCard.Types.Base.Types;
using static SKIT.FlurlHttpClient.Wechat.Api.Models.ChannelsECOrderGetResponse.Types.Order.Types.OrderDetail.Types;
namespace CoreCms.Net.Model.Entities
{
/// <summary>
/// 验券准备返回体
/// </summary>
public class CertificatePrepareResp
{
/// <summary>
/// 错误码
/// </summary>
public int error_code { get; set; }
/// <summary>
/// 错误码描述
/// </summary>
public string description { get; set; }
/// <summary>
/// 抖音订单id
/// </summary>
public string order_id { get; set; }
/// <summary>
/// 一次验券的标识, 在验券接口传入
/// </summary>
public string verify_token { get; set; }
/// <summary>
/// 可用团购券列表
/// </summary>
public List<Certificates> certificates { get; set; }
public class Certificates
{
/// <summary>
/// 加密券码, 在验券接口传入
/// </summary>
public string encrypted_code { get; set; }
/// <summary>
///券码有效期,截至时间,时间戳,单位秒
/// </summary>
public long expire_time { get; set; }
/// <summary>
/// 团购信息
/// </summary>
public Sku sku { get; set; }
public class Sku
{
/// <summary>
/// 团购SKU ID
/// </summary>
public string sku_id { get; set; }
/// <summary>
/// 团购名称
/// </summary>
public string title { get; set; }
/// <summary>
/// 团购类型(type=1团餐券; type=2代金券; type=3次卡)
/// </summary>
public int groupon_type { get; set; }
/// <summary>
/// 团购市场价,单位分
/// </summary>
public decimal market_price { get; set; }
/// <summary>
/// 团购售卖开始时间,时间戳,单位秒
/// </summary>
public long sold_start_time { get; set; }
/// <summary>
/// 商家系统(第三方)团购id
/// </summary>
public string third_sku_id { get; set; }
/// <summary>
/// 商家团购账号id
/// </summary>
public string account_id { get; set; }
}
/// <summary>
/// 金额信息
/// </summary>
public Amount amount { get; set; }
public class Amount
{
/// <summary>
/// 券原始金额,单位分
/// </summary>
public decimal original_amount { get; set; }
/// <summary>
/// 用户实付金额,单位分
/// </summary>
public decimal pay_amount { get; set; }
/// <summary>
/// 商家营销金额,单位分
/// </summary>
public decimal merchant_ticket_amount { get; set; }
/// <summary>
/// 支付优惠金额,单位分
/// </summary>
public decimal payment_discount_amount { get; set; }
/// <summary>
/// 券实付金额(=用户实付金额+支付优惠金额),单位分
/// </summary>
public decimal coupon_pay_amount { get; set; }
}
/// <summary>
/// 次卡信息
/// </summary>
public TimeCard time_card { get; set; }
public class TimeCard
{
/// <summary>
/// 次卡总次数
/// </summary>
public int times_count { get; set; }
/// <summary>
/// 次卡已使用次数
/// </summary>
public int times_used { get; set; }
}
}
/// <summary>
/// 商品信息
/// </summary>
public corecmsfood productInfo { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CoreCms.Net.Model.Entities
{
public class CertificateVerifyResp
{
/// <summary>
/// 错误码
/// </summary>
public int error_code;
/// <summary>
/// 错误码描述
/// </summary>
public string description;
/// <summary>
/// 验券结果
/// </summary>
public List<VerifyResults> verify_results;
public class VerifyResults
{
/// <summary>
/// 验券结果码,0表示成功,非0表示失败
/// </summary>
public int result { get; set; }
/// <summary>
/// 验券结果说明
/// </summary>
public string msg { get; set; }
/// <summary>
/// 代表验券传入的code或encrypted_code
/// </summary>
public string code { get; set; }
/// <summary>
/// 代表券码一次核销的标识(撤销时需要)
/// </summary>
public string verify_id { get; set; }
/// <summary>
///代表一张券码的标识(撤销时需要)
/// </summary>
public string certificate_id { get; set; }
/// <summary>
///代表抖音团购券的12位原始券码(抖音加密券码核销时)
/// </summary>
public string origin_code { get; set; }
/// <summary>
///代表企业号商家总店id(查询验券历史时需要)
/// </summary>
public string account_id { get; set; }
/// <summary>
///代表一张订单的标识
/// </summary>
public string order_id { get; set; }
}
}
}
控制器
using CoreCms.Net.Configuration;
using CoreCms.Net.DTO;
using CoreCms.Net.IServices;
using CoreCms.Net.Model.Entities;
using CoreCms.Net.Model.ViewModels.UI;
using CoreCms.Net.Services;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using System;
using CoreCms.Net.Filter;
using CoreCms.Net.WeChat.Service.Utilities;
using Azure.Core;
using CoreCms.Net.Model.Entities.Tiktok;
using CoreCms.Net.Model.FromBody;
using CoreCms.Net.Auth.HttpContextUser;
using Microsoft.AspNetCore.Authorization;
namespace CoreCms.Net.Web.WebApi.Controllers.Tiktok
{
[Description("抖音控制器")]
[Route("api/[controller]/[action]")]
[ApiExplorerSettings(GroupName = "Tiktok ")]
[ApiController]
[RequiredErrorForAdmin]
public class TiktokController : ControllerBase
{
private readonly ITiktokServices _tiktokServices;
private readonly ICoreCmsStoreServices _coreCmsStoreServices;
public TiktokController( ITiktokServices tiktokServices, ICoreCmsStoreServices coreCmsStoreServices)
{
_tiktokServices = tiktokServices;
_coreCmsStoreServices = coreCmsStoreServices;
}
[HttpPost]
[Description("验券准备")]
public async Task<TiktokBaseResp<CertificatePrepareResp>> CertificatePrepare(FMData data)
=> await _tiktokServices.CertificatePrepare(data.data);
[HttpPost]
[Description("验券准备 ( 根据券码 ) ")]
public async Task<TiktokBaseResp<CertificatePrepareResp>> CertificatePrepareByCode(FMData data)
=> await _tiktokServices.CertificatePrepareByCode(data.data);
[HttpPost]
[Description("获取ClientToken")]
public async Task<AdminUiCallBack> GetClientToken()
{
AdminUiCallBack jm = new()
{
code = 0,
msg = GlobalConstVars.GetDataSuccess,
data = await _tiktokServices.GetToken()
};
return jm;
}
[HttpPost]
[Description("验券")]
public async Task<TiktokBaseResp<CertificateVerifyResp>> CertificateVerify(CertificateVerifyReq req)
{
TiktokBaseResp<CertificateVerifyResp> tiktokBaseResp = await _tiktokServices.CertificateVerify(req);
return tiktokBaseResp;
}
}
}
服务层
using CoreCms.Net.Configuration;
using CoreCms.Net.Model.Entities.Tiktok;
using Google.Protobuf.WellKnownTypes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
using CoreCms.Net.Services.TikTok;
using SqlSugar.Extensions;
using CoreCms.Net.Caching.Manual;
using CoreCms.Net.Auth.HttpContextUser;
using CoreCms.Net.IServices;
using Org.BouncyCastle.Asn1.Ocsp;
using Essensoft.Paylink.Alipay.Domain;
using CoreCms.Net.Model.Entities;
using Microsoft.IdentityModel.Tokens;
using Aliyun.OSS.Util;
using System.Threading;
using System.Collections.Specialized;
using System.Web;
using CoreCms.Net.Loging;
using NPOI.SS.Formula.Functions;
namespace CoreCms.Net.Services
{
public class TiktokServices : ITiktokServices
{
private readonly TiktokConfig _config;
public TiktokServices()
{
// 读取配置文件
var APPID = AppSettingsHelper.GetContent("Tiktok", "APPID"); //抖音服务商的app_ID
var AppSecret = AppSettingsHelper.GetContent("Tiktok", "AppSecret"); // 抖音服务商的应用秘钥
var isSandBox = AppSettingsHelper.GetContent("Tiktok", "IsSandBox").ObjToBool(); // 是否沙箱环境
_config = new TiktokConfig(APPID, AppSecret, callBackUrl, isSandBox);
}
/// <summary>
/// 获取token
/// </summary>
/// <returns></returns>
public async Task<string> GetToken()
{
string responseBody = null;
string clientToken = string.Empty;
ClientTokenResp clientTokenResponse = null;
try
{
using (var client = new HttpClient())
{
string url = "https://open.douyin.com/oauth/client_token/";
var jsonData = new
{
grant_type = "client_credential",
client_key = _config.APPID,
client_secret = _config.AppSecret
};
string jsonString = JsonConvert.SerializeObject(jsonData);
StringContent content = new StringContent(jsonString, Encoding.UTF8, "application/json");
// 这段一定要写
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
responseBody = await response.Content.ReadAsStringAsync();
clientTokenResponse = JsonConvert.DeserializeObject<ClientTokenResp>(responseBody);
if (clientTokenResponse.data.error_code == 0 && !string.IsNullOrEmpty(clientTokenResponse.data.access_token))
{
//ManualDataCache.Instance.Set($"tiktok_client_token", clientTokenResponse.data.access_token, 6500);
clientToken = clientTokenResponse.data.access_token;
}
}
}
catch (HttpRequestException e)
{
Console.WriteLine("\nException Caught!");
Console.WriteLine("Message :{0} ", e.Message);
NLogUtil.WriteFileLog(NLog.LogLevel.Error, LogType.Other, "Tiktok获取失败", "请求token错误事件", e);
}
//}
return clientToken;
}
/// <summary>
/// 验券准备
/// </summary>
/// <param name="code">券码</param>
/// <returns></returns>
public async Task<TiktokBaseResp<CertificatePrepareResp>> CertificatePrepare(string shortLink)
{
TiktokBaseResp<CertificatePrepareResp> certificatePrepareResp = null;
// 验券前准备
try
{
string object_id = await GetDouYinUrlParams(shortLink, "object_id");
using (var client = new HttpClient())
{
string token = await GetToken();
//client.DefaultRequestHeaders.Add("access-clientToken", token);
client.DefaultRequestHeaders.Add("access-token", token);
// 设置请求的URL,包括查询字符串
string url = $"{TiktokConst.CERTIFICATE_PREPARE}?encrypted_data={object_id}";
// 发送GET请求
HttpResponseMessage response = await client.GetAsync(url);
// 确保HTTP成功状态值
response.EnsureSuccessStatusCode();
// 读取响应内容
string responseBody = await response.Content.ReadAsStringAsync();
certificatePrepareResp = JsonConvert.DeserializeObject<TiktokBaseResp<CertificatePrepareResp>>(responseBody);
}
}
catch (HttpRequestException e)
{
Console.WriteLine("\nException Caught!");
Console.WriteLine("Message :{0} ", e.Message);
NLogUtil.WriteFileLog(NLog.LogLevel.Error, LogType.Other, "Tiktok验券准备", "请求错误事件", e);
}
return certificatePrepareResp;
}
/// <summary>
/// 核销
/// </summary>
/// <param name="code">券码</param>
/// <returns></returns>
public async Task<TiktokBaseResp<CertificateVerifyResp>> CertificateVerify(CertificateVerifyReq certificateVerifyReq)
{
TiktokBaseResp<CertificateVerifyResp> certificateResponse = null;
// 验券
try
{
using (var client = new HttpClient())
{
string token = await GetToken();
client.DefaultRequestHeaders.Add("access-token", token);
// 准备要发送的JSON数据
var jsonData = certificateVerifyReq;
// 将JSON数据转换为字符串
string jsonString = JsonConvert.SerializeObject(jsonData);
// 创建HttpContent对象,并设置请求的内容类型
StringContent content = new StringContent(jsonString, Encoding.UTF8, "application/json");
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
// 发送POST请求
HttpResponseMessage response = await client.PostAsync(TiktokConst.CERTIFICATE_VERIFY, content);
// 确保HTTP成功状态值
response.EnsureSuccessStatusCode();
// 读取响应内容
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
certificateResponse = JsonConvert.DeserializeObject<TiktokBaseResp<CertificateVerifyResp>>(responseBody);
}
}
catch (HttpRequestException e)
{
Console.WriteLine("\nException Caught!");
Console.WriteLine("Message :{0} ", e.Message);
NLogUtil.WriteFileLog(NLog.LogLevel.Error, LogType.Other, "Tiktok验券失败", "Tiktok验券失败事件", e);
}
return certificateResponse;
}
/// <summary>
/// 获取抖音url参数
/// </summary>
/// <param name="url"></param>
/// <param name="key"></param>
/// <returns></returns>
private async Task<string> GetDouYinUrlParams(string shortLink, string key)
{
try
{
using (var client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(shortLink);
Uri uri = response.RequestMessage.RequestUri;
string v = uri.ObjToString();
// 读取响应内容
NameValueCollection queryParameters = HttpUtility.ParseQueryString(uri.Query);
return queryParameters[key];
}
}
catch (HttpRequestException e)
{
Console.WriteLine("\nException Caught!");
Console.WriteLine("Message :{0} ", e.Message);
}
return null;
}
}
}