最近在写特约服务商进件的由于微信官方没有DEMO,导致踩了很多坑,特把自己经验分享给大家。

注意几点:

1、上传图片签名不是把所有body内容都进行签名,只需签名计算的请求主体为meta的json串:{ "filename": "filea.jpg", "sha256": "hjkahkjsjkfsjk78687dhjahdajhk" }

2、签名的是私钥, 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY----- 亦不包括结尾的-----END PRIVATE KEY-----

3、上传参数meta、file、文件名必须新增双引号

4、MultipartFormDataContent 必须添加头文件multipart/form-data,不然一直签名错误。

5、头文件必须添加"user-agent"、"application/json“

代码如下:

上传代码:

         string private_key = @"商户私钥";
string mchid = "商户号";
string serialNo = "商户API证书序列号";
string filePath = @"H:\1.jpg";
string boundary = string.Format("--{0}", DateTime.Now.Ticks.ToString("x"));
var sha256 = SHAFile.SHA256File(filePath);
meta meta = new meta()
{
sha256 = sha256,
filename = System.IO.Path.GetFileName(serialNo)
};
var json = JsonConvert.SerializeObject(meta);
var httpHandler = new HttpHandler(mchid, serialNo, private_key, json);
HttpClient client = new HttpClient(httpHandler);
using (var requestContent = new MultipartFormDataContent(boundary))
{
requestContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); //这里必须添加
requestContent.Add(new StringContent(json, Encoding.UTF8, "application/json"), "\"meta\""); //这里主要必须要双引号
var fileInfo = new FileInfo(filePath);
using (var fileStream = fileInfo.OpenRead())
{
var content = new byte[fileStream.Length];
fileStream.Read(content, , content.Length);
var byteArrayContent = new ByteArrayContent(content);
byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("image/jpg");
requestContent.Add(byteArrayContent, "\"file\"", "\"" + meta.filename + "\""); //这里主要必须要双引号
using (var response = await client.PostAsync("https://api.mch.weixin.qq.com/v3/merchant/media/upload", requestContent)) //上传
using (var responseContent = response.Content)
{
string responseBody = await responseContent.ReadAsStringAsync(); //这里就可以拿到图片id了
return ResultHelper.QuickReturn(responseBody);
}
}
}

SHAFile 类:

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text; namespace Cloud.Pay.Common
{
public class SHAFile
{
/// 计算文件的 SHA256 值
/// </summary>
/// <param name="fileName">要计算 SHA256 值的文件名和路径</param>
/// <returns>SHA256值16进制字符串</returns>
public static string SHA256File(string fileName)
{
return HashFile(fileName, "sha256");
} /// <summary>
/// 计算文件的哈希值
/// </summary>
/// <param name="fileName">要计算哈希值的文件名和路径</param>
/// <param name="algName">算法:sha1,md5</param>
/// <returns>哈希值16进制字符串</returns>
private static string HashFile(string fileName, string algName)
{
if (!System.IO.File.Exists(fileName))
return string.Empty; FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
byte[] hashBytes = HashData(fs, algName);
fs.Close();
return ByteArrayToHexString(hashBytes);
} /// <summary>
/// 字节数组转换为16进制表示的字符串
/// </summary>
private static string ByteArrayToHexString(byte[] buf)
{
string returnStr = "";
if (buf != null)
{
for (int i = ; i < buf.Length; i++)
{
returnStr += buf[i].ToString("X2");
}
}
return returnStr;
} /// <summary>
/// 计算哈希值
/// </summary>
/// <param name="stream">要计算哈希值的 Stream</param>
/// <param name="algName">算法:sha1,md5</param>
/// <returns>哈希值字节数组</returns>
private static byte[] HashData(Stream stream, string algName)
{
HashAlgorithm algorithm;
if (algName == null)
{
throw new ArgumentNullException("algName 不能为 null");
}
if (string.Compare(algName, "sha256", true) == )
{
algorithm = SHA256.Create();
}
else
{
if (string.Compare(algName, "md5", true) != )
{
throw new Exception("algName 只能使用 sha256 或 md5");
}
algorithm = MD5.Create();
}
return algorithm.ComputeHash(stream);
}
}
}

