Ejemplo n.º 1
0
        /// <summary>
        /// 仅对订单进行退款时,如果订单已经分账,可以先调用此接口将指定的金额从分账接收方(仅限商户类型的分账接收方)回退给特约商户,然后再退款。<br/>
        /// 回退以原分账请求为依据,可以对分给分账接收方的金额进行多次回退,只要满足累计回退不超过该请求中分给接收方的金额。<br/>
        /// 此接口采用同步处理模式,即在接收到商户请求后,会实时返回处理结果。<br/><br/>
        /// 接口频率:30 QPS<br/>
        /// 是否需要证书:是
        /// </summary>
        /// <remarks>
        /// 此功能需要接收方在商户平台 - 交易中心 - 分账 - 分账接收设置下,开启同意分账回退后,才能使用。
        /// </remarks>
        /// <param name="mchId">微信支付分配的商户号。</param>
        /// <param name="subMchId">微信支付分配的子商户号。</param>
        /// <param name="appId">微信分配的公众账号 Id。</param>
        /// <param name="outOrderNo">原发起分账请求时使用的商户后台系统的分账单号。</param>
        /// <param name="outReturnNo">此回退单号是商户在自己后台生成的一个新的回退单号,在商户后台唯一。</param>
        /// <param name="returnAccountType">回退方类型(<see cref="ProfitSharingReceiverType"/>),暂时只支持从商户接收方回退分账金额。</param>
        /// <param name="returnAccount">回退方账号。</param>
        /// <param name="returnAmount">需要从分账接收方回退的金额,单位为分,只能为整数,不能超过原始分账单分出给该接收方的金额。</param>
        /// <param name="description">分账回退的原因描述。</param>
        /// <param name="subAppId">微信分配的子商户公众账号 Id,可空。</param>
        public async Task <XmlDocument> ProfitSharingReturnAsync(string mchId, string subMchId, string appId, string outOrderNo, string outReturnNo, string returnAccountType,
                                                                 string returnAccount, int returnAmount, string description, string subAppId = null)
        {
            var request = new WeChatPayParameters();

            request.AddParameter("mch_id", mchId);
            request.AddParameter("sub_mch_id", subMchId);
            request.AddParameter("appid", appId);
            request.AddParameter("out_order_no", outOrderNo);
            request.AddParameter("out_return_no", outReturnNo);
            request.AddParameter("return_account_type", returnAccountType);
            request.AddParameter("return_account", returnAccount);
            request.AddParameter("return_amount", returnAmount);
            request.AddParameter("description", description);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());

            request.AddParameter("sub_appid", subAppId);

            var options = await GetAbpWeChatPayOptions();

            var signStr = SignatureGenerator.Generate(request, new HMACSHA256(Encoding.UTF8.GetBytes(options.ApiKey)), options.ApiKey);

            request.AddParameter("sign", signStr);

            return(await RequestAndGetReturnValueAsync(ProfitSharingReturnUrl, request));
        }
        /// <summary>
        /// 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将
        /// 支付款按原路退到买家帐号上。
        /// </summary>
        /// <remarks>
        /// 注意:<br/>
        /// 1. 交易时间超过一年的订单无法提交退款。<br/>
        /// 2. 微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请
        /// 不要更换退款单号,请使用原商户退款单号。<br/>
        /// 3. 请求频率限制:150 QPS,即每秒钟正常的申请退款请求次数不超过 150 次。<br/>
        /// 错误或无效请求频率限制:6 QPS,即每秒钟异常或错误的退款申请请求不超过 6 次。<br/>
        /// 4. 每个支付订单的部分退款次数不能超过 50 次。
        /// </remarks>
        /// <param name="appId">服务商商户的 AppId。</param>
        /// <param name="mchId">微信支付分配的商户号。</param>
        /// <param name="subAppId">微信分配的子商户公众账号 Id。</param>
        /// <param name="subMchId">微信支付分配的子商户号。</param>
        /// <param name="transactionId">微信生成的订单号,在支付通知中有返回。</param>
        /// <param name="outTradeNo">
        /// 商户系统内部订单号,要求 32 个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。<br/>
        /// <paramref name="transactionId"/> 与 <paramref name="outTradeNo"/> 二选一,如果同时存在,优先级是:<paramref name="transactionId"/> 大于 <paramref name="outTradeNo"/>。
        /// </param>
        /// <param name="outRefundNo">商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。</param>
        /// <param name="totalFee">订单总金额,单位为分,只能为整数。</param>
        /// <param name="refundFee">退款总金额,单位为分,只能为整数,可部分退款。</param>
        /// <param name="refundFeeType">退款货币类型,需与支付一致,或者不填。</param>
        /// <param name="refundDesc">若商户传入,会在下发给用户的退款消息中体现退款原因,当订单退款金额 ≤1 元并且属于部分退款,则不会在退款消息中体现退款原因。</param>
        /// <param name="refundAccount">仅针对老资金流商户使用,具体参考 <see cref="RefundAccountType"/> 的定义。</param>
        /// <param name="notifyUrl">异步接收微信支付退款结果通知的回调地址,通知 Url 必须为外网可访问的 Url,不允许带参数。如果传递了该参数,则商户平台上配置的回调地址将不会生效。</param>
        public Task <XmlDocument> RefundAsync(string appId, string mchId, string subAppId, string subMchId, string transactionId, string outTradeNo, string outRefundNo,
                                              int totalFee, int refundFee, string refundFeeType, string refundDesc, string refundAccount, string notifyUrl)
        {
            var request = new WeChatPayParameters();

            request.AddParameter("appid", appId);
            request.AddParameter("mch_id", mchId);
            request.AddParameter("sub_appid", subAppId);
            request.AddParameter("sub_mch_id", subMchId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());
            request.AddParameter("transaction_id", transactionId);
            request.AddParameter("out_trade_no", outTradeNo);
            request.AddParameter("out_refund_no", outRefundNo);
            request.AddParameter("total_fee", totalFee);
            request.AddParameter("refund_fee", refundFee);
            request.AddParameter("refund_fee_type", refundFeeType);
            request.AddParameter("refund_desc", refundDesc);
            request.AddParameter("refund_account", refundAccount);
            request.AddParameter("notify_url", notifyUrl);

            var signStr = SignatureGenerator.Generate(request, MD5.Create(), AbpWeChatPayOptions.ApiKey);

            request.AddParameter("sign", signStr);

            return(RequestAndGetReturnValueAsync(RefundUrl, request));
        }
