---
url: "https://pay.weixin.qq.com/doc/v3/partner/4016756274.md"
title: "【单券-单品-兑换券】API请求示例"
doc_id: 4016756274
---

>更新时间：2025.12.25

curlJavaGo

```


## 场景5: 单券-单品-兑换券 (usage_mode=SINGLE, scope=SINGLE, type=EXCHANGE)

```
curl -X POST \
  https://api.mch.weixin.qq.com/v3/marketing/partner/product-coupon/product-coupons \
  -H "Authorization: WECHATPAY2-SHA256-RSA2048 mchid=\"服务商商户号\",..." \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "out_request_no": "SINGLE_SINGLE_EXCHANGE_20250101_005", # 必填，创建请求单号
    "brand_id": "120344", # 必填，品牌ID
    "scope": "SINGLE", # 必填，优惠范围：SINGLE=单品券（兑换券仅支持单品）
    "type": "EXCHANGE", # 必填，商品券类型：EXCHANGE=兑换券（仅scope=SINGLE时可用）
    "usage_mode": "SINGLE", # 必填，使用模式：SINGLE=单券模式

    # 注意：scope=SINGLE时，不在single_usage_info中配置优惠规则

    "display_info": { # 必填，商品券展示信息
      "name": "9.9元兑换原价99元商品", # 必填，商品券名称
      "image_url": "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx", # 必填，商品图片URL
      "background_url": "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx", # 必填，背景图URL
      "detail_image_url_list": [ # 选填，详情图URL列表
        "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx"
      ],
      "original_price": 9900, # 当scope=SINGLE时必填，商品原价，单位为分（99元）
      "combo_package_list": [ # 当scope=SINGLE时选填，套餐组合列表
        {
          "name": "超值套餐", # 必填，套餐名称
          "pick_count": 3, # 必填，可选单品数量
          "choice_list": [ # 必填，单品列表
            {
              "name": "汉堡", # 必填，单品名称
              "price": 1500, # 必填，单品价格，单位为分（15元）
              "count": 1, # 必填，单品数量
              "image_url": "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx", # 选填
              "mini_program_appid": "wx1234567890", # 选填
              "mini_program_path": "/pages/index/product" # 选填
            },
            {
              "name": "鸡翅", # 必填，单品名称
              "price": 1200, # 必填，单品价格，单位为分
              "count": 1, # 必填，单品数量
              "image_url": "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx", # 选填
              "mini_program_appid": "wx1234567890", # 选填
              "mini_program_path": "/pages/index/product" # 选填
            },
            {
              "name": "可乐", # 必填，单品名称
              "price": 500, # 必填，单品价格，单位为分
              "count": 1, # 必填，单品数量
              "image_url": "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx", # 选填
              "mini_program_appid": "wx1234567890", # 选填
              "mini_program_path": "/pages/index/product" # 选填
            }
          ]
        }
      ]
    },

    "out_product_no": "Product_SINGLE_005", # 选填，外部商品ID

    "stock": { # 必填，批次信息
      "remark": "8月单品兑换批次", # 选填，备注
      "coupon_code_mode": "WECHATPAY", # 必填，券Code分配模式

      "stock_send_rule": { # 必填，发放规则
        "max_count": 10000000, # 必填，总发放次数上限
        "max_count_per_day": 100000, # 选填，每日发放上限
        "max_count_per_user": 1 # 必填，每用户领取上限
      },

      "single_usage_rule": { # 当usage_mode=SINGLE时必填，单券使用规则
        "coupon_available_period": { # 必填，券可核销时间
          "available_begin_time": "2025-08-01T00:00:00+08:00", # 必填，开始时间
          "available_end_time": "2025-08-31T23:59:59+08:00", # 必填，结束时间
          "available_days": 30, # 选填，生效后N天有效
          "wait_days_after_receive": 0, # 选填，领取后N天生效
          "weekly_available_period": { # 选填，每周可用时间
            "day_list": ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY"] # 当配置此对象时必填
          },
          "irregular_available_period_list": [ # 选填，无规律可用时间段
            {
              "begin_time": "2025-08-15T00:00:00+08:00", # 当配置此列表时必填
              "end_time": "2025-08-15T23:59:59+08:00" # 当配置此列表时必填
            }
          ]
        },
        "exchange_coupon": { # 当type=EXCHANGE时必填，兑换券使用规则
          "threshold": 0, # 必填，门槛金额，单位为分，兑换券通常为0
          "exchange_price": 990 # 必填，兑换价格，单位为分，9.9元兑换填990
        }
      },

      "usage_rule_display_info": { # 必填，使用规则展示信息
        "coupon_usage_method_list": ["OFFLINE", "MINI_PROGRAM", "PAYMENT_CODE"], # 必填，使用方式
        "mini_program_appid": "wx1234567890", # 当coupon_usage_method_list包含MINI_PROGRAM时必填
        "mini_program_path": "/pages/index/product", # 当coupon_usage_method_list包含MINI_PROGRAM时必填
        "usage_description": "用9.9元兑换原价99元商品", # 必填，使用说明
        "coupon_available_store_info": { # 选填，可用门店信息
          "description": "所有门店可用", # 当配置此对象时必填
          "mini_program_appid": "wx1234567890", # 选填
          "mini_program_path": "/pages/index/store-list" # 选填
        }
      },

      "coupon_display_info": { # 必填，用户券展示信息
        "code_display_mode": "QRCODE", # 必填，Code展示模式
        "background_color": "Color010", # 选填，背景颜色
        "entrance_mini_program": { # 选填，小程序入口
          "appid": "wx1234567890", # 当配置此对象时必填
          "path": "/pages/index/product", # 当配置此对象时必填
          "entrance_wording": "立即兑换", # 当配置此对象时必填
          "guidance_wording": "获取更多优惠" # 当配置此对象时必填
        },
        "entrance_official_account": { # 选填，公众号入口
          "appid": "wx1234567890" # 当配置此对象时必填
        },
        "entrance_finder": { # 选填，视频号入口
          "finder_id": "gh_12345678", # 当配置此对象时必填
          "finder_video_id": "UDFsdf24df34dD456Hdf34", # 当配置此对象时必填
          "finder_video_cover_image_url": "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx" # 当配置此对象时必填
        }
      },

      "notify_config": { # 必填，事件通知配置
        "notify_appid": "wx4fd12345678" # 必填
      },

      "store_scope": "NONE" # 必填，可用门店范围
    }
  }'
```
```