meta类:

 public class meta
{
public string filename { get; set; } public string sha256 { get; set; }
}

头文件

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Cloud.Pay.Common
{
/// <summary>
/// 头文件
/// </summary>
public class HttpHandler : DelegatingHandler
{
private readonly string merchantId;
private readonly string serialNo;
private readonly string privateKey;
private readonly string json;
/// <summary>
/// 构造方法
/// </summary>
/// <param name="merchantId">商户号</param>
/// <param name="merchantSerialNo">证书序列号</param>
/// <param name="privateKey"> 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY----- 亦不包括结尾的-----END PRIVATE KEY-----</param>
/// <param name="json">签名json数据,默认不需要传入,获取body内容,如传入签名传入参数上传图片时需传入</param>
public HttpHandler(string merchantId, string merchantSerialNo, string privateKey, string json = "")
{ HttpClientHandler handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls12;
try
{
string certPath = System.IO.Path.Combine(Environment.CurrentDirectory, @"Cert\apiclient_cert.p12");
handler.ClientCertificates.Add(new X509Certificate2(certPath, "",
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet));
}
catch (Exception e)
{
throw new Exception("ca err(证书错误)");
}
handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true;
InnerHandler = handler;
this.merchantId = merchantId;
this.serialNo = merchantSerialNo;
this.privateKey = privateKey;
this.json = json;
} protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var auth = await BuildAuthAsync(request);
string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";
request.Headers.Add("Authorization", value);
request.Headers.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36");
MediaTypeWithQualityHeaderValue mediaTypeWithQualityHeader = new MediaTypeWithQualityHeaderValue("application/json");
request.Headers.Accept.Add(mediaTypeWithQualityHeader);
request.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8"));
return await base.SendAsync(request, cancellationToken);
} protected async Task<string> BuildAuthAsync(HttpRequestMessage request)
{
string method = request.Method.ToString();
string body = "";
if (method == "POST" || method == "PUT" || method == "PATCH")
{
if (!string.IsNullOrEmpty(json))
body = json;
else
{
var content = request.Content;
body = await content.ReadAsStringAsync();
}
}
string uri = request.RequestUri.PathAndQuery;
var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
string nonce = Guid.NewGuid().ToString("n");
string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n";
string signature = Sign(message);
return $"mchid=\"{merchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{serialNo}\",signature=\"{signature}\"";
} protected string Sign(string message)
{
byte[] keyData = Convert.FromBase64String(privateKey);
using (CngKey cngKey = CngKey.Import(keyData, CngKeyBlobFormat.Pkcs8PrivateBlob))
using (RSACng rsa = new RSACng(cngKey))
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
}
}
}
}

