跳到主要内容

🧩 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调用功能。

配置参数

参数类型必填说明
GatewayUrlstring网关地址,正式环境: https://pay.kudianvip.com,测试环境: https://pay.test.kudianvip.com
MchIdstring商户编号
AppIdstring应用ID
AppPrivateKeystring应用私钥(Base64编码格式),用于请求签名
AppSecretKeystringAES加密密钥,用于请求/响应内容加密
ApiPublicKeystringAPI平台公钥(Base64编码格式),用于响应验签
ConnectTimeoutint连接超时时间(秒),默认30秒
ReadTimeoutint读取超时时间(秒),默认30秒

主要方法

PostAsync(string apiPath, Dictionary<string, object> bizParams) -> Task<string>

发送POST请求到指定API路径。

参数:

  • apiPath: API路径,如 /api/pay/create
  • bizParams: 业务参数字典

返回:

  • 自动解密后的业务数据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是否正确
HttpRequestExceptionHTTP请求失败检查网络连接和网关地址
JsonExceptionJSON解析失败检查数据格式是否正确

常见问题

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();
}
预约咨询