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