private Dictionary <string, string> BuildInitiationRequestBody(
            SteamTransactionEntity transaction
            )
        {
            var body = new Dictionary <string, string> {
                ["key"]       = Env.GetString("STEAM_PUBLISHER_KEY"),
                ["orderid"]   = transaction.orderId.ToString(),
                ["steamid"]   = transaction.playerSteamId.ToString(),
                ["appid"]     = Env.GetString("STEAM_APP_ID"),
                ["itemcount"] = transaction.items.Count.ToString(),
                ["language"]  = transaction.language,
                ["currency"]  = transaction.currency
            };

            for (int i = 0; i < transaction.items.Count; i++)
            {
                var item = transaction.items[i];

                body[$"itemid[{i}]"]      = item.itemId.ToString();
                body[$"qty[{i}]"]         = item.quantity.ToString();
                body[$"amount[{i}]"]      = item.totalAmountInCents.ToString();
                body[$"description[{i}]"] = item.description;
                if (!string.IsNullOrWhiteSpace(item.category))
                {
                    body[$"category[{i}]"] = item.category;
                }
            }

            return(body);
        }
        // =========================================================================
        //                    Don't worry about the code below
        // =========================================================================



        #region "InitiateTransaction implementation"

        private void ValidateTransactionProposal(
            SteamTransactionEntity transaction
            )
        {
            if (transaction.EntityId != null)
            {
                throw new ArgumentException(
                          "Given transaction has already been initiated."
                          );
            }

            if (transaction.playerSteamId == 0)
            {
                throw new ArgumentException(
                          $"Given transaction does not have " +
                          $"{nameof(transaction.playerSteamId)} specified."
                          );
            }

            if (transaction.items.Count == 0)
            {
                throw new ArgumentException(
                          "Given transaction has no items inside of it."
                          );
            }
        }
        private void MarkTransactionAsCompleted(
            SteamTransactionEntity transaction
            )
        {
            transaction.state = SteamTransactionEntity.CompletedState;
            transaction.Save();

            Log.Info("Marked transaction as completed.");
        }
        private void MarkTransactionAsAuthorized(
            SteamTransactionEntity transaction
            )
        {
            transaction.state = SteamTransactionEntity.AuthorizedState;
            transaction.Save();

            Log.Info("Marked transaction as authorized.");
        }
        private void MarkTransactionAsInitiated(
            SteamTransactionEntity transaction,
            Response response
            )
        {
            transaction.state         = SteamTransactionEntity.InitiatedState;
            transaction.transactionId = ulong.Parse(
                response["response"]["params"]["transid"].AsString
                );
            transaction.Save();

            Log.Info("Marked transaction as initiated.");
        }
        private Response SendInitiationRequestToSteam(
            SteamTransactionEntity transaction
            )
        {
            // https://partner.steamgames.com/doc/webapi/ISteamMicroTxn#InitTxn

            var response = Http.Post(
                GetSteamApiUrl() + "InitTxn/v3/",
                BuildInitiationRequestBody(transaction)
                );

            response.Throw();

            return(response);
        }
        private void StoreFinalizationErrorAndThrow(
            SteamTransactionEntity transaction,
            Response response
            )
        {
            transaction.state = SteamTransactionEntity.FinalizationErrorState;
            transaction.errorCode
                = response["response"]["error"]["errorcode"].AsString;
            transaction.errorDescription
                = response["response"]["error"]["errordesc"].AsString;
            transaction.Save();

            throw new SteamMicrotransactionException(
                      "Steam rejected transaction finalization.",
                      transaction.orderId,
                      transaction.errorCode,
                      transaction.errorDescription
                      );
        }
        private Response SendFinalizationRequestToSteam(
            SteamTransactionEntity transaction
            )
        {
            // https://partner.steamgames.com/doc/webapi/ISteamMicroTxn#FinalizeTxn

            var response = Http.Post(
                GetSteamApiUrl() + "FinalizeTxn/v2/",
                new Dictionary <string, string> {
                ["key"]     = Env.GetString("STEAM_PUBLISHER_KEY"),
                ["orderid"] = transaction.orderId.ToString(),
                ["appid"]   = Env.GetString("STEAM_APP_ID")
            }
                );

            response.Throw();

            return(response);
        }
        private void GiveProductsToPlayer(SteamTransactionEntity transaction)
        {
            foreach (var item in transaction.items)
            {
                Type productType = Type.GetType(item.productClass);

                if (productType == null)
                {
                    throw new Exception(
                              $"Cannot find product {item.productClass}"
                              );
                }

                var instance = (IVirtualProduct)Activator.CreateInstance(
                    productType
                    );

                var method = productType.GetMethod(
                    nameof(IVirtualProduct.GiveToPlayer)
                    );

                if (method == null)
                {
                    throw new Exception(
                              $"Class {item.productClass} does not contain " +
                              $"method {nameof(IVirtualProduct.GiveToPlayer)}"
                              );
                }

                for (int i = 0; i < item.quantity; i++)
                {
                    method.Invoke(instance, new object[] { transaction });

                    Log.Info("Product has been given to the player.", item);
                }
            }
        }
        /// <summary>
        /// Call this method from anywhere within your game
        /// to initiate a new transaction
        /// </summary>
        /// <param name="transaction">Proposal of a new transaction</param>
        public void InitiateTransaction(SteamTransactionEntity transaction)
        {
            ValidateTransactionProposal(transaction);

            // You can add additional data to the transaction entity
            // e.g. the currently logged-in player ID
            transaction.authenticatedPlayerId = Auth.Id();

            StoreNewTransaction(transaction);

            Response response = SendInitiationRequestToSteam(transaction);

            if (response["response"]["result"].AsString != "OK")
            {
                StoreInitiationErrorAndThrow(transaction, response);
            }

            MarkTransactionAsInitiated(transaction, response);

            // The player will be prompted by the Steam App to authorize
            // and pay the transaction. Then Steam will notify your game
            // via a Steamworks callback that is handled automatically
            // by the SteamPurchasingClient class.
        }
 private void StoreNewTransaction(SteamTransactionEntity transaction)
 {
     transaction.state = SteamTransactionEntity.BeingPreparedState;
     transaction.Save();
 }