🧩 C#
声明
本 SDK 由 AI 生成,仅供参考使用。建议在实际生产环境使用前进行充分测试。
概述
平台 C# Server SDK 提供了便捷的服务端接入方式,支持签名生成、请求加密、响应验签和解密等核心功能。
功能特性
- 支持 SHA256withRSA 签名算法
- 支持 AES 对称加密/解密
- 灵活的 HTTP 客户端接口设计,支持多种 HTTP 客户端实现
- 自动处理签名和加密逻辑
- 完整的响应验签和解密
环境要求
- .NET 6.0+ 或 .NET Framework 4.6.1+
安装
NuGet 包
<PackageReference Include="System.Text.Json" Version="7.0.0"/>
SDK主要使用 .NET 标准库,无需额外依赖。
快速开始
1. 创建配置
using KudianSdk;
using KudianSdk.Http;
// 创建配置
var config = new ClientConfig
{
GatewayUrl = "https://pay.kudianvip.com", // 正式环境
// GatewayUrl = "https://pay.test.kudianvip.com", // 测试环境
MchId = "your_mch_id",
AppId = "your_app_id",
AppPrivateKey = "your_app_private_key", // Base64格式
AppSecretKey = "your_secret_key", // AES加密密钥
ApiPublicKey = "your_api_public_key" // Base64格式
};
// 创建客户端
var httpClient = new DefaultHttpClient();
var client = new KudianClient(config, httpClient);
2. 发起API请求
using System.Text.Json;
using System.Collections.Generic;
// 构造业务参数
var bizParams = new Dictionary<string, object>
{
{ "param1", "value1" },
{ "param2", "value2" }
};
// 发送请求
var response = await client.PostAsync("/api/path", bizParams);
// 解析响应
var apiResponse = JsonSerializer.Deserialize<ApiResponse>(response);
if (apiResponse.Code == 0)
{
Console.WriteLine($"业务数据: {apiResponse.Data}");
}
else
{
Console.WriteLine($"请求失败: {apiResponse.Msg}");
}
核心 API
KudianClient
主客户端类,提供所有API调用功能。
配置参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| GatewayUrl | string | 是 | 网关地址,正式环境: https://pay.kudianvip.com,测试环境: https://pay.test.kudianvip.com |
| MchId | string | 是 | 商户编号 |
| AppId | string | 是 | 应用ID |
| AppPrivateKey | string | 是 | 应用私钥(Base64编码格式),用于请求签名 |
| AppSecretKey | string | 是 | AES加密密钥,用于请求/响应内容加密 |
| ApiPublicKey | string | 是 | API平台公钥(Base64编码格式),用于响应验签 |
| ConnectTimeout | int | 否 | 连接超时时间(秒),默认30秒 |
| ReadTimeout | int | 否 | 读取超时时间(秒),默认30秒 |
主要方法
PostAsync(string apiPath, Dictionary<string, object> bizParams) -> Task<string>
发送POST请求到指定API路径。
参数:
apiPath: API路径,如/api/pay/createbizParams: 业务参数字典
返回:
- 自动解密后的业务数据JSON字符串
示例:
var params = new Dictionary<string, object>
{
{ "order_no", "ORDER202401010001" },
{ "amount", 10000 },
{ "subject", "测试订单" }
};
var result = await client.PostAsync("/api/pay/create", params);
VerifyNotify(Dictionary<string, string> notifyParams) -> bool
验证回调通知的签名。
参数:
notifyParams: 回调参数字典
返回:
true: 验签成功false: 验签失败
示例:
// 在你的ASP.NET Core Controller中
[HttpPost("notify")]
public async Task<IActionResult> HandleNotify([FromBody] JsonElement body)
{
var params = JsonSerializer.Deserialize<Dictionary<string, string>>(body);
// 验证签名
if (!client.VerifyNotify(params))
{
return BadRequest("FAIL");
}
// 获取业务数据(已自动解密)
var bizData = client.DecryptNotify(params["result"]);
Console.WriteLine($"回调数据: {bizData}");
// 处理业务逻辑...
return Ok("SUCCESS");
}
完整代码实现
1. ClientConfig.cs - 配置类
using System;
namespace KudianSdk
{
/// <summary>
/// SDK配置
/// </summary>
public class ClientConfig
{
/// <summary>
/// 网关地址
/// </summary>
public string GatewayUrl { get; set; } = string.Empty;
/// <summary>
/// 商户编号
/// </summary>
public string MchId { get; set; } = string.Empty;
/// <summary>
/// 应用ID
/// </summary>
public string AppId { get; set; } = string.Empty;
/// <summary>
/// 应用私钥(Base64格式)
/// </summary>
public string AppPrivateKey { get; set; } = string.Empty;
/// <summary>
/// API平台公钥(Base64格式)
/// </summary>
public string ApiPublicKey { get; set; } = string.Empty;
/// <summary>
/// AES加密密钥
/// </summary>
public string AppSecretKey { get; set; } = string.Empty;
/// <summary>
/// 连接超时时间(秒),默认30秒
/// </summary>
public int ConnectTimeout { get; set; } = 30;
/// <summary>
/// 读取超时时间(秒),默认30秒
/// </summary>
public int ReadTimeout { get; set; } = 30;
}
}
2. CryptoUtils.cs - 加密工具类
using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;
namespace KudianSdk
{
/// <summary>
/// 加密工具类
/// </summary>
public class CryptoUtils
{
/// <summary>
/// AES加密
/// </summary>
public static string AesEncrypt(string plainText, string key)
{
// 确保密钥是32字节
var keyBytes = Encoding.UTF8.GetBytes(key.PadRight(32).Substring(0, 32));
using (var aes = Aes.Create())
{
aes.Key = keyBytes;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
var encryptor = aes.CreateEncryptor();
var plainBytes = Encoding.UTF8.GetBytes(plainText);
var encrypted = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
return Convert.ToBase64String(encrypted);
}
}
/// <summary>
/// AES解密
/// </summary>
public static string AesDecrypt(string cipherText, string key)
{
// 确保密钥是32字节
var keyBytes = Encoding.UTF8.GetBytes(key.PadRight(32).Substring(0, 32));
using (var aes = Aes.Create())
{
aes.Key = keyBytes;
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.PKCS7;
var decryptor = aes.CreateDecryptor();
var cipherBytes = Convert.FromBase64String(cipherText);
var decrypted = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
return Encoding.UTF8.GetString(decrypted);
}
}
/// <summary>
/// SHA256withRSA签名
/// </summary>
public static string SignSHA256withRSA(byte[] message, byte[] privateKeyBytes)
{
using (var rsa = RSA.Create())
{
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
var signature = rsa.SignData(message, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signature);
}
}
/// <summary>
/// SHA256withRSA验签
/// </summary>
public static bool VerifySHA256withRSA(byte[] message, byte[] publicKeyBytes, string sign)
{
using (var rsa = RSA.Create())
{
rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
var signature = Convert.FromBase64String(sign);
return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
}
}
3. SignUtils.cs - 签名工具类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace KudianSdk
{
/// <summary>
/// 签名工具类
/// </summary>
public class SignUtils
{
/// <summary>
/// 生成签名字符串
/// 将参数按ASCII码排序后拼接成 key1=value1&key2=value2 格式
/// </summary>
public static string MakeSignString(Dictionary<string, string> params)
{
// 过滤sign字段和空值
var filtered = params
.Where(p => p.Key != "sign" && !string.IsNullOrEmpty(p.Value))
.Select(p => p.Key)
.OrderBy(k => k, StringComparer.Ordinal)
.ToList();
// 拼接字符串
var pairs = filtered.Select(k => $"{k}={params[k]}");
return string.Join("&", pairs);
}
}
}
4. IHttpClient.cs - HTTP客户端接口
using System.Collections.Generic;
using System.Threading.Tasks;
namespace KudianSdk.Http
{
/// <summary>
/// HTTP客户端接口
/// </summary>
public interface IHttpClient
{
/// <summary>
/// 发送POST请求
/// </summary>
Task<string> PostAsync(string url, Dictionary<string, string> headers, string requestBody);
}
}
5. DefaultHttpClient.cs - 默认HTTP客户端实现
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace KudianSdk.Http
{
/// <summary>
/// 基于HttpClient的默认实现
/// </summary>
public class DefaultHttpClient : IHttpClient
{
private readonly HttpClient _httpClient;
public DefaultHttpClient()
{
_httpClient = new HttpClient();
}
public DefaultHttpClient(int timeoutSeconds)
{
_httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(timeoutSeconds)
};
}
public async Task<string> PostAsync(string url, Dictionary<string, string> headers, string requestBody)
{
using (var request = new HttpRequestMessage(HttpMethod.Post, url))
{
// 设置请求头
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
// 设置请求体
request.Content = new StringContent(requestBody, Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
public void Dispose()
{
_httpClient?.Dispose();
}
}
}
6. KudianClient.cs - 主客户端类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using KudianSdk.Http;
namespace KudianSdk
{
/// <summary>
/// 酷点支付SDK主客户端
/// </summary>
public class KudianClient : IDisposable
{
private readonly ClientConfig _config;
private readonly IHttpClient _httpClient;
public KudianClient(ClientConfig config, IHttpClient httpClient)
{
_config = config ?? throw new ArgumentNullException(nameof(config));
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
}
/// <summary>
/// 发送POST请求
/// </summary>
public async Task<string> PostAsync(string apiPath, Dictionary<string, object> bizParams)
{
// 1. 构造完整请求URL
var url = $"{_config.GatewayUrl}{apiPath}";
// 2. 将业务参数转换为JSON并加密
var bizJson = JsonSerializer.Serialize(bizParams);
var encryptedContent = CryptoUtils.AesEncrypt(bizJson, _config.AppSecretKey);
// 3. 构造公共参数
var publicParams = new Dictionary<string, string>
{
{ "mch_id", _config.MchId },
{ "app_id", _config.AppId },
{ "timestamp", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString() },
{ "nonce_str", GenerateNonceStr() },
{ "sign_type", "SHA" },
{ "content", encryptedContent },
{ "version", "2.0" }
};
// 4. 生成签名
var signString = SignUtils.MakeSignString(publicParams);
var privateKeyBytes = Convert.FromBase64String(_config.AppPrivateKey);
var sign = CryptoUtils.SignSHA256withRSA(Encoding.UTF8.GetBytes(signString), privateKeyBytes);
publicParams["sign"] = sign;
// 5. 发送请求
var requestBody = JsonSerializer.Serialize(publicParams);
Console.WriteLine($"请求URL: {url}");
Console.WriteLine($"请求参数: {requestBody}");
var headers = new Dictionary<string, string>
{
{ "Content-Type", "application/json" }
};
var responseBody = await _httpClient.PostAsync(url, headers, requestBody);
Console.WriteLine($"响应结果: {responseBody}");
// 6. 解析响应并验签
using (var responseDoc = JsonDocument.Parse(responseBody))
{
var response = responseDoc.RootElement;
var code = response.GetProperty("code").GetInt32();
if (code == 0)
{
// 成功响应,验证签名
var responseSign = response.GetProperty("sign").GetString();
var verifyParams = new Dictionary<string, string>();
foreach (var property in response.EnumerateObject())
{
// 排除sign字段
if (property.Name != "sign" && property.Value.ValueKind != JsonValueKind.Null)
{
verifyParams[property.Name] = property.Value.ToString();
}
}
var verifyString = SignUtils.MakeSignString(verifyParams);
var publicKeyBytes = Convert.FromBase64String(_config.ApiPublicKey);
var verifyResult = CryptoUtils.VerifySHA256withRSA(
Encoding.UTF8.GetBytes(verifyString),
publicKeyBytes,
responseSign
);
if (!verifyResult)
{
throw new Exception("响应签名验证失败");
}
// 解密业务数据
var encryptedResult = response.GetProperty("result").GetString();
var decryptedData = CryptoUtils.AesDecrypt(encryptedResult, _config.AppSecretKey);
Console.WriteLine($"解密后的业务数据: {decryptedData}");
return decryptedData;
}
else
{
// 失败响应
var msg = response.GetProperty("msg").GetString();
throw new Exception($"请求失败: code={code}, msg={msg}");
}
}
}
/// <summary>
/// 验证回调通知签名
/// </summary>
public bool VerifyNotify(Dictionary<string, string> notifyParams)
{
if (!notifyParams.TryGetValue("sign", out var sign) || string.IsNullOrEmpty(sign))
{
return false;
}
var verifyParams = new Dictionary<string, string>(notifyParams);
var verifyString = SignUtils.MakeSignString(verifyParams);
var publicKeyBytes = Convert.FromBase64String(_config.ApiPublicKey);
return CryptoUtils.VerifySHA256withRSA(
Encoding.UTF8.GetBytes(verifyString),
publicKeyBytes,
sign
);
}
/// <summary>
/// 解密回调通知的业务数据
/// </summary>
public string DecryptNotify(string encryptedResult)
{
return CryptoUtils.AesDecrypt(encryptedResult, _config.AppSecretKey);
}
/// <summary>
/// 生成随机字符串
/// </summary>
private string GenerateNonceStr()
{
return Guid.NewGuid().ToString("N").Substring(0, 32);
}
public void Dispose()
{
if (_httpClient is IDisposable disposable)
{
disposable.Dispose();
}
}
}
}
7. ApiResponse.cs - 响应模型
using System.Text.Json.Serialization;
namespace KudianSdk
{
/// <summary>
/// API响应模型
/// </summary>
public class ApiResponse
{
[JsonPropertyName("code")]
public int Code { get; set; }
[JsonPropertyName("msg")]
public string Msg { get; set; } = string.Empty;
[JsonPropertyName("data")]
public object Data { get; set; } = new object();
}
}
可选实现
RestSharpClient.cs - RestSharp 客户端实现
using System.Collections.Generic;
using System.Threading.Tasks;
using RestSharp;
namespace KudianSdk.Http
{
/// <summary>
/// 基于RestSharp的HTTP客户端实现
/// </summary>
public class RestSharpClient : IHttpClient
{
private readonly RestClient _client;
public RestSharpClient()
{
var options = new RestClientOptions
{
MaxTimeout = 30000 // 30秒
};
_client = new RestClient(options);
}
public async Task<string> PostAsync(string url, Dictionary<string, string> headers, string requestBody)
{
var request = new RestRequest(url, Method.Post);
request.AddStringBody(requestBody, ContentType.Json);
foreach (var header in headers)
{
request.AddHeader(header.Key, header.Value);
}
var response = await _client.ExecuteAsync(request);
if (!response.IsSuccessful)
{
throw new System.Exception($"请求失败: {response.StatusCode}");
}
return response.Content ?? string.Empty;
}
}
}
使用示例
完整示例:创建支付订单
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using KudianSdk;
using KudianSdk.Http;
class Program
{
static async Task Main(string[] args)
{
// 1. 创建配置
var config = new ClientConfig
{
GatewayUrl = "https://pay.test.kudianvip.com",
MchId = "your_mch_id",
AppId = "your_app_id",
AppPrivateKey = "MIIEvQIBADANBgkqhkiG9w0BAQE...", // Base64格式的私钥
ApiPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA...", // Base64格式的公钥
AppSecretKey = "your_secret_key_32_bytes" // 32字节的AES密钥
};
// 2. 创建客户端
using var httpClient = new DefaultHttpClient();
using var client = new KudianClient(config, httpClient);
try
{
// 3. 构造业务参数
var bizParams = new Dictionary<string, object>
{
{ "order_no", $"ORDER{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}" },
{ "amount", 10000 },
{ "currency", "CNY" },
{ "subject", "测试商品" },
{ "notify_url", "https://your-domain.com/notify" }
};
// 4. 发送请求
var result = await client.PostAsync("/api/pay/create", bizParams);
// 5. 解析响应
var data = JsonSerializer.Deserialize<Dictionary<string, object>>(result);
Console.WriteLine("支付订单创建成功!");
Console.WriteLine($"订单号: {data["order_no"]}");
Console.WriteLine($"支付URL: {data["pay_url"]}");
}
catch (Exception ex)
{
Console.WriteLine($"订单创建失败: {ex.Message}");
}
}
}
ASP.NET Core 回调通知处理示例
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using KudianSdk;
using KudianSdk.Http;
[ApiController]
[Route("api/[controller]")]
public class NotifyController : ControllerBase
{
private readonly KudianClient _client;
public NotifyController()
{
var config = new ClientConfig
{
MchId = "your_mch_id",
AppId = "your_app_id",
AppPrivateKey = "your_app_private_key",
AppSecretKey = "your_secret_key",
ApiPublicKey = "your_api_public_key"
};
var httpClient = new DefaultHttpClient();
_client = new KudianClient(config, httpClient);
}
[HttpPost("pay")]
public async Task<IActionResult> HandlePayNotify([FromBody] JsonElement body)
{
try
{
// 1. 解析请求参数
var params = JsonSerializer.Deserialize<Dictionary<string, string>>(body);
// 2. 验证签名
if (!_client.VerifyNotify(params))
{
Console.WriteLine("签名验证失败");
return BadRequest("FAIL");
}
// 3. 解密业务数据
var bizData = _client.DecryptNotify(params["result"]);
Console.WriteLine($"收到支付回调: {bizData}");
// 4. 解析业务数据并处理
// TODO: 根据业务逻辑处理支付结果
// 更新订单状态、发货等
// 5. 返回成功
return Ok("SUCCESS");
}
catch (Exception ex)
{
Console.WriteLine($"处理回调失败: {ex.Message}");
return BadRequest("FAIL");
}
}
}
.NET Framework 回调通知处理示例
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Mvc;
using KudianSdk;
using KudianSdk.Http;
public class NotifyController : Controller
{
private readonly KudianClient _client;
public NotifyController()
{
var config = new ClientConfig
{
MchId = "your_mch_id",
AppId = "your_app_id",
AppPrivateKey = "your_app_private_key",
AppSecretKey = "your_secret_key",
ApiPublicKey = "your_api_public_key"
};
var httpClient = new DefaultHttpClient();
_client = new KudianClient(config, httpClient);
}
[HttpPost]
public async Task<ActionResult> Pay()
{
try
{
// 1. 读取请求体
using (var reader = new StreamReader(Request.InputStream))
{
var body = await reader.ReadToEndAsync();
var params = JsonSerializer.Deserialize<Dictionary<string, string>>(body);
// 2. 验证签名
if (!_client.VerifyNotify(params))
{
Console.WriteLine("签名验证失败");
return new ContentResult
{
Content = "FAIL",
ContentType = "text/plain",
ContentEncoding = Encoding.UTF8
};
}
// 3. 解密业务数据
var bizData = _client.DecryptNotify(params["result"]);
Console.WriteLine($"收到支付回调: {bizData}");
// 4. 解析业务数据并处理
// TODO: 根据业务逻辑处理支付结果
// 更新订单状态、发货等
// 5. 返回成功
return new ContentResult
{
Content = "SUCCESS",
ContentType = "text/plain",
ContentEncoding = Encoding.UTF8
};
}
}
catch (Exception ex)
{
Console.WriteLine($"处理回调失败: {ex.Message}");
return new ContentResult
{
Content = "FAIL",
ContentType = "text/plain",
ContentEncoding = Encoding.UTF8
};
}
}
}
异常处理
SDK可能抛出的异常:
| 异常类型 | 说明 | 处理建议 |
|---|---|---|
| ArgumentNullException | 配置参数为空 | 检查配置参数是否完整 |
| CryptographicException | 签名验签失败 | 检查密钥是否正确 |
| Exception | 加解密失败 | 检查AppSecretKey是否正确 |
| HttpRequestException | HTTP请求失败 | 检查网络连接和网关地址 |
| JsonException | JSON解析失败 | 检查数据格式是否正确 |
常见问题
1. 如何获取密钥?
- app_private_key: 商户自行生成RSA密钥对,将公钥上传到商户后台
- api_public_key: 从商户后台获取平台公钥
- secret_key: 从商户后台获取或设置
2. 如何生成RSA密钥对?
可以使用OpenSSL命令生成:
# 生成私钥
openssl genrsa -out app_private_key.pem 2048
# 提取公钥
openssl rsa -in app_private_key.pem -pubout -out app_public_key.pem
# 转换为PKCS8格式
openssl pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out app_private_key_pkcs8.pem
# 转换为Base64格式
base64 -w 0 app_private_key_pkcs8.pem
base64 -w 0 app_public_key.pem
3. 如何使用自定义HTTP客户端?
SDK支持自定义HTTP客户端实现,只需实现 IHttpClient 接口:
// 使用内置的DefaultHttpClient
var client1 = new KudianClient(config, new DefaultHttpClient());
// 使用RestSharp
var client2 = new KudianClient(config, new RestSharpClient());
// 自定义实现
public class CustomHttpClient : IHttpClient
{
public async Task<string> PostAsync(string url, Dictionary<string, string> headers, string requestBody)
{
// 自定义HTTP客户端实现
// 例如使用其他HTTP库或添加特殊逻辑
return await Task.FromResult("");
}
}
var client3 = new KudianClient(config, new CustomHttpClient());
4. 日志配置
SDK使用Console.WriteLine记录日志,可以替换为其他日志框架:
using Microsoft.Extensions.Logging;
// 在KudianClient中将Console.WriteLine替换为ILogger
_logger.LogInformation($"请求URL: {url}");
_logger.LogInformation($"请求参数: {requestBody}");
_logger.LogInformation($"响应结果: {responseBody}");
_logger.LogInformation($"解密后的业务数据: {decryptedData}");
5. .csproj 文件示例
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Text.Json" Version="7.0.0"/>
<!-- 如果使用RestSharp -->
<PackageReference Include="RestSharp" Version="110.2.0"/>
<!-- ASP.NET Core Web API -->
<PackageReference Include="Microsoft.AspNetCore.App"/>
</ItemGroup>
</Project>
6. 异步编程最佳实践
建议始终使用异步方法:
// ✅ 推荐:使用异步
var result = await client.PostAsync("/api/pay/create", bizParams);
// ❌ 不推荐:阻塞等待
var result = client.PostAsync("/api/pay/create", bizParams).GetAwaiter().GetResult();
在ASP.NET Core中,确保Controller方法使用async/await:
[HttpPost]
public async Task<IActionResult> CreateOrder([FromBody] OrderRequest request)
{
var result = await _client.PostAsync("/api/pay/create", request.ToDictionary());
// 处理结果...
return Ok();
}