Documentation index for AI agents

GS2-Money 트랜잭션 액션

검증/소비/입수 각 트랜잭션 액션의 사양

액션의 조합과 동시 실행

모든 서비스에 공통되는 전제는 트랜잭션 액션의 조합에 정리되어 있습니다. 먼저 그쪽을 읽어 주십시오. 이 절의 나머지는 GS2-Money 고유의 내용입니다.

GS2-Money의 트랜잭션 액션은 2종류의 대상을 다룹니다.

  • 월렛 : 네임스페이스·사용자·슬롯의 조합으로 결정됩니다. 업데이트할 때마다 전체가 다시 쓰입니다.
  • 영수증 : 영수증에서 추출한 거래 ID로 결정됩니다. 1건의 과금을 기록합니다. 하나의 트랜잭션에 사용자당 1건만 놓을 수 있습니다.
조작같은 행을 겹쳤을 때중첩 너머다른 대상이 되는 경계
월렛에의 가산
DepositByUserId
같은 가격이면 통합되어 수가 합산된다. 가격이 다른 가산은 실패한다실패한다네임스페이스·사용자·슬롯
월렛에서의 감산
WithdrawByUserId
통합되어 수가 합산된다실패한다네임스페이스·사용자·슬롯
영수증의 기록
RecordReceipt
실패한다같은 영수증이면 실패한다네임스페이스·사용자(행 자체는 영수증 단위)
영수증 기록의 취소
RevertRecordReceipt
실패한다실패하지 않는다네임스페이스·사용자(행 자체는 영수증 단위)

같은 사용자에 대한 영수증 기록끼리, 취소끼리는 영수증이 다르더라도 발행 시점에 오류가 됩니다. 영수증은 거래 ID를 거쳐 행에 도달하는데, 거래 ID를 추출하는 방법이 스토어마다 다르기 때문에 겉보기에 다른 영수증이 같은 과금을 가리키는 경우가 있습니다. 추측으로 합치지 않고, 하나의 트랜잭션에 1건만 놓아 주십시오. 영수증을 기록하는 진열 상품을 수량 2 이상으로 구입한 경우에도 액션이 개수만큼 쌓이므로 여기에 해당합니다.

마지막 열은 검사가 선을 긋는 위치이지 행의 위치가 아닙니다. 영수증은 지금도 1건마다 별도의 행을 가지므로, 안쪽에서 도착한 것은 행을 기준으로 판정됩니다. 같은 영수증의 기록끼리는 충돌하고, 다른 영수증의 기록끼리는 충돌하지 않으며, 취소끼리는 1건으로 통합됩니다(같은 행을 2번 지우는 것은 1번 지우는 것과 같기 때문입니다).

가산과 감산은 행이 다르고 경계가 같습니다. 같은 월렛에 대한 가산과 감산을 하나의 트랜잭션에 넣을 수는 없습니다.

가산은 수뿐만 아니라 가격도 가지며, 가격이 다른 가산끼리는 합산할 수 없습니다. 그 때문에 별개의 액션인 채로 남아 월렛 위에서 충돌합니다. 트랜잭션을 나누거나, 가격별로 다른 트랜잭션에서 한 번씩 가산해 주십시오. GS2-Money2에 이 제한이 없는 것은, 그쪽의 입금이 단일 금액이 아니라 리스트를 가지고 있기 때문입니다.

잔액은 통합 후의 합계에 대해 판정됩니다. 단독으로는 충분한 감산이라도 다른 감산과 합쳐지면 거부될 수 있습니다.

유상 잔액에서만 빼는 감산과 일반 감산은 같은 슬롯에 나열할 수 없습니다. 어느 잔액에서 뺄지가 어긋난 채 합산하면 의도하지 않은 잔액에서 빠지므로, paidOnly가 다른 감산을 같은 월렛에 지정하면 발행 시에 오류가 됩니다.

월렛과 영수증은 다른 대상입니다. 슬롯이 다르면 다른 월렛이므로, 하나의 트랜잭션에서 여러 슬롯에 가산하는 것은 문제없습니다.

중첩된 트랜잭션에 주의

월렛은 업데이트할 때마다 전체가 다시 쓰이므로, 안쪽과 바깥쪽 양쪽에서 건드리면 트랜잭션이 실패합니다. 나란히 작성하면 통합되었을 감산끼리라도, 한쪽이 안쪽에서 도착하면 통과하지 않습니다.

통화는 비용으로 사용되는 경우가 많으므로 이 상황은 일어나기 쉽습니다. 바깥쪽에서 월렛으로 지불하는 구매와, 안쪽에서도 월렛으로 지불하는 경품이 공존하면 통과하지 않습니다.

제한을 회피하고 싶은 경우