Ejemplo n.º 3
0
        /// <summary>
        /// 微信订单支付成功后,服务商代子商户发起分账请求,将结算后的钱分到分账接收方。多次分账请求仅会按照传入的分账接收方进行分账,不会对剩余的金额进行任何操作。
        /// 故操作成功后,在待分账金额不等于零时,订单依旧能够再次进行分账。<br/><br/>
        /// 接口频率:30 QPS <br/>
        /// 是否需要证书:是
        /// </summary>
        /// <remarks>
        /// 1. 对同一笔订单最多能发起 20 次多次分账请求。<br/>
        /// 2. 多次分账,可以将本商户作为分账接收方直接传入,实现释放资金给本商户的功能。
        /// </remarks>
        /// <param name="mchId">微信支付分配的商户号。</param>
        /// <param name="subMchId">微信支付分配的子商户号。</param>
        /// <param name="appId">微信分配的公众账号 Id。</param>
        /// <param name="transactionId">微信支付订单号,不能传递商户系统自己生成的订单号。</param>
        /// <param name="outOrderNo">服务商系统内部的分账单号,在服务商系统内部唯一(单次分账、多次分账、完结分账应使用不同的商户分账单号),同一分账单号多次请求等同一次。</param>
        /// <param name="receivers">分账接收方列表,不超过 50 个接收对象。</param>
        /// <param name="subAppId">微信分配的子商户公众账号 Id,可为空。</param>
        /// <exception cref="ArgumentException">当参数校验出现错误时,会抛出本异常。</exception>
        public async Task <XmlDocument> MultiProfitSharingAsync(string mchId, string subMchId, string appId, string transactionId, string outOrderNo,
                                                                IList <ProfitSharingReceiver> receivers, string subAppId = null)
        {
            if (receivers.Count > 50)
            {
                throw new ArgumentException("分账接收方最大不能超过 50 个。", nameof(receivers));
            }

            var request = new WeChatPayParameters();

            request.AddParameter("mch_id", mchId);
            request.AddParameter("sub_mch_id", subMchId);
            request.AddParameter("appid", appId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());
            request.AddParameter("transaction_id", transactionId);
            request.AddParameter("out_order_no", outOrderNo);
            request.AddParameter("receivers", JsonConvert.SerializeObject(receivers));

            request.AddParameter("sub_appid", subAppId);

            var options = await GetAbpWeChatPayOptions();

            var signStr = SignatureGenerator.Generate(request, new HMACSHA256(Encoding.UTF8.GetBytes(options.ApiKey)), options.ApiKey);

            request.AddParameter("sign", signStr);

            return(await RequestAndGetReturnValueAsync(MultiProfitSharingUrl, request));
        }
Ejemplo n.º 4
0
        /// <summary>
        /// 统一下单功能,支持除付款码支付场景以外的预支付交易单生成。生成后,根据返回的预支付交易会话标识,前端再根据不同的场景
        /// 唤起支付行为。
        /// </summary>
        /// <param name="appId">微信小程序或公众号的 AppId。</param>
        /// <param name="mchId">微信支付分配的商户号。</param>
        /// <param name="body">商品描述,例如 “腾讯充值中心 - QQ 会员充值” 。</param>
        /// <param name="orderNo">商户订单号,该订单号是商户系统内部生成的唯一编号。</param>
        /// <param name="totalFee">订单总金额,单位为分。</param>
        /// <param name="tradeType">本次的交易类型,具体参数可以参考 <see cref="TradeType"/> 的定义。</param>
        /// <param name="openId">用户标识,当 <paramref name="tradeType"/> 的类型为 <see cref="TradeType.JsApi"/> 时,本参数
        /// 必须传递。否则方法会抛出 <see cref="ArgumentException"/> 异常。</param>
        /// <param name="attach">附加参数,最大长度为 127,将来在微信支付进行回调通知时,会一并传递回来。</param>
        /// <returns>请求的结果,会被转换为 <see cref="XmlDocument"/> 实例并返回。</returns>
        public Task <XmlDocument> UnifiedOrderAsync(string appId, string mchId, string body, string orderNo, int totalFee,
                                                    string tradeType,
                                                    string openId = null,
                                                    string attach = null)
        {
            if (tradeType == TradeType.JsApi && string.IsNullOrEmpty(openId))
            {
                throw new ArgumentException($"当交易类型为 JsApi 时,参数 {nameof(openId)} 必须传递有效值。");
            }

            var request = new WeChatPayParameters();

            request.AddParameter("appid", appId);
            request.AddParameter("mch_id", mchId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());
            request.AddParameter("body", body);
            request.AddParameter("out_trade_no", orderNo);
            request.AddParameter("total_fee", totalFee);
            request.AddParameter("spbill_create_ip", "127.0.0.1");
            request.AddParameter("notify_url", AbpWeChatPayOptions.NotifyUrl);
            request.AddParameter("openid", openId);
            request.AddParameter("attach", attach);
            request.AddParameter("trade_type", tradeType);

            var signStr = SignatureGenerator.Generate(request, MD5.Create(), AbpWeChatPayOptions.ApiKey);

            request.AddParameter("sign", signStr);

            return(RequestAndGetReturnValueAsync(UnifiedOrderUrl, request));
        }
