{"record":{"id":"ef7453271355efa6","repo":"JeffreySu/WeiXinMPSDK","slug":"nameof-data-stock-type-stock-type-nameof-data-coupon-use","errorCode":null,"errorMessage":"当 {nameof(data.stock_type)} 为 {STOCK_TYPE} 时，{nameof(data.coupon_use_rule.fixed_normal_coupon)} 必填！","messagePattern":"当 (.+?) 为 (.+?) 时，(.+?) 必填！","errorType":"validation","errorClass":"TenpayApiRequestException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/Marketing/MarketingApis.Favor.cs","lineNumber":73,"sourceCode":"    public partial class MarketingApis\n    {\n        #region 代金券接口\n\n        /// <summary>\n        /// 创建代金券批次接口\n        /// <para>调用此接口创建微信支付代金券批次，创建完成后将获得代金券批次id。</para>\n        /// <para>更多详细请参考 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_1.shtml </para>\n        /// <para>提示：使用此功能必须在后台【产品中心】开通【预充值代金券】功能！</para>\n        /// </summary>\n        /// <param name=\"data\">微信支付需要POST的Data数据</param>\n        /// <param name=\"timeOut\">超时时间，单位为ms </param>\n        /// <returns></returns>\n        public async Task<CreateStockReturnJson> CreateStockAsync(CreateStockRequsetData data, int timeOut = Config.TIME_OUT)\n        {\n            const string STOCK_TYPE = \"NORMAL\";\n            if (data.stock_type == STOCK_TYPE && data.coupon_use_rule.fixed_normal_coupon == null)\n            {\n                throw new TenpayApiRequestException($\"当 {nameof(data.stock_type)} 为 {STOCK_TYPE} 时，{nameof(data.coupon_use_rule.fixed_normal_coupon)} 必填！\");\n            }\n\n            if (data.stock_use_rule.max_amount != data.stock_use_rule.max_coupons * data.coupon_use_rule.fixed_normal_coupon.coupon_amount)\n            {\n                throw new TenpayApiRequestException($\"{nameof(data.stock_use_rule.max_amount)} 必须等于 {nameof(data.stock_use_rule.max_coupons)} 乘以 {nameof(data.coupon_use_rule.fixed_normal_coupon.coupon_amount)}！\");\n            }\n\n            if (data.coupon_use_rule.fixed_normal_coupon.coupon_amount > data.coupon_use_rule.fixed_normal_coupon.transaction_minimum)\n            {\n                throw new TenpayApiRequestException($\"{nameof(data.coupon_use_rule.fixed_normal_coupon.coupon_amount)} 必须小于等于 {nameof(data.coupon_use_rule.fixed_normal_coupon.transaction_minimum)}！\");\n            }\n\n            var url = BasePayApis.GetPayApiUrl(Senparc.Weixin.Config.TenPayV3Host + \"/{0}v3/marketing/favor/coupon-stocks\");\n            TenPayApiRequest tenPayApiRequest = new(_tenpayV3Setting);\n            return await tenPayApiRequest.RequestAsync<CreateStockReturnJson>(url, data, timeOut);\n        }\n\n        /// <summary>","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/Marketing/MarketingApis.Favor.cs#L55-L91","documentation":"CreateStockAsync (MarketingApis.Favor.cs:73) validates the coupon-stock creation request locally before calling the WeChat marketing API. For stock_type \"NORMAL\" (代金券), the coupon_use_rule.fixed_normal_coupon object carries the coupon denomination and rules, so it is mandatory. If stock_type is NORMAL but fixed_normal_coupon is null, the library throws TenpayApiRequestException immediately, avoiding a guaranteed API rejection.","triggerScenarios":"Calling CreateStockAsync with CreateStockRequsetData.stock_type set to \"NORMAL\" while leaving data.coupon_use_rule.fixed_normal_coupon null (or unset) in the request object.","commonSituations":"Building the request from deserialized/partial JSON where only discount-type rules were populated; copying sample code for a DISCOUNT stock but changing stock_type to NORMAL without adding fixed_normal_coupon; model binder creating coupon_use_rule but leaving fixed_normal_coupon null.","solutions":["Populate data.coupon_use_rule.fixed_normal_coupon with coupon_amount, transaction_minimum, etc. when stock_type is NORMAL","Or change stock_type to a value that does not require fixed_normal_coupon (e.g. DISCOUNT) if that matches the intended coupon kind","Add a pre-call null check / constructor initialization so fixed_normal_coupon is always created with stock_type NORMAL"],"exampleFix":"// before\nvar data = new CreateStockRequsetData { stock_type = \"NORMAL\", ... }; // fixed_normal_coupon null\n\n// after\ndata.coupon_use_rule.fixed_normal_coupon = new FixedNormalCoupon {\n    coupon_amount = 100, transaction_minimum = 1000\n};","handlingStrategy":"validation","validationCode":"if (data.stock_type == \"NORMAL\" && data.coupon_use_rule?.fixed_normal_coupon == null)\n    throw new InvalidOperationException(\"fixed_normal_coupon is required when stock_type is NORMAL\");","typeGuard":"bool HasNormalCouponRule(CreateStockRequsetData d) =>\n    d?.stock_type != \"NORMAL\" || d.coupon_use_rule?.fixed_normal_coupon != null;","tryCatchPattern":"try { await api.CreateStockAsync(data); }\ncatch (TenpayApiRequestException ex) when (ex.Message.Contains(\"fixed_normal_coupon\"))\n{ logger.LogError(ex, \"NORMAL stock created without fixed_normal_coupon\"); throw; }","preventionTips":["Always initialize coupon_use_rule.fixed_normal_coupon when stock_type is NORMAL","Centralize coupon-stock request building in one factory method with these invariants","Write unit tests mirroring the library's pre-flight validations"],"tags":["tenpay","marketing","validation","coupon"],"backgroundTag":"missing-required-argument","analyzedSha":"be573f6f94bdbf718dd5f6cdecb137fbc7ff651e","analyzedAt":"2026-09-12T10:01:50.733Z","contentChangedAt":"2026-09-12T10:01:50.733Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}