월렛에의 가산과 영수증 기록의 취소는 획득 액션, 감산과 영수증의 기록은 소비 액션입니다. 가산끼리의 충돌은 acquireActionUseJobQueue를 활성화하면 해소되지만, 가산과 감산의 공존은 enableAtomicCommit을 비활성화하지 않으면 해소되지 않습니다. 통화를 다루는 이상, 이 판단은 신중하게 해 주십시오.

영수증의 기록에도, 기록의 취소에도 우회 수단이 없습니다. 검사는 어느 설정보다도 먼저 실행되며, 영수증은 1건의 과금이라 같은 과금을 이중으로 기록·취소해서는 안 되므로, 활성화해도 달라지지 않습니다. 트랜잭션을 나누고, 영수증을 기록하는 진열 상품은 1개씩 구입해 주십시오.

동시 실행과 재시도

월렛에 대한 업데이트는 리비전 대조를 수반하므로, 같은 월렛을 여러 요청이 동시에 업데이트하면 나중에 확정된 쪽이 충돌(409)이 됩니다. 요청 내용에 문제가 있는 것은 아니므로, 재시도하면 성공합니다. 재시도 시에는 최신 잔액으로 다시 판정됩니다.


Consume Action

소비 액션

Gs2Money:WithdrawByUserId

사용자 ID를 지정하여 지갑에서 잔액 소비

지정한 사용자의 지갑에서 지정한 양의 통화를 소비합니다.
paidOnly가 false인 경우, 무료 통화가 먼저 소비되고 그다음 유료 통화가 소비됩니다.

수량 지정 가능한 액션: 예

반전 가능한 액션: 예

타입활성화 조건필수기본값값 제한설명
namespaceNamestring
~ 128자네임스페이스 이름
네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.
userIdstring
~ 128자사용자ID
#{userId}로 설정하면 로그인 중인 사용자ID로 치환됩니다.
slotint
0 ~ 100000000슬롯 번호
플랫폼이나 컨텍스트별로 지갑 잔액을 분리하기 위한 식별자입니다.
슬롯을 달리하면 서로 다른 유료 통화 풀을 관리할 수 있습니다(예: iOS 구매는 슬롯 0, Android는 슬롯 1).
무료 통화는 네임스페이스의 shareFree 설정에 따라 모든 슬롯 간에 공유할 수도 있습니다.
countint
1 ~ 2147483646소비할 유료 통화 수량
paidOnlyboolfalse유료 통화만을 대상으로 할지 여부
timeOffsetTokenstring~ 1024자타임 오프셋 토큰
{
    "action": "Gs2Money:WithdrawByUserId",
    "request": {
        "namespaceName": "[string]네임스페이스 이름",
        "userId": "[string]사용자ID",
        "slot": "[int]슬롯 번호",
        "count": "[int]소비할 유료 통화 수량",
        "paidOnly": "[bool]유료 통화만을 대상으로 할지 여부",
        "timeOffsetToken": "[string]타임 오프셋 토큰"
    }
}
action: Gs2Money:WithdrawByUserId
request:
  namespaceName: "[string]네임스페이스 이름"
  userId: "[string]사용자ID"
  slot: "[int]슬롯 번호"
  count: "[int]소비할 유료 통화 수량"
  paidOnly: "[bool]유료 통화만을 대상으로 할지 여부"
  timeOffsetToken: "[string]타임 오프셋 토큰"
transaction.service("money").consume.withdraw_by_user_id({
    namespaceName="[string]네임스페이스 이름",
    userId="[string]사용자ID",
    slot="[int]슬롯 번호",
    count="[int]소비할 유료 통화 수량",
    paidOnly="[bool]유료 통화만을 대상으로 할지 여부",
    timeOffsetToken="[string]타임 오프셋 토큰",
})

Gs2Money:RecordReceipt

영수증 기록

스토어 플랫폼(Apple App Store / Google Play)으로부터의 구매 영수증을 기록·검증합니다.
부정 방지를 위해 플랫폼 서버에 대해 영수증이 검증됩니다. 재전송 공격 방지를 위해 중복된 영수증은 거부됩니다.

수량 지정 가능한 액션: 아니오

반전 가능한 액션: 아니오

타입활성화 조건필수기본값값 제한설명
namespaceNamestring
~ 128자네임스페이스 이름
네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.
userIdstring
~ 128자사용자ID
#{userId}로 설정하면 로그인 중인 사용자ID로 치환됩니다.
contentsIdstring
~ 1024자스토어 플랫폼에서 판매되는 콘텐츠 ID
receiptstring
~ 524288자영수증
timeOffsetTokenstring~ 1024자타임 오프셋 토큰
{
    "action": "Gs2Money:RecordReceipt",
    "request": {
        "namespaceName": "[string]네임스페이스 이름",
        "userId": "[string]사용자ID",
        "contentsId": "[string]스토어 플랫폼에서 판매되는 콘텐츠 ID",
        "receipt": "[string]영수증",
        "timeOffsetToken": "[string]타임 오프셋 토큰"
    }
}
action: Gs2Money:RecordReceipt
request:
  namespaceName: "[string]네임스페이스 이름"
  userId: "[string]사용자ID"
  contentsId: "[string]스토어 플랫폼에서 판매되는 콘텐츠 ID"
  receipt: "[string]영수증"
  timeOffsetToken: "[string]타임 오프셋 토큰"
