Skip to content

Commit

Permalink
v2.0
Browse files Browse the repository at this point in the history
* Refactored Android implementation to use IAB v3 (+ bug fixes)
* Improved consistency of data format across iOS and Android
* Implemented restoreTransactions() on iOS
  • Loading branch information
ataugeron committed Nov 16, 2013
1 parent b4806ca commit 588d952
Show file tree
Hide file tree
Showing 42 changed files with 2,193 additions and 1,987 deletions.
64 changes: 34 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
Air Native Extension for In App Purchases (iOS + Android)
======================================

This is an [Air native extension](http://www.adobe.com/devnet/air/native-extensions-for-air.html) for In App Purchases on iOS and Android. It has been developed by [FreshPlanet](http://freshplanet.com) and is used in the game [SongPop](http://songpop.fm).
This is an [Air native extension](http://www.adobe.com/devnet/air/native-extensions-for-air.html) for In-App Purchases on iOS and Android. It has been developed by [FreshPlanet](http://freshplanet.com) and is used in the game [SongPop](http://songpop.fm).


Notes
---------

* iOS implementation does NOT contain on-device receipt validation.
* Android implementation uses [In-app Billing Version 3](http://developer.android.com/google/play/billing/api.html).


Installation
Expand All @@ -13,32 +20,22 @@ On Android:

* you will need to add the following in your application descriptor:

```xml

<android>
<manifestAdditions><![CDATA[
<manifest android:installLocation="auto">
...
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="com.android.vending.BILLING" />
...
<application>
...
<service android:name="com.freshplanet.inapppurchase.BillingService" />
<receiver android:name="com.freshplanet.inapppurchase.BillingReceiver">
<intent-filter>
<action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
<action android:name="com.android.vending.billing.RESPONSE_CODE" />
<action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
</intent-filter>
</receiver>
...
</application>
</manifest>
```xml

<android>
<manifestAdditions><![CDATA[
<manifest android:installLocation="auto">
]]></manifestAdditions>
</android>
```
<uses-permission android:name="com.android.vending.BILLING" />
<application>
<service android:name="com.freshplanet.inapppurchase.BillingService" />
</application>
</manifest>
]]></manifestAdditions>
</android>
```



Expand All @@ -47,10 +44,17 @@ Build script

Should you need to edit the extension source code and/or recompile it, you will find an ant build script (build.xml) in the *build* folder:

cd /path/to/the/ane/build
mv example.build.config build.config
#edit the build.config file to provide your machine-specific paths
ant
```bash
cd /path/to/the/ane

# Setup build configuration
cd build
mv example.build.config build.config
# Edit build.config file to provide your machine-specific paths

# Build the ANE
ant
```


Authors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ package com.freshplanet.ane.AirInAppPurchase
}


public function init():void
public function init(googlePlayKey:String, debug:Boolean = false):void
{
if (this.isInAppPurchaseSupported)
{
extCtx.call("init");
trace("[InAppPurchase] init library");
extCtx.call("initLib", googlePlayKey, debug);
}
}

Expand All @@ -86,17 +87,33 @@ package com.freshplanet.ane.AirInAppPurchase
{
trace("[InAppPurchase] removing product from queue", productId, receipt);
extCtx.call("removePurchaseFromQueue", productId, receipt);

if (Capabilities.manufacturer.indexOf("iOS") > -1)
{
_iosPendingPurchases = _iosPendingPurchases.filter(function(jsonPurchase:String, index:int, purchases:Vector.<Object>):Boolean {
try
{
var purchase:Object = JSON.parse(jsonPurchase);
return JSON.stringify(purchase.receipt) != receipt;
}
catch(error:Error)
{
trace("[InAppPurchase] Couldn't parse purchase: " + jsonPurchase);
}
return false;
});
}
}
}



public function getProductsInfo(productsId:Array):void
public function getProductsInfo(productsId:Array, subscriptionIds:Array):void
{
if (this.isInAppPurchaseSupported)
{
trace("[InAppPurchase] get Products Info");
extCtx.call("getProductsInfo", productsId);
extCtx.call("getProductsInfo", productsId, subscriptionIds);
} else
{
this.dispatchEvent( new InAppPurchaseEvent(InAppPurchaseEvent.PRODUCT_INFO_ERROR) );
Expand Down Expand Up @@ -146,9 +163,24 @@ package com.freshplanet.ane.AirInAppPurchase
{
if (Capabilities.manufacturer.indexOf('Android') > -1)
{
trace("[InAppPurchase] restoring transactions");
extCtx.call("restoreTransaction");
}
else if (Capabilities.manufacturer.indexOf("iOS") > -1)
{
var jsonPurchases:String = "[" + _iosPendingPurchases.join(",") + "]";
var jsonData:String = "{ \"purchases\": " + jsonPurchases + "}";
dispatchEvent(new InAppPurchaseEvent(InAppPurchaseEvent.RESTORE_INFO_RECEIVED, jsonData));
}
}


public function stop():void
{
if (Capabilities.manufacturer.indexOf('Android') > -1)
{
trace("[InAppPurchase] stop library");
extCtx.call("stopLib");
}
}


Expand All @@ -159,15 +191,22 @@ package com.freshplanet.ane.AirInAppPurchase
return value;
}


private var _iosPendingPurchases:Vector.<Object> = new Vector.<Object>();

private function onStatus(event:StatusEvent):void
{
trace(event);
var e:InAppPurchaseEvent;
switch(event.code)
{
case "PRODUCT_INFO_RECEIVED":
e = new InAppPurchaseEvent(InAppPurchaseEvent.PRODUCT_INFO_RECEIVED, event.level);
break;
case "PURCHASE_SUCCESSFUL":
if (Capabilities.manufacturer.indexOf("iOS") > -1)
{
_iosPendingPurchases.push(event.level);
}
e = new InAppPurchaseEvent(InAppPurchaseEvent.PURCHASE_SUCCESSFULL, event.level);
break;
case "PURCHASE_ERROR":
Expand All @@ -179,12 +218,18 @@ package com.freshplanet.ane.AirInAppPurchase
case "PURCHASE_DISABLED":
e = new InAppPurchaseEvent(InAppPurchaseEvent.PURCHASE_DISABLED, event.level);
break;
case "PRODUCT_INFO_SUCCESS":
e = new InAppPurchaseEvent(InAppPurchaseEvent.PRODUCT_INFO_RECEIVED, event.level);
break;
case "PRODUCT_INFO_ERROR":
e = new InAppPurchaseEvent(InAppPurchaseEvent.PRODUCT_INFO_ERROR);
break;
case "SUBSCRIPTION_ENABLED":
e = new InAppPurchaseEvent(InAppPurchaseEvent.SUBSCRIPTION_ENABLED);
break;
case "SUBSCRIPTION_DISABLED":
e = new InAppPurchaseEvent(InAppPurchaseEvent.SUBSCRIPTION_DISABLED);
break;
case "RESTORE_INFO_RECEIVED":
e = new InAppPurchaseEvent(InAppPurchaseEvent.RESTORE_INFO_RECEIVED, event.level);
break;
default:

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,17 @@ package com.freshplanet.ane.AirInAppPurchase
// user cannot make a purchase
public static const PURCHASE_DISABLED:String = "purchaseDisabled";

// user can make a subscription
public static const SUBSCRIPTION_ENABLED:String = "subsEnabled";
// user cannot make a subscription
public static const SUBSCRIPTION_DISABLED:String = "subsDisabled";



public static const PRODUCT_INFO_RECEIVED:String = "productInfoReceived";
public static const PRODUCT_INFO_ERROR:String = "productInfoError";


public static const RESTORE_INFO_RECEIVED:String = "restoreInfoReceived";

// json encoded string (if any)
public var data:String;
Expand Down
9 changes: 0 additions & 9 deletions android/AndroidManifest.xml

This file was deleted.

144 changes: 144 additions & 0 deletions android/src/com/android/vending/billing/IInAppBillingService.aidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.android.vending.billing;

import android.os.Bundle;

/**
* InAppBillingService is the service that provides in-app billing version 3 and beyond.
* This service provides the following features:
* 1. Provides a new API to get details of in-app items published for the app including
* price, type, title and description.
* 2. The purchase flow is synchronous and purchase information is available immediately
* after it completes.
* 3. Purchase information of in-app purchases is maintained within the Google Play system
* till the purchase is consumed.
* 4. An API to consume a purchase of an inapp item. All purchases of one-time
* in-app items are consumable and thereafter can be purchased again.
* 5. An API to get current purchases of the user immediately. This will not contain any
* consumed purchases.
*
* All calls will give a response code with the following possible values
* RESULT_OK = 0 - success
* RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
* RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
* RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
* RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
* RESULT_ERROR = 6 - Fatal error during the API action
* RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
* RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
*/
interface IInAppBillingService {
/**
* Checks support for the requested billing API version, package and in-app type.
* Minimum API version supported by this interface is 3.
* @param apiVersion the billing version which the app is using
* @param packageName the package name of the calling app
* @param type type of the in-app item being purchased "inapp" for one-time purchases
* and "subs" for subscription.
* @return RESULT_OK(0) on success, corresponding result code on failures
*/
int isBillingSupported(int apiVersion, String packageName, String type);

/**
* Provides details of a list of SKUs
* Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
* with a list JSON strings containing the productId, price, title and description.
* This API can be called with a maximum of 20 SKUs.
* @param apiVersion billing API version that the Third-party is using
* @param packageName the package name of the calling app
* @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "DETAILS_LIST" with a StringArrayList containing purchase information
* in JSON format similar to:
* '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
* "title : "Example Title", "description" : "This is an example description" }'
*/
Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);

/**
* Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
* the type, a unique purchase token and an optional developer payload.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param sku the SKU of the in-app item as published in the developer console
* @param type the type of the in-app item ("inapp" for one-time purchases
* and "subs" for subscription).
* @param developerPayload optional argument to be sent back with the purchase information
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "BUY_INTENT" - PendingIntent to start the purchase flow
*
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
* If the purchase is successful, the result data will contain the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "INAPP_PURCHASE_DATA" - String in JSON format similar to
* '{"orderId":"12999763169054705758.1371079406387615",
* "packageName":"com.example.app",
* "productId":"exampleSku",
* "purchaseTime":1345678900000,
* "purchaseToken" : "122333444455555",
* "developerPayload":"example developer payload" }'
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
* was signed with the private key of the developer
* TODO: change this to app-specific keys.
*/
Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
String developerPayload);

/**
* Returns the current SKUs owned by the user of the type and package name specified along with
* purchase information and a signature of the data to be validated.
* This will return all SKUs that have been purchased in V3 and managed items purchased using
* V1 and V2 that have not been consumed.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param type the type of the in-app items being requested
* ("inapp" for one-time purchases and "subs" for subscription).
* @param continuationToken to be set as null for the first call, if the number of owned
* skus are too many, a continuationToken is returned in the response bundle.
* This method can be called again with the continuation token to get the next set of
* owned skus.
* @return Bundle containing the following key-value pairs
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
* failure as listed above.
* "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
* "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
* "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
* of the purchase information
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
* next set of in-app purchases. Only set if the
* user has more owned skus than the current list.
*/
Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);

/**
* Consume the last purchase of the given SKU. This will result in this item being removed
* from all subsequent responses to getPurchases() and allow re-purchase of this item.
* @param apiVersion billing API version that the app is using
* @param packageName package name of the calling app
* @param purchaseToken token in the purchase information JSON that identifies the purchase
* to be consumed
* @return 0 if consumption succeeded. Appropriate error values for failures.
*/
int consumePurchase(int apiVersion, String packageName, String purchaseToken);
}

0 comments on commit 588d952

Please sign in to comment.