{"record":{"id":"f75145f74914bfb0","repo":"JeffreySu/WeiXinMPSDK","slug":"nameof-data-stock-use-rule-max-amount-nameof-data-stock-use","errorCode":null,"errorMessage":"{nameof(data.stock_use_rule.max_amount)} 必须等于 {nameof(data.stock_use_rule.max_coupons)} 乘以 {nameof(data.coupon_use_rule.fixed_normal_coupon.coupon_amount)}！","messagePattern":"(.+?) 必须等于 (.+?) 乘以 (.+?)！","errorType":"validation","errorClass":"TenpayApiRequestException","httpStatus":null,"severity":"error","filePath":"src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/Marketing/MarketingApis.Favor.cs","lineNumber":78,"sourceCode":"        /// 创建代金券批次接口\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>\n        /// 激活代金券批次接口\n        /// <para>制券成功后，通过调用此接口激活批次，如果是预充值代金券，激活时会从商户账户余额中锁定本批次的营销资金</para>\n        /// <para>更多详细请参考 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter9_1_3.shtml </para>\n        /// </summary>\n        /// <param name=\"stock_id\">批次号 微信为每个代金券批次分配的唯一id</param>","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/JeffreySu/WeiXinMPSDK/blob/be573f6f94bdbf718dd5f6cdecb137fbc7ff651e/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/Marketing/MarketingApis.Favor.cs#L60-L96","documentation":"CreateStockAsync (MarketingApis.Favor.cs:78) enforces the WeChat rule that a NORMAL stock's total budget (stock_use_rule.max_amount) must equal max_coupons × fixed_normal_coupon.coupon_amount. This arithmetic is validated client-side and throws TenpayApiRequestException when the numbers are inconsistent, because the API would reject the mismatched budget.","triggerScenarios":"Calling CreateStockAsync where data.stock_use_rule.max_amount != data.stock_use_rule.max_coupons * data.coupon_use_rule.fixed_normal_coupon.coupon_amount (e.g. budget set independently of coupon count/amount, or units in fen vs yuan mixed up).","commonSituations":"Entering amounts in yuan while the API expects fen (so 1 yuan coupon × 1000 coupons ≠ 100000 budget typed as 1000); changing max_coupons or coupon_amount after setting max_amount and forgetting to recompute; computing budget with int truncation.","solutions":["Recompute and set stock_use_rule.max_amount = max_coupons * coupon_amount before calling","Ensure coupon_amount and transaction_minimum are both in fen (cents), and the budget is computed from those fen values","Add a guard/validation in the code that builds the request to derive max_amount from the other two values"],"exampleFix":"// before\nstockUseRule.max_amount = 1000;   // mismatch\nstockUseRule.max_coupons = 500;\ncouponUseRule.fixed_normal_coupon.coupon_amount = 100;\n\n// after\nstockUseRule.max_coupons = 500;\ncouponUseRule.fixed_normal_coupon.coupon_amount = 100; // fen\nstockUseRule.max_amount = stockUseRule.max_coupons * couponUseRule.fixed_normal_coupon.coupon_amount; // 50000","handlingStrategy":"validation","validationCode":"if (data.stock_use_rule.max_amount != data.stock_use_rule.max_coupons * data.coupon_use_rule.fixed_normal_coupon.coupon_amount)\n    throw new InvalidOperationException(\"max_amount must equal max_coupons * coupon_amount\");","typeGuard":"bool BudgetMatchesCoupons(CreateStockRequsetData d) =>\n    d.stock_use_rule.max_amount == d.stock_use_rule.max_coupons * (d.coupon_use_rule.fixed_normal_coupon?.coupon_amount ?? 0);","tryCatchPattern":"try { await api.CreateStockAsync(data); }\ncatch (TenpayApiRequestException ex) when (ex.Message.Contains(\"max_amount\"))\n{ logger.LogError(ex, \"Stock budget arithmetic mismatch\"); throw; }","preventionTips":["Derive max_amount programmatically instead of typing it","Keep all monetary values in fen","Add an invariant check in the request builder"],"tags":["tenpay","marketing","validation","coupon"],"backgroundTag":"invalid-argument-value","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"}