diff --git a/Runtime/Client/LootLockerEndPoints.cs b/Runtime/Client/LootLockerEndPoints.cs
index d0d0071d..1585fd06 100644
--- a/Runtime/Client/LootLockerEndPoints.cs
+++ b/Runtime/Client/LootLockerEndPoints.cs
@@ -218,6 +218,10 @@ public class LootLockerEndPoints
public static EndPointClass querySteamPurchaseRedemptionStatus = new EndPointClass("store/steam/redeem/query", LootLockerHTTPMethod.POST);
public static EndPointClass finalizeSteamPurchaseRedemption = new EndPointClass("store/steam/redeem/finalise", LootLockerHTTPMethod.POST);
+ // Refund
+ [Header("Refund")]
+ public static EndPointClass refundByEntitlementIds = new EndPointClass("game/refund/v1", LootLockerHTTPMethod.POST);
+
// Triggers
public static EndPointClass InvokeTriggers = new EndPointClass("triggers/cozy-crusader/v1", LootLockerHTTPMethod.POST);
diff --git a/Runtime/Game/LootLockerSDKManager.cs b/Runtime/Game/LootLockerSDKManager.cs
index 80ef20d5..ae1ef969 100644
--- a/Runtime/Game/LootLockerSDKManager.cs
+++ b/Runtime/Game/LootLockerSDKManager.cs
@@ -6994,6 +6994,38 @@ public static void FinalizeSteamPurchaseRedemption(string entitlementId, Action<
LootLockerServerRequest.CallAPI(forPlayerWithUlid, LootLockerEndPoints.finalizeSteamPurchaseRedemption.endPoint, LootLockerEndPoints.finalizeSteamPurchaseRedemption.httpMethod, body, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
}
+ ///
+ /// Refund one or more previously purchased items by their entitlement IDs.
+ ///
+ /// On success, the response contains:
+ /// - player_inventory_events: the assets removed from (or skipped in) the player's inventory
+ /// - currency_refunded: currency credited back to the player's wallet (the purchase price returned)
+ /// - currency_clawback: currency debited from the player's wallet (currency rewards from the entitlement reclaimed)
+ /// - warnings: any conditions that could not be fully reversed (e.g. non-reversible rewards, insufficient funds). A non-empty warnings list does not mean the refund failed.
+ ///
+ /// The list of entitlement IDs to refund
+ /// onComplete Action for handling the response of type LootLockerRefundByEntitlementIdsResponse
+ /// Optional : Execute the request for the specified player. If not supplied, the default player will be used.
+ public static void RefundByEntitlementIds(string[] entitlementIds, Action onComplete, string forPlayerWithUlid = null)
+ {
+ if (!CheckInitialized(false, forPlayerWithUlid))
+ {
+ onComplete?.Invoke(LootLockerResponseFactory.SDKNotInitializedError(forPlayerWithUlid));
+ return;
+ }
+ if (entitlementIds == null || entitlementIds.Length == 0)
+ {
+ onComplete?.Invoke(LootLockerResponseFactory.ClientError("entitlementIds must not be null or empty", forPlayerWithUlid));
+ return;
+ }
+ var body = LootLockerJson.SerializeObject(new LootLockerRefundByEntitlementIdsRequest
+ {
+ entitlement_ids = entitlementIds
+ });
+
+ LootLockerServerRequest.CallAPI(forPlayerWithUlid, LootLockerEndPoints.refundByEntitlementIds.endPoint, LootLockerEndPoints.refundByEntitlementIds.httpMethod, body, onComplete: (serverResponse) => { LootLockerResponse.Deserialize(onComplete, serverResponse); });
+ }
+
#endregion
#region Collectables
diff --git a/Runtime/Game/Requests/PurchaseRequest.cs b/Runtime/Game/Requests/PurchaseRequest.cs
index 665097c5..a4dfb39c 100644
--- a/Runtime/Game/Requests/PurchaseRequest.cs
+++ b/Runtime/Game/Requests/PurchaseRequest.cs
@@ -243,6 +243,139 @@ public class LootLockerFinalizeSteamPurchaseRedemptionRequest
///
public string entitlement_id { get; set; }
}
+
+ //==================================================
+ // Refund Data Definitions
+ //==================================================
+
+ ///
+ /// Request to refund items by their entitlement IDs
+ ///
+ public class LootLockerRefundByEntitlementIdsRequest
+ {
+ ///
+ /// The list of entitlement IDs to refund
+ ///
+ public string[] entitlement_ids { get; set; }
+ }
+
+ ///
+ /// Describes an asset that was added or removed from the player's inventory as part of a refund
+ ///
+ public class LootLockerRefundInventoryEvent
+ {
+ ///
+ /// The legacy numeric ID of the asset
+ ///
+ public ulong asset_id { get; set; }
+ ///
+ /// Display name of the asset
+ ///
+ public string name { get; set; }
+ ///
+ /// The action taken on this asset: "removed" if the asset was successfully taken back from the player's inventory, "skipped" if it could not be removed (e.g. already consumed or transferred)
+ ///
+ public string action { get; set; }
+ }
+
+ ///
+ /// A currency amount credited or debited as part of a refund
+ ///
+ public class LootLockerRefundCurrencyEntry
+ {
+ ///
+ /// The ULID of the currency
+ ///
+ public string currency_id { get; set; }
+ ///
+ /// Short code identifying the currency (e.g. "gold", "gems")
+ ///
+ public string currency_code { get; set; }
+ ///
+ /// The amount credited or debited, represented as a string to support arbitrary precision
+ ///
+ public string amount { get; set; }
+ }
+
+ ///
+ /// A reward that could not be reversed as part of a refund
+ ///
+ public class LootLockerRefundNonReversibleReward
+ {
+ ///
+ /// The kind of reward: "progression_points" (points were added to a progression) or "progression_reset" (a progression was reset to its initial state)
+ ///
+ public string kind { get; set; }
+ ///
+ /// The ULID of the progression that was affected
+ ///
+ public string id { get; set; }
+ ///
+ /// Display name of the progression
+ ///
+ public string name { get; set; }
+ ///
+ /// The number of points that were granted and cannot be reversed. Only present for "progression_points"
+ ///
+ public string amount { get; set; }
+ }
+
+ ///
+ /// A warning detail for a specific condition encountered during refund processing
+ ///
+ public class LootLockerRefundWarningDetail
+ {
+ ///
+ /// The warning category: "non_reversible_rewards", "insufficient_funds", "already_refunded", or "refund_failed"
+ ///
+ public string type { get; set; }
+ ///
+ /// Human-readable explanation of the warning
+ ///
+ public string message { get; set; }
+ ///
+ /// The specific rewards that could not be reversed. Only present when type is "non_reversible_rewards"
+ ///
+ public LootLockerRefundNonReversibleReward[] rewards { get; set; }
+ }
+
+ ///
+ /// Warnings encountered during refund processing for a particular entitlement
+ ///
+ public class LootLockerRefundWarning
+ {
+ ///
+ /// The entitlement ULID this warning applies to
+ ///
+ public string entitlement_id { get; set; }
+ ///
+ /// One or more warning conditions for this entitlement
+ ///
+ public LootLockerRefundWarningDetail[] details { get; set; }
+ }
+
+ ///
+ /// Response from refunding items by entitlement IDs
+ ///
+ public class LootLockerRefundByEntitlementIdsResponse : LootLockerResponse
+ {
+ ///
+ /// Assets that were added or removed from the player's inventory as part of the refund
+ ///
+ public LootLockerRefundInventoryEvent[] player_inventory_events { get; set; }
+ ///
+ /// Currency amounts credited back to the player's wallet (i.e. the purchase price being returned)
+ ///
+ public LootLockerRefundCurrencyEntry[] currency_refunded { get; set; }
+ ///
+ /// Currency amounts debited from the player's wallet (i.e. currency rewards from the entitlement being reclaimed)
+ ///
+ public LootLockerRefundCurrencyEntry[] currency_clawback { get; set; }
+ ///
+ /// Warnings encountered during refund processing, grouped by entitlement. A non-empty warnings array does not mean the refund failed — it means some aspects could not be fully reversed.
+ ///
+ public LootLockerRefundWarning[] warnings { get; set; }
+ }
}
namespace LootLocker