인앱 구매 인증
# 수익 창출 모델 및 도구의 범위는 광고 지원, 소액 결제, 프리미엄, 일회성 구매 및 이들 사이에서 매우 다양합니다. 이러한 솔루션에서 핵심 도구는 인앱 구매(IAP)입니다. IAP를 통해 잠금 해제, 게임 내 소모품, 프리미엄 액세스 구독 등을 위한 단일 구매가 가능합니다.
가장 일반적인 인앱 구매 구현을 쉽게 공격할 수 있는 경우가 매우 많습니다. 일반적으로 다음과 같은 사항에 중점을 둡니다.
성공을 나타내는 클라이언트 가짜 구매 응답 제공 유효한 구매 응답을 여러 번 재생 구매 응답을 다른 클라이언트와 공유하여 여러 플레이어가 단일 구매를 통해 보상 받기 인앱 구매를 위해서는 신뢰할 수 있는 출처가 필요합니다. Nakama는 구매 및 구매 내역을 확인하고 추적하여 다음과 같은 상당한 취약점 및 문제점을 해결합니다.
인앱 구매 인증은 플랫폼에 관계없이 Apple , Google 및 Huawei 구매에 사용할 수 있습니다. 단일 제품 및 구독 구매가 모두 지원됩니다.
Unity IAP 를 통한 Apple 및 Google 구매도 지원됩니다.
인증 결과 해석
# 인증 결과에는 검증된 구매 목록이 포함됩니다.
Apple은 하나의 영수증에 여러 구매를 포함할 수 있으므로 결과 목록의 인증된 각 구매에는 인증된 구매와, 이전에 확인되었는지 여부를 나타내는 boolean
값만 포함됩니다. 이전에 Nakama에 의해 구매가 이미 인증된 경우 “이전 확인” 값은 개발자가 새로운 구매를 구별하고 재생 공격으로부터 보호할 수 있도록 true
이(가) 됩니다.
Google 및 Huawei의 경우, 각 인증은 단일 구매에 해당하며 인증 응답 목록에 포함되며 위와 같이 “이전 확인” 값도 있습니다.
인증된 각 구매에는 개발자가 어떤 이유로든 필요로 하는 경우 공급자 인증 응답의 페이로드도 포함됩니다.
구매/영수증이 유효하지 않거나 어떤 이유로든 인증하지 못하거나 공급자에게 연결할 수 없으면 오류가 반환됩니다.
Apple
# Nakama는 iOS에서 제품 및 구독에 대한 구매 인증을 지원합니다.
Apple 구매 영수증은 인증을 위해 Apple로 전송됩니다. Apple에서 제안한 대로 Production 및 Sandbox 서버는 모두 영수증 인증에 사용됩니다.
설정
# 앱 스토어에 대한 영수증을 인증하기 위해 Nakama는 앱의 공유 암호를 요구합니다.
앱의 인앱 구매 관리 섹션의 App Store Connect 에서 공유 암호 설정:Apple App Store Connect
Nakama 구성에 사용할 공유 암호를 기록해 두십시오.Apple App Store Connect 공유 암호
Nakama의 iap.apple.shared_password
구성 플래그 값을 위에서 생성한 공유 암호 값으로 설정합니다.
구매 인증
# Nakama는 iOS 7 이상 영수증 인증만 지원합니다.
Apple 영수증에는 여러 구매가 포함될 수 있으며 Nakama는 모든 구매를 인증하지만 구매 기록은 개별적으로 저장합니다.
Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/purchase/apple \
--user 'defaultkey:' \
--data '{" receipt":" base64_encoded_receipt_data"}'
Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local function validate_receipt ( receipt )
local result = client.validate_purchase_apple ( receipt )
if result.error then
print ( result.message )
return
end
pprint ( result )
end
-- Use https://defold.com/extension-iap/
iap.set_listener ( function ( self , transaction , error )
if not error then
validate_receipt ( transaction.receipt )
end
end )
iap.buy ( "com.defold.nakama.goldbars-10" )
Client
1
2
3
4
5
6
string appleReceipt = "<receipt>" ;
var response = await client . ValidatePurchaseAppleAsync ( session , appleReceipt );
foreach ( var validatedPurchase in response . ValidatedPurchases )
{
System . Console . WriteLine ( "Validated purchase: " + validatedPurchase );
}
Client
1
2
3
4
5
const appleReceipt = "<receipt>" ;
const result = await client . validatePurchaseApple ( session , appleReceipt );
result . validatedPurchases . forEach ( validatedPurchase => {
console . info ( "Validated purchase:" , validatedPurchase )
});
Client
1
2
3
4
5
6
7
var apple_receipt = "<receipt>"
var result : NakamaAPI . ApiValidatePurchaseResponse = yield ( client . validate_purchase_apple_async ( session , apple_receipt ), "completed" )
if result . is_exception ():
print ( "An error occurred: %s " % result )
return
for p in result . validated_purchase :
print ( "Validated purchase: %s " % p . validated_purchase )
Client
1
2
3
4
5
6
7
8
POST /v2/iap/purchase/apple
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
"receipt" : "base64_encoded_Apple_receipt_payload"
}
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
제공되는 런타임 구매 인증 기능 은 기능 참조 페이지를 참조하세요.
구독 인증
# Client
1
2
3
4
string appleReceipt = "<receipt>" ;
var response = await client . ValidateSubscriptionAppleAsync ( session , appleReceipt );
System . Console . WriteLine ( "Validated subscription: " + response . ValidatedSubscription );
Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/subscription/apple \
--user 'defaultkey:' \
--data '{" receipt":" base64_encoded_receipt_data"}'
Client
1
2
3
4
5
6
7
8
POST /v2/iap/subscription/apple
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
"receipt" : "base64_encoded_Apple_receipt_payload"
}
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
Code snippet for this language gdscript has not been found. Please choose another language to show equivalent examples.
Code snippet for this language JavaScript/Cocos2d-js has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
제공된 런타임 구독 인증 기능 에 대한 기능 참조 페이지를 참조하세요.
Google
# Nakama는 Android에서 제품 및 구독에 대한 구매 인증을 지원합니다.
설정
# Play 스토어에 대한 영수증을 인증하기 위해 Nakama에서 Google 서비스 계정 ClientEmail
및 PrivateKey
을(를) 요청합니다. 이런 값을 구하려면:
Google API Console 에서 서비스 계정 설정:서비스 계정 생성
서비스 계정을 만들었으면 키를 생성해야 합니다:키 생성
이 키를 JSON 파일로 다운로드합니다:JSON 키 생성
이 JSON 파일을 열고 ClientEmail
및 PrivateKey
값을 추출합니다.
해당 값을 iap.google.client_email
및 iap.google.private_key
에 대한 각각의 Nakama 구성 값으로 설정합니다.
마지막으로 구매 인증 API에 대한 액세스 권한을 Nakama에 부여해야 합니다. Google Play 개발자 콘솔 에서 설정 > API 액세스 로 이동합니다:API 액세스 생성
이전 단계에서 만든 서비스 계정이 위에 나열되어야 합니다. 서비스 계정에 대한 액세스 권한을 부여하여 API에 액세스하고 서비스 계정에 가시성 , 재무 데이터 보기 및 주문 관리 에 대한 액세스 권한을 부여해야 합니다. 이러한 권한은 Nakama가 Google Play에 대한 영수증 인증에 필요합니다.액세스 부여
사용자 및 권한 으로 이동하여 서비스 계정이 올바르게 설정되었는지 확인합니다.액세스 권한이 있는 사용자 나열
구매 인증
# Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/purchase/google \
--user 'defaultkey:' \
--data '{" purchase":" json_encoded_purchase_data"}'
Client
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local function validate_receipt ( receipt )
local result = client.validate_purchase_google ( receipt )
if result.error then
print ( result.message )
return
end
pprint ( result )
end
-- Use https://defold.com/extension-iap/
iap.set_listener ( function ( self , transaction , error )
if not error then
validate_receipt ( transaction.receipt )
end
end )
iap.buy ( "com.defold.nakama.goldbars-10" )
Client
1
2
3
4
5
6
string googleReceipt = "<receipt>" ;
var response = await client . ValidatePurchaseGoogleAsync ( session , googleReceipt );
foreach ( var validatedPurchase in response . ValidatedPurchases )
{
System . Console . WriteLine ( "Validated purchase: " + validatedPurchase );
}
Client
1
2
3
4
5
const googleReceipt = "<receipt>" ;
const result = await client . validatePurchaseGoogle ( session , googleReceipt );
result . validatedPurchases . forEach ( validatedPurchase => {
console . info ( "Validated purchase:" , validatedPurchase )
});
Client
1
2
3
4
5
6
7
var google_receipt = "<receipt>"
var result : NakamaAPI . ApiValidatePurchaseResponse = yield ( client . validate_purchase_google_async ( session , google_receipt ), "completed" )
if result . is_exception ():
print ( "An error occurred: %s " % result )
return
for p in result . validated_purchase :
print ( "Validated purchase: %s " % p . validated_purchase )
Client
1
2
3
4
5
6
7
8
POST /v2/iap/purchase/google
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
"purchase" :"json_encoded_purchase_data"
}
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
제공되는 런타임 구매 인증 기능 은 기능 참조 페이지를 참조하세요.
구독 인증
# Client
1
2
3
4
string googleReceipt = "<receipt>" ;
var response = await client . ValidateSubscriptionGoogleAsync ( session , googleReceipt );
System . Console . WriteLine ( "Validated subscription: " + response . ValidatedSubscription );
Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/subscription/google \
--user 'defaultkey:' \
--data '{" purchase":" json_encoded_purchase_data"}'
Client
1
2
3
4
5
6
7
8
POST /v2/iap/subscription/google
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
"purchase" :"json_encoded_purchase_data"
}
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
Code snippet for this language gdscript has not been found. Please choose another language to show equivalent examples.
Code snippet for this language JavaScript/Cocos2d-js has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
제공된 런타임 구독 인증 기능 에 대한 기능 참조 페이지를 참조하세요.
Huawei
# Nakama는 IAP 인증 서비스를 기준으로 Huawei 구매를 인증합니다. Huawei에서 제안한 바와 같이 Huawei 서비스에 연락하기 전에 제공된 서명에 대해 구매 데이터의 유효성도 확인됩니다. 어떤 이유로든 데이터가 유효하지 않은 경우 Huawei의 인증 서비스에서 인증하기 전에 구매는 거부됩니다.
구매 인증
# Client
1
2
3
curl "http://127.0.0.1:7350/v2/iap/purchase/huawei \
--user 'defaultkey:' \
--data '{" purchase":" json_encoded_purchase_data"," signature":" purchase_data_signature"}'
Client
1
-- Huawei purchases are not yet supported by https://defold.com/extension-iap/
Client
1
2
3
4
5
6
7
string huaweiReceipt = "<receipt>" ;
string huaweiSignature = "<signature>" ;
var response = await client . ValidatePurchaseHuaweiAsync ( session , huaweiReceipt , huaweiSignature );
foreach ( var validatedPurchase in response . ValidatedPurchases )
{
System . Console . WriteLine ( "Validated purchase: " + validatedPurchase );
}
Client
1
2
3
4
5
6
const huaweiReceipt = "<receipt>" ;
const huaweiSignature = "<signature>" ;
const result = await client . validatePurchaseHuawei ( session , huaweiReceipt , huaweiSignature );
result . validatedPurchases . forEach ( validatedPurchase => {
console . info ( "Validated purchase:" , validatedPurchase )
});
Client
1
2
3
4
5
6
7
8
var huawei_receipt = "<receipt>"
var huawei_signature = "<signature>"
var result : NakamaAPI . ApiValidatePurchaseResponse = yield ( client . validate_purchase_huawei_async ( session , huawei_receipt , huawei_signature ), "completed" )
if result . is_exception ():
print ( "An error occurred: %s " % result )
return
for p in result . validated_purchase :
print ( "Validated purchase: %s " % p . validated_purchase )
Client
1
2
3
4
5
6
7
8
9
POST /v2/iap/purchase/huawei
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
{
"purchase" :"json_encoded_purchase_data" ,
"signature" :"purchase_data_signature"
}
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
제공되는 런타임 구매 인증 기능 은 기능 참조 페이지를 참조하세요.
Unity IAP
# Unity IAP 는 Apple 및 Google을 비롯한 가장 인기 있는 앱 스토어와의 통합을 지원합니다.
Unity IAP 구매 영수증에는 다음 정보가 포함되어 있습니다.
구매 인증
#
Code snippet for this language Lua has not been found. Please choose another language to show equivalent examples.
Code snippet for this language TypeScript has not been found. Please choose another language to show equivalent examples.
Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type unityIAP struct {
Payload string `json:"Payload"`
Store string `json:"Store"`
TransactionID string `json:"TransactionID"`
}
switch unityIAP . Store {
case "GooglePlay" :
validatedReceipt , err = nk . PurchaseValidateGoogle ( ctx , userID , wrapper . Payload )
case "AppleAppStore" :
validatedReceipt , err = nk . PurchaseValidateApple ( ctx , userID , wrapper . Payload )
default :
logger . Warn ( "Unrecognised store type in Unity IAP." )
return ErrBadInput
}
if err != nil {
if err == runtime . ErrPurchaseReceiptAlreadySeen {
logger . WithField ( "err" , err ). Warn ( "Receipt replay attack." )
} else {
logger . WithField ( "err" , err ). Error ( "Receipt validation error." )
}
return ErrBadInput
}
구독
# 구매 및 구독 인증 외에도 특정 제품 ID로 구독하거나 지정된 사용자의 모든 구독을 나열할 수도 있습니다.
구독하기
# Client
1
2
3
4
string productId = "<productId>" ;
var response = await client . GetSubscriptionAsync ( session , productId );
System . Console . WriteLine ( "Subscription: " + response );
Client
1
2
curl "http://127.0.0.1:7350/v2/iap/subscription/{productId} \
-H 'Authorization: Bearer <session token>'
Client
1
2
3
4
5
GET /v2/iap/subscription/{ productId}
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
Code snippet for this language gdscript has not been found. Please choose another language to show equivalent examples.
Code snippet for this language JavaScript/Cocos2d-js has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
제공된 런타임 구독 인증 기능 에 대한 기능 참조 페이지를 참조하세요.
사용자 구독 나열
# Client
1
2
3
4
5
6
7
string userId = "<userId>" ;
var response = await client . ListSubscriptionsAsync ( userId );
foreach ( var validatedSubscription in response . ValidatedSubscriptions )
{
System . Console . WriteLine ( "Subscription: " + validatedSubscription );
}
Client
1
2
curl "http://127.0.0.1:7350/v2/iap/subscription/limit=<limit>&cursor=<cursor>" \
-H 'Authorization: Bearer <session token>'
Client
1
2
3
4
5
GET /v2/iap/subscription/limit= <limit>& cursor = <cursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
Code snippet for this language C++/Unreal/Cocos2d-x has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Java/Android has not been found. Please choose another language to show equivalent examples.
Code snippet for this language gdscript has not been found. Please choose another language to show equivalent examples.
Code snippet for this language JavaScript/Cocos2d-js has not been found. Please choose another language to show equivalent examples.
Code snippet for this language Defold has not been found. Please choose another language to show equivalent examples.
제공된 런타임 구독 인증 기능 에 대한 기능 참조 페이지를 참조하세요.