= Google Play integration =
= Operations =
== 1. Initial Purchase ==
Some action will be called on Dating Frontend when user purchases subscription.
User's purchase needs to be verified.
Idea here is to make `transaction.init` api call to Payment.
After `transaction.init` api call, we make `transaction.finish` api call to Payment.
Payment will verify purchase inside `transaction.init` api call and based on verification `transaction.finish` will succeed or fail.
Verification includes calling Google Play api - `Purchases.subscriptions` , `get`.
Both api calls (`transaction.init` and `transaction.finish`) happen inside 1 request, ie it is similar to how Credit Card payment flows, ie. there is no need to use notifications to create user's subscription - it is instant.
Verification is done using [[https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/get | Purchases.subscriptions: get]] api call to Google.
Flow: {F70224}
== 2. Rebill ==
Using rebill schedule on Payment.
Since we know when subscription should expire, we can schedule rebill at that time.
Rebill procedure will just check subscription status on Google and if subscription is expired - we know it is stopped on Google and we should stop it on Payment and on Dating (using notifications), otherwise - subscription is still active which means that the Rebill happened (Google does all billing).
We're using somewhat generous rebill schedule for start - allowing failed retries, third stops subscription.
Rebill will make this [[https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/get | Purchases.subscriptions: get]] api call to Google.
== 3. Stop ==
[[https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/cancel | Purchases.subscriptions: cancel]] is that. It should be valid for subscriptions (Premium).
It will just cancel subscription. Subscription will expire after currently paid period.
== 4. Refund ==
[[https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/revoke | Purchases.subscriptions: revoke]] - this is Stop & refund option that we use. It will refund and immediately expire user's subscription.
== 5. Edit ==
TODO / TBD (limited options for subscriptions - defer call)
= Server =
== Frontend ==
=== 1. Initial purchase action ===
Parameters from mobile app:
Must have:
- packageName string The package name of the application for which this subscription was purchased (for example, 'com.some.thing').
- subscriptionId string The purchased subscription ID (for example, 'monthly001').
- token string The token provided to the user's device when the subscription was purchased.
Other parameters can be added as needed by Mobile app or server app
We use subscriptionId's like: `m1_2534_2_premium`, `m1_2534_14_premium`, `m1_2534_160_premium`
`system_id`.`site_id`.`pay_item_id`.`payment_item_type` - should be string that has meaning on server app.
== Admin ==
=== 1. Refund ===
Config mostly, maybe some smaller code updates.
=== 2. Stop, Cancel ===
Config mostly, maybe some smaller code updates.
=== 3. Refund ===
Config mostly, maybe some smaller code updates.
= Payment =
== 1. Initial purchase ==
Must:
- verify purchase : [[https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/get | Purchases.subscriptions: get]]
== 2. Rebill ==
- get subscription status : [[https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/get | Purchases.subscriptions: get]]
- send notification to Dating
== 3. Stop ==
- cancel subscription on Google : [[https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/cancel | Purchases.subscriptions: cancel]]
- send notification to Dating
== 4. Refund ==
- refund and stop subscription : [[https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/revoke | Purchases.subscriptions: revoke]]
- send notification to Dating
== Transactions ==
Google deals with financial details, ie we do not get any transaction identifiers, amounts ...
For all operations that we save as transactions on Payment (initial purchase, rebill, refund) we can fill transactions table record.
Transactions details can be stored in fixed fields table (not using json) by having all fields for all responses for all api commands that we call (there should not be too many of them).
All possible response keys from these 2 apis should be keys in transaction details table:
- [[https://developers.google.com/android-publisher/api-ref/purchases/subscriptions | Purchases.subscriptions]]
- [[https://developers.google.com/android-publisher/api-ref/purchases/products | Purchases.products]]
Refund via Google api is only possible for last subscription charge. Other transactions can be refunded from some Google Admin
== Google Parameters for each item ==
Each call to Google Play API requires 3 params:
- packageName string The package name of the application for which this subscription was purchased (for example, 'com.some.thing').
- subscriptionId string The purchased subscription ID (for example, 'monthly001').
- token string The token provided to the user's device when the subscription was purchased.
We get these values when initial purchase action on Dating is called, and then they are passed to Payment in `transaction.finish` call.
These values are per item. We can store them in some additional table (user_id + item_id + these params).
= Google Authorization =
Service account with key (json) from https://console.cloud.google.com/iam-admin/serviceaccounts/project?project=myproject should be parsed and filled to appropriate pp_params_* table
= Testing environment =
None
-----
= Verification =
Google Play verification is done using this lib: https://github.com/google/google-api-php-client(GooglePlayDevelopers API). It's pretty much well documented on what/how to do.
Ios needs call to their store service with password and receipt-data(appStoreReceipt, not transactionReceipt) sent in request payload. There basically receipt-data is decrypted into array of all receipts ever made for that account and returned for verification. Verification is done using this: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1 - maybe a bit confusing with all field changes and deprecation - for auto-renewing subscriptions should be enough to check only //status// - if it's 0 -> subscription is OK, if 21006 -> expired. Non renewing subscriptions would need for server app to keep track of subscriptions, expiry dates, etc.
= Settings =
Settings and data needed for verification:
- for GooglePlay we use Service Accounts(Google Cloud Platform -> IAM & Admin) (seemed easier to implement verification using Service Accounts, with above mentioned php lib). So, one should be created for android app("Editor" role is fine) with private key in json format - that json key is provided to above mentioned php lib for verification. Also, Google Play Developers API should be enabled for that app(Google Cloud Platform -> API Manager->Library, under Mobile APIs)
- for Ios, for auto-renewable subscriptions password is needed - app’s shared secret (a hexadecimal string).
-----
= Payment API usage =
Usage is similar to Credit Card purchase, only different fields are sent. First call to transaction.init, then transaction.finish. In transaction.finish instead of regular payment, we check for successful payment on GooglePlay. If check is successful - new transaction/subscription is created.
== transaction.init ==
Similar to Credit Card payment, pretty much all relevant fields are used for mobile integration as well.
==== Input Parameters ====
<table>
<tr><th>Name</th><th>Type</th><th>Required</th><th>Description</th><th>Note</th></tr>
<tr><td>pp_type</td><td>(string)</td><td>yes</td><td>Payment processor type</td><td>GP for GooglePlay, IO for Ios(Apple)</td></tr>
<tr><td>tracking_order</td><td>(int)</td><td>no</td><td>Order ID to be tracked on client app.</td><td></td></tr>
<tr><td>tracking_user</td><td>(int)</td><td>yes</td><td>User ID to be tracked on client app.</td><td></td></tr>
<tr><td>tracking_tag</td><td>(int)</td><td>yes</td><td>Tag ID to be tracked on client app.</td><td></td></tr>
<tr><td>first_name</td><td>(string)</td><td>yes</td><td>First Name</td><td></td></tr>
<tr><td>last_name</td><td>(string)</td><td>yes</td><td>Last Name</td><td></td></tr>
<tr><td>zipcode</td><td>(string)</td><td>yes</td><td>User ZIP code</td><td></td></tr>
<tr><td>country</td><td>(string)</td><td>yes</td><td>Country abbr.</td><td></td></tr>
<tr><td>email</td><td>(string)</td><td>yes</td><td>User email address</td><td></td></tr>
<tr><td>ip</td><td>(string)</td><td>yes</td><td>IP address from which user is making the transaction</td><td></td></tr>
<tr><td>host</td><td>(string)</td><td>yes</td><td>Host form which user is making the transaction</td><td></td></tr>
<tr><td>currency</td><td>(string)</td><td>yes</td><td>Currency abbr.</td><td></td></tr>
<tr><td>order_type</td><td>(string)</td><td>no</td><td>Allowed values '' (empty string) and `xsale`. Determine if order is Cross Sale or not.</td><td></td></tr>
<tr><td>return_url</td><td>(string)</td><td>no</td><td>Return URL (used for PayPal like PP's)</td><td>Not used</td></tr>
<tr><td>cancel_url</td><td>(string)</td><td>no</td><td>Cancel URL (used for PayPal like PP's)</td><td>Not used</td></tr>
<tr><td>items*</td><td>(array)</td><td>yes</td><td>Array of items</td><td>Should be created to match service offered in Mobile App</td></tr>
<tr><td>override</td><td>(int)</td><td>no</td><td>Used for [[ public/payment/overrides/ | Payment Processor Account Override]]</td><td>Not used</td></tr>
<tr><td>override_tag</td><td>(int)</td><td>no</td><td>Used for [[ public/payment/overrides/ | Payment Processor Account Override]]</td><td>Not used</td></tr>
<tr><td>override_type</td><td>(string)</td><td>no</td><td>Allowed values: '' (empty string) and `x-sales`. See [[ public/payment/overrides/ | Payment Processor Account Override]]</td><td>Not used</td></tr>
</table>
items*
<table>
<tr><th>Name</th><th>Type</th><th>Required</th><th>Description</th></tr>
<tr><td>trial_amount</td><td>float</td><td>no</td><td>Trial Amount</td></tr>
<tr><td>trial_unit</td><td>string (day|week|month|year)</td><td>no</td><td>Trial unit</td></tr>
<tr><td>trial_period</td><td>int</td><td>no</td><td>Trial period</td></tr>
<tr><td>amount</td><td>float</td><td>yes</td><td>rebill amount</td></tr>
<tr><td>rebill_unit</td><td>string (day|week|month|year)</td><td>yes</td><td>rebill unit</td></tr>
<tr><td>rebill_period</td><td>int</td><td>yes</td><td>rebill period</td></tr>
<tr><td>max_rebill_count</td><td>int</td><td>yes</td><td>max rebill count (-1 unlimited)</td></tr>
<tr><td>description</td><td>string</td><td>yes</td><td>Item description</td></tr>
</table>
NOTE: Items should be exact copy of services offered in mobile app - prices and duration must match.
==== Return ====
<table>
<tr><th>Name</th><th>Type</th><th>Description</th><th></th></tr>
<tr><td>result</td><td>array</td><td>Result array with data</td><td></td></tr>
<tr><th></th><th>Name</th><th>Type</th><th>Description</th></tr>
<tr><td></td><td>Hash</td><td>String(32)</td><td>Order identification hash - for usage in transaction.finish</td></tr>
<tr><td></td><td>Redirect</td><td>void</td><td>Not in use</td></tr>
</table>
Example Request:
```lang=json
{
"command": "transaction.init",
"data": {
"tracking_order": "0",
"tracking_user": "4152905",
"tracking_tag": "14",
"zipcode": "34206",
"country": "RS",
"email": "zjskwsj@gmail.com",
"first_name": "zjskwsj",
"last_name": "",
"ip": "46.40.22.12",
"host": "12-22-40-46.dynamic.stcable.net",
"currency": "RSD",
"pp_type": "GP",
"tax_rate": "0",
"tax_amount": "0",
"items": [
{
"tracking_item": "172",
"amount": "2999.99",
"rebill_unit": "day",
"rebill_period": "1",
"trial_amount": "0.00",
"trial_unit": "day",
"trial_period": "0",
"max_rebill_count": "-1",
"description": "Mobile app test"
}
],
"order_type": "",
"override_type": ""
},
"token": "dating-frontend-m1@staging",
"password": "26ff2170e0034d29d702c65d888aaec1"
}
```
Example Response:
```lang=json
{
"code": "700",
"status": "Action completed successfully",
"command": "transaction.init",
"result": {
"hash": "148069xxxxxx3599bd6a0e891c4bdadc",
"redirect": null
},
"request": {
"command": "transaction.init",
"data": {
"tracking_order": "0",
"tracking_user": "4152905",
"tracking_tag": "14",
"zipcode": "34206",
"country": "RS",
"email": "zjskwsj@gmail.com",
"first_name": "zjskwsj",
"last_name": "",
"ip": "46.40.22.12",
"host": "12-22-40-46.dynamic.stcable.net",
"currency": "RSD",
"pp_type": "GP",
"tax_rate": "0",
"tax_amount": "0",
"items": [
{
"tracking_item": "172",
"amount": "2999.99",
"rebill_unit": "day",
"rebill_period": "1",
"trial_amount": "0.00",
"trial_unit": "day",
"trial_period": "0",
"max_rebill_count": "-1",
"description": "Mobile app test"
}
],
"order_type": "",
"override_type": ""
},
"token": "dating-frontend-m1@staging",
"password": "26ff2170e0034d29d702c65d888aaec1"
},
"ts": 1480696496,
"origin_ip": "69.61.28.237",
"errors": null
}
```
== transaction.finish ==
Finish transaction that has been started previously. Check if payment exists on GooglePlay/iTunes. If exists create subscription/transaction.
==== Input Parameters ====
Input parameters and response differ for GooglePlay/iTunes.
===== GooglePlay =====
<table>
<tr><th>Name</th><th>Type</th><th>Required</th><th>Description</th><th>Example</th></tr>
<tr><td>hash</td><td>(string)</td><td>yes</td><td>Hash string of order that is related to transaction</td><td>1480691234563599bd6a0e891c4bdadc</td></tr>
<tr><td>pp_type</td><td>(string)</td><td>yes</td><td>Payment processor type</td><td>GP,IO</td></tr>
<tr><td>purchase_id</td><td>(string)</td><td>yes</td><td>Name of the service as created on GooglePlay. Should be created so it has meaning for server app. It's just name for payment app that will be used in payment checks</td><td>m1_14_123_premium</td></tr>
<tr><td>purchase_token</td><td>(string)</td><td>yes</td><td>Verification string that GooglePlay provides to mobile app</td><td>dlpjpcgmhpgnpigfnnionmka.AO-J1Ox-ylRzN4odIalERHQDclB2KA6Fbyzp_Os0T6h1FBAtqd1IsgiuzuvBWI5nKPSpmzRdi3JA-fH6jtO2JdCq5KimYzRa4eWoj50MC1Txk4hXyYcq4Lk4TNLNym-1Tqy2AxRZKXTk</td></tr>
<tr><td>package_name</td><td>(string)</td><td>yes</td><td>Mobile app's package name</td><td>com.mydomain.mypackage</td></tr>
<tr><td>purchase_type</td><td>(string)</td><td>yes</td><td>Purchase type. Can be 0 or 1</td><td>0 for subscription, 1 for product</td></tr>
<tr><td>merchant_order_number</td><td>(string)</td><td>yes</td><td>Order ID on GooglePlay service - Not used. If don't have it in receipt, send 0.0</td><td>[merchant_id](20).[order_id](17)</td></tr>
</table>
Example Request:
```lang=json
{
"command": "transaction.finish",
"data": {
"hash": "148069xxxxxx3599bd6a0e891c4bdadc",
"pp_type": "GP",
"purchase_id": "m1_14_172_premium",
"package_name": "com.hellosingles.hellocowboys",
"purchase_token": "dlpjpcgmhpgnpigfnnionmka.AO-J1Ox-ylRzN4odIalERHQDclB2KA6Fbyzp_Os0T6h1FBAtqd1IsgiuzuvBWI5nKPSpmzRdi3JA-fH6jtO2JdCq5KimYzRa4eWoj50MC1Txk4hXyYcq4Lk4TNLNym-1Tqy2AxRZKXTk",
"merchant_order_number": "0.0",
"purchase_type": "0"
},
"token": "dating-frontend-m1@staging",
"password": "26ff2170e0034d29d702c65d888aaec1"
}
```
Example Response:
```lang=json
{
"code": "700",
"status": "Action completed successfully",
"command": "transaction.finish",
"result": {
"success": true,
"payment_tran_id": 112195,
"items_data": [
{
"tracking_item": "172",
"item_id": "38251"
}
],
"sub_ids": [
"14390"
]
},
"request": {
"command": "transaction.finish",
"data": {
"hash": "148069xxxxxx3599bd6a0e891c4bdadc",
"pp_type": "GP",
"purchase_id": "m1_14_172_premium",
"package_name": "com.hellosingles.hellocowboys",
"purchase_token": "dlpjpcgmhpgnpigfnnionmka.AO-J1Ox-ylRzN4odIalERHQDclB2KA6Fbyzp_Os0T6h1FBAtqd1IsgiuzuvBWI5nKPSpmzRdi3JA-fH6jtO2JdCq5KimYzRa4eWoj50MC1Txk4hXyYcq4Lk4TNLNym-1Tqy2AxRZKXTk",
"merchant_order_number": "0.0",
"purchase_type": "0"
},
"token": "dating-frontend-m1@staging",
"password": "26ff2170e0034d29d702c65d888aaec1"
},
"ts": 1480696496,
"origin_ip": "69.61.28.237",
"errors": null
}
```
===== Ios =====
<table>
<tr><th>Name</th><th>Type</th><th>Required</th><th>Description</th><th>Example</th></tr>
<tr><td>hash</td><td>(string)</td><td>yes</td><td>Hash string of order that is related to transaction</td><td>1480691234563599bd6a0e891c4bdadc</td></tr>
<tr><td>pp_type</td><td>(string)</td><td>yes</td><td>Payment processor type</td><td>GP,IO</td></tr>
<tr><td>receipt-data</td><td>(string)</td><td>yes</td><td>Receipt data - appStoreReceipt</td><td>huge base 64 encoded string</td></tr>
</table>
==== Return ====
<table>
<tr><th>Name</th><th>Type</th><th>Description</th><th></th><th></th></tr>
<tr><td>result</td><td>array</td><td>Result array with data</td><td></td><td></td></tr>
<tr><th></th><th>Name</th><th>Type</th><th>Description</th><th></th></tr>
<tr><td></td><td>success</td><td>Bool</td><td>Whether check was successful or not</td><td></td></tr>
<tr><td></td><td>payment_tran_id</td><td>int</td><td>Transaction ID on payment app</td><td></td></tr>
<tr><td></td><td>items_data</td><td>array</td><td>Item IDs on server app and payment app </td><td></td></tr>
<tr><th></th><th></th><th>Name</th><th>Type</th><th>Description</th></tr>
<tr><td></td><td></td><td>tracking_item</td><td>int</td><td>Item ID on server app(client)</td></tr>
<tr><td></td><td></td><td>item_id</td><td>int</td><td>Item ID on payment app</td></tr>
<tr><th></th><th>Name</th><th>Type</th><th>Description</th><th></th></tr>
<tr><td></td><td>sub_id</td><td>array</td><td>Array of created Subscription IDs(int)</td><td></td></tr>
<tr><td></td><td>previous_item_id</td><td>Int</td><td>Not in use. Used for non-renewing subscriptions*</td><td></td></tr>
<tr><td></td><td>expire_ts</td><td>Int</td><td>Not in use. Used for non-renewing subscriptions*</td><td></td></tr>
</table>
//* Currently only renewing subscriptions are supported//
Example Request:
```lang=json
{
"command": "transaction.finish",
"data": {
"hash": "148070xxxxxxxx2201daac1c8c558f74",
"pp_type": "IO",
"purchase_id": "m1_14_175_premium",
"receipt-data": "xxx"
},
"token": "dating-frontend-m1@staging",
"password": "26ff2170e0034d29d702c65d888aaec1"
}
```
Example Response:
```lang=json
{
"code": "700",
"status": "Action completed successfully",
"command": "transaction.finish",
"result": {
"success": true,
"payment_tran_id": 112196,
"items_data": [
{
"tracking_item": "175",
"item_id": "38252"
}
],
"sub_ids": [
"14391"
],
"previous_item_id": null,
"expire_ts": 1480790362
},
"request": {
"command": "transaction.finish",
"data": {
"hash": "148070xxxxxxxx2201daac1c8c558f74",
"pp_type": "IO",
"purchase_id": "m1_14_175_premium",
"receipt-data": "xxx"
},
"token": "dating-frontend-m1@staging",
"password": "26ff2170e0034d29d702c65d888aaec1"
},
"ts": 1480703962,
"origin_ip": "69.61.28.237",
"errors": null
}
```