Ejemplo n.º 5
0
        protected override object Execute(string host, ApiAttribute attr, Hashtable table, Type genericType)
        {
            if (attr == null)
            {
                throw new FubeiSdkException("接口未设置ApiAttribute");
            }
            var appConfig = GalileoApiConfig.Instance.GetApiConfig(attr.Category) ?? new GalileoApiConfig.ApiConfig();

            var param = new ApiRequestParam(attr)
            {
                AppId      = appConfig.AppId,
                VendorSn   = appConfig.VendorSn,
                BizContent = table.ContainsKey(ApiConstants.JsonTag)
                    ? table[ApiConstants.JsonTag].ToString()
                    : JsonConvert.SerializeObject(table, ApiConstants.JsonSerializerSettings)
            };

            var paramList = ConvertToKeyValueList(param, attr);
            // 根据vendorSn是否为空来确定使用AppSecret或者VendorSecret
            var secret = string.IsNullOrEmpty(appConfig.VendorSn) ? appConfig.AppSecret : appConfig.VendorSecret;

            var signature = SignatureGenerator.GetSign(paramList, p => p + secret);

            param.Signature = signature.Signature;
            paramList.Add(new KeyValuePair <string, object>(ApiConstants.Sign, signature.Signature));

            var data = DoRequest(host, attr, paramList);

            // 处理请求
            return(AfterExecution(data, genericType, new RequestCarrier
            {
                Host = host,
                RequestMethod = param.Method
            }));
        }
