.Net Core微信服务商二次进件
最近商城进行微信服务商二次进件的开发,大致有几个点
一,服务商签名
二,服务商证书获取
三,图片上传
四,敏感信息加密
五,查询进件状态
除此之外,就是进件信息的拼装
电商二级商户进件申请单-状态流转
一 服务商签名
首先准备必须的配置:商户号、证书、秘钥、小程序appid、appsecret
#region 服务商签名
private string SrvPayBuildAuthAsync(string uri, string body, string method = "POST")
{
var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
string nonce = Guid.NewGuid().ToString(); string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n";
string signature = SrvSign(message); return $"mchid=\"{_wxCfg.SrvPayMerchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{_wxCfg.SrvPayCertNo}\",signature=\"{signature}\"";
} private string SrvSign(string message)
{
var bytes = Utils.ReadBytesIfExist(_wxCfg.SrvPayCertFile);
if (bytes is null)
{
return "";
}
X509Certificate2 cert = new(bytes, _wxCfg.SrvPayMerchantId);
RSA rsa = cert.GetRSAPrivateKey();
var signData = rsa.SignData(Encoding.UTF8.GetBytes(message), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signData);
}
二 获取证书
分为:第一步获取证书,第二步解密证书
1 获取证书
https://api.mch.weixin.qq.com/v3/certificates
#region 获取平台证书
public async Task<CertificatesOutModel> GetSrvCert()
{
string uri = "/v3/certificates";
var auth = SrvPayBuildAuthAsync(uri, "", "GET");
var header = new Dictionary<string, string>
{
{ "Authorization",$"WECHATPAY2-SHA256-RSA2048 {auth}"},
{ "Accept","*/*" },
{ "Accept-Encoding","gzip,deflate,brn" },
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46" },
};
return await GetUrlAsync<CertificatesOutModel>(uri, header);
}
#endregion
使用的实体:CertificatesOutModel
public sealed class CertificatesOutModel : IWXResponse
{
[JsonPropertyName("data")]
public IEnumerable<Certificates> Data { get; set; }
public string Code { get; set; }
public string Message { get; set; }
}
public class Certificates
{
[JsonPropertyName("serial_no")]
public string SerialNo { get; set; } [JsonPropertyName("effective_time")]
public string EffectiveTime { get; set; } [JsonPropertyName("expire_time")]
public string ExpireTime { get; set; } [JsonPropertyName("encrypt_certificate")]
public EncryptCertificate EncryptCertificate { get; set; }
}
请求方法:GetUrlAsync
protected async Task<T> GetUrlAsync<T>(string url, Dictionary<string, string> headers = null)
{
HttpResponseMessage res = null;
try
{
if (headers != null && headers.Count > 0)
{
foreach (var header in headers)
{
_client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
}
}
res = await _client.GetAsync(url);
res.EnsureSuccessStatusCode();
var result = await res.Content.ReadAsStringAsync();
if (result == null)
{
return default;
} return result.ToJson<T>();
}
catch
{
var result = await res.Content.ReadAsStringAsync();
if (result == null)
{
return default;
}
return result.ToJson<T>();
}
}
2,解密证书
//获取证书
var cert = await _wxClient.GetSrvCert();
var certificateModel = cert.Data.FirstOrDefault();
if (!cert.Data.Any())
{
return new MKResult<V3WXPayApplymentIdOutModel>(code: 400, msg: "未获取到平台证书");
}
if (!string.IsNullOrEmpty(applyment.Body.SerialNo))
{
certificateModel = cert.Data.SingleOrDefault(s => s.SerialNo == applyment.Body.SerialNo);
}
certificateModel.EncryptCertificate.Ciphertext = AESUtility.AesGcmDecrypt(
_wxCfg.SrvApiV3Key,
certificateModel.EncryptCertificate.AssociatedData,
certificateModel.EncryptCertificate.Nonce,
certificateModel.EncryptCertificate.Ciphertext
);
解密方法
public class AESUtility
{
public static string AesGcmDecrypt(string key, string associatedData, string nonce, string ciphertext)
{
GcmBlockCipher gcmBlockCipher = new(new AesEngine());
AeadParameters aeadParameters = new(
new KeyParameter(Encoding.UTF8.GetBytes(key)),
128,
Encoding.UTF8.GetBytes(nonce),
Encoding.UTF8.GetBytes(associatedData));
gcmBlockCipher.Init(false, aeadParameters); byte[] data = Convert.FromBase64String(ciphertext);
byte[] plaintext = new byte[gcmBlockCipher.GetOutputSize(data.Length)];
int length = gcmBlockCipher.ProcessBytes(data, 0, data.Length, plaintext, 0);
gcmBlockCipher.DoFinal(plaintext, length);
return Encoding.UTF8.GetString(plaintext);
}
}
三,上传图片
因为我的图片保存在oss,首先要网络图片Bytes,对图片进行sha256,方法在后面
protected async Task<byte[]> GetUrlBytesAsync(string url, Dictionary<string, string> headers = null)
{
try
{
if (headers != null && headers.Count > 0)
{
foreach (var header in headers)
{
_client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
}
} var res = await _client.GetAsync(url);
res.EnsureSuccessStatusCode();
return await res.Content.ReadAsByteArrayAsync();
}
catch
{
return default;
}
}
然后上传图片
/// <summary>
/// 上传图片
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task<MKResult<V3WXPayFileUploadOutModel>> UploadFile(string url)
{ string fileContentType;
string filetype;
if (url!.Contains(".bmp", StringComparison.OrdinalIgnoreCase))
{
fileContentType = "image/bmp";
filetype = ".bmp";
}
else if (url!.Contains(".jpg", StringComparison.OrdinalIgnoreCase))
{
fileContentType = "image/jpeg";
filetype = ".jpg";
}
else if (url!.Contains(".jpeg", StringComparison.OrdinalIgnoreCase))
{
fileContentType = "image/jpeg";
filetype = ".jpeg";
}
else
{
fileContentType = "image/png";
filetype = ".png";
} UploadMerchantMediaImageRequest meta = new();
var fileBytes = await GetUrlBytesAsync(url);//获取网络图片Bytes
if ((fileBytes?.Length ?? 0) == 0)
{
return new MKResult<V3WXPayFileUploadOutModel>(code: 400, msg: "转换图片失败");
}
meta.FileHash = GetHash(fileBytes);
meta.FileName = Guid.NewGuid().ToString("N").ToLower() + filetype; string boundary = "--BOUNDARY--" + DateTimeOffset.Now.Ticks.ToString("x");
using var fileContent = new ByteArrayContent(fileBytes);
using var metaContent = new StringContent(meta.ToJson(), Encoding.UTF8, "application/json");
using var httpContent = new MultipartFormDataContent(boundary);
httpContent.Add(metaContent, "\"meta\"");//meta 必须要加双引号
httpContent.Add(fileContent, "\"file\"", "\"" + meta.FileName + "\"");//必须要加双引号
httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data; boundary=" + boundary);// boundary不能加引号
metaContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(fileContentType); var uri = $"/v3/merchant/media/upload";
var res = await V3UpLoadFile<V3WXPayFileUploadOutModel>(uri, meta.ToJson(), httpContent);
return new MKResult<V3WXPayFileUploadOutModel>(res, 1);
}
private async Task<T> V3UpLoadFile<T>(string uri, string meta, MultipartFormDataContent content)
{
var auth = SrvPayBuildAuthAsync(uri, meta);
var header = new Dictionary<string, string>
{
{ "Authorization",$"WECHATPAY2-SHA256-RSA2048 {auth}"},
{ "Accept","*/*" },
{ "Accept-Encoding","gzip,deflate,brn" },
{ "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46" },
};
return await V3PostFileAsync<T>(uri, header, content);
}
protected async Task<T> V3PostFileAsync<T>(string url, Dictionary<string, string> headers, MultipartFormDataContent content)
{
HttpResponseMessage res = null;
try
{
if (headers != null && headers.Count > 0)
{
foreach (var header in headers)
{
_client.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
}
}
res = await _client.PostAsync(url, content);
res.EnsureSuccessStatusCode();
var result = await res.Content.ReadAsStringAsync();
if (result == null)
{
return default;
}
return result.ToJson<T>();
}
catch
{
var result = await res.Content.ReadAsStringAsync();
if (result == null)
{
return default;
}
return result.ToJson<T>();
}
finally
{
if (content != null)
{
content.Dispose();
}
}
}
#region 二进制内容进行sha256
private static string GetHash(byte[] bytes)
{
if (bytes == null) throw new ArgumentNullException(nameof(bytes)); using SHA256 sha = SHA256.Create();
byte[] hashBytes = sha.ComputeHash(bytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
四,敏感信息加密
使用获取到的证书certificateModel,进行加密
public static class RSAUtility
{
public static string RSAEncrypt(string text, Certificates certificateModel)
{
var bytes = Encoding.UTF8.GetBytes(certificateModel.EncryptCertificate.Ciphertext);
using var x509 = new X509Certificate2(bytes);
var rsaParam = x509.GetRSAPublicKey().ExportParameters(false);
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParam);
var buff = rsa.Encrypt(Encoding.UTF8.GetBytes(text), true);
return Convert.ToBase64String(buff);
} }
五,查询进件状态
直接使用进件返回的Id,调用接口查询就Ok了
.Net Core微信服务商二次进件的更多相关文章
- 微信小微商户申请入驻 .NET C#实现微信小微商户进件API
微信小微商户申请入驻 .NET C#实现微信小微商户进件API官方小微商户专属接口文档 微信支付SDK 微信支付官方SDK与DEMO下载 图片上传 图片上传接口API文档 证书下载 证书下载接口API ...
- easywechat微信开发SDK之小微商户进件(二)
正式开始进件之前需要准备几个东西 1.服务商商户号 2.API密钥 微信服务商后台中设置 3.APIv3密钥 微信服务商后台中设置 4.API证书路径 登录服务商后台下载 生成证书官方又文档的 很 ...
- easywechat微信开发SDK之小微商户进件(一)
微信本身不提供小微商户进件的SDK,偶然发现easywechat这么个东西,官网地址是https://www.easywechat.com/ 整合了微信开发中常用的接口,包括微信公众号相关接口,微信 ...
- .Net微信服务商平台ApiV3接口
最近做个对接微信服务商平台的小程序项目,大概要实现的流程是:a)特约商户进件 > b)生成带参数的小程序码 > c)小程序支付 > d)分账,记录一下,希望能对需要的朋友有所帮助 开 ...
- Android微信九宫格图片展示控件
版权声明:本文为xing_star原创文章,转载请注明出处! 本文同步自http://javaexception.com/archives/214 Android微信九宫格图片展示控件 半年前,公司产 ...
- 基于Jquery WeUI的微信开发H5页面控件的经验总结(2)
在微信开发H5页面的时候,往往借助于WeUI或者Jquery WeUI等基础上进行界面效果的开发,由于本人喜欢在Asp.net的Web界面上使用JQuery,因此比较倾向于使用 jQuery WeUI ...
- winform窗体(二)——控件
一.窗体的事件 每一个窗体都有一个事件,这个窗体加载完成之后执行哪一段代码 位置:1)右键属性→事件→load 双击进入 2)双击窗体任意一个位置进入 删除事件:先将事件页面里面的挂好的事件删除,再删 ...
- 微信连WiFi关注公众号流程更新 解决ios微信扫描二维码不关注就能上网的问题
前几天鼓捣了一下微信连WiFi功能,设置还蛮简单的,但ytkah发现如果是ios版微信扫描微信连WiFi生成的二维码不用关注公众号就可以直接上网了,而安卓版需要关注公众号才能上网,这样就少了很多ios ...
- 关于微信扫描二维码下载apk文件的细节设计
微信使用的人数越来越多,渐渐的用户形成了一种习惯,扫描二维码的时候,也会打开微信去扫描,但是微信不支持第三方的链接下载,有些厂商已经发现了这一特点,所以在使用二维码下载自家的app时,会做一个提示,引 ...
随机推荐
- appium+python运行自动化测试提示“find_element() takes from 1 to 3 positional arguments but 14 were given”错误
1.运行后提示"find_element() takes from 1 to 3 positional arguments but 14 were given",在网上找了很多解决 ...
- Mysql常用sql语句(15)- cross join 交叉连接
测试必备的Mysql常用sql语句 https://www.cnblogs.com/poloyy/category/1683347.html 前言 交叉连接就是求多表之间的笛卡尔积 讲道理..这个我都 ...
- 通过HttpURLConnection下载图片到本地--下载附件
一.背景说明 现在我做的系统中,需要有一个下载附件的功能,其实就是下载图片到本地中.相应的图片保存在多媒体系统中,我们只能拿到它的资源地址(url),而不是真实的文件. 这里记录的是下载单个图片.下篇 ...
- python库--jieba(中文分词)
import jieba 精确模式,试图将句子最精确地切开,适合文本分析:全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义:搜索引擎模式,在精确模式的基础上,对长词再次切 ...
- Python - poetry(4)管理环境
环境隔离 poetry 核心之一:使项目环境隔离,意味着始终和本地全局 Python 环境隔离 poetry 首先会检查当前项目是否在虚拟环境中运行:如果是将直接使用它,而不创建新的:如果不是,poe ...
- 10.6Java学习
1.类,对象,方法的定义.2.标识符分为两类:关键字/常见的基本类型:boolean(布尔型),byte(字节型),char(字符型),double(双精度),float(浮点),int(整型),lo ...
- android开发使用jxl创建Excel
这周水了几天,今天把博客赶上,找找状态. 周五的时候终于完成了课堂测试第二阶段,主要的难点就是生成Excel表并将填写的数据插入到Excel表中. 一.jxl使用 1.创建或读取一个工作薄 Workb ...
- 使用manacher算法解决最长回文子串问题
要解决的问题 求一个字符串最长回文子串是什么.且时间复杂度 O(N) 具体描述可参考: LeetCode_5_最长回文子串 LintCode_200_最长回文子串 暴力解法 以每个字符为中心向左右两边 ...
- webpack learn4-1配置css单独分离打包
1 先安装extract-text-webpack-plugin npm i extract-text-webpack-plugin 2 配置webpack.config.js
- ecshop首页调用团购说明
要在首页调用购买. 发现在首页还不能直接调用团购说明.查看了一下代码发现要修改下才能调 打开根目录的 index.php 文件找到 $sql = 'SELECT gb.act_id AS group_ ...