23. 外部服务支持Part 2: 支付验证

通过Google Play或Apple AppStore等支付平台实现In-App Purchase的过程为, 1)先在游戏客户端运用相应平台的SDK结算后获取商品信息和结算收据, 2)然后将其传输给服务器,验证收据的有效性,并确认其是否是已经使用过的收据。

因此,对游戏服务器开发人员来说,支持所有平台是一件相当繁琐的事情。 iFun引擎包含了多种支付平台的收据验证功能。

23.1. iFun Biller

iFun引擎使用名为 iFun Biller 的程序,作为专门负责支付验证工作的agent。 游戏服务器向iFun Biller发送支付验证请求,iFun Biller对不同平台进行验证,将成功的支付验证和用户信息共同记录下来。

Note

iFun引擎使用单独的支付验证agent是为了不影响游戏服务器,以能够添加新的结算或更改结算。

同时,使用agent还可以使非游戏服务器的其他服务器通过agent的REST API来使用支付相关服务,十分便利。

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 가 겹치거나 존재하지 않는 경우 해당 데이터는 새로운 테이블로 옮겨가지 못할 수 있습니다.

1.0.0-3215 experimental 보다 상위 버전 빌러에서는 기존 구글 플레이 영수증 테이블이 아닌 새로운 테이블을 사용하도록 변경되었습니다. 아래 명령어로 데이터를 옮길 수 있습니다.

# mysql에 로그인합니다.
$ mysql -u {{mysql_id}} -p {{mysql_db_name}}

# 로그인 후 쿼리를 입력합니다.
> INSERT IGNORE INTO tb_receipt_google_play_v3 \
  (biller_client_id, local_account, service_provider, package_name, \
  product_id, purchase_token_hash, purchase_token, kind, purchase_time, \
  timezone, purchase_state, consumption_state, developer_payload, \
  order_id, acknowledgement_state, purchase_type, timestamp) \
  SELECT biller_client_id, local_account, service_provider, package_name, \
  product_id, purchase_token_hash, purchase_token, kind, purchase_time, \
  timezone, purchase_state, consumption_state, developer_payload, \
  "" as order_id, -1 as acknowledgement_state, -1 as purchase_type, timestamp \
  FROM tb_receipt_google_play;

23.1.1. 支持平台

目前支持如下所示外部支付平台(后续将持续扩充可支持的外部平台。)

  • Google Play

  • Apple AppStore

  • OneStore(SK TStore)

23.1.2. 安装方法

Tip

iFun Biller设置方法(MANIFEST.json)use_biller 设置为 false ,游戏服务器就会以批准所有支付验证请求的测试模式运行。该设置在无需实际认证的开发初期阶段十分有用,此时无需安装iFun Biller。

23.1.2.1. Ubuntu环境

$ sudo apt-get update
$ sudo apt-get install funapi-biller1

23.1.2.2. CentOS环境

$ sudo yum install funapi-biller1
$ sudo systemctl enable funapi-biller

23.1.3. 运行方法

23.1.3.1. Ubuntu 14.04或Centos 6环境下

打开 /etc/default/funapi-biller 文件,并修改为 enabled=1

然后执行以下命令。

$ sudo service funapi-biller start

23.1.3.2. Ubuntu 16.04或Centos 7环境下

执行以下命令。

$ sudo systemctl enable funapi-biller
$ sudo systemctl start funapi-biller

23.1.4. 查看任务

23.1.4.1. Ubuntu 14.04或Centos 6环境下

$ sudo service funapi-biller status

23.1.4.2. Ubuntu 16.04或Centos 7环境下

$ sudo systemctl status funapi-biller

23.1.4.3. Log文件

/var/log/funapi/funapi-biller/ 中生成日志文件。

23.1.5. iFun Biller设置方法(MANIFEST.json)

Note

该项目是iFun Biller自身的设置内容。使用iFun Biller设置游戏服务器时,请参考 游戏服务器端支付验证设置参数

