예) API 통신에는 영문 코드로 통신하고 DB에 저장할 때는 맵핑 정의에 따른 숫자 포맷으로 저장해서 DB 인덱스 사이즈를 줄임
정합성 보장 등을 위해서 PK나 유니크 인덱스에 숫자 포맷 값으로 인덱스를 생성해두시는게 좋습니다.
영문 코드
이름
숫자포맷
재무(회계) 유료 여부
일본 자금결제법상 유료 여부
설명
PAID
유료
1
유료
유료
유저가 실제 돈을 주고 구입한 재화로 보너스는 제외
PAID_BONUS
유료의 보너스
2
무료
(정책 변경될 수 있음)
무료
유저가 구입한 유료 코인에 덤으로 주는 보너스(자금결제법에서는 무료 처리해도 무방, 공탁금 줄일 수 있음)
PAID_INVEN
유료인데 상품 보관함 용
7
유료
무료
상품 보관함에서 임시로 지급되는 유료 재화
PAID_INVEN_BONUS
유료의 보너스인데 상품 보관함 용
8
유료
무료
상품 보관함에서 임시로 지급되는 덤 코인, 보너스 코인
FREE_BUY_PRODUCT
무료 상품 구매
14
무료
무료
회계 및 자금결제법상 무료로 관리되는 '권리' 형태의 상품을 구입 후 충전할 때 사용
' - 예) 로그인하면 무료 코인을 주는 상품 구입 -> 게임에서는 내일 로그인하면 해당 타입으로 코인 충전(단, BM에 구매 즉시 지급되는 코인을 추가하고 해당 코인은 유료으로 처리)
FREE_AD
무료 광고
19
무료
무료
광고로 발생한 무료 재화.
' - 예)유니티 또는 구글 광고 SDK를 붙여서 BM을 준비할때. 광고를 보면 코인을 지급
FREE_OP
무료 운영
21
무료
무료
푸시 및 쿠폰, 로그인 보상 등의 이유로 내부 운영 목적상에서 충전된 코인
FREE_SVC
무료 서비스
25
무료
무료
게임내의 컨텐츠 요소로 발생한 충전된 무료 재화.
' - 예) 게임 던전 클리어시 무료 코인 지급
AUCTION_BIDDING
경매장 입찰용
31
무료
무료
경매장 입찰에 사용되는 임시 코인(낙찰 시점에 유/무료 처리 등 고민해야할 부분이 있음)
코인 차감 순서
유료 코인을 먼저 차감합니다. 이용약관 10조 5항 3호에도 관련 내용이 명시되어 있습니다.(2023-11-28 기준)
유료 코인을 먼저 차감하지 않는다면 게임 종료, 환불 등의 이슈가 발생하면 개발사 및 회사는 큰 손해를 입을 수 있습니다.
예) 운영 성격의 코인이 많이 지급되었는데 무료을 먼저 사용하게 된다면, 유저의 유료(유료) 코인 잔액은 차감이 안되고 계속 남아있어서 모두 환불 금액에 포함되어야하는 문제가 발생
이용약관 유료 먼저 차감한다는 내용 발췌
③ 회원이 구매한 아이템은 획득 방식에 따라 유/무료 속성으로 구분됩니다. 회원이 유/무료 속성의 아이템을 모두 보유하고 있고 일부를 사용하였을 경우, 유료 속성이 우선 차감되고, 그 이후 무료 속성이 차감됩니다. 유료 속성만 보유 시에 차감 순서는 선입선출 방식(먼저 획득한 순서대로 차감되는 방식)에 따릅니다. 단, 사정에 따라 다르게 적용될 수 있으며 이 경우 서비스 내 또는 커뮤니티, 홈페이지 등을 통해 공지합니다.
코인 관리 DB 설계(참고용) - RDBMS
아래 예제는 Mysql 기준으로 참고를 위해서 안내드리는 수준의 내용입니다.
해당 row(또는 column)외 필요하신 추가 내용을 적용하셔도 무방합니다.
다만, 꼭 관련 테이블들은 트랜잭션으로 묶어서 정합성에 문제가 생기지 않도록 해야합니다.
이때 외부 API 호출 등 응답이 느린 로직이 있다면 트랜잭션 lock time이 길어지고, 다른 유저가 해당 데이터를 변경해야하는 로직이 있다면 데드락에 빠질 수 있으니 주의
(권장)확장성을 고려해서 충전 타입을 행 형태로 사용한 설계
장점: 정책 또는 법규 등이 변경되어도 DB에 alter를 수행하지 않아도 되는 확장성의 장점
단점: 유저의 코인 잔액을 조회하거나 변경할 때 여러 row를 읽어야하며 동시성에 대해서 더 잘 고민해야 함
아래 샘플 내용은 참고를 위한 테이블 설계 및 쿼리입니다. 상황에 맞춰서 테이블명과 컬럼명 변경, 인덱스 조정 등이 진행되어도 괜찮습니다. 다만 증/차감 누락이나 중복 충전 등 크리티컬한 문제는 주의해주세요.
테이블 DDL 샘플
-- 유저의 코인 잔액 테이블(마스터 테이블)
CREATE TABLE `coin_balance`
(
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '플레이어ID(유저ID)',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`charge_type` TINYINT UNSIGNED NOT NULL COMMENT '코인의 충전타입 구분(유상,무상, 보너스 등)',
`balance` BIGINT NOT NULL COMMENT '코인의 현재 잔액(운영상의 이슈로 마이너스 잔액 지원 되어야함. 예. 버그 악용 유저의 재화 회수. 마이너스 통장 개념)',
`created_at` TIMESTAMP NOT NULL COMMENT '최초 저장 일시',
`updated_at` TIMESTAMP NOT NULL COMMENT '변경일시(최초에는 저장될때와 같은 일시로 저장)',
PRIMARY KEY (`player_id`, `coin_cd`, `charge_type`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 현재 코인(재화)의 잔액 관리 테이블(charge_type이 늘어날 수 있음을 감안하여 설계)'
;
-- 유저별 일자별 코인 증/차감 데이터(단, 해당일에 증/차감이 발생하지 않은 유저의 데이터는 존재하지 않음)
CREATE TABLE `coin_balance_daily`
(
`target_ymd` DATE NOT NULL COMMENT '대상일',
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '플레이어ID(유저ID)',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`charge_type` TINYINT UNSIGNED DEFAULT NULL COMMENT '코인의 충전타입 구분(유상,무상, 보너스 등)',
`gain_coin` BIGINT UNSIGNED DEFAULT NULL COMMENT '충전으로 획득한 코인',
`use_coin` BIGINT UNSIGNED DEFAULT NULL COMMENT '사용하게되어 소모된 코인',
`balance` BIGINT NOT NULL COMMENT '잔액',
`created_at` TIMESTAMP NOT NULL COMMENT '최초 저장일시',
`updated_at` TIMESTAMP NOT NULL COMMENT '변경일시(최초에는 저장될때와 같은 일시로 저장)',
PRIMARY KEY (`target_ymd`, `player_id`, `coin_cd`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 일자별 코인 증차감 통계 데이터(재화 증차감 발생할 때 같은 트랜잭션내에 같이 저장)'
;
CREATE TABLE `coin_balance_hist`
(
`hist_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK',
`req_id` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '요청ID(중복 요청 방어 목적 및 추적용)',
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '유저ID',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`charge_type` TINYINT UNSIGNED NOT NULL COMMENT '코인의 충전타입 구분(유상,무상, 보너스 등)',
`player_country` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '유저의 국가정보(국가별 법률 준수 목적으로 필요시 저장)',
`amount` INT NOT NULL COMMENT '증감되는 변경 양',
`balance` BIGINT NOT NULL COMMENT '재화 잔액',
`balance_change_type` ENUM ('PLUS','MINUS') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '잔액 변경 종류(증/차감)',
`reason` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '증감되는 사유',
`memo` VARCHAR(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '메모(필요시 저장)',
`hist_created_at` TIMESTAMP NOT NULL COMMENT '이력저장일시',
PRIMARY KEY (`hist_id`),
UNIQUE KEY `IDX_reqId_coinCd_chargeType` (`req_id`, `coin_cd`, `charge_type`),
KEY `IDX_playerId_histCreatedAt` (`player_id`, `hist_created_at`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 재화 잔액 변경 이력(N개의 charge type에 증/차감이 발생하면 N개의 이력이 한번에 insert될 수 있으며 이때의 req_id는 같은 값)'
;
참고용 쿼리 샘플
-- 유저의 특정 코인 총 잔액을 조회(charge_type을 제외하고 group by 후 sum)
SELECT coin_cd AS coinCd,
SUM(balance) AS balance
FROM coin_balance
WHERE player_id = 'playerId'
AND coin_cd = 'GEM'
GROUP BY coin_cd
ORDER BY NULL
;
-- 차감을 위한 잔액 조회 쿼리(charge_type에 따른 차감 순서를 정의하고 싶으면, DB쿼리의 charge_type 필드를 대상으로 소팅해서 조회 후 처리하거나 로직에서 소팅해서 처리)
-- FIELD기능을 꼭 사용할 필요 없으며, 사용하게되면 하드코딩 안하도록 주의
SELECT charge_type AS chargetType,
coin_cd AS coinCd,
balance AS balance
FROM coin_balance
WHERE player_id = 'playerId'
AND coin_cd = 'GEM'
ORDER BY FIELD(chargetType, 1, 2, 7, 8, 14, 19, 21, 25, 31) ASC
;
-- GEM이라는 코인에 대해서 유료 충전. DB로 동시성 이슈에 대한 방어 및 트랜잭션 처리를 명확하게 하기위해 upsert쿼리를 사용해서 insert(최초 추가) 또는 update(증가) 처리
INSERT INTO `coin_balance`
(`player_id`,
`coin_cd`,
`charge_type`,
`balance`,
`created_at`,
`updated_at`)
VALUES ('playerId',
'GEM',
7,
100,
NOW(),
NOW())
ON DUPLICATE KEY
UPDATE balance = balance + 100,
updated_at = NOW()
;
-- 차감
-- update 쿼리로 차감하되 동시성 이슈를 막기 위해서 현재 잔액 조회를 다른 쿼리에서 진행하지않고, 같은 쿼리에서 처리
예)
UPDATE
user_coin_balance_#{tableShardNo}
SET
balance = balance - (#{amount}),
블라블라
WHERE 블라블라
-----
기타 쿼리는 생략
단순화된 열 형태의 설계
여러 코인 충전 형태 중 필수인 일부 충전 방식에 대해서 컬럼 기반으로 관리
1개의 행(DB row)에 충전 타입에 따른 컬럼을 개별적으로 추가해서 잔액을 관리
유료, 무료, 유료 보너스 3가지 구분은 필수
아래 샘플 내용은 참고를 위한 테이블 설계 및 쿼리입니다. 상황에 맞춰서 테이블명과 컬럼명 변경, 인덱스 조정 등이 진행되어도 괜찮습니다. 다만 증/차감 누락이나 중복 충전 등 크리티컬한 문제는 주의해주세요.
개발은 쉽지만 확장성이 부족한 열(컬럼) 잔액을 관리하는 DB 설계
테이블 DDL 샘플
CREATE TABLE `coin_balance_column_type`
(
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '플레이어ID(유저ID)',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`balance_paid` BIGINT NOT NULL COMMENT '유료 코인의 현재 잔액',
`balance_paid_bonus` BIGINT NOT NULL COMMENT '유료 보너스 코인의 현재 잔액',
`balance_free` BIGINT NOT NULL COMMENT '무료 코인의 현재 잔액',
`created_at` TIMESTAMP NOT NULL COMMENT '최초 저장 일시',
`updated_at` TIMESTAMP NOT NULL COMMENT '변경일시(최초에는 저장될때와 같은 일시로 저장)',
PRIMARY KEY (`player_id`, `coin_cd`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 현재 코인(재화)의 잔액 관리 테이블(간소화 버전)'
;
CREATE TABLE `coin_balance_daily_column_type`
(
`target_ymd` DATE NOT NULL COMMENT '대상일',
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '플레이어ID(유저ID)',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`gain_coin` BIGINT UNSIGNED DEFAULT NULL COMMENT '충전으로 획득한 코인',
`use_coin` BIGINT UNSIGNED DEFAULT NULL COMMENT '사용하게되어 소모된 코인',
`balance_paid` BIGINT NOT NULL COMMENT '유료 코인의 현재 잔액',
`balance_paid_bonus` BIGINT NOT NULL COMMENT '유료 보너스 코인의 현재 잔액',
`balance_free` BIGINT NOT NULL COMMENT '무료 코인의 현재 잔액',
`created_at` TIMESTAMP NOT NULL COMMENT '최초 저장일시',
`updated_at` TIMESTAMP NOT NULL COMMENT '변경일시(최초에는 저장될때와 같은 일시로 저장)',
PRIMARY KEY (`target_ymd`, `player_id`, `coin_cd`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 일자별 코인 증차감 통계 데이터(재화 증차감 발생할 때 같은 트랜잭션내에 같이 저장)'
;
CREATE TABLE `coin_balance_hist_column_type`
(
`hist_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PK',
`req_id` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '요청ID(중복 요청 방어 목적 및 추적용)',
`player_id` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '유저ID',
`coin_cd` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '코인 종류(GEM 등)',
`player_country` VARCHAR(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '유저의 국가정보(국가별 법률 준수 목적으로 필요시 저장)',
`amount` INT NOT NULL COMMENT '증감되는 변경 양',
`balance_paid` BIGINT NOT NULL COMMENT '유료 코인의 현재 잔액',
`balance_paid_bonus` BIGINT NOT NULL COMMENT '유료 보너스 코인의 현재 잔액',
`balance_free` BIGINT NOT NULL COMMENT '무료 코인의 현재 잔액',
`balance_change_type` ENUM ('PLUS','MINUS') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '잔액 변경 종류(증/차감)',
`reason` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '증감되는 사유',
`memo` VARCHAR(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '메모(필요시 저장)',
`hist_created_at` TIMESTAMP NOT NULL COMMENT '이력저장일시',
PRIMARY KEY (`hist_id`),
UNIQUE KEY `IDX_reqId_coinCd` (`req_id`, `coin_cd`),
KEY `IDX_playerId_histCreatedAt` (`player_id`, `hist_created_at`)
) ENGINE = INNODB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT ='유저의 재화 잔액 변경 이력'
참고용 쿼리 샘플
row 방식 쿼리를 참고하되 charge_type에 대해서 행을 열로 변경한 부분을 고려해서 수정해서 사용
코인 관리 DB 설계(참고용) - NoSQL
단순화된 열 형태의 설계
여러 코인 충전 형태 중 필수인 일부 충전 방식에 대해서 필드 기반으로 관리
1개의 행(Document)에 충전 타입에 따른 필드를 개별적으로 추가해서 잔액을 관리
유료, 무료, 유료 보너스 3가지 구분은 필수
샘플 내의 내용은 참고를 위한 내용입니다. 상황에 맞춰서 컬렉션(필드 및 인덱스 등)은 변경되어도 괜찮습니다. 다만 증/차감 누락이나 중복 충전 등 크리티컬한 문제는 주의해주세요.
샘플 컬렉션 정의 - coin_balance
유저의 현재 코인(재화)의 잔액을 관리하는 마스터 컬렉션
재화의 증/차감 변경이 발생할 때마다 데이터가 insert 또는 update되어 저장되는 컬렉션
코인 코드에 따라서 유저는 N개의 Document를 보유할 수 있음
<필드 정의>
필드
데이터 타입
설명
예
_id
object
6698b68bd8cd923a387c3572
player_id
string
플레이어ID(유저ID)
test_user
coin_cd
string
코인 종류(GEM 등)
GEM
balance_paid
number
유료 코인의 현재 잔액
5
balance_paid_bonus
number
유료 보너스 코인의 현재 잔액
3
balance_free
number
무료 코인의 현재 잔액
0
created_at
object
최초 저장 일시
ISODate("2024-07-18T06:43:44.099Z")
updated_at
object
변경일시(최초에는 저장될때와 같은 일시로 저장)
ISODate("2024-07-18T06:43:44.099Z")
상세 참고사항
- 유저의 코인 잔액 컬렉션(마스터)
1. 컬렉션 생성
db.createCollection('coin_balance');
2. 인덱스 생성(유니크)
-- 유니크 인덱스를 생성하여 데이터 정합성 문제를 방지
db.coin_balance.createIndex( { player_id:1, coin_cd:1}, { unique: true } );
3. 테스트용 커맨드
-- 신규 충전 insert
db.coin_balance.insert( {"player_id":"test_user", "coin_cd":"GEM", "balance_paid":5, "balance_paid_bonus":3, "balance_free":0, "created_at": new Date(), "updated_at": new Date()} );
db.coin_balance.insert( {"player_id":"test_user", "coin_cd":"GOLD", "balance_paid":5, "balance_paid_bonus":3, "balance_free":0, "created_at": new Date(), "updated_at": new Date()} );
-- find결과(필수 필드들 참고)
> db.coin_balance.find().pretty()
{
"_id" : ObjectId("6698b9a0d8cd923a387c3574"),
"player_id" : "test_user",
"coin_cd" : "GEM",
"balance_paid" : 5,
"balance_paid_bonus" : 3,
"balance_free" : 0,
"created_at" : ISODate("2024-07-18T06:43:44.099Z"),
"updated_at" : ISODate("2024-07-18T06:43:44.099Z")
}
;
-- 데이터 타입 확인을 위한 커맨드
var doc = db.coin_balance.findOne({ _id: ObjectId("6698b9a0d8cd923a387c3574") });
for (var key in doc) {
print(key + ": " + typeof doc[key]);
}
;
-- 데이터 타입 확인 결과 내용 샘플
_id: object
player_id: string
coin_cd: string
balance_paid: number
balance_paid_bonus: number
balance_free: number
created_at: object
updated_at: object
컬렉션 정의 - coin_balance_hist
유저의 재화 잔액 변경 이력을 저장하는 컬렉션
재화의 증/차감 변경이 발생할 때마다 데이터가 insert되어 저장되는 컬렉션(Update가 발생하면 안됨)
<필드 정의>
필드
데이터 타입
설명
예
_id
object
6698b68bd8cd923a387c3572
req_id
string
요청ID(중복 요청 방어 목적 및 추적용)
uuid-player_id-balance-plus-01
player_id
string
플레이어ID(유저ID)
test_user
coin_cd
string
코인 종류(GEM 등)
GEM
player_country
string
유저의 국가정보(국가별 법률 준수 목적으로 필요시 저장)
KR
amount
number
증감되는 변경 양
5
balance_paid
number
유료 코인의 현재 잔액
5
balance_paid_bonus
number
유료 보너스 코인의 현재 잔액
3
balance_free
number
무료 코인의 현재 잔액
0
balance_change_type
string
잔액 변경 종류(증/차감)
PLUS 또는 MINUS
PLUS
reason
string
증감되는 사유
plus_test
memo
string
메모(필요시 저장)
memo_test
hist_created_at
object
이력저장일시
ISODate("2024-07-18T07:54:53.388Z")
<상세 참고 사항>
-- 유저의 재화 잔액 변경 이력을 저장하는 컬렉션(이력 컬렉션)
1. 컬렉션 생성
db.createCollection('coin_balance_hist');
2. 인덱스 생성
-- req_id를 이용한 유니크 인덱스로 데이터 오류 방지
db.coin_balance_hist.createIndex( { req_id:1, coin_cd:1}, { unique: true } );
db.coin_balance_hist.createIndex( { player_id:1, hist_created_at:-1});
3. 테스트용 커맨드
-- 신규 이력 insert
db.coin_balance_hist.insert(
{
"req_id":"uuid-player_id-balance-plus-01",
"player_id":"test_user",
"coin_cd":"GEM",
"player_country":"KR",
"amount":5,
"balance_paid":5,
"balance_paid_bonus":3,
"balance_free":0,
"balance_change_type":"PLUS",
"reason":"plus_test",
"memo":"memo_test",
"hist_created_at":new Date()
}
);
-- find결과(필수 필드들 참고)
> db.coin_balance_hist.find().pretty()
{
"_id" : ObjectId("6698ca4dd8cd923a387c3578"),
"req_id" : "uuid-player_id-balance-plus-01",
"player_id" : "test_user",
"coin_cd" : "GEM",
"player_country" : "KR",
"amount" : 5,
"balance_paid" : 5,
"balance_paid_bonus" : 3,
"balance_free" : 0,
"balance_change_type" : "PLUS",
"reason" : "plus_test",
"memo" : "memo_test",
"hist_created_at" : ISODate("2024-07-18T07:54:53.388Z")
}
-- 데이터 타입 확인을 위한 커맨드
var doc = db.coin_balance_hist.findOne({ _id: ObjectId("6698ca4dd8cd923a387c3578") });
for (var key in doc) {
print(key + ": " + typeof doc[key]);
}
-- 데이터 타입 확인 결과 내용 샘플
_id: object
req_id: string
player_id: string
coin_cd: string
player_country: string
amount: number
balance_paid: number
balance_paid_bonus: number
balance_free: number
balance_change_type: string
reason: string
memo: string
hist_created_at: object