- 뒤끝 SDK 버전 : SDK 5.11.7 버전 & 뒤끝 펑션 [2025-02-06] 버전
- 프로젝트명 : golem
- 스테이터스 코드 : 401
- 에러 코드 : BadUnauthorizedException
- 에러 메시지 : bad bad,signature,잘못된,signature,입니다,잘못된 bad,signature,잘못된,signature,입니다
TransactionWriteV2 를 통해 두개의 테이블에 동시에 insert 요청을 했습니다.
뒤끝펑션 디버그 환경이나 pc에서는 문제 없이 잘 동작했습니다.
하지만 안드로이드 빌드 환경에서는 TransactionWriteV2 함수에서 401 에러가 발생하며 정상적으로 작동하지 않았습니다.
아래는 코드입니다.
// 아이템을 지급하고 기존 데이터에 합산
private BackendReturnObject GrantAllRewards(JsonData playerPaidAsset, ChartItem purchasedItem, out string resultItems, Param purchasesLog)
{
// 서버 전송용
Param paidAssetParam = new Param();
// TransactionWriteV2 파라미터 객체 생성
List<TransactionValue> transactionList = new List<TransactionValue>();
// 유료 재화 테이블 삽입
if (playerPaidAsset == null)
{
JsonData newPaidItems = new JsonData();
newPaidItems = CreateNewItemData(purchasedItem);
paidAssetParam.Add(ItemsKey, newPaidItems.ToJson());
resultItems = newPaidItems.ToJson();
// paidAssetParam에 대한 Insert 작업을 트랜잭션에 추가
transactionList.Add(TransactionValue.SetInsert(PlayerPaidAssetTableName, paidAssetParam));
}
// 유료 재화 테이블 업데이트
else
{
JsonData playerPaidItems = GetPlayerPaidItems(playerPaidAsset); // asset 에서 items 뽑아오기
UpdateExistingItemData(playerPaidItems, purchasedItem); // 현재 items에서 구매한 아이템 추가
paidAssetParam.Add(ItemsKey, playerPaidItems.ToJson()); // 서버에 전송을 위해 param에 추가
resultItems = playerPaidItems.ToJson(); // 변경된 아이템 반환을 위해 asset에 반영
// paidAssetParam에 대한 Update 작업을 트랜잭션에 추가
transactionList.Add(TransactionValue.SetUpdate(PlayerPaidAssetTableName, new Where(), paidAssetParam));
}
// 구매 기록 남기기
transactionList.Add(TransactionValue.SetInsert(PlayerPurchasesTableName, purchasesLog));
// 두 작업(유료 아이템 지급/업데이트와 구매 내역 추가)을 하나의 트랜잭션으로 실행합니다.
return Backend.GameData.TransactionWriteV2(transactionList);
}
혹시 전체 코드가 필요하시면 아래 코드를 참고해주세요.
전체 코드
using Amazon.Lambda.Core;
using BackEnd;
using LitJson;
using System;
using System.Collections.Generic;
using System.IO;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
namespace BackendFunction
{
public class BFunc
{
private const string PlayerPaidAssetTableName = "PlayerPaidAsset";
private const string PlayerPurchasesTableName = "PlayerPurchaseHistory";
private const string ItemsKey = "Items";
private const string PerksKey = "Perks";
private const string IsSuccessKey = "IsSuccess";
private const string ErrorKey = "Error";
public Stream Function(Stream stream, ILambdaContext context)
{
var result = new Param();
result.Add(IsSuccessKey, false);
if (!InitializeBackend(ref stream, result))
{
return ReturnErrorObject(result, "Backend initialization failed");
}
if (!ValidateRequiredKeys(result))
{
return ReturnErrorObject(result, "Missing required keys: productId, receipt");
}
string productId = Backend.Content["productId"].ToString();
string receipt = Backend.Content["receipt"].ToString();
// 영수증 검증
if (!ValidateReceipt(receipt, result, out string errorLog))
{
return ReturnErrorObject(result, errorLog);
}
var purchasedItem = GetChartItem(productId);
if (purchasedItem == null)
{
return ReturnErrorObject(result, "Invalid productId");
}
var playerPaidAsset = GetPlayerPaidAsset();
Param purchasesLog = new Param
{
{ "ItemId", purchasedItem.itemId },
{ "Receipt", receipt }
};
var rewardResult = GrantAllRewards(playerPaidAsset, purchasedItem, out string resultItems, purchasesLog);
if (rewardResult == null || !rewardResult.IsSuccess())
{
return ReturnErrorObject(result, "Failed to grant reward: " + rewardResult.GetMessage());
}
result[IsSuccessKey] = true;
result.Add(ItemsKey, resultItems);
result.Add("Purchases", purchasesLog);
return Backend.StringToStream(result.GetJson());
}
private bool InitializeBackend(ref Stream stream, Param result)
{
try
{
Backend.Initialize(ref stream);
return true;
}
catch (Exception e)
{
ReturnErrorObject(result, "Initialize failed: " + e.ToString());
return false;
}
}
private bool ValidateRequiredKeys(Param result)
{
return Backend.HasKey("productId") && Backend.HasKey("receipt");
}
private bool ValidateReceipt(string receipt, Param result, out string errorLog)
{
errorLog = "";
try
{
var receiptJson = JsonMapper.ToObject(receipt);
var payload = receiptJson["Payload"];
var store = receiptJson["Store"];
if (payload == null || store == null)
{
errorLog = "Invalid receipt structure";
return false;
}
if (store.ToString().Equals("GooglePlay", StringComparison.OrdinalIgnoreCase))
{
bool isValid = GoogleReceipt(payload, out errorLog);
return isValid;
}
else if (store.ToString().Equals("fake", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return true;
}
catch (Exception ex)
{
errorLog = "Unexpected error occurred: " + ex.Message;
return false;
}
}
private bool AppleReceipt(JsonData payload)
{
return false;
}
private bool GoogleReceipt(JsonData payload, out string validationError)
{
validationError = "";
try
{
var json = JsonMapper.ToObject(JsonMapper.ToObject(payload.ToString())["json"].ToString());
if (json == null)
{
validationError = "json is null";
return false;
}
string purchaseToken = json["purchaseToken"].ToString();
string productId = json["productId"].ToString();
var validation = Backend.Receipt.IsValidateGooglePurchase(productId, purchaseToken, "receiptDescription");
if (!validation.IsSuccess())
{
validationError = validation.GetMessage();
return false;
}
return true;
}
catch (Exception ex)
{
validationError = "Google receipt validation failed: " + ex.Message;
return false;
}
}
// 아이템을 지급하고 기존 데이터에 합산
private BackendReturnObject GrantAllRewards(JsonData playerPaidAsset, ChartItem purchasedItem, out string resultItems, Param purchasesLog)
{
// 서버 전송용
Param paidAssetParam = new Param();
// TransactionWriteV2 파라미터 객체 생성
List<TransactionValue> transactionList = new List<TransactionValue>();
// 유료 재화 테이블 삽입
if (playerPaidAsset == null)
{
JsonData newPaidItems = new JsonData();
newPaidItems = CreateNewItemData(purchasedItem);
paidAssetParam.Add(ItemsKey, newPaidItems.ToJson());
resultItems = newPaidItems.ToJson();
// paidAssetParam에 대한 Insert 작업을 트랜잭션에 추가
transactionList.Add(TransactionValue.SetInsert(PlayerPaidAssetTableName, paidAssetParam));
}
// 유료 재화 테이블 업데이트
else
{
JsonData playerPaidItems = GetPlayerPaidItems(playerPaidAsset); // asset 에서 items 뽑아오기
UpdateExistingItemData(playerPaidItems, purchasedItem); // 현재 items에서 구매한 아이템 추가
paidAssetParam.Add(ItemsKey, playerPaidItems.ToJson()); // 서버에 전송을 위해 param에 추가
resultItems = playerPaidItems.ToJson(); // 변경된 아이템 반환을 위해 asset에 반영
// paidAssetParam에 대한 Update 작업을 트랜잭션에 추가
transactionList.Add(TransactionValue.SetUpdate(PlayerPaidAssetTableName, new Where(), paidAssetParam));
}
// 구매 기록 남기기
transactionList.Add(TransactionValue.SetInsert(PlayerPurchasesTableName, purchasesLog));
// 두 작업(유료 아이템 지급/업데이트와 구매 내역 추가)을 하나의 트랜잭션으로 실행합니다.
return Backend.GameData.TransactionWriteV2(transactionList);
}
// 새로운 아이템 데이터 생성
private JsonData CreateNewItemData(ChartItem item)
{
JsonData itemsArray = new JsonData();
for (int i = 0; i < item.itemIds.Length && i < item.itemCounts.Length; i++)
{
if (item.itemIds[i] != 0 && item.itemCounts[i] > 0)
{
JsonData newItem = new JsonData();
newItem["id"] = item.itemIds[i];
newItem["count"] = item.itemCounts[i];
itemsArray.Add(newItem);
}
}
return itemsArray;
}
// 기존 아이템 데이터 업데이트
private void UpdateExistingItemData(JsonData playerPaidItems, ChartItem item)
{
for (int i = 0; i < item.itemIds.Length && i < item.itemCounts.Length; i++)
{
if (item.itemIds[i] != 0 && item.itemCounts[i] > 0)
{
UpdateOrAddItem(playerPaidItems, item.itemIds[i], item.itemCounts[i]);
}
}
}
// 아이템 추가
private void UpdateOrAddItem(JsonData items, int id, int count)
{
foreach (JsonData item in items)
{
if (int.TryParse(item["id"].ToString(), out int itemId))
{
if (itemId == id)
{
if (int.TryParse(item["count"].ToString(), out int currentCount))
{
item["count"] = currentCount + count;
}
return;
}
}
}
JsonData newItem = new JsonData { ["id"] = id, ["count"] = count };
items.Add(newItem);
}
private JsonData GetPlayerPaidItems(JsonData asset)
{
if (asset == null || !asset.ContainsKey(ItemsKey))
{
Console.WriteLine("playerPaidAsset에 Items 키가 없거나 null입니다.");
return new JsonData(); // 빈 JsonData 반환
}
JsonData items = JsonMapper.ToObject(asset[ItemsKey].ToString());
if (!items.IsArray)
{
Console.WriteLine("Items 데이터가 배열 형식이 아닙니다.");
items = new JsonData();
items.SetJsonType(JsonType.Array);
}
return items;
}
private JsonData GetPlayerPaidAsset()
{
BackendReturnObject returnObject = Backend.GameData.GetMyData(PlayerPaidAssetTableName, new Where(), 1);
if (returnObject.IsSuccess() && returnObject.GetFlattenJSON()["rows"].Count > 0)
{
return returnObject.GetFlattenJSON()["rows"][0];
}
return null;
}
private ChartItem GetChartItem(string productId)
{
string selectedProbabilityFileId = "163901";
var bro = Backend.Chart.GetChartContents(selectedProbabilityFileId);
if (!bro.IsSuccess()) return null;
foreach (LitJson.JsonData data in bro.FlattenRows())
{
if (data["productId"].ToString() == productId)
{
ChartItem item = new ChartItem();
item.SetData(data);
return item;
}
}
return null;
}
static Stream ReturnErrorObject(Param result, string err)
{
result.Add(ErrorKey, err);
return Backend.StringToStream(result.GetJson());
}
}
// 뒤끝의 기본 제공 차트를 이용하면 만든 아이템입니다.
public class ChartItem
{
public int itemId;
public string productId;
public string image;
public string category;
public string name;
public string desc;
public string subdesc;
public string costtype;
public float priceUS;
public float priceKR;
public int shopstock;
public int restocktime;
public int[] itemIds = new int[8];
public int[] itemCounts = new int[8];
public void SetData(JsonData jsonData)
{
foreach (var field in GetType().GetFields())
{
if (jsonData.ContainsKey(field.Name))
{
if (field.FieldType == typeof(int))
field.SetValue(this, int.Parse(jsonData[field.Name].ToString()));
else if (field.FieldType == typeof(float))
field.SetValue(this, float.Parse(jsonData[field.Name].ToString()));
else
field.SetValue(this, jsonData[field.Name].ToString());
}
}
// item & itemcount 배열 처리
for (int i = 0; i < 8; i++)
{
string itemKey = $"item{i + 1}";
string valueKey = $"itemcount{i + 1}";
if (jsonData.ContainsKey(itemKey))
{
itemIds[i] = int.TryParse(jsonData[itemKey].ToString(), out int val) ? val : 0;
}
if (jsonData.ContainsKey(valueKey))
{
itemCounts[i] = int.TryParse(jsonData[valueKey].ToString(), out int val) ? val : 0;
}
}
}
public override string ToString()
{
return $"itemId : {itemId}\n" +
$"name : {name}\n" +
$"desc : {desc}\n" +
$"priceUS : {priceUS}, priceKR : {priceKR}\n" +
$"shopstock : {shopstock}, restocktime : {restocktime}\n" +
$"items : {string.Join(", ", itemIds)}\n" +
$"values : {string.Join(", ", itemCounts)}";
}
}
}