Ejemplo n.º 6
0
        public void GetSignature_sameKeyAndData_GivesSameSignature()
        {
            var signature1 = SignatureGenerator.GetSignature("testKey", "testdata");
            var signature2 = SignatureGenerator.GetSignature("testKey", "testdata");

            Assert.Equal(signature1, signature2);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// 统一下单功能,支持除付款码支付场景以外的预支付交易单生成。
        /// </summary>
        public async Task <XmlDocument> UnifiedOrderAsync(string appId, string mchId, string body, string orderNo, int totalFee,
                                                          string tradeType,
                                                          string openId = null)
        {
            var request = new WeChatPayParameters();

            request.AddParameter("appid", appId);
            request.AddParameter("mch_id", mchId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());
            request.AddParameter("body", body);
            request.AddParameter("out_trade_no", orderNo);
            request.AddParameter("total_fee", totalFee);
            request.AddParameter("spbill_create_ip", "127.0.0.1");
            request.AddParameter("notify_url", _abpWeChatPayOptions.NotifyUrl);
            request.AddParameter("openid", openId);
            request.AddParameter("trade_type", tradeType);

            var signStr = SignatureGenerator.Generate(request, MD5.Create(), _abpWeChatPayOptions.ApiKey);

            request.AddParameter("sign", signStr);

            var result = await WeChatPayApiRequester.RequestAsync(TargetUrl, request.ToXmlStr());

            if (result.SelectSingleNode("/xml/err_code") != null ||
                result.SelectSingleNode("/xml/return_code")?.InnerText != "SUCCESS" ||
                result.SelectSingleNode("/xml/return_msg")?.InnerText != "OK")
            {
                throw new UserFriendlyException($"调用微信支付接口失败。");
            }

            return(result);
        }
Ejemplo n.º 8
0
        /// <summary>
        /// 根据微信订单号或者商户订单号,查询订单的详细信息。如果两个参数都被填写,优先使用微信订单号进行查询。
        /// </summary>
        /// <remarks>
        /// 商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。一般来说,都是因为商户系统没有收到支付通知,需要主动
        /// 查询订单的状态才会调用本方法进行查询。
        /// </remarks>
        /// <param name="appId">微信小程序或公众号的 AppId。</param>
        /// <param name="mchId">微信支付分配的商户号。</param>
        /// <param name="weChatOrderNo">微信订单号。</param>
        /// <param name="orderNo">商户订单号,该订单号是商户系统内部生成的唯一编号。</param>
        /// <returns>请求的结果,会被转换为 <see cref="XmlDocument"/> 实例并返回。</returns>
        /// <exception cref="ArgumentException">当微信订单号和商户订单号都为 null 时,会抛出本异常。</exception>
        public async Task <XmlDocument> OrderQueryAsync(string appId, string mchId, string weChatOrderNo = null, string orderNo = null)
        {
            if (string.IsNullOrEmpty(weChatOrderNo) && string.IsNullOrEmpty(orderNo))
            {
                throw new ArgumentException("微信订单号或商户订单号必须传递一个有效参数。");
            }

            var request = new WeChatPayParameters();

            request.AddParameter("appid", appId);
            request.AddParameter("mch_id", mchId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());

            if (!string.IsNullOrEmpty(weChatOrderNo))
            {
                request.AddParameter("transaction_id", weChatOrderNo);
            }
            else
            {
                request.AddParameter("out_trade_no", orderNo);
            }

            var options = await GetAbpWeChatPayOptions();

            var signStr = SignatureGenerator.Generate(request, MD5.Create(), options.ApiKey);

            request.AddParameter("sign", signStr);

            return(await RequestAndGetReturnValueAsync(OrderQueryUrl, request));
        }
Ejemplo n.º 9
0
    public async Task <Response> TransactStatusAsync(Request request)
    {
        if (request == null)
        {
            throw new ArgumentException("invalid `request` param supplied.");
        }

        var keyValuePairs = request.GetType().GetProperties().Where(x => x.GetValue(request, null) != null).ToDictionary(x => x.Name, y => y.GetValue(request).ToString());

        var SHA256 = SignatureGenerator.Generate(keyValuePairs, salt);

        request.HASH = SHA256;

        var requestPayload = Newtonsoft.Json.JsonConvert.SerializeObject(request);

        Console.WriteLine($"requestPayload : {requestPayload}");

        using (var httpClient = new HttpClient())
        {
            var requestFromServer = await httpClient.PostAsync($"{baseUrl}pgws/transact", new StringContent(requestPayload, Encoding.UTF8, "application/json"));

            requestFromServer.EnsureSuccessStatusCode();
            string requestFromServer_Body = await requestFromServer.Content.ReadAsStringAsync();

            Console.WriteLine($"requestFromServer_Body : {requestFromServer_Body}");

            return(JsonConvert.DeserializeObject <Response>(requestFromServer_Body));
        }
    }
Ejemplo n.º 10
0
            public void SampleSizeRatioParameterCorrectlySetsSampleSizeRatioProperty()
            {
                var expected = 30.0;

                var generator = new SignatureGenerator(sampleSizeRatio: expected);

                Assert.Equal(expected, generator.SampleSizeRatio);
            }
Ejemplo n.º 11
0
            public void NoiseCutoffParameterCorrectlySetsNoiseCutoffProperty()
            {
                var expected = 20.0;

                var generator = new SignatureGenerator(noiseCutoff: expected);

                Assert.Equal(expected, generator.NoiseCutoff);
            }
Ejemplo n.º 12
0
            public void GridSizeParameterCorrectlySetsGridSizeProperty()
            {
                uint expected = 10;

                var generator = new SignatureGenerator(gridSize: expected);

                Assert.Equal(expected, generator.GridSize);
            }
        /// <summary>
        /// Adds the HMAC Authentication headers to the client
        /// </summary>
        /// <param name="client">The client that will be used to make the request</param>
        /// <param name="requestUri">The Uri that the request will be sent to</param>
        /// <param name="method">The method that the request will use</param>
        /// <param name="content">The content of the request</param>
        public virtual void AddHmacHeaders(HttpClient client, Uri requestUri, HttpMethod method, string content = null)
        {
            string requestHttpMethod = method.Method;

            string fullHeader = SignatureGenerator.GenerateFullHmacSignature(requestUri.AbsoluteUri, requestHttpMethod, AppId, SecretKey, content);

            client.DefaultRequestHeaders.Add(HeaderName, fullHeader);
        }
Ejemplo n.º 14
0
        /// <summary>generates a new Db4oDatabase object with a unique signature.</summary>
        /// <remarks>generates a new Db4oDatabase object with a unique signature.</remarks>
        public static Db4objects.Db4o.Ext.Db4oDatabase Generate()
        {
            StatefulBuffer writer = new StatefulBuffer(null, 300);

            new LatinStringIO().Write(writer, SignatureGenerator.GenerateSignature());
            return(new Db4objects.Db4o.Ext.Db4oDatabase(writer.GetWrittenBytes(), Runtime.CurrentTimeMillis
                                                            ()));
        }
Ejemplo n.º 15
0
            public void EnableAutocropParameterCorrectlySetsEnableAutocropProperty()
            {
                var generator = new SignatureGenerator(enableAutocrop: false);

                Assert.False(generator.EnableAutocrop);

                generator = new SignatureGenerator();
                Assert.True(generator.EnableAutocrop);
            }
Ejemplo n.º 16
0
        public virtual void Test()
        {
            StatefulBuffer writer = new StatefulBuffer(null, 300);
            string         stringRepresentation = SignatureGenerator.GenerateSignature();

            new LatinStringIO().Write(writer, stringRepresentation);
            Signature signature = new Signature(writer.GetWrittenBytes());

            Assert.AreEqual(stringRepresentation, signature.ToString());
        }
        internal SignatureGenerator GetSignaturGenerator()
        {
            var document          = DomainUtility.GetDirectDocument();
            var sender            = CoreDomainUtility.GetSender();
            var signers           = DomainUtility.GetSigner();
            var manifest          = new Manifest(sender, document, signers);
            var x509Certificate2  = CoreDomainUtility.GetTestCertificate();
            var signaturGenerator = new SignatureGenerator(x509Certificate2, document, manifest);

            return(signaturGenerator);
        }
Ejemplo n.º 18
0
        public object Get()
        {
            var id          = "6MKOqxGiGU4AUk44";
            var key         = "ufu7nS8kS59awNihtjSonMETLI0KLy";
            var host        = "http://post-test.oss-cn-hangzhou.aliyuncs.com";
            var callbackUrl = "http://oss-demo.aliyuncs.com:23450";

            var generator = new SignatureGenerator(id, key, host, callbackUrl);
            var obj       = generator.Generate("test");

            return(obj);
        }
        /// <summary>
        /// 除付款码支付场景以外,商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再按不同场景生成交易串调起支付。
        /// </summary>
        /// <param name="appId">服务商商户的 AppId。</param>
        /// <param name="mchId">微信支付分配的商户号。</param>
        /// <param name="subAppId">微信分配的子商户公众账号 Id。<br/>如需在支付完成后获取 <paramref name="subOpenId"/> 则此参数必传。</param>
        /// <param name="subMchId">微信支付分配的子商户号。</param>
        /// <param name="deviceInfo">终端设备号 (门店号或收银设备 Id),注意:PC 网页或 JSAPI 支付请传 "WEB"。</param>
        /// <param name="receipt">传入 Y 时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效。</param>
        /// <param name="body">具体的商品描述信息,建议根据不同的场景传递不同的描述信息。</param>
        /// <param name="detail">商品详细描述,对于使用单品优惠的商户,该字段必须按照规范上传。</param>
        /// <param name="attach">附加数据,在查询 API 和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据。</param>
        /// <param name="outTradeNo">商户系统内部订单号,要求 32 个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一。</param>
        /// <param name="feeType">符合 ISO 4217 标准的三位字母代码,默认人民币:CNY。</param>
        /// <param name="totalFee">订单总金额,只能为整数,单位是分。</param>
        /// <param name="billCreateIp">调用微信支付 API 的机器 IP,可以使用 IPv4 或 IPv6。</param>
        /// <param name="timeStart">订单生成时间,格式为 yyyyMMddHHmmss。</param>
        /// <param name="timeExpire">订单失效时间,格式为 yyyyMMddHHmmss。</param>
        /// <param name="goodsTag">订单优惠标记,代金券或立减优惠功能的参数。</param>
        /// <param name="notifyUrl">接收微信支付异步通知回调地址,通知 Url 必须为直接可访问的 Url,不能携带参数。</param>
        /// <param name="tradeType">交易类型,请参考 <see cref="TradeType"/> 的定义。</param>
        /// <param name="productId">当 <paramref name="tradeType"/> 参数为 <see cref="TradeType.Native"/> 时,此参数必填。</param>
        /// <param name="limitPay">指定支付方式,传递 no_credit 则说明不能使用信用卡支付。</param>
        /// <param name="openId">当 <paramref name="tradeType"/> 参数为 <see cref="TradeType.JsApi"/> 时,此参数必填。如果选择传 <paramref name="subOpenId"/>, 则必须传 <paramref name="subAppId"/>。</param>
        /// <param name="subOpenId">当 <paramref name="tradeType"/> 参数为 <see cref="TradeType.JsApi"/> 时,此参数必填。如果选择传 <paramref name="subOpenId"/>, 则必须传 <paramref name="subAppId"/>。</param>
        /// <param name="sceneInfo">该字段常用于线下活动时的场景信息上报,支持上报实际门店信息,商户也可以按需求自己上报相关信息。</param>
        public Task <XmlDocument> UnifiedOrderAsync(string appId, string mchId, string subAppId, string subMchId, string deviceInfo, string receipt,
                                                    string body, string detail, string attach, string outTradeNo, string feeType, int totalFee,
                                                    string billCreateIp, string timeStart, string timeExpire, string goodsTag, string notifyUrl, string tradeType, string productId,
                                                    string limitPay, string openId, string subOpenId, string sceneInfo)
        {
            if (tradeType == TradeType.JsApi && string.IsNullOrEmpty(openId))
            {
                throw new ArgumentException($"当交易类型为 JsApi 时,参数 {nameof(openId)} 必须传递有效值。");
            }

            if (tradeType == TradeType.Native && string.IsNullOrEmpty(productId))
            {
                throw new ArgumentException($"当交易类型为 Native 时,参数 {nameof(productId)} 必须传递有效值。");
            }

            if (!string.IsNullOrEmpty(subOpenId) && string.IsNullOrEmpty(subAppId))
            {
                throw new ArgumentException($"传递子商户的 OpenId 时,参数 {nameof(subAppId)} 必须传递有效值。");
            }

            var request = new WeChatPayParameters();

            request.AddParameter("appid", appId);
            request.AddParameter("mch_id", mchId);
            request.AddParameter("sub_appid", subAppId);
            request.AddParameter("sub_mch_id", subMchId);
            request.AddParameter("device_info", deviceInfo);
            request.AddParameter("receipt", receipt);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());
            request.AddParameter("body", body);
            request.AddParameter("detail", detail);
            request.AddParameter("attach", attach);
            request.AddParameter("out_trade_no", outTradeNo);
            request.AddParameter("fee_type", feeType);
            request.AddParameter("total_fee", totalFee);
            request.AddParameter("spbill_create_ip", billCreateIp);
            request.AddParameter("time_start", timeStart);
            request.AddParameter("time_expire", timeExpire);
            request.AddParameter("goods_tag", goodsTag);
            request.AddParameter("notify_url", notifyUrl);
            request.AddParameter("trade_type", tradeType);
            request.AddParameter("product_id", productId);
            request.AddParameter("limit_pay", limitPay);
            request.AddParameter("openid", openId);
            request.AddParameter("sub_openid", subOpenId);
            request.AddParameter("scene_info", sceneInfo);

            var signStr = SignatureGenerator.Generate(request, MD5.Create(), AbpWeChatPayOptions.ApiKey);

            request.AddParameter("sign", signStr);

            return(RequestAndGetReturnValueAsync(UnifiedOrderUrl, request));
        }