iFun Biller也是通过iFun引擎编写的程序。 因此,iFun Biller也可以通过 MANIFEST.json 更改设置值。 iFun Biller的MANIFEST.json位于 /usr/share/funapi-biller/default/manifests/MANIFEST.json

可以指定如下设置值。

  • protobuf_listen_port: 指定iFun引擎游戏服务器与iFun Biller通信时的TCP port编号。(type=uint16, default=12810)

  • http_listen_port: 指定通过REST API与iFun Biller通信时的HTTP port编号。(type=uint16, default=12811)

  • bypass: 是否无条件通过支付验证。使iFun Biller以测试模式运行。与在iFun引擎游戏服务器中指定为 use_biller=false 的效果相同。(type=bool, default=false)

  • mysql_server_url: 输入保存所使用的收据信息的DB服务器IP地址和端口。(type=string, default=”tcp://127.0.0.1:3306”)

  • mysql_id: 输入保存所使用的收据信息的DB用户ID。(type=string, default=”funapibiller1”)

  • mysql_pw: 输入保存所使用的收据信息的DB用户密码。(type=string, default=”qlffj1!!”)

  • mysql_db_name: 输入保存所使用的收据信息的DB名。(type=string, default=”funapi_biller1”)

  • mysql_db_connection_count: 用于使iFun Biller和MySQL Server进行通信的connection pool size。(type=uint16, default=1)

  • biller_use_db_auto_schema_generation: iFun Biller驱动时,若DB数据表和PROCEDURE不存在,是否自动生成。(type=bool, default=true)

  • export_db_schema_to_file: 当在该项目中指定文件路径,且为 biller_use_db_auto_schema_generation: false 时,将在相应路径下创建iFun Biller所需的DB schema,然后关闭iFun Biller。(type=string, default=””)

Tip

若更新代理,原来的MANIFEST.json将被覆盖。为了防止该问题,可以按照 临时重写MANIFEST.json 中的介绍,使用override文件。

/etc/funapi-biller/MANIFEST.override.json:

{
  "override": {
    "FunapiBillerServer": {
      "mysql_server_url": "tcp://10.10.10.10:36060",
      "mysql_id": "biller",
      "mysql_pw": "biller",
      ...
    },
    ...
  }
}

23.2. 支付验证

有使用iFun引擎提供的函数的方法(此时通过TCP通信),以及独立运用RESTful API的方法。

23.2.1. 在iFun引擎中调用函数的方法

iFun引擎使用单一的支付认证请求接口,与平台种类无关。 运用以下两个函数。分别是非同步、同步函数。

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)

此时, ReceiptValidationRequest 具有如下struct。

ReceiptValidationRequest(const string &authentication_provider,
                         const string &authentication_id,
                         const Receipt &receipt)

Important

ReceiptValidationRequest 的第一个和第二个参数是与用户ID认证有关的内容,而不是支付认证,敬请注意。 这是因为iFun Biller在进行支付验证时,会同时记录用户信息。

而且各个种类平台生成 Receipt 的方式是不同的。 为此,针对各个平台提供了以下Utility函数。

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)

Tip

更具体的内容请参考 API文件

23.2.1.1. Google Play支付验证

这里我们将Facebook用户在游戏中支付后对相应结算收据内容进行验证并对实施结算的Facebook 用户信息进行记录的过程分成同步方式和非同步方式两种方式进行分析。

23.2.1.1.1. 同步方式
 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);
  }
}
23.2.1.1.2. 非同步方式

非同步方式与同步方式类似,但使用 ValidateReceipt() ,而不是 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);
}

23.2.1.2. 苹果商店支付验证

这里我们将Facebook用户在游戏中支付后对相应结算收据内容进行验证并对实施结算的Facebook 用户信息进行记录的过程分成同步方式和非同步方式两种方式进行分析。

23.2.1.2.1. 同步方式
 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);
  }
}
23.2.1.2.2. 非同步方式

