--- a/net/minecraft/world/item/trading/MerchantOffer.java
+++ b/net/minecraft/world/item/trading/MerchantOffer.java
@@ -21,22 +_,38 @@
                 Codec.INT.lenientOptionalFieldOf("demand", 0).forGetter(merchantOffer -> merchantOffer.demand),
                 Codec.FLOAT.lenientOptionalFieldOf("priceMultiplier", 0.0F).forGetter(merchantOffer -> merchantOffer.priceMultiplier),
                 Codec.INT.lenientOptionalFieldOf("xp", 1).forGetter(merchantOffer -> merchantOffer.xp)
+                , Codec.BOOL.lenientOptionalFieldOf("Paper.IgnoreDiscounts", false).forGetter(merchantOffer -> merchantOffer.ignoreDiscounts) // Paper
             )
             .apply(instance, MerchantOffer::new)
     );
     public static final StreamCodec<RegistryFriendlyByteBuf, MerchantOffer> STREAM_CODEC = StreamCodec.of(
         MerchantOffer::writeToStream, MerchantOffer::createFromStream
     );
-    private final ItemCost baseCostA;
-    private final Optional<ItemCost> costB;
-    private final ItemStack result;
-    private int uses;
-    private final int maxUses;
-    private final boolean rewardExp;
-    private int specialPriceDiff;
-    private int demand;
-    private final float priceMultiplier;
-    private final int xp;
+    public ItemCost baseCostA;
+    public Optional<ItemCost> costB;
+    public final ItemStack result;
+    public int uses;
+    public int maxUses;
+    public boolean rewardExp;
+    public int specialPriceDiff;
+    public int demand;
+    public float priceMultiplier;
+    public int xp;
+    public boolean ignoreDiscounts; // Paper - Add ignore discounts API
+
+    // CraftBukkit start
+    private @javax.annotation.Nullable org.bukkit.craftbukkit.inventory.CraftMerchantRecipe bukkitHandle;
+
+    public org.bukkit.craftbukkit.inventory.CraftMerchantRecipe asBukkit() {
+        return (this.bukkitHandle == null) ? this.bukkitHandle = new org.bukkit.craftbukkit.inventory.CraftMerchantRecipe(this) : this.bukkitHandle;
+    }
+
+    public MerchantOffer(ItemCost baseCostA, Optional<ItemCost> costB, ItemStack result, int uses, int maxUses, int experience, float priceMultiplier, int demand, final boolean ignoreDiscounts, org.bukkit.craftbukkit.inventory.CraftMerchantRecipe bukkit) { // Paper
+        this(baseCostA, costB, result, uses, maxUses, experience, priceMultiplier, demand);
+        this.ignoreDiscounts = ignoreDiscounts; // Paper
+        this.bukkitHandle = bukkit;
+    }
+    // CraftBukkit end
 
     private MerchantOffer(
         ItemCost baseCostA,
@@ -49,6 +_,7 @@
         int demand,
         float priceMultiplier,
         int xp
+        , final boolean ignoreDiscounts // Paper
     ) {
         this.baseCostA = baseCostA;
         this.costB = costB;
@@ -60,6 +_,7 @@
         this.demand = demand;
         this.priceMultiplier = priceMultiplier;
         this.xp = xp;
+        this.ignoreDiscounts = ignoreDiscounts; // Paper
     }
 
     public MerchantOffer(ItemCost baseCostA, ItemStack result, int maxUses, int xp, float priceMultiplier) {
@@ -75,7 +_,7 @@
     }
 
     public MerchantOffer(ItemCost baseCostA, Optional<ItemCost> costB, ItemStack result, int _uses, int maxUses, int xp, float priceMultiplier, int demand) {
-        this(baseCostA, costB, result, _uses, maxUses, true, 0, demand, priceMultiplier, xp);
+        this(baseCostA, costB, result, _uses, maxUses, true, 0, demand, priceMultiplier, xp, false); // Paper
     }
 
     private MerchantOffer(MerchantOffer other) {
@@ -90,6 +_,7 @@
             other.demand,
             other.priceMultiplier,
             other.xp
+            , other.ignoreDiscounts // Paper
         );
     }
 
@@ -125,6 +_,7 @@
 
     public void updateDemand() {
         this.demand = this.demand + this.uses - (this.maxUses - this.uses);
+        if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.preventNegativeVillagerDemand) this.demand = Math.max(0, this.demand); // Paper - Fix MC-163962
     }
 
     public ItemStack assemble() {
@@ -205,7 +_,11 @@
         if (!this.satisfiedBy(playerOfferA, playerOfferB)) {
             return false;
         } else {
-            playerOfferA.shrink(this.getCostA().getCount());
+            // CraftBukkit start
+            if (!this.getCostA().isEmpty()) {
+                playerOfferA.shrink(this.getCostA().getCount());
+            }
+            // CraftBukkit end
             if (!this.getCostB().isEmpty()) {
                 playerOfferB.shrink(this.getCostB().getCount());
             }