Ejemplo n.º 20
0
        /// <summary>
        /// 根据商户号获取平台证书。
        /// </summary>
        /// <param name="mchId">微信支付分配的商户号。</param>
        public Task <XmlDocument> GetCertificateAsync(string mchId)
        {
            var request = new WeChatPayParameters();

            request.AddParameter("mch_id", mchId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());
            request.AddParameter("sign_type", "HMAC-SHA256");

            request.AddParameter("sign", SignatureGenerator.Generate(request, new HMACSHA256(Encoding.UTF8.GetBytes(AbpWeChatPayOptions.ApiKey)), AbpWeChatPayOptions.ApiKey));

            return(RequestAndGetReturnValueAsync(GetCertificateUrl, request));
        }
        public static DocumentBundle CreateAsice(Job job, X509Certificate2 certificate, IAsiceConfiguration asiceConfiguration)
        {
            var manifest = new Manifest(job.Sender, (Document)job.Document, job.Signers)
            {
                AuthenticationLevel         = job.AuthenticationLevel,
                IdentifierInSignedDocuments = job.IdentifierInSignedDocuments
            };
            var signature = new SignatureGenerator(certificate, job.Document, manifest);

            var asiceArchive = GetAsiceArchive(job, asiceConfiguration, job.Document, signature, manifest);

            return(new DocumentBundle(asiceArchive.GetBytes()));
        }
Ejemplo n.º 22
0
        public async Task IetfIntegrationDefaultTest()
        {
            Uri requestUri = new Uri("https://example.com/foo?param=value&pet=dog");

            SignatureSpecification signatureSpecification = new SignatureSpecification()
            {
                Algorithm     = "rsa-sha256",
                HashAlgorithm = "SHA-256",
                KeyId         = "Test",
                Headers       = new []
                {
                    "(request-target)",
                    "host",
                    "date",
                    "content-type",
                    "digest",
                    "content-length"
                }
            };

            IStringSigner stringSigner = new RSAStringSigner(signatureSpecification, itefPrivateKey.ToRSAParameters());
            HttpSignatureStringExtractor httpSignatureExtractor = new HttpSignatureStringExtractor();
            ISignatureGenerator          signatureGenerator     = new SignatureGenerator(stringSigner, httpSignatureExtractor, signatureSpecification.KeyId);

            var httpSignatureHandler = new SignatureDelegatingHandler(signatureGenerator, signatureSpecification);
            var httpDigestDelegate   = new DigestDelegatingHandler(new DigestGenerator(signatureSpecification));

            httpDigestDelegate.InnerHandler   = httpSignatureHandler;
            httpSignatureHandler.InnerHandler = new Mocks.DelegatingHandlerMock();
            var invoker = new HttpMessageInvoker(httpDigestDelegate);

            HttpRequestMessage requestMessage = new HttpRequestMessage();

            var content = new StringContent("{\"hello\": \"world\"}");

            content.Headers.ContentType   = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
            content.Headers.ContentLength = 18;

            requestMessage.RequestUri = requestUri;
            requestMessage.Method     = HttpMethod.Post;
            requestMessage.Content    = content;
            requestMessage.Headers.TryAddWithoutValidation("Date", "Thu, 05 Jan 2014 21:31:40 GMT");
            requestMessage.Headers.Add("Host", "example.com");

            var result = await invoker.SendAsync(requestMessage, CancellationToken.None);

            string expectedSignature = "keyId=\"Test\",algorithm=\"rsa-sha256\",headers=\"(request-target) host date content-type digest content-length\",signature=\"Ef7MlxLXoBovhil3AlyjtBwAL9g4TN3tibLj7uuNB3CROat/9KaeQ4hW2NiJ+pZ6HQEOx9vYZAyi+7cmIkmJszJCut5kQLAwuX+Ms/mUFvpKlSo9StS2bMXDBNjOh4Auj774GFj4gwjS+3NhFeoqyr/MuN6HsEnkvn6zdgfE2i0=\"";

            Assert.Equal(expectedSignature, requestMessage.Headers.Authorization.Parameter);
        }