非同步方式与同步方式类似,但使用 ValidateReceipt() ,而不是 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);
}

23.2.1.3. TStore支付验证

这里我们将Facebook用户在游戏中支付后对相应结算收据内容进行验证并对实施结算的Facebook 用户信息进行记录的过程分成同步方式和非同步方式两种方式进行分析。

23.2.1.3.1. 同步方式
 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
void example(const string &facebook_id,
             const string &one_store_txid, const string &one_store_appid,
             const string &one_store_signdata, const string &one_store_product) {
  std::vector<string> products;
  products.push_back(one_store_product);
  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
public void Example(string facebook_id,
                    string one_store_txid, string one_store_appid,
                    string one_store_signdata, string one_store_product)
{
  List<string> products = new List<string>();
  products.Add(one_store_product);

  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);
  }
}
23.2.1.3.2. 非同步方式

非同步方式可参考Google Play、Apple AppStore的说明,以及上述同步方式的说明, 按照相同的方式编写即可。

23.2.2. 利用REST API的方法

可以不使用iFun引擎,直接向iFun Biller请求REST API。 根据如下内容,以JSON format将各个参数包含在HTTP POST的body中,进行请求即可。

23.2.2.1. 平台连接初始化

POST /v1/authentication
Request JSON Object
  • biller_client_id (string) – 由于当前版本不实施验证,所以输入适当的值即可。

  • (为GooglePlay时) google_play_client_id (string) – GooglePlay client id

  • string(为GooglePlay时)google_play_client_secret – GooglePlay client secret

  • (为GooglePlay时) google_play_refresh_token (string) – GooglePlay refresh token

Response JSON Object
  • result (integer) –

    • 0: 成功

    • 2: 参数错误

    • 其他: 其他错误

    • 2: 已经认证的会话

    • 3: 错误的id

    • 4: 错误的认证密钥

    • 5: 错误的服务提供商

    • 6: 错误的服务提供商(google)认证参数

    • 2000: 参数错误

    • 其他: 其他错误

  • description (string) – 错误说明

  • sessionid (string) – session id,在发送收据验证请求时包含在内一同发送。

23.2.2.2. Google Play收据验证

POST /v1/validation/googleplay
Request JSON Object
  • sessionid (string) – 通过认证获取到的session id

  • player_id (string) – 空字符串或保存在biller database中的player id

  • player_service_provider (string) – 空字符串或保存在biller database中的player service provider

  • package_name (string) – google play package name,通过google play client sdk获取

  • product_id (string) – google play product id,通过google play client sdk获取

  • purchase_token (string) – google play purchase token,通过google play client sdk获取

Response JSON Object
  • result (integer) –

    • 0: 成功

    • 1000: 已处理的收据

    • 1001: 错误的收据

    • 1002: 错误的服务提供商

    • 1003: 未认证的服务提供商

    • 1004: 已取消的收据

    • 1005: 未认证的会话

    • 1006: The receipt does not include product information

    • 1007: 보류중인 영수증

    • 2000: 参数错误

    • 其他: 其他错误

23.2.2.3. Apple AppStore收据验证

POST /v1/validation/appleappstore
Request JSON Object
  • sessionid (string) – 通过认证获取到的session id

  • player_id (string) – 空字符串或保存在biller database中的player id

  • player_service_provider (string) – 空字符串或保存在biller database中的player service provider

  • receipt_data (string) – appstore receipt data,通过apple appstore client sdk获取

  • product_id (string) – appstore product id,通过apple appstore client sdk获取

  • quantity (string) – quantity,通过apple appstore client sdk获取

Response JSON Object
  • result (integer) –

    • 0: 成功

    • 1000: 已处理的收据

    • 1001: 错误的收据

    • 1002: 错误的服务提供商

    • 1003: 未认证的服务提供商

    • 1004: 已取消的收据

    • 1005: 未认证的会话

    • 1006: 구매 내역이 담기지 않은 영수증

    • 2000: 参数错误

    • 其他: 其他错误