transaction.service("money").consume.record_receipt({
    namespaceName="[string]네임스페이스 이름",
    userId="[string]사용자ID",
    contentsId="[string]스토어 플랫폼에서 판매되는 콘텐츠 ID",
    receipt="[string]영수증",
    timeOffsetToken="[string]타임 오프셋 토큰",
})

Acquire Action

입수 액션

Gs2Money:DepositByUserId

사용자 ID를 지정하여 지갑 잔액에 가산

지정한 사용자의 지갑에 지정한 양의 통화를 추가합니다.
가격이 0인 경우는 무료 통화로, 그 외의 경우는 유료 통화로 취급됩니다.

수량 지정 가능한 액션: 예

반전 가능한 액션: 예

타입활성화 조건필수기본값값 제한설명
namespaceNamestring
~ 128자네임스페이스 이름
네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.
userIdstring
~ 128자사용자ID
#{userId}로 설정하면 로그인 중인 사용자ID로 치환됩니다.
slotint
0 ~ 100000000슬롯 번호
플랫폼이나 컨텍스트별로 지갑 잔액을 분리하기 위한 식별자입니다.
슬롯을 달리하면 서로 다른 유료 통화 풀을 관리할 수 있습니다(예: iOS 구매는 슬롯 0, Android는 슬롯 1).
무료 통화는 네임스페이스의 shareFree 설정에 따라 모든 슬롯 간에 공유할 수도 있습니다.
pricefloat
0 ~ 100000.0구매 가격
countint
1 ~ 2147483646지급할 유료 통화 수량
timeOffsetTokenstring~ 1024자타임 오프셋 토큰
{
    "action": "Gs2Money:DepositByUserId",
    "request": {
        "namespaceName": "[string]네임스페이스 이름",
        "userId": "[string]사용자ID",
        "slot": "[int]슬롯 번호",
        "price": "[float]구매 가격",
        "count": "[int]지급할 유료 통화 수량",
        "timeOffsetToken": "[string]타임 오프셋 토큰"
    }
}
action: Gs2Money:DepositByUserId
request:
  namespaceName: "[string]네임스페이스 이름"
  userId: "[string]사용자ID"
  slot: "[int]슬롯 번호"
  price: "[float]구매 가격"
  count: "[int]지급할 유료 통화 수량"
  timeOffsetToken: "[string]타임 오프셋 토큰"
transaction.service("money").acquire.deposit_by_user_id({
    namespaceName="[string]네임스페이스 이름",
    userId="[string]사용자ID",
    slot="[int]슬롯 번호",
    price="[float]구매 가격",
    count="[int]지급할 유료 통화 수량",
    timeOffsetToken="[string]타임 오프셋 토큰",
})

Gs2Money:RevertRecordReceipt

사용자 ID를 지정하여 영수증 기록 삭제

트랜잭션 ID를 추출하여 해당하는 레코드를 삭제함으로써, 이전에 기록된 영수증을 취소합니다.
스토어 플랫폼으로부터의 환불이나 차지백 처리에 사용합니다.

수량 지정 가능한 액션: 아니오

반전 가능한 액션: 아니오

타입활성화 조건필수기본값값 제한설명
namespaceNamestring
~ 128자네임스페이스 이름
네임스페이스 고유의 이름입니다. 영숫자 및 -(하이픈) _(언더스코어) .(마침표)로 지정합니다.
userIdstring
~ 128자사용자ID
#{userId}로 설정하면 로그인 중인 사용자ID로 치환됩니다.
receiptstring
~ 524288자영수증
timeOffsetTokenstring~ 1024자타임 오프셋 토큰
{
    "action": "Gs2Money:RevertRecordReceipt",
    "request": {
        "namespaceName": "[string]네임스페이스 이름",
        "userId": "[string]사용자ID",
        "receipt": "[string]영수증",
        "timeOffsetToken": "[string]타임 오프셋 토큰"
    }
}
action: Gs2Money:RevertRecordReceipt
request:
  namespaceName: "[string]네임스페이스 이름"
  userId: "[string]사용자ID"
  receipt: "[string]영수증"
  timeOffsetToken: "[string]타임 오프셋 토큰"
transaction.service("money").acquire.revert_record_receipt({
    namespaceName="[string]네임스페이스 이름",
    userId="[string]사용자ID",
    receipt="[string]영수증",
    timeOffsetToken="[string]타임 오프셋 토큰",
})