微信支付v3版本NET 图片上传API的更多相关文章

  1. 微信小程序简单封装图片上传组件

    微信小程序简单封装图片上传组件 希望自己 "day day up" -----小陶 我从哪里来 在写小程序的时候需要上传图片,个人觉得官方提供的 Uploader 组件不是太好用, ...

  2. Java微信公众平台开发_07_JSSDK图片上传

    一.本节要点 1.获取jsapi_ticket //2.获取getJsapiTicket的接口地址,有效期为7200秒 private static final String GET_JSAPITIC ...

  3. jsp中简易版本的图片上传程序

    1.下载相应的组件的最新版本 Commons FileUpload 可以在http://jakarta.apache.org/commons/fileupload/下载 附加的Commons IO   ...

  4. 微信JS-SDK接口 + FLASK实现图片上传

    最近在做一个项目从全球各地采集图片,考虑采用微信JS-SDK来简化开发.图片会首先上传到微信的服务器,返回一个id,然后根据这个id去微信服务器获取图片.微信提供可选择的压缩图片功能.图片首先上传到微 ...

  5. 微信支付V3版本的那些事

    最近在接入微信支付这块功能,博客园也有很多博友发表了支付的各种吐槽和解决之道,基于那些经验分享之上,我也来说说我的填坑之路. 1:准备工作,首先去申请注册一个公众号——服务号,然后将微信支付功能开通, ...

  6. php微信支付v3版本签名生成

    前几天需要对接微信支付卡包营销活动需要对接微信新版SDKv3版 签名生成规则,微信的官方文档里面说明的还算可以吧,不过个人觉得不太理想- -.  自己调试的时候调试了半天才找了错误原因. https: ...

  7. thinkphp3.2.x版本中图片上传缩略图的解决方案

    调用方式很简单 get_sc($cover_id,[$width=180,$height=auto,$cut]) @param $cover_id 图片ID___ @param $width 宽度__ ...

  8. MVC微信浏览器图片上传(img转Base64)

    因公司业务需要,需要做一个微信公众号里的图片上传功能,主要用到的技术就是 img转base64 上到服务器 话不多说, 贴代码 先看前端显示出来的东西 OK 图片不重要,看代码 <!--微信图片 ...

  9. 微信APP支付V3版本签名 && APP下单/订单查询接口Python版实现

    问题背景 最近接入微信支付,微信官方并没有提供Python版的服务端SDK,因而只能根据文档手动实现一版,这里记录一下微信支付的整体流程.踩坑过程与最终具体实现. 微信支付APP下单流程 根据微信官方 ...

随机推荐

  1. 悄摸直播(三)—— 搭建rtmp服务器(smart_rtmpd - rtmp服务器搭建)

    悄摸直播 -- javaCV实现本机摄像头画面远程直播 搭建rtmp服务器 一.素材 rtmp服务器:smart_rtmpd ffmpeg工具:ffmpeg.exe 二.搭建 1.下载smart_rt ...

  2. 5、调试显示应该使用 DebuggerDisplay 而不是误用 ToString

    using System.Diagnostics; namespace ShouldCode.Console { [DebuggerDisplay("Prop1:{Prop1};Prop2: ...

  3. 【 Tomcat 】tomcat8.0 基本参数调优配置-----(1)

    Tomcat 的缺省配置是不能稳定长期运行的,也就是不适合生产环境,它会死机,让你不断重新启动,甚至在午夜时分唤醒你.对于操作系统优化来说,是尽可能的增大可使用的内存容量.提高CPU 的频率,保证文件 ...

  4. Linux 断网安装MySQL5.x操作步骤

    1.下载MySQL安装包 由于官网下载比较慢,暂时我这边采用的方法是通过网易云的centos镜像下载的地址是 http://mirrors.163.com/mysql/Downloads/MySQL- ...

  5. Spring Boot自动装配

    前言 一些朋友问我怎么读源码,这篇文章结合我看源码时候一些思路给大家聊聊,我主要从这三个方向出发: 确定目标,这个目标要是一个具体,不要一上来我要看懂Spring,这是不可能的,目标要这么来定,比如看 ...

  6. css 透明度

    <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8& ...

  7. 【实战】使用 Kettle 工具将 mysql 数据增量导入到 MongoDB 中

    最近有一个将 mysql 数据导入到 MongoDB 中的需求,打算使用 Kettle 工具实现.本文章记录了数据导入从0到1的过程,最终实现了每秒钟快速导入约 1200 条数据.一起来看吧~ 一.K ...

  8. 整理OD一些快捷键和零碎知识点

    第一次记录:2019.9.15 完成了近期基本知识点的记录 第二次记录:2019.9.16 更新VB和的Delphi的汇编代码特点 介绍几个快捷键: Alt+B 断点编辑器,空格键可切换断点状态 ct ...

  9. jade 的 考古

    Jade是一款高性能简洁易懂的模板引擎(加上这两个字我想起了发动机,为什么不直接叫发动机呢), Jade是Haml的Javascript实现, 在服务端(NodeJS)及客户端均有支持. haml 是 ...

  10. zabbix3.4源码安装步骤

    zabbix3.4源码安装步骤1.安装环境: 安装机器:22.224.9.227 以下简称监控机: 操作系统信息:内核版本: Linux version 2.6.18-194.1.AXS3 (pack ...