23.2.2.4. TStore收据验证

POST /v1/validation/tstore
Request JSON Object
  • sessionid (string) – 通过认证获取到的session id

  • player_id (string) – 空字符串或保存在biller database中的player id

  • player_service_provider (string) – 空字符串或保存在biller database中的player service provider

  • txid (string) – OneStore txid(购买时由IAP颁发的Unique值)

  • appid (string) – OneStore appid(部分付费的商品ID)

  • signdata (string) – OneStore signdata(电子收据数据)

  • products (string[]) – OneStore product id(string) array

  • test (boolean) – 如为true,则使用OneStore测试(开发用) IAP服务器

Response JSON Object
  • result (integer) –

    • 0: 成功

    • 1000: 已处理的收据

    • 1001: 错误的收据

    • 1002: 错误的服务提供商

    • 1003: 未认证的服务提供商

    • 1005: 未认证的会话

    • 2000: 参数错误

    • 其他: 其他错误

23.2.2.5. 示例: Apple AppStore支付验证

初始化

$ curl -X POST --data '{"biller_client_id":"test"}' \
       http://localhost:12811/v1/authentication
  {"result": 0, "description": "", "sessionid": "f43fd097-7c64-49a9-8edb-efeb0969d05f"}

Appstore收据验证

$ 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}

23.3. 실제 구매 내역이 없는 영수증 처리

애플 앱스토어의 경우 결제 후 발급되는 영수증 안에 구매한 상품 정보가 비어있는 경우가 있습니다. 이 경우 빌러는 결제를 검증할 방법이 없기 때문에 kFailProductInformationNotFound(에러 코드: 1006) 를 응답하게 됩니다.

만약 빌러로 결제 검증 요청을 보냈을 때 응답 결과가 kFailProductInformationNotFound(에러 코드: 1006) 라면, 게임 서버는 이 내용을 클라이언트로 전달해서 클라이언트가 구매 내역을 포함한 영수증을 재발급하도록 해야 합니다.

Important

영수증에 실제 구매내역이 담겨있지 않다고해서, 올바르지 않은 영수증은 아닙니다. 상황에 따라서 영수증에는 구매내역이 담겨있지 않을 수 있습니다.

영수증을 재발급하는 방법에 대해서는 In-App Purchase Programming Guide - Refreshing the App Receipt 를 참고해주세요.

23.4. 트랜잭션 아이디 가져오기

결제 검증 요청 시에 ValidateReceipt2 혹은 ValidateReceiptSync2 함수를 이용하면 영수증에 포함된 구매 내역의 transaction id 를 가져올 수 있습니다.

게임 서버는 응답 받은 transaction id 를 클라이언트로 다시 돌려주어 클라이언트가 직접 구매한 정보와 맞는지 대조해볼 수 있습니다.

지원하는 결제 검증 플랫폼 마다 해당하는 transaction id 는 아래와 같습니다.

플랫폼

정보

애플 앱스토어

검증 된 영수증 내 in_app 필드 내 original_transaction_id

원스토어(구 티스토어)

결제 검증 요청 시에 사용된 tx_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);
}
추후 지원 예정입니다.

23.5. 游戏服务器端支付验证设置参数

Note

该设置参数是使用iFun Biller的iFun引擎游戏服务器的值。iFun Biller自身的设置参数请参考 iFun Biller设置方法(MANIFEST.json)

  • use_biller: 是否激活与iFun Biller agent的通信。若为 false ,则bypass所有验证过程,并视为已经成功。(type=bool, default=false)

  • remote_biller_ip_address: iFun Biller运行的远程主机的IP地址。(type=string, default=”0.0.0.0”)

  • remote_biller_port: iFun Biller运行的远程主机的IP端口编号。(type=uint64, default=0)

各个支付处理中所需的其他设置

Warning

若在GooglePlay中进行refresh token,则之前的access token将失效。请注意不要将Refresh token用于其他地方。