Ejemplo n.º 23
0
        /// <summary>
        /// 使用申请入驻接口提交你的小微商户资料,申请后一般 5 分钟左右可以查询到具体的申请结果。
        /// </summary>
        /// <param name="version">接口版本号,固定版本号为 3.0。</param>
        /// <param name="certSn">平台证书序列号,通过 <see cref="GetCertificateAsync"/> 接口获取。</param>
        /// <param name="mchId">服务商的商户号。</param>
        /// <param name="businessCode">业务申请编号,服务商自定义的商户唯一编号。每个编号对应一个申请单,每个申请单审核通过后会生成一个微信支付商户号。</param>
        /// <param name="idCardCopy">身份证人像面照片,请使用 <see cref="UploadMediaAsync"/> 接口预先上传图片,传递其 media_id 。</param>
        /// <param name="idCardNational">身份证国徽面照片,请使用 <see cref="UploadMediaAsync"/> 接口预先上传图片,传递其 media_id。</param>
        /// <param name="idCardName">身份证姓名,请填写小微商户本人身份证上的姓名,使用 <see cref="WeChatPayToolUtility"/> 提供的加密方法进行加密。</param>
        /// <param name="idCardNumber">身份证号码,15 位数字 或  17 位数字 + 1 位数字 | X ,使用 <see cref="WeChatPayToolUtility"/> 提供的加密方法进行加密。</param>
        /// <param name="idCardValidTime">身份证有效期限,格式应该和 ["1970-01-01","长期"] 一致,结束时间需要大于开始时间,需要和上传的身份证内容一致。</param>
        /// <param name="accountName">开户名称,必须与身份证姓名一致,使用 <see cref="WeChatPayToolUtility"/> 提供的加密方法进行加密。</param>
        /// <param name="accountBank">开户银行,请参考 <see cref="AccountBanks"/> 类型中定义的银行列表。</param>
        /// <param name="bankAddressCode">开户银行省市编码,至少精确到市,请参考 https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=22_1 提供的对照表。</param>
        /// <param name="bankName">
        /// 开户银行全称(含支行),17 家直连银行无需填写(在 <see cref="AccountBanks"/> 定义的),其他银行请务必填写。<br/>
        /// 需填写银行全称,如 "深圳农村商业银行 XXX 支行",详细信息请参考 https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=22_1。</param>
        /// <param name="accountNumber">银行账号,数字,长度遵循系统支持的对私卡号长度要求,使用 <see cref="WeChatPayToolUtility"/> 提供的加密方法进行加密。</param>
        /// <param name="storeName">
        /// 门店名称,最长 50 个中文字符。<br/>
        /// 门店场所:填写门店名称<br/>
        /// 流动经营 / 便民服务:填写经营 / 服务名称<br/>
        /// 线上商品 / 服务交易:填写线上店铺名称<br/>
        /// </param>
        /// <param name="storeAddressCode">
        /// 门店省市编码,至少精确到市,参考 https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=22_1 提供的对照表。<br/>
        /// 门店场所:填写门店省市编码<br/>
        /// 流动经营 / 便民服务:填写经营 / 服务所在地省市编码<br/>
        /// 线上商品 / 服务交易:填写卖家所在地省市编码<br/>
        /// </param>
        /// <param name="storeStreet">
        /// 门店街道名称,最长 500 个中文字符(无需填写省市信息)。<br/>
        /// 门店场所:填写店铺详细地址,具体区 / 县及街道门牌号或大厦楼层<br/>
        /// 流动经营 / 便民服务:填写 “无 "<br/>
        /// 线上商品 / 服务交易:填写电商平台名称<br/>
        /// </param>
        /// <param name="storeLongitude">门店经度。</param>
        /// <param name="storeLatitude">门店纬度。</param>
        /// <param name="storeEntrancePic">
        /// 门店门口照片,请使用 <see cref="UploadMediaAsync"/> 接口预先上传图片,传递其 media_id 。<br/>
        /// 门店场所:提交门店门口照片,要求招牌清晰可见<br/>
        /// 流动经营 / 便民服务:提交经营 / 服务现场照片<br/>
        /// 线上商品 / 服务交易:提交店铺首页截图<br/>
        /// </param>
        /// <param name="indoorPic">
        /// 店内环境照片,请使用 <see cref="UploadMediaAsync"/> 接口预先上传图片,传递其 media_id 。<br/>
        /// 门店场所:提交店内环境照片<br/>
        /// 流动经营 / 便民服务:可提交另一张经营 / 服务现场照片<br/>
        /// 线上商品 / 服务交易:提交店铺管理后台截图<br/>
        /// </param>
        /// <param name="addressCertification">经营场地证明,门面租赁合同扫描件或经营场地证明(需与身份证同名),请使用 <see cref="UploadMediaAsync"/> 接口预先上传图片,传递其 media_id 。</param>
        /// <param name="merchantShortName">商户简称,UTF-8 格式,中文占 3 个字节,即最多 16 个汉字长度。将在支付完成页向买家展示,需与商家的实际经营场景相符。</param>
        /// <param name="servicePhone">客服电话,UTF-8 格式,中文占 3 个字节,即最多 16 个汉字长度。在交易记录中向买家展示,请确保电话畅通以便平台回拨确认。</param>
        /// <param name="productDesc">
        /// 售卖商品 / 提供服务描述,请填写以下描述之一:<br/>
        /// 餐饮、线下零售、居民生活服务、休闲娱乐、交通出行、其他。
        /// </param>
        /// <param name="rate">费率,由服务商指定,具体有效费率枚举值,可以参考 https://pay.weixin.qq.com/wiki/doc/api/xiaowei.php?chapter=22_1。</param>
        /// <param name="businessAdditionDesc">补充说明,可填写需要额外说明的文字。</param>
        /// <param name="businessAdditionPics">补充材料,最多可上传 5 张照片,请填写已预先上传图片生成好的 MediaID,例如 ["123","456"]。</param>
        /// <param name="contact">
        /// 超级管理员姓名,和身份证姓名一致。超级管理员需在开户后进行签约,并可接收日常重要管理信息和进行资金操作,请确定其为商户法定代表人或负责人。<br/>
        /// 使用 <see cref="WeChatPayToolUtility"/> 提供的加密方法进行加密。
        /// </param>
        /// <param name="contactPhone">手机号码,11 位数字,手机号码,使用 <see cref="WeChatPayToolUtility"/> 提供的加密方法进行加密。</param>
        /// <param name="contactEmail">联系邮箱,需要带 @,遵循邮箱格式校验,使用 <see cref="WeChatPayToolUtility"/> 提供的加密方法进行加密。</param>
        /// <returns></returns>
        public async Task <XmlDocument> SubmitAsync(string version, string certSn, string mchId, string businessCode,
                                                    string idCardCopy, string idCardNational, string idCardName, string idCardNumber, string idCardValidTime,
                                                    string accountName, string accountBank, string bankAddressCode, [CanBeNull] string bankName, string accountNumber,
                                                    string storeName, string storeAddressCode, string storeStreet, [CanBeNull] string storeLongitude, [CanBeNull] string storeLatitude,
                                                    string storeEntrancePic, string indoorPic, [CanBeNull] string addressCertification, [CanBeNull] string merchantShortName,
                                                    string servicePhone, string productDesc, string rate, [CanBeNull] string businessAdditionDesc, [CanBeNull] string businessAdditionPics,
                                                    string contact, string contactPhone, [CanBeNull] string contactEmail)
        {
            var request = new WeChatPayParameters();

            request.AddParameter("version", version);
            request.AddParameter("cert_sn", certSn);
            request.AddParameter("mch_id", mchId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());
            request.AddParameter("sign_type", "HMAC-SHA256");
            request.AddParameter("business_code", businessCode);
            request.AddParameter("id_card_copy", idCardCopy);
            request.AddParameter("id_card_national", idCardNational);
            request.AddParameter("id_card_name", idCardName);
            request.AddParameter("id_card_number", idCardNumber);
            request.AddParameter("id_card_valid_time", idCardValidTime);
            request.AddParameter("account_name", accountName);
            request.AddParameter("account_bank", accountBank);
            request.AddParameter("bank_address_code", bankAddressCode);
            request.AddParameter("bank_name", bankName);
            request.AddParameter("account_number", accountNumber);
            request.AddParameter("store_name", storeName);
            request.AddParameter("store_address_code", storeAddressCode);
            request.AddParameter("store_street", storeStreet);
            request.AddParameter("store_longitude", storeLongitude);
            request.AddParameter("store_latitude", storeLatitude);
            request.AddParameter("store_entrance_pic", storeEntrancePic);
            request.AddParameter("indoor_pic", indoorPic);
            request.AddParameter("address_certification", addressCertification);
            request.AddParameter("merchant_shortname", merchantShortName);
            request.AddParameter("service_phone", servicePhone);
            request.AddParameter("product_desc", productDesc);
            request.AddParameter("rate", rate);
            request.AddParameter("business_addition_desc", businessAdditionDesc);
            request.AddParameter("business_addition_pics", businessAdditionPics);
            request.AddParameter("contact", contact);
            request.AddParameter("contact_phone", contactPhone);
            request.AddParameter("contact_email", contactEmail);

            var options = await GetAbpWeChatPayOptions();

            request.AddParameter("sign", SignatureGenerator.Generate(request, new HMACSHA256(Encoding.UTF8.GetBytes(options.ApiKey)), options.ApiKey));
            return(await RequestAndGetReturnValueAsync(SubmitUrl, request));
        }
