22. 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.

22.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.

22.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
  • SK TStore

22.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.

22.1.2.1. For Ubuntu

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

22.1.2.2. For CentOS

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

22.1.3. How to execute

22.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

22.1.3.2. For Ubuntu 16.04 or CentOS 7

Run the following command:

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

22.1.4. How to check the operation

22.1.4.1. For Ubuntu 14.04 or CentOS 6

$ sudo service funapi-biller status

22.1.4.2. For Ubuntu 16.04 or CentOS 7

$ sudo systemctl status funapi-biller

22.1.4.3. Log file

Logs are output to /var/log/funapi/funapi-biller/.

22.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=””)

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",
      ...
    },
    ...
  }
}

22.2. Receipt Verification

You can use the function provided by iFun Engine (in this case, communication is through TCP) or independently use RESTful API.

22.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.

void ValidateReceipt(const ReceiptValidationRequest &request,
                     const BillingResponseHandler &handler)

bool ValidateReceiptSync(const ReceiptValidationRequest &request,
                         ReceiptValidationResponse *response)

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 MakeTStoreReceipt(const string &txid,
                          const string &appid,
                          const string &signdata,
                          const std::vector< string > &products,
                          bool use_tstore_test_server)

Tip

See API documentation for more details.

22.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.

22.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);
  }
}
22.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);
}

22.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.

22.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 ValidateReceipt(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);
  }
}
22.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);
}

22.2.1.3. 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.

22.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
void example(const string &facebook_id,
             const string &tstore_txid, const string &tstore_appid,
             const string &tstore_signdata, const string &tstore_product) {
  std::vector<string> products;
  products.push_back(tstore_product);
  Receipt receipt = MakeTStoreReceipt(tstore_txid, tstore_appid, tstore_signdata,
                                      products);

  ReceiptValidationRequest request("Facebook", facebook_id, receipt);
  ReceiptValidationResponse response;

  if (not ValidateReceipt(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 tstore_txid, string tstore_appid,
                    string tstore_signdata, string tstore_product)
{
  List<string> products = new List<string>();
  products.Add(tstore_product);

  string receipt = Billing.MakeTStoreReceipt (
      tstore_txid,  tstore_appid, tstore_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);
  }
}
22.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.

22.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.

22.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.
  • (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
Response JSON Object:
 
  • result (integer) –
    • 0: Succeeded
    • 2: Parameter error
    • Other: Other error
    • 2: Already authenticated session
    • 3: Incorrect ID
    • 4: Incorrect authentication key
    • 5: Incorrect service provider
    • 6: Incorrect service provider (Google) authentication parameter
    • 2000: Parameter error
    • Others: Other error
  • description (string) – Error description
  • sessionid (string) – Included when sending session ID and billing requests.

22.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: Succeeded
    • 1000: Already processed receipt
    • 1001: Incorrect receipt
    • 1002: Incorrect service provider
    • 1003: Unauthorized service provider
    • 1004: Canceled receipt
    • 1005: Unauthorized session
    • 2000: Parameter error
    • Others: Other error

22.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: Succeeded
    • 1000: Already processed receipt
    • 1001: Incorrect receipt
    • 1002: Incorrect service provider
    • 1003: Unauthorized service provider
    • 1004: Canceled receipt
    • 1005: Unauthorized session
    • 2000: Parameter error
    • Others: Other error

22.2.2.4. 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) – TStore txid (unique value issued by IAP server on purchase)
  • appid (string) – TStore appid (partially paid parent product ID)
  • signdata (string) – TStore signdata (electronic payment data)
  • products (string[]) – TStore product id(string) array
  • test (boolean) – If true, uses TStore test (for development) IAP server
Response JSON Object:
 
  • result (integer) –
    • 0: Succeeded
    • 1000: Already processed receipt
    • 1001: Incorrect receipt
    • 1002: Incorrect service provider
    • 1003: Unauthorized service provider
    • 1005: Unauthorized session
    • 2000: Parameter error
    • Others: Other error

22.2.2.5. 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}

22.3. 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

  • 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=””)

Warning

Previous access tokens are canceled when a refresh token is issued by Google Play. Note that refresh tokens cannot be used elsewhere.