需配合微信支付工具库 WXPayUtility 使用，请参考 [Java](https://pay.weixin.qq.com/doc/v3/partner/4014985777.md)

```
package com.java.demo;

import com.java.utils.WXPayUtility; // 引用微信支付工具库，参考：https://pay.weixin.qq.com/doc/v3/partner/4014985777
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 场景5：创建商品券 - 单券-单品-兑换券
 *
 * 场景说明：
 * - usage_mode: SINGLE（单券模式）
 * - scope: SINGLE（单品券）
 * - type: EXCHANGE（兑换券）
 *
 * 优惠规则配置位置：stock.single_usage_rule.exchange_coupon（单品券在single_usage_rule中配置）
 */
public class SingleExchangeSingleJava {
  private static String HOST = "https://api.mch.weixin.qq.com";
  private static String METHOD = "POST";
  private static String PATH = "/v3/marketing/partner/product-coupon/product-coupons";

  public static void main(String[] args) {
    // TODO: 请准备商户开发必要参数，参考：https://pay.weixin.qq.com/doc/v3/partner/4013080340
    SingleExchangeSingleJava client = new SingleExchangeSingleJava(
      "19xxxxxxxx",                    // 商户号，是由微信支付系统生成并分配给每个商户的唯一标识符，商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
      "1DDE55AD98Exxxxxxxxxx",         // 商户API证书序列号，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924
      "/path/to/apiclient_key.pem",    // 商户API证书私钥文件路径，本地文件路径
      "PUB_KEY_ID_xxxxxxxxxxxxx",      // 微信支付公钥ID，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
      "/path/to/wxp_pub.pem"           // 微信支付公钥文件路径，本地文件路径
    );

    CreateProductCouponRequest request = new CreateProductCouponRequest();
    // 必填，创建请求单号，6-40个字符，品牌侧需保持唯一性
    request.outRequestNo = "SINGLE_SINGLE_EXCHANGE_20250101_005";
    // 必填，品牌ID，由微信支付分配
    request.brandId = "120344";
    // 必填，优惠范围：SINGLE-单品券(兑换券仅支持单品券)
    request.scope = ProductCouponScope.SINGLE;
    // 必填，商品券类型：NORMAL-满减券, DISCOUNT-折扣券, EXCHANGE-兑换券(仅单品券)
    request.type = ProductCouponType.EXCHANGE;
    // 必填，使用模式：SINGLE-单券, PROGRESSIVE_BUNDLE-多次优惠
    request.usageMode = UsageMode.SINGLE;
    // 选填，商户侧商品券唯一标识
    request.outProductNo = "Product_SINGLE_005";

    // 必填，商品券展示信息
    request.displayInfo = new ProductCouponDisplayInfo();
    // 必填，商品券名称，最多12个字符
    request.displayInfo.name = "9.9元兑换原价99元商品";
    // 必填，商品券图片URL
    request.displayInfo.imageUrl = "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx";
    // 选填，背景图URL
    request.displayInfo.backgroundUrl = "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx";
    // 选填，详情图URL列表，最多6张
    request.displayInfo.detailImageUrlList = new ArrayList<>();
    {
      request.displayInfo.detailImageUrlList.add("https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx");
    };
    // 条件必填(单品券)，商品原价(单位：分)，scope=SINGLE时必填
    request.displayInfo.originalPrice = 9900L;
    // 条件必填(单品券)，套餐组合，scope=SINGLE时必填
    request.displayInfo.comboPackageList = new ArrayList<>();
    {
      ComboPackage comboPackage = new ComboPackage();
      comboPackage.name = "超值套餐";
      comboPackage.pickCount = 1L;
      comboPackage.choiceList = new ArrayList<>();
      ComboPackageChoice choice = new ComboPackageChoice();
      choice.name = "指定商品";
      choice.price = 9900L;
      choice.count = 1L;
      choice.imageUrl = "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx";
      comboPackage.choiceList.add(choice);
      request.displayInfo.comboPackageList.add(comboPackage);
    };

    // 条件必填，批次信息(当usage_mode=SINGLE时必填)
    request.stock = new StockForCreate();
    request.stock.remark = "8月单品兑换批次";
    request.stock.couponCodeMode = CouponCodeMode.WECHATPAY;
    request.stock.stockSendRule = new StockSendRule();
    request.stock.stockSendRule.maxCount = 10000000L;
    request.stock.stockSendRule.maxCountPerDay = 100000L;
    request.stock.stockSendRule.maxCountPerUser = 1L;

    // 单券使用规则（scope=SINGLE时在此配置优惠规则）
    request.stock.singleUsageRule = new SingleUsageRule();
    request.stock.singleUsageRule.couponAvailablePeriod = new CouponAvailablePeriod();
    request.stock.singleUsageRule.couponAvailablePeriod.availableBeginTime = "2025-08-01T00:00:00+08:00";
    request.stock.singleUsageRule.couponAvailablePeriod.availableEndTime = "2025-08-31T23:59:59+08:00";
    request.stock.singleUsageRule.couponAvailablePeriod.availableDays = 30L;
    request.stock.singleUsageRule.couponAvailablePeriod.waitDaysAfterReceive = 0L;
    request.stock.singleUsageRule.couponAvailablePeriod.weeklyAvailablePeriod = new FixedWeekPeriod();
    request.stock.singleUsageRule.couponAvailablePeriod.weeklyAvailablePeriod.dayList = new ArrayList<>();
    {
      request.stock.singleUsageRule.couponAvailablePeriod.weeklyAvailablePeriod.dayList.add(WeekEnum.MONDAY);
      request.stock.singleUsageRule.couponAvailablePeriod.weeklyAvailablePeriod.dayList.add(WeekEnum.TUESDAY);
      request.stock.singleUsageRule.couponAvailablePeriod.weeklyAvailablePeriod.dayList.add(WeekEnum.WEDNESDAY);
      request.stock.singleUsageRule.couponAvailablePeriod.weeklyAvailablePeriod.dayList.add(WeekEnum.THURSDAY);
      request.stock.singleUsageRule.couponAvailablePeriod.weeklyAvailablePeriod.dayList.add(WeekEnum.FRIDAY);
    };
    // 条件必填，兑换券使用规则(当type=EXCHANGE时在此配置)
    request.stock.singleUsageRule.exchangeCoupon = new ExchangeCouponUsageRule();
    // 必填，门槛金额(单位：分)，0表示无门槛
    request.stock.singleUsageRule.exchangeCoupon.threshold = 0L;
    // 必填，兑换价格(单位：分)，用户实际支付金额
    request.stock.singleUsageRule.exchangeCoupon.exchangePrice = 990L;

    // 使用规则展示信息
    request.stock.usageRuleDisplayInfo = new UsageRuleDisplayInfo();
    request.stock.usageRuleDisplayInfo.couponUsageMethodList = new ArrayList<>();
    {
      request.stock.usageRuleDisplayInfo.couponUsageMethodList.add(CouponUsageMethod.OFFLINE);
      request.stock.usageRuleDisplayInfo.couponUsageMethodList.add(CouponUsageMethod.MINI_PROGRAM);
      request.stock.usageRuleDisplayInfo.couponUsageMethodList.add(CouponUsageMethod.PAYMENT_CODE);
    };
    request.stock.usageRuleDisplayInfo.miniProgramAppid = "wx1234567890";
    request.stock.usageRuleDisplayInfo.miniProgramPath = "/pages/index/product";
    request.stock.usageRuleDisplayInfo.usageDescription = "用9.9元兑换原价99元商品";
    request.stock.usageRuleDisplayInfo.couponAvailableStoreInfo = new CouponAvailableStoreInfo();
    request.stock.usageRuleDisplayInfo.couponAvailableStoreInfo.description = "所有门店可用，可使用小程序查看门店列表";
    request.stock.usageRuleDisplayInfo.couponAvailableStoreInfo.miniProgramAppid = "wx1234567890";
    request.stock.usageRuleDisplayInfo.couponAvailableStoreInfo.miniProgramPath = "/pages/index/store-list";

    // 用户券展示信息
    request.stock.couponDisplayInfo = new CouponDisplayInfo();
    request.stock.couponDisplayInfo.codeDisplayMode = CouponCodeDisplayMode.QRCODE;
    request.stock.couponDisplayInfo.backgroundColor = "Color010";
    request.stock.couponDisplayInfo.entranceMiniProgram = new EntranceMiniProgram();
    request.stock.couponDisplayInfo.entranceMiniProgram.appid = "wx1234567890";
    request.stock.couponDisplayInfo.entranceMiniProgram.path = "/pages/index/product";
    request.stock.couponDisplayInfo.entranceMiniProgram.entranceWording = "立即兑换";
    request.stock.couponDisplayInfo.entranceMiniProgram.guidanceWording = "获取更多优惠";
    request.stock.couponDisplayInfo.entranceOfficialAccount = new EntranceOfficialAccount();
    request.stock.couponDisplayInfo.entranceOfficialAccount.appid = "wx1234567890";
    request.stock.couponDisplayInfo.entranceFinder = new EntranceFinder();
    request.stock.couponDisplayInfo.entranceFinder.finderId = "gh_12345678";
    request.stock.couponDisplayInfo.entranceFinder.finderVideoId = "UDFsdf24df34dD456Hdf34";
    request.stock.couponDisplayInfo.entranceFinder.finderVideoCoverImageUrl = "https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx";

    // 事件通知配置
    request.stock.notifyConfig = new NotifyConfig();
    request.stock.notifyConfig.notifyAppid = "wx4fd12345678";
    request.stock.storeScope = StockStoreScope.NONE;

    try {
      CreateProductCouponResponse response = client.run(request);
      // TODO: 请求成功，继续业务逻辑
      System.out.println(response);
    } catch (WXPayUtility.ApiException e) {
      // TODO: 请求失败，根据状态码执行不同的逻辑
      e.printStackTrace();
    }
  }

  public CreateProductCouponResponse run(CreateProductCouponRequest request) {
    String uri = PATH;
    String reqBody = WXPayUtility.toJson(request);

    Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
    reqBuilder.addHeader("Accept", "application/json");
    reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
    reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, reqBody));
    reqBuilder.addHeader("Content-Type", "application/json");
    RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
    reqBuilder.method(METHOD, requestBody);
    Request httpRequest = reqBuilder.build();

    // 发送HTTP请求
    OkHttpClient client = new OkHttpClient.Builder().build();
    try (Response httpResponse = client.newCall(httpRequest).execute()) {
      String respBody = WXPayUtility.extractBody(httpResponse);
      if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
        // 2XX 成功，验证应答签名
        WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
            httpResponse.headers(), respBody);

        // 从HTTP应答报文构建返回数据
        return WXPayUtility.fromJson(respBody, CreateProductCouponResponse.class);
      } else {
        throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
      }
    } catch (IOException e) {
      throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
    }
  }

  private final String mchid;
  private final String certificateSerialNo;
  private final PrivateKey privateKey;
  private final String wechatPayPublicKeyId;
  private final PublicKey wechatPayPublicKey;

  public SingleExchangeSingleJava(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
    this.mchid = mchid;
    this.certificateSerialNo = certificateSerialNo;
    this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
    this.wechatPayPublicKeyId = wechatPayPublicKeyId;
    this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
  }

  public static class CreateProductCouponRequest {
    @SerializedName("out_request_no")
    public String outRequestNo;

    @SerializedName("scope")
    public ProductCouponScope scope;

    @SerializedName("type")
    public ProductCouponType type;

    @SerializedName("usage_mode")
    public UsageMode usageMode;

    @SerializedName("single_usage_info")
    public SingleUsageInfo singleUsageInfo;

    @SerializedName("progressive_bundle_usage_info")
    public ProgressiveBundleUsageInfo progressiveBundleUsageInfo;

    @SerializedName("display_info")
    public ProductCouponDisplayInfo displayInfo;

    @SerializedName("out_product_no")
    public String outProductNo;

    @SerializedName("stock")
    public StockForCreate stock;

    @SerializedName("stock_bundle")
    public StockBundleForCreate stockBundle;

    @SerializedName("brand_id")
    public String brandId;
  }

  public static class CreateProductCouponResponse {
    @SerializedName("product_coupon_id")
    public String productCouponId;

    @SerializedName("scope")
    public ProductCouponScope scope;

    @SerializedName("type")
    public ProductCouponType type;

    @SerializedName("usage_mode")
    public UsageMode usageMode;

    @SerializedName("single_usage_info")
    public SingleUsageInfo singleUsageInfo;

    @SerializedName("progressive_bundle_usage_info")
    public ProgressiveBundleUsageInfo progressiveBundleUsageInfo;

    @SerializedName("display_info")
    public ProductCouponDisplayInfo displayInfo;

    @SerializedName("out_product_no")
    public String outProductNo;

    @SerializedName("state")
    public ProductCouponState state;

    @SerializedName("stock")
    public StockEntity stock;

    @SerializedName("stock_bundle")
    public StockBundleEntity stockBundle;

    @SerializedName("brand_id")
    public String brandId;
  }

  public enum ProductCouponScope {
    @SerializedName("ALL")
    ALL,
    @SerializedName("SINGLE")
    SINGLE
  }

  public enum ProductCouponType {
    @SerializedName("NORMAL")
    NORMAL,
    @SerializedName("DISCOUNT")
    DISCOUNT,
    @SerializedName("EXCHANGE")
    EXCHANGE
  }

  public enum UsageMode {
    @SerializedName("SINGLE")
    SINGLE,
    @SerializedName("PROGRESSIVE_BUNDLE")
    PROGRESSIVE_BUNDLE
  }

  public static class SingleUsageInfo {
    @SerializedName("normal_coupon")
    public NormalCouponUsageRule normalCoupon;

    @SerializedName("discount_coupon")
    public DiscountCouponUsageRule discountCoupon;
  }

  public static class ProgressiveBundleUsageInfo {
    @SerializedName("count")
    public Long count;

    @SerializedName("interval_days")
    public Long intervalDays;
  }

  public static class ProductCouponDisplayInfo {
    @SerializedName("name")
    public String name;

    @SerializedName("image_url")
    public String imageUrl;

    @SerializedName("background_url")
    public String backgroundUrl;

    @SerializedName("detail_image_url_list")
    public List<String> detailImageUrlList;

    @SerializedName("original_price")
    public Long originalPrice;

    @SerializedName("combo_package_list")
    public List<ComboPackage> comboPackageList;
  }

  public static class StockForCreate {
    @SerializedName("remark")
    public String remark;

    @SerializedName("coupon_code_mode")
    public CouponCodeMode couponCodeMode;

    @SerializedName("stock_send_rule")
    public StockSendRule stockSendRule;

    @SerializedName("single_usage_rule")
    public SingleUsageRule singleUsageRule;

    @SerializedName("usage_rule_display_info")
    public UsageRuleDisplayInfo usageRuleDisplayInfo;

    @SerializedName("coupon_display_info")
    public CouponDisplayInfo couponDisplayInfo;

    @SerializedName("notify_config")
    public NotifyConfig notifyConfig;

    @SerializedName("store_scope")
    public StockStoreScope storeScope;
  }

  public static class StockBundleForCreate {
    @SerializedName("remark")
    public String remark;

    @SerializedName("coupon_code_mode")
    public CouponCodeMode couponCodeMode;

    @SerializedName("stock_send_rule")
    public StockSendRule stockSendRule;

    @SerializedName("progressive_bundle_usage_rule")
    public StockBundleUsageRule progressiveBundleUsageRule;

    @SerializedName("usage_rule_display_info")
    public UsageRuleDisplayInfo usageRuleDisplayInfo;

    @SerializedName("coupon_display_info")
    public CouponDisplayInfo couponDisplayInfo;

    @SerializedName("notify_config")
    public NotifyConfig notifyConfig;

    @SerializedName("store_scope")
    public StockStoreScope storeScope;
  }

  public enum ProductCouponState {
    @SerializedName("AUDITING")
    AUDITING,
    @SerializedName("EFFECTIVE")
    EFFECTIVE,
    @SerializedName("DEACTIVATED")
    DEACTIVATED
  }

  public static class StockEntity {
    @SerializedName("product_coupon_id")
    public String productCouponId;

    @SerializedName("stock_id")
    public String stockId;

    @SerializedName("remark")
    public String remark;

    @SerializedName("coupon_code_mode")
    public CouponCodeMode couponCodeMode;

    @SerializedName("coupon_code_count_info")
    public CouponCodeCountInfo couponCodeCountInfo;

    @SerializedName("stock_send_rule")
    public StockSendRule stockSendRule;

    @SerializedName("single_usage_rule")
    public SingleUsageRule singleUsageRule;

    @SerializedName("usage_rule_display_info")
    public UsageRuleDisplayInfo usageRuleDisplayInfo;

    @SerializedName("coupon_display_info")
    public CouponDisplayInfo couponDisplayInfo;

    @SerializedName("notify_config")
    public NotifyConfig notifyConfig;

    @SerializedName("store_scope")
    public StockStoreScope storeScope;

    @SerializedName("sent_count_info")
    public StockSentCountInfo sentCountInfo;

    @SerializedName("state")
    public StockState state;

    @SerializedName("deactivate_request_no")
    public String deactivateRequestNo;

    @SerializedName("deactivate_time")
    public String deactivateTime;

    @SerializedName("deactivate_reason")
    public String deactivateReason;

    @SerializedName("brand_id")
    public String brandId;
  }

  public static class StockBundleEntity {
    @SerializedName("stock_bundle_id")
    public String stockBundleId;

    @SerializedName("stock_list")
    public List<StockEntityInBundle> stockList = new ArrayList<StockEntityInBundle>();
  }

  public static class NormalCouponUsageRule {
    @SerializedName("threshold")
    public Long threshold;

    @SerializedName("discount_amount")
    public Long discountAmount;
  }

  public static class DiscountCouponUsageRule {
    @SerializedName("threshold")
    public Long threshold;

    @SerializedName("percent_off")
    public Long percentOff;
  }

  public static class ComboPackage {
    @SerializedName("name")
    public String name;

    @SerializedName("pick_count")
    public Long pickCount;

    @SerializedName("choice_list")
    public List<ComboPackageChoice> choiceList = new ArrayList<ComboPackageChoice>();
  }

  public enum CouponCodeMode {
    @SerializedName("WECHATPAY")
    WECHATPAY,
    @SerializedName("UPLOAD")
    UPLOAD,
    @SerializedName("API_ASSIGN")
    API_ASSIGN
  }

  public static class StockSendRule {
    @SerializedName("max_count")
    public Long maxCount;

    @SerializedName("max_count_per_day")
    public Long maxCountPerDay;

    @SerializedName("max_count_per_user")
    public Long maxCountPerUser;
  }

  public static class SingleUsageRule {
    @SerializedName("coupon_available_period")
    public CouponAvailablePeriod couponAvailablePeriod;

    @SerializedName("normal_coupon")
    public NormalCouponUsageRule normalCoupon;

    @SerializedName("discount_coupon")
    public DiscountCouponUsageRule discountCoupon;

    @SerializedName("exchange_coupon")
    public ExchangeCouponUsageRule exchangeCoupon;
  }

  public static class UsageRuleDisplayInfo {
    @SerializedName("coupon_usage_method_list")
    public List<CouponUsageMethod> couponUsageMethodList = new ArrayList<CouponUsageMethod>();

    @SerializedName("mini_program_appid")
    public String miniProgramAppid;

    @SerializedName("mini_program_path")
    public String miniProgramPath;

    @SerializedName("app_path")
    public String appPath;

    @SerializedName("usage_description")
    public String usageDescription;

    @SerializedName("coupon_available_store_info")
    public CouponAvailableStoreInfo couponAvailableStoreInfo;
  }

  public static class CouponDisplayInfo {
    @SerializedName("code_display_mode")
    public CouponCodeDisplayMode codeDisplayMode;

    @SerializedName("background_color")
    public String backgroundColor;

    @SerializedName("entrance_mini_program")
    public EntranceMiniProgram entranceMiniProgram;

    @SerializedName("entrance_official_account")
    public EntranceOfficialAccount entranceOfficialAccount;

    @SerializedName("entrance_finder")
    public EntranceFinder entranceFinder;
  }

  public static class NotifyConfig {
    @SerializedName("notify_appid")
    public String notifyAppid;
  }

  public enum StockStoreScope {
    @SerializedName("NONE")
    NONE,
    @SerializedName("ALL")
    ALL,
    @SerializedName("SPECIFIC")
    SPECIFIC
  }

  public static class StockBundleUsageRule {
    @SerializedName("coupon_available_period")
    public CouponAvailablePeriod couponAvailablePeriod;

    @SerializedName("normal_coupon_list")
    public List<NormalCouponUsageRule> normalCouponList;

    @SerializedName("discount_coupon_list")
    public List<DiscountCouponUsageRule> discountCouponList;

    @SerializedName("exchange_coupon_list")
    public List<ExchangeCouponUsageRule> exchangeCouponList;
  }

  public static class CouponCodeCountInfo {
    @SerializedName("total_count")
    public Long totalCount;

    @SerializedName("available_count")
    public Long availableCount;
  }

  public static class StockSentCountInfo {
    @SerializedName("total_count")
    public Long totalCount;

    @SerializedName("today_count")
    public Long todayCount;
  }

  public enum StockState {
    @SerializedName("AUDITING")
    AUDITING,
    @SerializedName("SENDING")
    SENDING,
    @SerializedName("PAUSED")
    PAUSED,
    @SerializedName("STOPPED")
    STOPPED,
    @SerializedName("DEACTIVATED")
    DEACTIVATED
  }

  public static class StockEntityInBundle {
    @SerializedName("product_coupon_id")
    public String productCouponId;

    @SerializedName("stock_id")
    public String stockId;

    @SerializedName("remark")
    public String remark;

    @SerializedName("coupon_code_mode")
    public CouponCodeMode couponCodeMode;

    @SerializedName("coupon_code_count_info")
    public CouponCodeCountInfo couponCodeCountInfo;

    @SerializedName("stock_send_rule")
    public StockSendRule stockSendRule;

    @SerializedName("progressive_bundle_usage_rule")
    public StockUsageRule progressiveBundleUsageRule;

    @SerializedName("stock_bundle_info")
    public StockBundleInfo stockBundleInfo;

    @SerializedName("usage_rule_display_info")
    public UsageRuleDisplayInfo usageRuleDisplayInfo;

    @SerializedName("coupon_display_info")
    public CouponDisplayInfo couponDisplayInfo;

    @SerializedName("notify_config")
    public NotifyConfig notifyConfig;

    @SerializedName("store_scope")
    public StockStoreScope storeScope;

    @SerializedName("sent_count_info")
    public StockSentCountInfo sentCountInfo;

    @SerializedName("state")
    public StockState state;

    @SerializedName("deactivate_request_no")
    public String deactivateRequestNo;

    @SerializedName("deactivate_time")
    public String deactivateTime;

    @SerializedName("deactivate_reason")
    public String deactivateReason;

    @SerializedName("brand_id")
    public String brandId;
  }

  public static class ComboPackageChoice {
    @SerializedName("name")
    public String name;

    @SerializedName("price")
    public Long price;

    @SerializedName("count")
    public Long count;

    @SerializedName("image_url")
    public String imageUrl;

    @SerializedName("mini_program_appid")
    public String miniProgramAppid;

    @SerializedName("mini_program_path")
    public String miniProgramPath;
  }

  public static class CouponAvailablePeriod {
    @SerializedName("available_begin_time")
    public String availableBeginTime;

    @SerializedName("available_end_time")
    public String availableEndTime;

    @SerializedName("available_days")
    public Long availableDays;

    @SerializedName("wait_days_after_receive")
    public Long waitDaysAfterReceive;

    @SerializedName("weekly_available_period")
    public FixedWeekPeriod weeklyAvailablePeriod;

    @SerializedName("irregular_available_period_list")
    public List<TimePeriod> irregularAvailablePeriodList;
  }

  public static class ExchangeCouponUsageRule {
    @SerializedName("threshold")
    public Long threshold;

    @SerializedName("exchange_price")
    public Long exchangePrice;
  }

  public enum CouponUsageMethod {
    @SerializedName("OFFLINE")
    OFFLINE,
    @SerializedName("MINI_PROGRAM")
    MINI_PROGRAM,
    @SerializedName("APP")
    APP,
    @SerializedName("PAYMENT_CODE")
    PAYMENT_CODE
  }

  public static class CouponAvailableStoreInfo {
    @SerializedName("description")
    public String description;

    @SerializedName("mini_program_appid")
    public String miniProgramAppid;

    @SerializedName("mini_program_path")
    public String miniProgramPath;
  }

  public enum CouponCodeDisplayMode {
    @SerializedName("INVISIBLE")
    INVISIBLE,
    @SerializedName("BARCODE")
    BARCODE,
    @SerializedName("QRCODE")
    QRCODE
  }

  public static class EntranceMiniProgram {
    @SerializedName("appid")
    public String appid;

    @SerializedName("path")
    public String path;

    @SerializedName("entrance_wording")
    public String entranceWording;

    @SerializedName("guidance_wording")
    public String guidanceWording;
  }

  public static class EntranceOfficialAccount {
    @SerializedName("appid")
    public String appid;
  }

  public static class EntranceFinder {
    @SerializedName("finder_id")
    public String finderId;

    @SerializedName("finder_video_id")
    public String finderVideoId;

    @SerializedName("finder_video_cover_image_url")
    public String finderVideoCoverImageUrl;
  }

  public static class StockUsageRule {
    @SerializedName("coupon_available_period")
    public CouponAvailablePeriod couponAvailablePeriod;

    @SerializedName("normal_coupon")
    public NormalCouponUsageRule normalCoupon;

    @SerializedName("discount_coupon")
    public DiscountCouponUsageRule discountCoupon;

    @SerializedName("exchange_coupon")
    public ExchangeCouponUsageRule exchangeCoupon;
  }

  public static class StockBundleInfo {
    @SerializedName("stock_bundle_id")
    public String stockBundleId;

    @SerializedName("stock_bundle_index")
    public Long stockBundleIndex;
  }

  public static class FixedWeekPeriod {
    @SerializedName("day_list")
    public List<WeekEnum> dayList;

    @SerializedName("day_period_list")
    public List<PeriodOfTheDay> dayPeriodList;
  }

  public static class TimePeriod {
    @SerializedName("begin_time")
    public String beginTime;

    @SerializedName("end_time")
    public String endTime;
  }

  public enum WeekEnum {
    @SerializedName("MONDAY")
    MONDAY,
    @SerializedName("TUESDAY")
    TUESDAY,
    @SerializedName("WEDNESDAY")
    WEDNESDAY,
    @SerializedName("THURSDAY")
    THURSDAY,
    @SerializedName("FRIDAY")
    FRIDAY,
    @SerializedName("SATURDAY")
    SATURDAY,
    @SerializedName("SUNDAY")
    SUNDAY
  }

  public static class PeriodOfTheDay {
    @SerializedName("begin_time")
    public Long beginTime;

    @SerializedName("end_time")
    public Long endTime;
  }

}
```

需配合微信支付工具库 wxpay\_utility 使用，请参考 [Go](https://pay.weixin.qq.com/doc/v3/partner/4015119446.md)

```
package main

import (
	"bytes"
	"demo/wxpay_utility" // 引用微信支付工具库，参考 https://pay.weixin.qq.com/doc/v3/partner/4015119446
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

// 场景5：创建商品券 - 单券-单品-兑换券
//
// 场景说明：
// - usage_mode: SINGLE（单券模式）
// - scope: SINGLE（单品券）
// - type: EXCHANGE（兑换券）
//
// ⚠️ 重要：
// 1. 兑换券仅支持单品券（scope=SINGLE）
// 2. 单品券的优惠规则配置在 stock.single_usage_rule.exchange_coupon 中（不是single_usage_info）
//
// 接口文档：https://pay.weixin.qq.com/doc/v3/partner/4015781289

func main() {
	// TODO: 请准备商户开发必要参数，参考：https://pay.weixin.qq.com/doc/v3/partner/4013080340
	config, err := wxpay_utility.CreateMchConfig(
		"19xxxxxxxx",                 // 商户号，是由微信支付系统生成并分配给每个商户的唯一标识符，商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
		"1DDE55AD98Exxxxxxxxxx",      // 商户API证书序列号，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013058924
		"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径，本地文件路径
		"PUB_KEY_ID_xxxxxxxxxxxxx",   // 微信支付公钥ID，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
		"/path/to/wxp_pub.pem",       // 微信支付公钥文件路径，本地文件路径
	)
	if err != nil {
		fmt.Println(err)
		return
	}

	request := &CreateProductCouponRequest{
		// ==================== 一级参数 ====================
		// 必填：创建请求单号，品牌侧需保持唯一性，6-40个字符
		OutRequestNo: wxpay_utility.String("SINGLE_SINGLE_EXCHANGE_20250101_005"),
		// 必填：品牌ID，由微信支付分配
		BrandId: wxpay_utility.String("120344"),
		// 必填：优惠范围，SINGLE=单品券(兑换券仅支持单品券)
		Scope: PRODUCTCOUPONSCOPE_SINGLE.Ptr(),
		// 必填：商品券类型，EXCHANGE=兑换券(仅单品券支持)
		Type: PRODUCTCOUPONTYPE_EXCHANGE.Ptr(),
		// 必填：使用模式，SINGLE=单券模式
		UsageMode: USAGEMODE_SINGLE.Ptr(),
		// 选填：商户侧商品券唯一标识
		OutProductNo: wxpay_utility.String("Product_SINGLE_005"),

		// ⚠️ scope=SINGLE时，不在single_usage_info中配置优惠规则
		// 优惠规则在 stock.single_usage_rule 中配置

		// ==================== 必填：商品券展示信息 ====================
		DisplayInfo: &ProductCouponDisplayInfo{
			// 必填：商品券名称，最多20个字符
			Name: wxpay_utility.String("9.9元兑换原价99元商品"),
			// 必填：商品图片URL，需通过图片上传接口获取
			ImageUrl: wxpay_utility.String("https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx"),
			// 选填：背景图URL
			BackgroundUrl: wxpay_utility.String("https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx"),
			// 选填：详情图URL列表，最多6张
			DetailImageUrlList: []string{
				"https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx",
			},
			// 条件必填(单品券)：商品原价(单位：分)，scope=SINGLE时必填
			OriginalPrice: wxpay_utility.Int64(9900),
			// 条件必填(单品券)：套餐组合，scope=SINGLE时必填
			ComboPackageList: []ComboPackage{
				{
					Name:      wxpay_utility.String("超值套餐"),
					PickCount: wxpay_utility.Int64(1),
					ChoiceList: []ComboPackageChoice{
						{
							Name:     wxpay_utility.String("指定商品"),
							Price:    wxpay_utility.Int64(9900),
							Count:    wxpay_utility.Int64(1),
							ImageUrl: wxpay_utility.String("https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx"),
						},
					},
				},
			},
		},

		// ==================== 批次信息（必填） ====================
		Stock: &StockForCreate{
			// 选填：备注
			Remark: wxpay_utility.String("8月单品兑换批次"),
			// 必填：券Code分配模式，WECHATPAY=微信支付生成
			CouponCodeMode: COUPONCODEMODE_WECHATPAY.Ptr(),

			// ---------- 发放规则（必填） ----------
			StockSendRule: &StockSendRule{
				// 必填：总发放次数上限
				MaxCount: wxpay_utility.Int64(10000000),
				// 选填：每日发放上限
				MaxCountPerDay: wxpay_utility.Int64(100000),
				// 必填：每用户领取上限
				MaxCountPerUser: wxpay_utility.Int64(1),
			},

			// ---------- 单券使用规则（scope=SINGLE时在此配置优惠规则） ----------
			SingleUsageRule: &SingleUsageRule{
				// 券可核销时间（必填）
				CouponAvailablePeriod: &CouponAvailablePeriod{
					// 必填：开始时间，RFC3339格式
					AvailableBeginTime: wxpay_utility.String("2025-08-01T00:00:00+08:00"),
					// 必填：结束时间
					AvailableEndTime: wxpay_utility.String("2025-08-31T23:59:59+08:00"),
					// 选填：生效后N天有效，最多365天
					AvailableDays: wxpay_utility.Int64(30),
					// 选填：领取后N天生效，最多30天，0表示立即生效
					WaitDaysAfterReceive: wxpay_utility.Int64(0),
					// 选填：每周可用时间
					WeeklyAvailablePeriod: &FixedWeekPeriod{
						// 当配置weekly_available_period时必填：可用星期列表
						DayList: []WeekEnum{
							WEEKENUM_MONDAY,
							WEEKENUM_TUESDAY,
							WEEKENUM_WEDNESDAY,
							WEEKENUM_THURSDAY,
							WEEKENUM_FRIDAY,
						},
					},
				},
				// ⚠️【关键】当type=EXCHANGE时，在此配置兑换券优惠规则
				ExchangeCoupon: &ExchangeCouponUsageRule{
					// 必填：门槛金额，单位为分，0表示无门槛
					Threshold: wxpay_utility.Int64(0),
					// 必填：兑换价格，单位为分，9.9元填990
					ExchangePrice: wxpay_utility.Int64(990),
				},
			},

			// ---------- 使用规则展示信息（必填） ----------
			UsageRuleDisplayInfo: &UsageRuleDisplayInfo{
				// 必填：券使用方式列表
				CouponUsageMethodList: []CouponUsageMethod{
					COUPONUSAGEMETHOD_OFFLINE,      // 线下滴码核销
					COUPONUSAGEMETHOD_MINI_PROGRAM, // 线上小程序核销
					COUPONUSAGEMETHOD_PAYMENT_CODE, // 微信支付付款码核销
				},
				// 当coupon_usage_method_list包含MINI_PROGRAM时必填：核销小程序AppID
				MiniProgramAppid: wxpay_utility.String("wx1234567890"),
				// 当coupon_usage_method_list包含MINI_PROGRAM时必填：核销小程序路径
				MiniProgramPath: wxpay_utility.String("/pages/index/product"),
				// 必填：使用说明
				UsageDescription: wxpay_utility.String("用9.9元兑换原价99元商品"),
				// 选填：可用门店信息
				CouponAvailableStoreInfo: &CouponAvailableStoreInfo{
					// 当配置coupon_available_store_info时必填：门店描述
					Description: wxpay_utility.String("所有门店可用"),
					// 选填：查看门店的小程序AppID
					MiniProgramAppid: wxpay_utility.String("wx1234567890"),
					// 选填：查看门店的小程序路径
					MiniProgramPath: wxpay_utility.String("/pages/index/store-list"),
				},
			},

			// ---------- 用户券展示信息（必填） ----------
			CouponDisplayInfo: &CouponDisplayInfo{
				// 必填：Code展示模式，QRCODE=二维码
				CodeDisplayMode: COUPONCODEDISPLAYMODE_QRCODE.Ptr(),
				// 选填：背景颜色
				BackgroundColor: wxpay_utility.String("Color010"),
				// 选填：小程序入口
				EntranceMiniProgram: &EntranceMiniProgram{
					Appid:           wxpay_utility.String("wx1234567890"),
					Path:            wxpay_utility.String("/pages/index/product"),
					EntranceWording: wxpay_utility.String("立即兑换"),
					GuidanceWording: wxpay_utility.String("获取更多优惠"),
				},
				// 选填：公众号入口
				EntranceOfficialAccount: &EntranceOfficialAccount{
					Appid: wxpay_utility.String("wx1234567890"),
				},
				// 选填：视频号入口
				EntranceFinder: &EntranceFinder{
					FinderId:                 wxpay_utility.String("gh_12345678"),
					FinderVideoId:            wxpay_utility.String("UDFsdf24df34dD456Hdf34"),
					FinderVideoCoverImageUrl: wxpay_utility.String("https://wxpaylogo.qpic.cn/wxpaylogo/xxxxx/xxx"),
				},
			},

			// ---------- 事件通知配置（必填） ----------
			NotifyConfig: &NotifyConfig{
				// 必填：事件通知AppID
				NotifyAppid: wxpay_utility.String("wx4fd12345678"),
			},

			// 必填：可用门店范围，NONE=不限制
			StoreScope: STOCKSTORESCOPE_NONE.Ptr(),
		},
	}

	response, err := CreateProductCoupon(config, request)
	if err != nil {
		fmt.Printf("请求失败: %+v\n", err)
		// TODO: 请求失败，根据状态码执行不同的处理
		return
	}

	// TODO: 请求成功，继续业务逻辑
	fmt.Printf("请求成功: %+v\n", response)
}

func CreateProductCoupon(config *wxpay_utility.MchConfig, request *CreateProductCouponRequest) (response *CreateProductCouponResponse, err error) {
	const (
		host   = "https://api.mch.weixin.qq.com"
		method = "POST"
		path   = "/v3/marketing/partner/product-coupon/product-coupons"
	)

	reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
	if err != nil {
		return nil, err
	}
	reqBody, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}
	httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
	if err != nil {
		return nil, err
	}
	httpRequest.Header.Set("Accept", "application/json")
	httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
	httpRequest.Header.Set("Content-Type", "application/json")
	authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
	if err != nil {
		return nil, err
	}
	httpRequest.Header.Set("Authorization", authorization)

	client := &http.Client{}
	httpResponse, err := client.Do(httpRequest)
	if err != nil {
		return nil, err
	}
	respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
	if err != nil {
		return nil, err
	}
	if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
		// 2XX 成功，验证应答签名
		err = wxpay_utility.ValidateResponse(
			config.WechatPayPublicKeyId(),
			config.WechatPayPublicKey(),
			&httpResponse.Header,
			respBody,
		)
		if err != nil {
			return nil, err
		}
		response := &CreateProductCouponResponse{}
		if err := json.Unmarshal(respBody, response); err != nil {
			return nil, err
		}
		return response, nil
	} else {
		return nil, wxpay_utility.NewApiException(
			httpResponse.StatusCode,
			httpResponse.Header,
			respBody,
		)
	}
}

// ==================== 请求/响应结构体定义 ====================

type CreateProductCouponRequest struct {
	OutRequestNo    *string                   `json:"out_request_no,omitempty"`
	BrandId         *string                   `json:"brand_id,omitempty"`
	Scope           *ProductCouponScope       `json:"scope,omitempty"`
	Type            *ProductCouponType        `json:"type,omitempty"`
	UsageMode       *UsageMode                `json:"usage_mode,omitempty"`
	SingleUsageInfo *SingleUsageInfo          `json:"single_usage_info,omitempty"`
	DisplayInfo     *ProductCouponDisplayInfo `json:"display_info,omitempty"`
	OutProductNo    *string                   `json:"out_product_no,omitempty"`
	Stock           *StockForCreate           `json:"stock,omitempty"`
}

type CreateProductCouponResponse struct {
	ProductCouponId *string                   `json:"product_coupon_id,omitempty"`
	Scope           *ProductCouponScope       `json:"scope,omitempty"`
	Type            *ProductCouponType        `json:"type,omitempty"`
	UsageMode       *UsageMode                `json:"usage_mode,omitempty"`
	SingleUsageInfo *SingleUsageInfo          `json:"single_usage_info,omitempty"`
	DisplayInfo     *ProductCouponDisplayInfo `json:"display_info,omitempty"`
	OutProductNo    *string                   `json:"out_product_no,omitempty"`
	State           *ProductCouponState       `json:"state,omitempty"`
	Stock           *StockEntity              `json:"stock,omitempty"`
	BrandId         *string                   `json:"brand_id,omitempty"`
}

// ==================== 枚举类型定义 ====================

type ProductCouponScope string

func (e ProductCouponScope) Ptr() *ProductCouponScope { return &e }

const (
	PRODUCTCOUPONSCOPE_ALL    ProductCouponScope = "ALL"
	PRODUCTCOUPONSCOPE_SINGLE ProductCouponScope = "SINGLE"
)

type ProductCouponType string

func (e ProductCouponType) Ptr() *ProductCouponType { return &e }

const (
	PRODUCTCOUPONTYPE_NORMAL   ProductCouponType = "NORMAL"
	PRODUCTCOUPONTYPE_DISCOUNT ProductCouponType = "DISCOUNT"
	PRODUCTCOUPONTYPE_EXCHANGE ProductCouponType = "EXCHANGE"
)

type UsageMode string

func (e UsageMode) Ptr() *UsageMode { return &e }

const (
	USAGEMODE_SINGLE             UsageMode = "SINGLE"
	USAGEMODE_PROGRESSIVE_BUNDLE UsageMode = "PROGRESSIVE_BUNDLE"
)

type ProductCouponState string

func (e ProductCouponState) Ptr() *ProductCouponState { return &e }

const (
	PRODUCTCOUPONSTATE_AUDITING    ProductCouponState = "AUDITING"
	PRODUCTCOUPONSTATE_EFFECTIVE   ProductCouponState = "EFFECTIVE"
	PRODUCTCOUPONSTATE_DEACTIVATED ProductCouponState = "DEACTIVATED"
)

type CouponCodeMode string

func (e CouponCodeMode) Ptr() *CouponCodeMode { return &e }

const (
	COUPONCODEMODE_WECHATPAY  CouponCodeMode = "WECHATPAY"
	COUPONCODEMODE_UPLOAD     CouponCodeMode = "UPLOAD"
	COUPONCODEMODE_API_ASSIGN CouponCodeMode = "API_ASSIGN"
)

type StockStoreScope string

func (e StockStoreScope) Ptr() *StockStoreScope { return &e }

const (
	STOCKSTORESCOPE_NONE     StockStoreScope = "NONE"
	STOCKSTORESCOPE_ALL      StockStoreScope = "ALL"
	STOCKSTORESCOPE_SPECIFIC StockStoreScope = "SPECIFIC"
)

type CouponUsageMethod string

func (e CouponUsageMethod) Ptr() *CouponUsageMethod { return &e }

const (
	COUPONUSAGEMETHOD_OFFLINE      CouponUsageMethod = "OFFLINE"
	COUPONUSAGEMETHOD_MINI_PROGRAM CouponUsageMethod = "MINI_PROGRAM"
	COUPONUSAGEMETHOD_APP          CouponUsageMethod = "APP"
	COUPONUSAGEMETHOD_PAYMENT_CODE CouponUsageMethod = "PAYMENT_CODE"
)

type CouponCodeDisplayMode string

func (e CouponCodeDisplayMode) Ptr() *CouponCodeDisplayMode { return &e }

const (
	COUPONCODEDISPLAYMODE_INVISIBLE CouponCodeDisplayMode = "INVISIBLE"
	COUPONCODEDISPLAYMODE_BARCODE   CouponCodeDisplayMode = "BARCODE"
	COUPONCODEDISPLAYMODE_QRCODE    CouponCodeDisplayMode = "QRCODE"
)

type WeekEnum string

func (e WeekEnum) Ptr() *WeekEnum { return &e }

const (
	WEEKENUM_MONDAY    WeekEnum = "MONDAY"
	WEEKENUM_TUESDAY   WeekEnum = "TUESDAY"
	WEEKENUM_WEDNESDAY WeekEnum = "WEDNESDAY"
	WEEKENUM_THURSDAY  WeekEnum = "THURSDAY"
	WEEKENUM_FRIDAY    WeekEnum = "FRIDAY"
	WEEKENUM_SATURDAY  WeekEnum = "SATURDAY"
	WEEKENUM_SUNDAY    WeekEnum = "SUNDAY"
)

type StockState string

func (e StockState) Ptr() *StockState { return &e }

const (
	STOCKSTATE_AUDITING    StockState = "AUDITING"
	STOCKSTATE_SENDING     StockState = "SENDING"
	STOCKSTATE_PAUSED      StockState = "PAUSED"
	STOCKSTATE_STOPPED     StockState = "STOPPED"
	STOCKSTATE_DEACTIVATED StockState = "DEACTIVATED"
)

// ==================== 数据模型结构体定义 ====================

// 单券模式优惠信息（scope=ALL时使用）
type SingleUsageInfo struct {
	NormalCoupon   *NormalCouponUsageRule   `json:"normal_coupon,omitempty"`
	DiscountCoupon *DiscountCouponUsageRule `json:"discount_coupon,omitempty"`
}

type ProductCouponDisplayInfo struct {
	Name               *string        `json:"name,omitempty"`
	ImageUrl           *string        `json:"image_url,omitempty"`
	BackgroundUrl      *string        `json:"background_url,omitempty"`
	DetailImageUrlList []string       `json:"detail_image_url_list,omitempty"`
	OriginalPrice      *int64         `json:"original_price,omitempty"`
	ComboPackageList   []ComboPackage `json:"combo_package_list,omitempty"`
}

type StockForCreate struct {
	Remark               *string               `json:"remark,omitempty"`
	CouponCodeMode       *CouponCodeMode       `json:"coupon_code_mode,omitempty"`
	StockSendRule        *StockSendRule        `json:"stock_send_rule,omitempty"`
	SingleUsageRule      *SingleUsageRule      `json:"single_usage_rule,omitempty"`
	UsageRuleDisplayInfo *UsageRuleDisplayInfo `json:"usage_rule_display_info,omitempty"`
	CouponDisplayInfo    *CouponDisplayInfo    `json:"coupon_display_info,omitempty"`
	NotifyConfig         *NotifyConfig         `json:"notify_config,omitempty"`
	StoreScope           *StockStoreScope      `json:"store_scope,omitempty"`
}

type StockEntity struct {
	ProductCouponId      *string               `json:"product_coupon_id,omitempty"`
	StockId              *string               `json:"stock_id,omitempty"`
	Remark               *string               `json:"remark,omitempty"`
	CouponCodeMode       *CouponCodeMode       `json:"coupon_code_mode,omitempty"`
	StockSendRule        *StockSendRule        `json:"stock_send_rule,omitempty"`
	SingleUsageRule      *SingleUsageRule      `json:"single_usage_rule,omitempty"`
	UsageRuleDisplayInfo *UsageRuleDisplayInfo `json:"usage_rule_display_info,omitempty"`
	CouponDisplayInfo    *CouponDisplayInfo    `json:"coupon_display_info,omitempty"`
	NotifyConfig         *NotifyConfig         `json:"notify_config,omitempty"`
	StoreScope           *StockStoreScope      `json:"store_scope,omitempty"`
	State                *StockState           `json:"state,omitempty"`
	BrandId              *string               `json:"brand_id,omitempty"`
}

type NormalCouponUsageRule struct {
	Threshold      *int64 `json:"threshold,omitempty"`
	DiscountAmount *int64 `json:"discount_amount,omitempty"`
}

type DiscountCouponUsageRule struct {
	Threshold  *int64 `json:"threshold,omitempty"`
	PercentOff *int64 `json:"percent_off,omitempty"`
}

type ExchangeCouponUsageRule struct {
	Threshold     *int64 `json:"threshold,omitempty"`
	ExchangePrice *int64 `json:"exchange_price,omitempty"`
}

type ComboPackage struct {
	Name       *string              `json:"name,omitempty"`
	PickCount  *int64               `json:"pick_count,omitempty"`
	ChoiceList []ComboPackageChoice `json:"choice_list,omitempty"`
}

type ComboPackageChoice struct {
	Name             *string `json:"name,omitempty"`
	Price            *int64  `json:"price,omitempty"`
	Count            *int64  `json:"count,omitempty"`
	ImageUrl         *string `json:"image_url,omitempty"`
	MiniProgramAppid *string `json:"mini_program_appid,omitempty"`
	MiniProgramPath  *string `json:"mini_program_path,omitempty"`
}

type StockSendRule struct {
	MaxCount        *int64 `json:"max_count,omitempty"`
	MaxCountPerDay  *int64 `json:"max_count_per_day,omitempty"`
	MaxCountPerUser *int64 `json:"max_count_per_user,omitempty"`
}

// 单券使用规则（scope=ALL时只配置CouponAvailablePeriod，scope=SINGLE时还需配置优惠规则）
type SingleUsageRule struct {
	CouponAvailablePeriod *CouponAvailablePeriod   `json:"coupon_available_period,omitempty"`
	NormalCoupon          *NormalCouponUsageRule   `json:"normal_coupon,omitempty"`
	DiscountCoupon        *DiscountCouponUsageRule `json:"discount_coupon,omitempty"`
	ExchangeCoupon        *ExchangeCouponUsageRule `json:"exchange_coupon,omitempty"`
}

type CouponAvailablePeriod struct {
	AvailableBeginTime           *string          `json:"available_begin_time,omitempty"`
	AvailableEndTime             *string          `json:"available_end_time,omitempty"`
	AvailableDays                *int64           `json:"available_days,omitempty"`
	WaitDaysAfterReceive         *int64           `json:"wait_days_after_receive,omitempty"`
	WeeklyAvailablePeriod        *FixedWeekPeriod `json:"weekly_available_period,omitempty"`
	IrregularAvailablePeriodList []TimePeriod     `json:"irregular_available_period_list,omitempty"`
}

type FixedWeekPeriod struct {
	DayList []WeekEnum `json:"day_list,omitempty"`
}

type TimePeriod struct {
	BeginTime *string `json:"begin_time,omitempty"`
	EndTime   *string `json:"end_time,omitempty"`
}

type UsageRuleDisplayInfo struct {
	CouponUsageMethodList    []CouponUsageMethod       `json:"coupon_usage_method_list,omitempty"`
	MiniProgramAppid         *string                   `json:"mini_program_appid,omitempty"`
	MiniProgramPath          *string                   `json:"mini_program_path,omitempty"`
	UsageDescription         *string                   `json:"usage_description,omitempty"`
	CouponAvailableStoreInfo *CouponAvailableStoreInfo `json:"coupon_available_store_info,omitempty"`
}

type CouponAvailableStoreInfo struct {
	Description      *string `json:"description,omitempty"`
	MiniProgramAppid *string `json:"mini_program_appid,omitempty"`
	MiniProgramPath  *string `json:"mini_program_path,omitempty"`
}

type CouponDisplayInfo struct {
	CodeDisplayMode         *CouponCodeDisplayMode   `json:"code_display_mode,omitempty"`
	BackgroundColor         *string                  `json:"background_color,omitempty"`
	EntranceMiniProgram     *EntranceMiniProgram     `json:"entrance_mini_program,omitempty"`
	EntranceOfficialAccount *EntranceOfficialAccount `json:"entrance_official_account,omitempty"`
	EntranceFinder          *EntranceFinder          `json:"entrance_finder,omitempty"`
}

type EntranceMiniProgram struct {
	Appid           *string `json:"appid,omitempty"`
	Path            *string `json:"path,omitempty"`
	EntranceWording *string `json:"entrance_wording,omitempty"`
	GuidanceWording *string `json:"guidance_wording,omitempty"`
}

type EntranceOfficialAccount struct {
	Appid *string `json:"appid,omitempty"`
}

type EntranceFinder struct {
	FinderId                 *string `json:"finder_id,omitempty"`
	FinderVideoId            *string `json:"finder_video_id,omitempty"`
	FinderVideoCoverImageUrl *string `json:"finder_video_cover_image_url,omitempty"`
}

type NotifyConfig struct {
	NotifyAppid *string `json:"notify_appid,omitempty"`
}
```