Ejemplo n.º 24
0
        /// <summary>
        /// 使用申请入驻接口提交小微商户资料后,一般 5 分钟左右可以通过该查询接口查询具体的申请结果。
        /// </summary>
        /// <param name="version">接口版本号,默认值 1.0。</param>
        /// <param name="mchId">服务商的商户号。</param>
        /// <param name="applyentId">商户申请单号,微信支付分配的申请单号。</param>
        /// <param name="businessCode">业务申请编号,服务商自定义的商户唯一编号,当 <paramref name="applyentId"/>  已填写时,此字段无效。</param>
        /// <returns></returns>
        public Task <XmlDocument> GetStateAsync(string version, string mchId, string applyentId, string businessCode)
        {
            var request = new WeChatPayParameters();

            request.AddParameter("version", version);
            request.AddParameter("mch_id", mchId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());
            request.AddParameter("sign_type", "HMAC-SHA256");
            request.AddParameter("applyment_id", applyentId);
            request.AddParameter("business_code", businessCode);

            request.AddParameter("sign", SignatureGenerator.Generate(request, new HMACSHA256(Encoding.UTF8.GetBytes(AbpWeChatPayOptions.ApiKey)), AbpWeChatPayOptions.ApiKey));

            return(RequestAndGetReturnValueAsync(GetStateUrl, request));
        }
