Mobile Integration
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 Purchases.subscriptions: get api call to Google.
Flow:
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 Purchases.subscriptions: get api call to Google.
3. Stop
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
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 : Purchases.subscriptions: get
2. Rebill
- get subscription status : Purchases.subscriptions: get
- send notification to Dating
3. Stop
- cancel subscription on Google : Purchases.subscriptions: cancel
- send notification to Dating
4. Refund
- refund and stop subscription : 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:
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
Name | Type | Required | Description | Note |
---|---|---|---|---|
pp_type | (string) | yes | Payment processor type | GP for GooglePlay, IO for Ios(Apple) |
tracking_order | (int) | no | Order ID to be tracked on client app. | |
tracking_user | (int) | yes | User ID to be tracked on client app. | |
tracking_tag | (int) | yes | Tag ID to be tracked on client app. | |
first_name | (string) | yes | First Name | |
last_name | (string) | yes | Last Name | |
zipcode | (string) | yes | User ZIP code | |
country | (string) | yes | Country abbr. | |
(string) | yes | User email address | ||
ip | (string) | yes | IP address from which user is making the transaction | |
host | (string) | yes | Host form which user is making the transaction | |
currency | (string) | yes | Currency abbr. | |
order_type | (string) | no | Allowed values '' (empty string) and xsale. Determine if order is Cross Sale or not. | |
return_url | (string) | no | Return URL (used for PayPal like PP's) | Not used |
cancel_url | (string) | no | Cancel URL (used for PayPal like PP's) | Not used |
items* | (array) | yes | Array of items | Should be created to match service offered in Mobile App |
override | (int) | no | Used for Payment Processor Account Override | Not used |
override_tag | (int) | no | Used for Payment Processor Account Override | Not used |
override_type | (string) | no | Allowed values: '' (empty string) and x-sales. See Payment Processor Account Override | Not used |
items*
Name | Type | Required | Description |
---|---|---|---|
trial_amount | float | no | Trial Amount |
trial_unit | string (day|week|month|year) | no | Trial unit |
trial_period | int | no | Trial period |
amount | float | yes | rebill amount |
rebill_unit | string (day|week|month|year) | yes | rebill unit |
rebill_period | int | yes | rebill period |
max_rebill_count | int | yes | max rebill count (-1 unlimited) |
description | string | yes | Item description |
Return
Name | Type | Description | |
---|---|---|---|
result | array | Result array with data | |
Name | Type | Description | |
Hash | String(32) | Order identification hash - for usage in transaction.finish | |
Redirect | void | Not in use |
Example 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" }
Example Response:
{ "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
Name | Type | Required | Description | Example |
---|---|---|---|---|
hash | (string) | yes | Hash string of order that is related to transaction | 1480691234563599bd6a0e891c4bdadc |
pp_type | (string) | yes | Payment processor type | GP,IO |
purchase_id | (string) | yes | 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 | m1_14_123_premium |
purchase_token | (string) | yes | Verification string that GooglePlay provides to mobile app | dlpjpcgmhpgnpigfnnionmka.AO-J1Ox-ylRzN4odIalERHQDclB2KA6Fbyzp_Os0T6h1FBAtqd1IsgiuzuvBWI5nKPSpmzRdi3JA-fH6jtO2JdCq5KimYzRa4eWoj50MC1Txk4hXyYcq4Lk4TNLNym-1Tqy2AxRZKXTk |
package_name | (string) | yes | Mobile app's package name | com.mydomain.mypackage |
purchase_type | (string) | yes | Purchase type. Can be 0 or 1 | 0 for subscription, 1 for product |
merchant_order_number | (string) | yes | Order ID on GooglePlay service - Not used. If don't have it in receipt, send 0.0 | [merchant_id](20).[order_id](17) |
Example 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" }
Example Response:
{ "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
Name | Type | Required | Description | Example |
---|---|---|---|---|
hash | (string) | yes | Hash string of order that is related to transaction | 1480691234563599bd6a0e891c4bdadc |
pp_type | (string) | yes | Payment processor type | GP,IO |
purchase_id | (string) | yes | Name of the payment service as created on Apple's service. Should be created so it has meaning for server app. It's just name for payment app that will be used in payment checks | m1_14_123_premium |
receipt-data | (string) | yes | Receipt data - appStoreReceipt | huge base 64 encoded string |
Return
Name | Type | Description | ||
---|---|---|---|---|
result | array | Result array with data | ||
Name | Type | Description | ||
success | Bool | Whether check was successful or not | ||
payment_tran_id | int | Transaction ID on payment app | ||
items_data | array | Item IDs on server app and payment app | ||
Name | Type | Description | ||
tracking_item | int | Item ID on server app(client) | ||
item_id | int | Item ID on payment app | ||
Name | Type | Description | ||
sub_id | array | Array of created Subscription IDs(int) | ||
previous_item_id | Int | Not in use. Used for non-renewing subscriptions* | ||
expire_ts | Int | Not in use. Used for non-renewing subscriptions* |
* Currently only renewing subscriptions are supported
Example 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" }
Example Response:
{ "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 }
- Last Author
- asimic
- Last Edited
- Aug 2 2017, 19:03