24. External service part 2: Billing¶
When implementing in-app purchases using payment platforms such as Apple’s AppStore or Google Play: 1) The game client uses that platform’s SDK for payment, then receives product data and a payment receipt; 2) This is sent to the server to verify the payment and check that the receipt has not already been used.
For this reason, it is burdensome for developers to support all the various available platforms. iFun Engine includes a billing verification feature.
24.1. iFun Biller¶
iFun Engine uses a program called iFun Biller as an agent to exclusively handle billing. The game server sends billing requests to iFun Biller, and iFun Biller checks each actual platform and records user data with successful payments.
Note
iFun Engine’s use of a separate billing agent doesn’t affect game servers and is so that billing can be added or changed.
With the use of an agent, it is easy to use billing services through the agent’s REST API on the game server and other servers as well.
Important
1.0.0-2466
보다 상위 버전 빌러에서는 기존 애플 영수증 테이블이 아닌
새로운 영수증 테이블을 사용합니다.
기존 테이블에서 새로운 테이블에 영수증 검증 데이터를 옮기기 위해서 아래
명령어를 입력합니다.
# mysql에 로그인합니다.
$ mysql -u {{mysql_id}} -p {{mysql_db_name}}
# 로그인 후 쿼리를 입력합니다.
> INSERT IGNORE INTO tb_receipt_apple_appstore2 SELECT * FROM tb_receipt_apple_appstore;
original_transaction_id
가 겹치거나 존재하지 않는 경우 해당 데이터는
새로운 테이블로 옮겨가지 못할 수 있습니다.
Important
1.0.0-3219 experimental
와 1.0.0-4322 stable
버전 부터는
기존 구글 플레이 영수증 테이블이 아닌 새로운 테이블을 사용하도록 변경되었습니다.
기존 데이터가 쌓여있는 tb_receipt_google_play
테이블을 백업하신 후에
삭제하시거나 아래 명령어를 사용해서 새로운 테이블로 이전 해 주시기 바랍니다.
주의: 쌓여있는 데이터가 많은 경우 작업에 오랜 시간이 소요될 수 있습니다.
# 쿼리 스크립트를 다운로드 합니다.
$ wget https://www.ifunfactory.com/engine/support/ifun-biller-migrate-db-scheme-for-google-api-v3.sql
# 스크립트를 실행합니다.
$ mysql -u {{mysql_id}} -p {{mysql_db_name}} < ifun-biller-migrate-db-scheme-for-google-api-v3.sql
24.1.1. Supported platforms¶
The following external payment platforms are currently supported. (We plan to expand to more platforms in future.)
Google Play
Apple AppStore
OneStore(SK TStore)
24.1.2. How to install¶
Tip
If Setting up iFun Biller (MANIFEST.json)’s use_biller
is set to false
, the game server runs in test mode and assumes all billing requests are bypassed. This setting is used during the development phase, and there is no need to install iFun Biller in these instances.
24.1.2.1. For Ubuntu¶
$ sudo apt-get update
$ sudo apt-get install funapi-biller1
24.1.2.2. For CentOS¶
$ sudo yum install funapi-biller1
$ sudo systemctl enable funapi-biller
24.1.3. How to execute¶
24.1.3.1. For Ubuntu 14.04 or CentOS 6¶
Open the file /etc/default/funapi-biller
and modify enabled=1
.
Run the following command:
$ sudo service funapi-biller start
24.1.3.2. For Ubuntu 16.04 or CentOS 7¶
Run the following command:
$ sudo systemctl enable funapi-biller
$ sudo systemctl start funapi-biller
24.1.4. How to check the operation¶
24.1.4.1. For Ubuntu 14.04 or CentOS 6¶
$ sudo service funapi-biller status
24.1.4.2. For Ubuntu 16.04 or CentOS 7¶
$ sudo systemctl status funapi-biller
24.1.4.3. Log file¶
Logs are output to /var/log/funapi/funapi-biller/
.
24.1.5. Setting up iFun Biller (MANIFEST.json)¶
Note
This is to set up iFun Biller itself. Settings for game servers using iFun Biller are in Game server-side billing parameters.
iFun Biller was, of course, also written with iFun Engine.
Therefore, you can naturally change settings in iFun Biller’s MANIFEST.json
.
iFun Biller’s MANIFEST.json is in /usr/share/funapi-biller/default/manifests/MANIFEST.json
.
Configure as follows.
protobuf_listen_port: Sets the TCP port number for the iFun Engine game server to communicate with iFun Biller. (type=uint16, default=12810)
http_listen_port: Sets the HTTP port number to communicate with iFun Biller through REST API. (type=uint16, default=12811)
bypass: Whether or not to bypass all billing. Runs iFun Biller in test mode. This has the same effect as setting
use_biller=false
on the iFun Engine game server. (type=bool, default=false)mysql_server_url: Enter the IP address and port of the DB server where used billing data was saved. (type=string, default=”tcp://127.0.0.1:3306”)
mysql_id: Enter the user ID of the DB where billing data will be saved.(type=string, default=”funapibiller1”)
mysql_pw: Enter the user password for the DB where billing data will be saved. (type=string, default=”qlffj1!!”)
mysql_db_name: Enter the name of the DB where billing data will be saved. (type=string, default=”funapi_biller1”)
mysql_db_connection_count: Connection pool size used for iFun Biller to communicate with the MySQL server. (type=uint16, default=1)
biller_use_db_auto_schema_generation: Sets whether or not to automatically create a DB table and procedure when iFun Biller is running without one. (type=bool, default=true)
export_db_schema_to_file: If a file path is given in this item and
biller_use_db_auto_schema_generation: false
, iFun Biller creates the required DB schema under that path and closes. (type=string, default=””)biller_use_one_store_test_server: 원스토어 결제 검증 시 원스토어 테스트용 호스트 사용 여부 설정. (type=bool, default=false)
Warning
2019년 12월 이후로 버전 3 이전 버전의 Google Play Developer API 지원이 종료됩니다. 이전 버전 API 의 지원이 종료되면 biller_use_google_play_developer_api_v3 옵션이 사라지고 항상 버전 3 API 을 사용하도록 수정됩니다.
Tip
When the agent is updated, the existing MANIFEST.json is overwritten. To prevent this, you can use an override file as described in Temporarily overriding MANIFEST.json.
/etc/funapi-biller/MANIFEST.override.json:
{
"override": {
"FunapiBillerServer": {
"mysql_server_url": "tcp://10.10.10.10:36060",
"mysql_id": "biller",
"mysql_pw": "biller",
...
},
...
}
}
24.2. Receipt Verification¶
You can use the function provided by iFun Engine (in this case, communication is through TCP) or independently use RESTful API.
24.2.1. Invoking the iFun Engine function¶
iFun Engine uses a single billing request interface regardless of platform type. Both asynchronous and synchronous functions are applied.
typedef function<void(const ReceiptValidationRequest &request,
const ReceiptValidationResponse &response,
const bool &error)> BillingResponseHandler
typedef function<void(const ReceiptValidationRequest &request,
const ReceiptValidationResponse &response,
const std::vector<string> &transaction_ids,
const bool &error)> BillingResponseHandler2
void ValidateReceipt(const ReceiptValidationRequest &request,
const BillingResponseHandler &handler)
void ValidateReceipt2(const ReceiptValidationRequest &request,
const BillingResponseHandler2 &handler)
bool ValidateReceiptSync(const ReceiptValidationRequest &request,
ReceiptValidationResponse *response)
bool ValidateReceiptSync2(const ReceiptValidationRequest &request,
ReceiptValidationResponse *response,
std::vector<string> *transaction_ids)
You can think of ReceiptValidationRequest
in the following struct.
ReceiptValidationRequest(const string &authentication_provider,
const string &authentication_id,
const Receipt &receipt)
Important
Be aware that the first and second parameters of ReceiptValidationRequest
relate to user authentication and not payment.
This is because iFun Biller records user data when authenticating payment.
The way a Receipt
is created differs by platform type.
The following utility functions are provided for each platform for this purpose.
Receipt MakeGooglePlayReceipt(const string &package_name,
const string &product_id,
const string &purchase_token)
Receipt MakeAppleAppStoreReceipt(const string &receipt_data,
const string &product_id,
int64_t quantity)
Receipt MakeOneStoreReceipt(const string &txid,
const string &appid,
const string &signdata,
const std::vector< string > &products,
bool use_one_store_test_server)
Receipt MakeOneStoreReceiptV5(const string &purchase_id,
const string &package_name,
const string &product_id)
Tip
See API documentation for more details.
24.2.1.1. Google Play billing¶
Let’s examine an example in which a Facebook user makes a payment while playing, the payment data is verified, and the paid Facebook user’s data is recorded synchronously and asynchronously.
24.2.1.1.1. Synchronous approach¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | void example(const string &facebook_id,
const string &google_package_name, const string &google_product_id,
const string &google_purchase_token) {
Receipt receipt = MakeGooglePlayReceipt(google_package_name, google_product_id,
google_purchase_token);
ReceiptValidationRequest request("Facebook", facebook_id, receipt);
ReceiptValidationResponse response;
if (not ValidateReceiptSync(request, &response)) {
LOG(ERROR) << "billing system error";
return;
}
if (response == kSuccess) {
// Give the item matched to product_id
// ...
return;
}
if (response == kFailWrongReceipt) {
LOG(WARNING) << "Maybe forged receipt";
} else if (response == kFailAlreadyProvisioned) {
LOG(WARNING) << "Already provisioned";
} else {
LOG(WARNING) << "Receipt validation error: " << response;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public void Example(
string facebook_id,
string google_package_name,
string google_product_id,
string google_purchase_token)
{
string receipt = Billing.MakeGooglePlayReceipt (
google_package_name, google_product_id, google_purchase_token);
Billing.ReceiptValidationRequest req =
new Billing.ReceiptValidationRequest (
"Facebook", facebook_id, receipt);
Billing.ReceiptValidationResponse rep;
if (!Billing.ValidateReceiptSync (req, out rep))
{
Log.Info ("billing system error");
return;
}
if (rep == Billing.ReceiptValidationResponse.kSuccess)
{
// Give the item matched to product_id
// ...
return;
}
if (rep == Billing.ReceiptValidationResponse.kFailWrongReceipt) {
Log.Warning ("Maybe forged receipt");
} else if (rep == Billing.ReceiptValidationResponse.kFailAlreadyProvisioned) {
Log.Warning ("Already provisioned");
} else {
Log.Warning ("Receipt validation error: {0}", rep);
}
}
|
24.2.1.1.2. Asynchronous approach¶
Like the synchronous method, the asynchronous method uses ValidateReceipt()
rather than ValidateReceiptSync()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | void OnReceiptValidated(
const ReceiptValidationRequest &request,
const ReceiptValidationResponse &response,
const bool &error) {
if (error) {
LOG(ERROR) << "billing system error";
return;
}
if (response == kSuccess) {
// Give the item matched to product_id
// ...
return;
}
if (response == kFailWrongReceipt) {
LOG(WARNING) << "Maybe forged receipt";
} else if (response == kFailAlreadyProvisioned) {
LOG(WARNING) << "Already provisioned";
} else {
LOG(WARNING) << "Receipt validation error: " << response;
}
}
void example(const string &facebook_id,
const string &google_package_name, const string &google_product_id,
const string &google_purchase_token) {
Receipt receipt = MakeGooglePlayReceipt(
google_package_name, google_product_id, google_purchase_token);
ReceiptValidationRequest request("Facebook", facebook_id, receipt);
BillingResponseHandler callback = OnReceiptValidated;
ValidateReceipt(request, callback);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | void OnReceiptValidated(
Billing.ReceiptValidationRequest request,
Billing.ReceiptValidationResponse response,
bool error)
{
if (error)
{
Log.Info ("billing system error");
return;
}
if (response == Billing.ReceiptValidationResponse.kSuccess)
{
// Give the item matched to product_id
// ...
return;
}
if (response == Billing.ReceiptValidationResponse.kFailWrongReceipt)
{
Log.Warning ("Maybe forged receipt");
}
else if (response == Billing.ReceiptValidationResponse.kFailAlreadyProvisioned)
{
Log.Warning ("Already provisioned");
}
else
{
Log.Warning ("Receipt validation error: {0}", response);
}
}
void Example(
string facebook_id,
string google_package_name,
string google_product_id,
string google_purchase_token)
{
string receipt = Billing.MakeGooglePlayReceipt (
google_package_name, google_product_id, google_purchase_token);
Billing.ReceiptValidationRequest req =
new Billing.ReceiptValidationRequest (
"Facebook", facebook_id, receipt);
Billing.ValidateReceipt (req, (
Billing.ReceiptValidationRequest request,
Billing.ReceiptValidationResponse response,
bool error) => {
OnReceiptValidated (request, response, error);
});
// 아래와 같이 delegate를 사용하여 콜백 함수를 등록 할 수도 있습니다.
// Billing.ResponseHandler handler = delegate (
// Billing.ReceiptValidationRequest request,
// Billing.ReceiptValidationResponse response,
// bool error) {
// ...
// };
// Billing.ValidateReceipt (req, handler);
}
|
24.2.1.2. Apple App Store billing¶
Let’s examine an example in which a Facebook user makes a payment while playing, the payment data is verified, and the paid Facebook user’s data is recorded synchronously and asynchronously.
24.2.1.2.1. Synchronous approach¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | void example(const string &facebook_id,
const string &apple_receipt_data, const string &apple_product_id,
int quantity) {
Receipt receipt = MakeAppleAppStoreReceipt(
apple_receipt_data, apple_product_id, quantity);
ReceiptValidationRequest request("Facebook", facebook_id, receipt);
ReceiptValidationResponse response;
if (not ValidateReceiptSync(request, &response)) {
LOG(ERROR) << "billing system error";
return;
}
if (response == kSuccess) {
// Give the item matched to product_id
// ...
return;
}
if (response == kFailWrongReceipt) {
LOG(WARNING) << "wrong receipt";
} else if (response == kFailAlreadyProvisioned) {
LOG(WARNING) << "already provisioned";
} else {
LOG(WARNING) << "receipt validation error: " << response;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public void Example(
string facebook_id,
string apple_receipt_data,
string apple_product_id,
int quantity)
{
string receipt = Billing.MakeAppleAppStoreReceipt (
apple_receipt_data, apple_product_id, quantity);
Billing.ReceiptValidationRequest req =
new Billing.ReceiptValidationRequest (
"Facebook", facebook_id, receipt);
Billing.ReceiptValidationResponse rep;
if (!Billing.ValidateReceiptSync (req, out rep))
{
Log.Info ("billing system error");
return;
}
if (rep == Billing.ReceiptValidationResponse.kSuccess)
{
// Give the item matched to product_id
// ...
return;
}
if (rep == Billing.ReceiptValidationResponse.kFailWrongReceipt) {
Log.Warning ("wrong receipt");
} else if (rep == Billing.ReceiptValidationResponse.kFailAlreadyProvisioned) {
Log.Warning ("already provisioned");
} else {
Log.Warning ("receipt validation error: {0}", rep);
}
}
|
24.2.1.2.2. Asynchronous approach¶
Like the synchronous method, the asynchronous method uses ValidateReceipt()
rather than ValidateReceiptSync()
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | void OnReceiptValidated(
const ReceiptValidationRequest &request,
const ReceiptValidationResponse &response,
const bool &error) {
if (error) {
LOG(ERROR) << "billing system error";
return;
}
if (response == kSuccess) {
// Give the item matched to product_id
// ...
return;
}
if (response == kFailWrongReceipt) {
LOG(WARNING) << "wrong receipt";
} else if (response == kFailAlreadyProvisioned) {
LOG(WARNING) << "already provisioned";
} else {
LOG(WARNING) << "receipt validation error: " << response;
}
}
void example(const string &facebook_id,
const string &apple_receipt_data, const string &apple_product_id,
int quantity) {
Receipt receipt = MakeAppleAppStoreReceipt(
apple_receipt_data, apple_product_id, quantity);
ReceiptValidationRequest request("Facebook", facebook_id, receipt);
BillingResponseHandler callback = bind(&OnReceiptValidated, _1, _2, _3);
ValidateReceipt(request, callback);
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | void OnReceiptValidated(
Billing.ReceiptValidationRequest request,
Billing.ReceiptValidationResponse response,
bool error)
{
if (error)
{
Log.Info ("billing system error");
return;
}
if (response == Billing.ReceiptValidationResponse.kSuccess)
{
// Give the item matched to product_id
// ...
return;
}
if (response == Billing.ReceiptValidationResponse.kFailWrongReceipt)
{
Log.Warning ("wrong receipt");
}
else if (response == Billing.ReceiptValidationResponse.kFailAlreadyProvisioned)
{
Log.Warning ("already provisioned");
}
else
{
Log.Warning ("receipt validation error: {0}", response);
}
}
void Example(
string facebook_id,
string apple_receipt_data,
string apple_product_id,
int quantity)
{
string receipt = Billing.MakeAppleAppStoreReceipt (
google_package_name, google_product_id, google_purchase_token);
Billing.ReceiptValidationRequest req =
new Billing.ReceiptValidationRequest (
"Facebook", facebook_id, receipt);
Billing.ValidateReceipt (req, (
Billing.ReceiptValidationRequest request,
Billing.ReceiptValidationResponse response,
bool error) => {
OnReceiptValidated (request, response, error);
});
// 아래와 같이 delegate를 사용하여 콜백 함수를 등록 할 수도 있습니다.
// Billing.ResponseHandler handler = delegate (
// Billing.ReceiptValidationRequest request,
// Billing.ReceiptValidationResponse response,
// bool error) {
// ...
// };
// Billing.ValidateReceipt (req, handler);
}
|
24.2.1.3. OneStore(TStore) billing¶
Let’s examine an example in which a Facebook user makes a payment while playing, the payment data is verified, and the paid Facebook user’s data is recorded synchronously and asynchronously.
24.2.1.3.1. Synchronous approach¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | void example(const string &facebook_id,
const string &one_store_purchase_id, const string &one_store_package_name,
const string &one_store_product_id) {
Receipt receipt = MakeOneStoreReceiptV5(
one_store_purchase_id, one_store_package_name, one_store_product_id);
// SDK V16(API V4) 영수증 검증 시에는 MakeOneStoreReceipt() 함수로 영수증을 생성해 검증해야 합니다.
// Receipt receipt = MakeOneStoreReceipt(one_store_txid, one_store_appid, one_store_signdata,
// products);
ReceiptValidationRequest request("Facebook", facebook_id, receipt);
ReceiptValidationResponse response;
if (not ValidateReceiptSync(request, &response)) {
LOG(ERROR) << "billing system error";
return;
}
if (response == kSuccess) {
// Give the item matched to product_id
// ...
return;
}
if (response == kFailWrongReceipt) {
LOG(WARNING) << "wrong receipt";
} else if (response == kFailAlreadyProvisioned) {
LOG(WARNING) << "already provisioned";
} else {
LOG(WARNING) << "receipt validation error: " << response;
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public void Example(string facebook_id,
string one_store_purchase_id, string one_store_package_name,
string one_store_product_id)
{
string receipt = Billing.MakeOneStoreReceiptV5 (
one_store_purchase_id, one_store_package_name, one_store_product_id);
// SDK V16(API V4) 영수증 검증 시에는 MakeOneStoreReceipt() 함수로 영수증을 생성해 검증해야 합니다.
// string receipt = Billing.MakeOneStoreReceipt (
// one_store_txid, one_store_appid, one_store_signdata, products, true);
Billing.ReceiptValidationRequest req =
new Billing.ReceiptValidationRequest (
"Facebook", facebook_id, receipt);
Billing.ReceiptValidationResponse rep;
if (!Billing.ValidateReceiptSync (req, out rep))
{
Log.Info ("billing system error");
return;
}
if (rep == Billing.ReceiptValidationResponse.kSuccess)
{
// Give the item matched to product_id
// ...
return;
}
if (rep == Billing.ReceiptValidationResponse.kFailWrongReceipt) {
Log.Warning ("wrong receipt");
} else if (rep == Billing.ReceiptValidationResponse.kFailAlreadyProvisioned) {
Log.Warning ("already provisioned");
} else {
Log.Warning ("receipt validation error: {0}", rep);
}
}
|
24.2.1.3.2. Asynchronous approach¶
Refer to the Google Play and Apple AppStore explanations and the synchronous method description above to use the same method.
24.2.2. How to use REST API¶
You can request REST API directly from iFun Biller without using iFun Engine. All parameters below are in JSON format and can be put in the HTTP POST body for requests.
24.2.2.1. Resetting platform connections¶
-
POST
/v1/authentication
¶ - Request JSON Object
biller_client_id (string) – Enter an appropriate value since authentication is not available in the current version.
(GooglePlay 서비스 계정을 이용하는 경우) google_play_private_key (string) – GooglePlay service account private key
(GooglePlay 서비스 계정을 이용하는 경우) google_play_client_email (string) – GooglePlay service account client email
(for GooglePlay) google_play_client_id (string) – GooglePlay client id
(for GooglePlay) google_play_client_secret (string) – GooglePlay client secret
(for GooglePlay) google_play_refresh_token (string) – GooglePlay refresh token
(for OneStore) one_store_client_id (string) – OneStore client id
(for OneStore) one_store_client_secret (string) – OneStore client_secret
- Response JSON Object
result (integer) –
0
: Succeeded2
: Parameter errorOther: Other error
2
: Already authenticated session3
: Incorrect ID4
: Incorrect authentication key5
: Incorrect service provider6
: Incorrect service provider (Google) authentication parameter2000
: Parameter errorOthers: Other error
description (string) – Error description
sessionid (string) – Included when sending session ID and billing requests.
24.2.2.2. Google Play billing¶
-
POST
/v1/validation/googleplay
¶ - Request JSON Object
sessionid (string) – Verified session ID
player_id (string) – Empty text string or player ID saved in the biller DB
player_service_provider (string) – Empty text string or player service provider saved in the biller DB
package_name (string) – Google Play package name, received using Google Play client SDK
product_id (string) – Google Play product ID, received using Google Play client SDK
purchase_token (string) – Google Play purchase token, received using Google Play client SDK
- Response JSON Object
result (integer) –
0
: Succeeded1000
: Already processed receipt1001
: Incorrect receipt1002
: Incorrect service provider1003
: Unauthorized service provider1004
: Canceled receipt1005
: Unauthorized session1007
: 보류중인 영수증2000
: Parameter errorOthers: Other error
24.2.2.3. Apple AppStore billing¶
-
POST
/v1/validation/appleappstore
¶ - Request JSON Object
sessionid (string) – Authenticated session ID
player_id (string) – Empty text string or player ID saved in the biller DB
player_service_provider (string) – Empty text string or player service provider saved in the biller DB
receipt_data (string) – App Store receipt data, received using Apple App Store client SDK
product_id (string) – App Store product ID, received using Apple App Store client SDK
quantity (string) – Quantity, received using Apple App Store client SDK
- Response JSON Object
result (integer) –
0
: Succeeded1000
: Already processed receipt1001
: Incorrect receipt1002
: Incorrect service provider1003
: Unauthorized service provider1004
: Canceled receipt1005
: Unauthorized session1006
: The receipt does not include product information2000
: Parameter errorOthers: Other error
24.2.2.4. OneStore(TStore) billing¶
-
POST
/v1/validation/tstore
¶ - Request JSON Object
sessionid (string) – Authenticated session ID
player_id (string) – Empty text string or player ID saved in biller DB
player_service_provider (string) – Empty text string or player server provider saved in biller DB
txid (string) – OneStore txid (unique value issued by IAP server on purchase)
appid (string) – OneStore appid (partially paid parent product ID)
signdata (string) – OneStore signdata (electronic payment data)
products (string[]) – OneStore product id(string) array
test (boolean) – If true, uses OneStore test (for development) IAP server
- Response JSON Object
result (integer) –
0
: Succeeded1000
: Already processed receipt1001
: Incorrect receipt1002
: Incorrect service provider1003
: Unauthorized service provider1005
: Unauthorized session2000
: Parameter errorOthers: Other error
24.2.2.5. OneStore(SDK17, API5) 영수증 검증¶
-
POST
/v1/validation/onestore
¶ - Request JSON Object
sessionid (string) – 인증을 통하여 얻은 session id
player_id (string) – 빈 문자열 또는 biller database 에 저장할 player id
player_service_provider (string) – 빈 문자열 또는 biller database 에 저장할 player service provider
purchase_id (string) – OneStore purchase id(구매시 IAP서버에서 발급되는 구매 ID)
package_name (string) – OneStore package name(API를 호출하는 앱의 패키지 네임)
product_id (string) – OneStore product id(In-App ID)
- Response JSON Object
result (integer) –
0
: 성공1000
: 이미 처리된 영수증1001
: 올바르지 않은 영수증1002
: 올바르지 않은 서비스 프로바이더1003
: 인증되지 않은 서비스 프로바이더1005
: 인증되지 않은 세션2000
: 파라미터 오류나머지: 기타 오류
24.2.2.6. Example: Apple AppStore billing¶
Resetting
$ curl -X POST --data '{"biller_client_id":"test"}' \
http://localhost:12811/v1/authentication
{"result": 0, "description": "", "sessionid": "f43fd097-7c64-49a9-8edb-efeb0969d05f"}
App Store billing
$ curl -X POST --data '{"sessionid":"f43fd097-7c64-49a9-8edb-efeb0969d05f", "quantity": 1, \
"product_id":"xxx", "receipt_data":"xxx"}' \
http://localhost:12811/v1/validation/appleappstore
{"result": 0}
24.3. 실제 구매 내역이 없는 영수증 처리¶
애플 앱스토어의 경우 결제 후 발급되는 영수증 안에 구매한 상품 정보가 비어있는 경우가 있습니다. 이 경우 빌러는 결제를 검증할 방법이 없기 때문에
kFailProductInformationNotFound(에러 코드: 1006)
를 응답하게 됩니다.
만약 빌러로 결제 검증 요청을 보냈을 때 응답 결과가
kFailProductInformationNotFound(에러 코드: 1006)
라면,
게임 서버는 이 내용을 클라이언트로 전달해서 클라이언트가 구매 내역을 포함한 영수증을 재발급하도록 해야 합니다.
Important
영수증에 실제 구매내역이 담겨있지 않다고해서, 올바르지 않은 영수증은 아닙니다. 상황에 따라서 영수증에는 구매내역이 담겨있지 않을 수 있습니다.
영수증을 재발급하는 방법에 대해서는 In-App Purchase Programming Guide - Refreshing the App Receipt 를 참고해주세요.
24.4. 트랜잭션 아이디 가져오기¶
결제 검증 요청 시에 ValidateReceipt2
혹은 ValidateReceiptSync2
함수를 이용하면 영수증에 포함된 구매 내역의 transaction id 를 가져올 수
있습니다.
게임 서버는 응답 받은 transaction id 를 클라이언트로 다시 돌려주어 클라이언트가 직접 구매한 정보와 맞는지 대조해볼 수 있습니다.
지원하는 결제 검증 플랫폼 마다 해당하는 transaction id 는 아래와 같습니다.
플랫폼 |
정보 |
애플 앱스토어 |
검증 된 영수증 내 |
원스토어(구 티스토어) |
결제 검증 요청 시에 사용된 |
Warning
GooglePlay 결제 검증 요청 시에는 transaction id 를 반환하지 않습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | void example(const string &facebook_id,
const string &apple_receipt_data,
const string &apple_product_id,
int quantity) {
Receipt receipt = MakeAppleAppStoreReceipt(
apple_receipt_data, apple_product_id, quantity);
std::vector<std::string> transaction_ids;
ReceiptValidationRequest request("Facebook", facebook_id, receipt);
ReceiptValidationResponse response;
if (not ValidateReceiptSync2(request, &response, &transaction_ids)) {
LOG(ERROR) << "billing system error";
return;
}
if (response == kSuccess) {
// Give the item matched to product_id
// ...
return;
}
if (response == kFailWrongReceipt) {
LOG(WARNING) << "wrong receipt";
} else if (response == kFailAlreadyProvisioned) {
LOG(WARNING) << "already provisioned";
} else {
LOG(WARNING) << "receipt validation error: " << response;
}
}
|
추후 지원 예정입니다.
비동기 방식은 동기화 방식과 유사하지만, ValidateReceiptSync2()
대신 ValidateReceipt2()
를 사용합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | void OnReceiptValidated(
const ReceiptValidationRequest &request,
const ReceiptValidationResponse &response,
const std::vector<string> &transaction_ids,
const bool &error) {
if (error) {
LOG(ERROR) << "billing system error";
return;
}
if (response == kSuccess) {
// Give the item matched to product_id
// ...
return;
}
if (response == kFailWrongReceipt) {
LOG(WARNING) << "wrong receipt";
} else if (response == kFailAlreadyProvisioned) {
LOG(WARNING) << "already provisioned";
} else {
LOG(WARNING) << "receipt validation error: " << response;
}
}
void example(const string &facebook_id,
const string &apple_receipt_data,
const string &apple_product_id,
int quantity) {
Receipt receipt = MakeAppleAppStoreReceipt(
apple_receipt_data, apple_product_id, quantity);
ReceiptValidationRequest request("Facebook", facebook_id, receipt);
BillingResponseHandler callback =
bind(&OnReceiptValidated, _1, _2, _3, _4);
ValidateReceipt2(request, callback);
}
|
추후 지원 예정입니다.
24.5. Game server-side billing parameters¶
Note
These parameters are iFun Engine game server values using iFun Biller. Please see Setting up iFun Biller (MANIFEST.json) for parameters for iFun Biller itself.
use_biller: Enables communication with iFun Biller agent. If
false
, all verification is bypassed and authentication is considered successful. (type=bool, default=false)remote_biller_ip_address: IP address for remote host using iFun Biller. (type=string, default=”0.0.0.0”)
remote_biller_port: Port number for remote host using iFun Biller. (type=uint64, default=0)
Additional required settings for each billing process
use_googleplay_service_account: 구글 플레이 서비스 계정 사용 유무. (type=bool, default=false)
googleplay_service_account_json_path: 구글 플레이 서비스 계정 json 파일 경로. (type=string, default=””)
googleplay_refresh_token: Google Play refresh token. (See`GooglePlay Authorization documentation`_ for details). (type=string, default=””)
googleplay_client_id: Google Play client ID. (See`GooglePlay Authorization documentation`_ for details). (type=string, default=””)
googleplay_client_secret: Google Play client secret. (See`GooglePlay Authorization documentation`_ for details). (type=string, default=””)
onestore_client_id: 원스토어의 클라이언트 아이디. (개발자 센터의 Apps - InApp정보 - 인증 및 라이선스 창에서 확인 가능). (type=string, default=””)
onestore_client_secret: 원스토어의 클라이언트 시크릿. (개발자 센터의 Apps - InApp정보 - 인증 및 라이선스 창에서 확인 가능). (type=string, default=””)
Note
구글 플레이 서비스 계정을 사용할 경우 서비스 계정에 재무 데이터 권한을 주어야 합니다. 권한 적용 시까지 24 시간 이상이 걸릴 수 있습니다.
Note
googleplay_service_account_json_path
에 상대 경로를 입력할 경우 {project-name}-source/misc_data/
을 기준으로 파일을 찾습니다.
{project-name}-source/misc_data/
에 있는 파일들은 서버와 함께 패키징 되므로 필요 시 해당 폴더에 서비스 계정 json 파일을 위치시킨 후
상대 경로를 입력해 주세요.
Warning
Previous access tokens are canceled when a refresh token is issued by Google Play. Note that refresh tokens cannot be used elsewhere.