Ejemplo n.º 25
0
        /// <summary>
        /// 根据商户订单号,关闭指定的订单。
        /// </summary>
        /// <remarks>
        /// 以下情况需要调用关单接口:商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;系统下单后,
        /// 用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。订单生成后不能马上调用关单接口,最短调用时间间隔为 5 分钟。
        /// </remarks>
        /// <param name="appId">微信小程序或公众号的 AppId。</param>
        /// <param name="mchId">微信支付分配的商户号。</param>
        /// <param name="orderNo">商户订单号,该订单号是商户系统内部生成的唯一编号。</param>
        /// <returns>请求的结果,会被转换为 <see cref="XmlDocument"/> 实例并返回。</returns>
        public Task <XmlDocument> CloseOrderAsync(string appId, string mchId, string orderNo)
        {
            var request = new WeChatPayParameters();

            request.AddParameter("appid", appId);
            request.AddParameter("mch_id", mchId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());

            request.AddParameter("out_trade_no", orderNo);

            var signStr = SignatureGenerator.Generate(request, MD5.Create(), AbpWeChatPayOptions.ApiKey);

            request.AddParameter("sign", signStr);

            return(RequestAndGetReturnValueAsync(CloseOrderUrl, request));
        }
            public void InitializesWithDocumentDirectManifestAndCertificate()
            {
                //Arrange
                var document         = DomainUtility.GetDirectDocument();
                var sender           = CoreDomainUtility.GetSender();
                var manifest         = new Manifest(sender, document, DomainUtility.GetSigner());
                var x509Certificate2 = CoreDomainUtility.GetTestCertificate();

                //Act
                var signatur = new SignatureGenerator(x509Certificate2, document, manifest);

                //Assert
                Assert.Equal(document, signatur.Attachables.ElementAt(0));
                Assert.Equal(manifest, signatur.Attachables.ElementAt(1));
                Assert.Equal(x509Certificate2, signatur.Certificate);
            }
Ejemplo n.º 27
0
        /// <summary>
        /// 发起分账请求后,可调用此接口查询分账结果;发起分账完结请求后,可调用此接口查询分账完结的执行结果。<br/><br/>
        /// 接口频率:80 QPS <br/>
        /// 是否需要证书:否
        /// </summary>
        /// <param name="mchId">微信支付分配的商户号。</param>
        /// <param name="subMchId">微信支付分配的子商户号。</param>
        /// <param name="transactionId">微信支付订单号。</param>
        /// <param name="outOrderNo">
        /// 查询分账结果,输入申请分账时的商户分账单号。 <br/>
        /// 查询分账完结的执行结果,输入发起分账完结时的商户分账单号。</param>
        public Task <XmlDocument> ProfitSharingQueryAsync(string mchId, string subMchId, string transactionId, string outOrderNo)
        {
            var request = new WeChatPayParameters();

            request.AddParameter("mch_id", mchId);
            request.AddParameter("sub_mch_id", subMchId);
            request.AddParameter("nonce_str", RandomHelper.GetRandom());
            request.AddParameter("transaction_id", transactionId);
            request.AddParameter("out_order_no", outOrderNo);

            var signStr = SignatureGenerator.Generate(request, new HMACSHA256(Encoding.UTF8.GetBytes(AbpWeChatPayOptions.ApiKey)), AbpWeChatPayOptions.ApiKey);

            request.AddParameter("sign", signStr);

            return(RequestAndGetReturnValueAsync(ProfitSharingQueryUrl, request));
        }
        public void Generate_TwitterGetRequest_ReturnsCorrectSignatureBaseString()
        {
            var target = new SignatureGenerator();

            var expected = "POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521";

            var result = target.Generate(POST,
                                         new Uri("https://api.twitter.com/1/statuses/update.json?include_entities=true&status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21"),
                                         "xvz1evFS4wEEPTGEFPHBog",
                                         "HMAC-SHA1",
                                         "370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
                                         "1.0",
                                         "kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
                                         "1318622958");

            Assert.AreEqual(expected, result);
        }
Ejemplo n.º 29
0
 private static string LogFile()
 {
     if (enabled)
     {
         if (logFileName != null)
         {
             return(logFileName);
         }
         logFileName = "db4oDTrace_" + DateHandlerBase.Now() + "_" + SignatureGenerator.GenerateSignature
                           () + ".log";
         logFileName = logFileName.Replace(' ', '_');
         logFileName = logFileName.Replace(':', '_');
         logFileName = logFileName.Replace('-', '_');
         return(logFilePath + logFileName);
     }
     return(null);
 }
Ejemplo n.º 30
0
        /// <summary>
        /// 图片上传功能,用于上传证件照片等数据。
        /// </summary>
        /// <param name="mchId">服务商商户号或渠道号。</param>
        /// <param name="imagePath">图片的文件路径。</param>
        /// <returns>图片关联的 Id。</returns>
        public async Task <string> UploadMediaAsync(string mchId, string imagePath)
        {
            if (new FileInfo(imagePath).Length > MaxMediaFileSize)
            {
                throw new ArgumentOutOfRangeException(nameof(imagePath), "指定的图像文件大小超过 2 M。");
            }

            var bytes     = File.ReadAllBytes(imagePath);
            var mediaHash = MD5.Create().ComputeHash(bytes).Select(b => b.ToString("X2")).JoinAsString("");

            // 构建并计算签名值。
            var parameters = new WeChatPayParameters();

            parameters.AddParameter("mch_id", mchId);
            parameters.AddParameter("media_hash", mediaHash);

            var options = await GetAbpWeChatPayOptions();

            var sign = SignatureGenerator.Generate(parameters, MD5.Create(), options.ApiKey);

            // 构建表单请求。
            var fileContent = new ByteArrayContent(bytes);

            fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/jpeg");

            var form = new MultipartFormDataContent
            {
                { new StringContent(mchId), "\"mch_id\"" },
                { new ByteArrayContent(bytes), "\"media\"", $"\"{HttpUtility.UrlEncode(Path.GetFileName(imagePath))}\"" },
                { new StringContent(mediaHash), "\"media_hash\"" },
                { new StringContent(sign), "\"sign\"" }
            };

            // 处理 Boundary 蛋疼的引号问题,参考 https://developers.de/blogs/damir_dobric/archive/2013/09/10/problems-with-webapi-multipart-content-upload-and-boundary-quot-quotes.aspx 。
            var boundaryValue = form.Headers.ContentType.Parameters.Single(p => p.Name == "boundary");

            boundaryValue.Value = boundaryValue.Value.Replace("\"", String.Empty);

            using var client = HttpClientFactory.CreateClient("WeChatPay");
            using var resp   = await client.PostAsync(UploadMediaUrl, form);

            var xmlDocument = new XmlDocument();

            xmlDocument.LoadXml(await resp.Content.ReadAsStringAsync());
            return(xmlDocument.SelectSingleNode("/xml/media_id")?.InnerText);
        }