diff --git a/ouya_iap.hxproj b/ouya_iap.hxproj
index 88f74b6..94fd70f 100644
--- a/ouya_iap.hxproj
+++ b/ouya_iap.hxproj
@@ -16,19 +16,23 @@
-
+
+
+
+
-
+
-
+
-
+
+
diff --git a/src/IAPHandler.hx b/src/IAPHandler.hx
new file mode 100644
index 0000000..f27f49a
--- /dev/null
+++ b/src/IAPHandler.hx
@@ -0,0 +1,131 @@
+import flash.display.Bitmap;
+import flash.display.Sprite;
+import flash.utils.ByteArray;
+import haxe.io.Bytes;
+import haxe.crypto.BaseCode;
+
+import openfl.Assets;
+
+#if android
+import openfl.utils.JNI;
+import tv.ouya.console.api.OuyaController;
+import tv.ouya.console.api.OuyaFacade;
+import openfl.events.JoystickEvent;
+#end
+
+class IAPHandler
+{
+
+ public var initCall:Dynamic;
+ public var requestProductListCall:Dynamic;
+ public var getProductListIDsCall:Dynamic;
+ public var requestReceiptsCall:Dynamic;
+ public var getReceiptProductIDsCall:Dynamic;
+ public var requestPurchaseCall:Dynamic;
+ public var ouyaFacadeObject:Dynamic;
+
+ public function new( ouyaFacadeObject:Dynamic, DERKeyPath:String )
+ {
+ this.ouyaFacadeObject = ouyaFacadeObject;
+
+ #if android
+
+ // bind methods to JNI
+ trace("=================== JNI linking methods...");
+ initCall = openfl.utils.JNI.createStaticMethod
+ ("com.jarnik.iaptest.OUYA_IAP", "init", "(Lorg/haxe/nme/HaxeObject;Ltv/ouya/console/api/OuyaFacade;Ljava/lang/String;)V", true);
+ requestProductListCall = openfl.utils.JNI.createStaticMethod
+ ("com.jarnik.iaptest.OUYA_IAP", "requestProductList", "([Ljava/lang/String;)V", true);
+ getProductListIDsCall = openfl.utils.JNI.createStaticMethod
+ ("com.jarnik.iaptest.OUYA_IAP", "getProductListIDs", "()Ljava/lang/String;", true);
+ requestReceiptsCall = openfl.utils.JNI.createStaticMethod
+ ("com.jarnik.iaptest.OUYA_IAP", "requestReceipts", "()V", true);
+ getReceiptProductIDsCall = openfl.utils.JNI.createStaticMethod
+ ("com.jarnik.iaptest.OUYA_IAP", "getReceiptProductIDs", "()Ljava/lang/String;", true);
+ requestPurchaseCall = openfl.utils.JNI.createStaticMethod
+ ("com.jarnik.iaptest.OUYA_IAP", "requestPurchase", "(Ljava/lang/String;)V", true);
+ trace("=================== JNI methods linked!");
+ var appKey:ByteArray = Assets.getBytes( DERKeyPath );
+
+ // I don't know how to pass ByteArray to JNI, let's use Base64 encoding
+ var BASE:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ var base64:BaseCode = new BaseCode( Bytes.ofString( BASE ) );
+ var appKey64:String = base64.encodeBytes( appKey ).toString();
+
+ // init the IAP
+ var params:Array = [this, ouyaFacadeObject, appKey64 ];
+ initCall( params );
+ #end
+ }
+
+ public function requestProductList( products:Array ):Void {
+ trace("requesting " + products.join(" "));
+ requestProductListCall( [products] );
+ }
+
+ public function requestPurchase( product:String ):Void {
+ trace("purchasing " + product );
+ requestPurchaseCall( [product] );
+ }
+
+ public function requestReceipts():Void {
+ trace("requestReceipts");
+ requestReceiptsCall( [] );
+ }
+
+ public function getProductListIDs():String {
+ // will return list of received product identifiers, delimited by space character
+ return getProductListIDsCall();
+ }
+
+ public function getReceiptProductIDs():String {
+ // will return list of purchased products identifiers, delimited by space character
+ return getReceiptProductIDsCall();
+ }
+
+ // ==================================== CALLBACKS - OVERRIDE THESE! =======================
+
+ // ==== Product List
+ public function onProductListReceived()
+ {
+ var p:Array = getProductListIDs().split(" ");
+ trace("=== onProductListReceived! " + p.join(" "));
+
+ requestReceipts();
+ }
+ public function onProductListFailed(error:String)
+ {
+ trace("=== onProductListFailed! "+error);
+ }
+
+ // ==== Purchasing
+ public function onPurchaseSuccess(productID:String)
+ {
+ trace("=== onPurchaseSuccess! "+productID);
+ }
+ public function onPurchaseFailed(error:String)
+ {
+ trace("=== onPurchaseFailed! "+error);
+ }
+ public function onPurchaseCancelled()
+ {
+ trace("=== onPurchaseCancelled! ");
+ }
+
+ // ==== Receipt List
+ public function onReceiptsReceived()
+ {
+ var p:Array = getReceiptProductIDs().split(" ");
+ trace("=== onReceiptsReceived! "+p.join("x"));
+ }
+ public function onReceiptsFailed( error:String )
+ {
+ trace("=== onReceiptsFailed "+error);
+ }
+ public function onReceiptsCancelled()
+ {
+ trace("=== onReceiptsCancelled ");
+ }
+}
+
+
diff --git a/src/Main.hx b/src/Main.hx
index b921e5e..d2a9c1c 100644
--- a/src/Main.hx
+++ b/src/Main.hx
@@ -16,17 +16,23 @@ import openfl.events.JoystickEvent;
class Main extends Sprite {
public static inline var OUYA_DEVELOPER_ID:String = "a589aa6a-cf50-4f72-9313-0a515e4dab95";
+ public static inline var PRODUCT_IDENTIFIER:String = "test_sss_full";
+ public static inline var DER_KEY_PATH:String = "assets/key.der";
+
#if android
public static var ouyaFacade:OuyaFacade;
#end
- private var p:IAP_Handler;
+ private var handler:MyIAPHandler;
public function new () {
- super ();
+ // Help:
+ // How do you call Haxe from Java (Android) http://www.openfl.org/forums/general-discussion/how-do-you-call-haxe-java-android/
+ // writing JNI bindings http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-4.3
+ // JNI elements https://nekonme.googlecode.com/svn/trunk/project/android/JNI.cpp
- addChild( new Bitmap( Assets.getBitmapData("assets/OUYA_O.png" ) ) );
+ super ();
#if android
var getContext = JNI.createStaticMethod ("org.haxe.nme.GameActivity", "getContext", "()Landroid/content/Context;", true);
@@ -34,132 +40,20 @@ class Main extends Sprite {
ouyaFacade = OuyaFacade.getInstance();
ouyaFacade.init( getContext(), OUYA_DEVELOPER_ID );
trace("OUYA controller & facade inited!");
- //ShopOUYA.init();
-
- // How do you call Haxe from Java (Android) http://www.openfl.org/forums/general-discussion/how-do-you-call-haxe-java-android/
- p = new IAP_Handler( ouyaFacade.__jobject );
-
- p.requestProductList(["test_sss_full", "__DECLINED__THIS_PURCHASE"]);
+ handler = new MyIAPHandler( ouyaFacade.__jobject, DER_KEY_PATH );
+ handler.requestProductList(["test_sss_full", "__DECLINED__THIS_PURCHASE"]);
+ addChild( new Bitmap( Assets.getBitmapData("assets/OUYA_O.png" ) ) );
stage.addEventListener (JoystickEvent.BUTTON_DOWN, stage_onJoystickButtonDown);
-
#end
-
}
#if android
private function stage_onJoystickButtonDown( e:JoystickEvent ):Void {
- trace("pressed button, will purchase");
- p.requestPurchase( "test_sss_full" );
+ trace("OUYA button pressed, starting purchase");
+ handler.requestPurchase( PRODUCT_IDENTIFIER );
}
#end
-}
-
-class IAP_Handler
-{
-
- public var initCall:Dynamic;
- public var requestProductListCall:Dynamic;
- public var getProductListIDsCall:Dynamic;
- public var requestReceiptsCall:Dynamic;
- public var getReceiptProductIDsCall:Dynamic;
- public var requestPurchaseCall:Dynamic;
- public var ouyaFacadeObject:Dynamic;
-
- public function new( ouyaFacadeObject:Dynamic )
- {
- this.ouyaFacadeObject = ouyaFacadeObject;
- #if android
- //var fn = openfl.utils.JNI.createStaticMethod("com.jarnik.iaptest.OUYA_IAP", "purchaseSomething", "(Lorg/haxe/nme/HaxeObject;)V", true);
- //fn([this]);
-
- // writing JNI bindings http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html#zz-4.3
- // JNI elements https://nekonme.googlecode.com/svn/trunk/project/android/JNI.cpp
- trace("=================== JNI linking");
- initCall = openfl.utils.JNI.createStaticMethod
- ("com.jarnik.iaptest.OUYA_IAP", "init", "(Lorg/haxe/nme/HaxeObject;Ltv/ouya/console/api/OuyaFacade;Ljava/lang/String;)V", true);
- // (Lorg/haxe/nme/HaxeObject;Ltv/ouya/console/api/OuyaFacade;[B)V
- requestProductListCall = openfl.utils.JNI.createStaticMethod
- ("com.jarnik.iaptest.OUYA_IAP", "requestProductList", "([Ljava/lang/String;)V", true);
- getProductListIDsCall = openfl.utils.JNI.createStaticMethod
- ("com.jarnik.iaptest.OUYA_IAP", "getProductListIDs", "()Ljava/lang/String;", true);
- requestPurchaseCall = openfl.utils.JNI.createStaticMethod
- ("com.jarnik.iaptest.OUYA_IAP", "requestPurchase", "(Ljava/lang/String;)V", true);
- requestReceiptsCall = openfl.utils.JNI.createStaticMethod
- ("com.jarnik.iaptest.OUYA_IAP", "requestReceipts", "()V", true);
- getReceiptProductIDsCall = openfl.utils.JNI.createStaticMethod
- ("com.jarnik.iaptest.OUYA_IAP", "getReceiptProductIDs", "()Ljava/lang/String;", true);
- trace("=================== JNI linked!");
- var appKey:ByteArray = Assets.getBytes("assets/key.der");
-
- // I don't know how to pass ByteArray to JNI, let's use Base64
- var BASE64:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- var base64:BaseCode = new BaseCode( Bytes.ofString( BASE64 ) );
- var appKey64:String = base64.encodeBytes( appKey ).toString();
-
- var params:Array = [this, ouyaFacadeObject, appKey64 ];
- trace("gonna run initCall from Haxe");
- initCall( params );
- #end
- }
-
- public function requestProductList( products:Array ):Void {
- trace("requesting " + products.join(" "));
- requestProductListCall( [products] );
- }
-
- public function requestPurchase( product:String ):Void {
- trace("purchasing " + product );
- requestPurchaseCall( [product] );
- }
-
- public function requestReceipts():Void {
- trace("requestReceipts");
- requestReceiptsCall( [] );
- }
-
- // ==================================== CALLBACKS =======================
-
- public function onProductListReceived()
- {
- var p:Array = getProductListIDsCall().split(" ");
- trace("=== onProductListReceived! >> " + p.join("x"));
-
- requestReceipts();
- }
- public function onProductListFailed(error:String)
- {
- trace("=== onProductListFailed! "+error);
- }
-
- public function onPurchaseSuccess(productID:String)
- {
- trace("=== onPurchaseSuccess! "+productID);
- }
- public function onPurchaseFailed(error:String)
- {
- trace("=== onPurchaseFailed! "+error);
- }
- public function onPurchaseCancelled()
- {
- trace("=== onPurchaseCancelled! ");
- }
-
- public function onReceiptsReceived()
- {
- var p:Array = getReceiptProductIDsCall().split(" ");
- trace("=== onReceiptsReceived! >> "+p.join("x"));
- }
- public function onReceiptsFailed( error:String )
- {
- trace("=== onReceiptsFailed "+error);
- }
- public function onReceiptsCancelled()
- {
- trace("=== onReceiptsCancelled ");
- }
-}
-
-
+}
\ No newline at end of file
diff --git a/src/MyIAPHandler.hx b/src/MyIAPHandler.hx
new file mode 100644
index 0000000..691f729
--- /dev/null
+++ b/src/MyIAPHandler.hx
@@ -0,0 +1,8 @@
+
+class MyIAPHandler extends IAPHandler
+{
+
+
+}
+
+
diff --git a/src/java/com/jarnik/iaptest/OUYA_IAP.java b/src/java/com/jarnik/iaptest/OUYA_IAP.java
index be66a4d..e6413cf 100644
--- a/src/java/com/jarnik/iaptest/OUYA_IAP.java
+++ b/src/java/com/jarnik/iaptest/OUYA_IAP.java
@@ -36,26 +36,8 @@
public class OUYA_IAP
{
- /*
- public static void requestProduct(final HaxeObject callback)
- {
- GameActivity.getInstance().runOnUiThread
- (
- new Runnable()
- {
- public void run()
- {
- callback.call("onPurchase", new Object[] {"junk"});
- }
- }
- );
- //callback.call("onPurchase", new Object[] {"junk"});
- }*/
+ // This is mostly a copy-paste of ODK's iap-sample-app --Jaroslav Meloun
- /**
- * The outstanding purchase request UUIDs.
- */
-
private static final Map mOutstandingPurchaseRequests = new HashMap();
public static List PRODUCT_IDENTIFIER_LIST;
@@ -71,13 +53,7 @@ public static void init(final HaxeObject callback, OuyaFacade ouyaFacade, String
mOuyaFacade = ouyaFacade;
mCallback = callback;
- Log.d("IAP", "Java here, running init!");
-
- Log.d("IAP", "got APPLICATION_KEY_64 "+APPLICATION_KEY_64);
-
byte[] APPLICATION_KEY = Base64.decode( APPLICATION_KEY_64, Base64.NO_WRAP );
- Log.d("IAP", "received APP KEY bytes "+APPLICATION_KEY[0]+" "+APPLICATION_KEY[1]);
-
// Create a PublicKey object from the key data downloaded from the developer portal.
try {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(APPLICATION_KEY);
@@ -92,14 +68,10 @@ public static void init(final HaxeObject callback, OuyaFacade ouyaFacade, String
public static void requestProductList(String[] products)
{
- Log.d("IAP", " will request "+products[0]+" etc " );
-
PRODUCT_IDENTIFIER_LIST = new ArrayList();
for ( String s : products )
PRODUCT_IDENTIFIER_LIST.add( new Purchasable( s ) );
- Log.d("IAP", "========== created product list of "+PRODUCT_IDENTIFIER_LIST.size() );
-
mOuyaFacade.requestProductList(PRODUCT_IDENTIFIER_LIST, new CancelIgnoringOuyaResponseListener>() {
@Override
public void onSuccess(final ArrayList products) {
@@ -107,7 +79,7 @@ public void onSuccess(final ArrayList products) {
for(Product p : products) {
Log.d("IAP", p.getName() + " costs " + p.getPriceInCents());
}
- Log.d("IAP", "========== requestProductList SUCCESS "+products.size() );
+ Log.d("IAP", "Received "+products.size()+" products." );
mCallback.call("onProductListReceived", new Object[] {} );
}
@@ -117,16 +89,13 @@ public void onFailure(int errorCode, String errorMessage, Bundle optionalData) {
// here to tell you that your app needs to handle this case: if your app doesn't display
// something, the user won't know of the failure.
//Toast.makeText(IapSampleActivity.this, "Could not fetch product information (error " + errorCode + ": " + errorMessage + ")", Toast.LENGTH_LONG).show();
- Log.d("IAP", "========== requestProductList FAIL " );
mCallback.call("onProductListFailed", new Object[] { errorCode + ": " + errorMessage } );
}
});
-
- Log.d("IAP", "========== requested product list " );
- //callback.call("onPurchase", new Object[] {"junk"});
}
public static String getProductListIDs() {
+ // will return list of received product identifiers, delimited by space character
String product_ids = "";
for ( int i = 0; i < mProductList.size(); i++ )
product_ids += ( i > 0 ? " ": "" ) + mProductList.get( i ).getIdentifier();
@@ -139,20 +108,15 @@ public static void requestPurchase( String productName )
throws GeneralSecurityException, UnsupportedEncodingException, JSONException {
Product product = null;
- for ( Product p: mProductList ) {
- Log.d("IAP", "testing "+p.getIdentifier()+" against "+productName);
+ for ( Product p: mProductList )
if ( p.getIdentifier().equals( productName ) ) {
- Log.d("IAP", "FOUND IT!");
product = p;
break;
}
- }
if ( product == null ) {
- Log.w("IAP", "Requested product ID "+productName+" not found in requested products!" );
+ Log.w("IAP", "Requested product ID "+productName+" not found in products!" );
return;
}
-
- Log.d("IAP", "========== requesting purchase of "+product.getName() );
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
@@ -166,7 +130,6 @@ public static void requestPurchase( String productName )
purchaseRequest.put("uuid", uniqueId);
purchaseRequest.put("identifier", product.getIdentifier());
String purchaseRequestJson = purchaseRequest.toString();
- Log.w("IAP", "HEYYA requesting "+product.getIdentifier()+" uuid "+uniqueId);
byte[] keyBytes = new byte[16];
sr.nextBytes(keyBytes);
@@ -194,7 +157,6 @@ public static void requestPurchase( String productName )
synchronized (mOutstandingPurchaseRequests) {
mOutstandingPurchaseRequests.put(uniqueId, product);
}
- Log.w("IAP", "HEYYA ouyaFacade.requestPurchase");
mOuyaFacade.requestPurchase(purchasable, new PurchaseListener(product));
}
@@ -232,7 +194,6 @@ public static class PurchaseListener implements OuyaResponseListener {
public void onSuccess(String result) {
Product product;
String id;
- Log.w("IAP", "HEYYA PurchaseListener.onSuccess");
try {
OuyaEncryptionHelper helper = new OuyaEncryptionHelper();
@@ -282,14 +243,11 @@ public void onSuccess(String result) {
return;
}
- //TODO request recipes
- //requestReceipts();
mCallback.call("onPurchaseSuccess", new Object[] { mProduct.getIdentifier() } );
}
@Override
public void onFailure(int errorCode, String errorMessage, Bundle optionalData) {
- Log.w("IAP", "onPurchaseFailed "+errorCode+": "+errorMessage);
mCallback.call("onPurchaseFailed", new Object[] { errorCode+": "+errorMessage } );
}
@@ -298,9 +256,7 @@ public void onFailure(int errorCode, String errorMessage, Bundle optionalData) {
*/
@Override
public void onCancel() {
- Log.w("IAP", "HEYYA PurchaseListener.onCancel");
mCallback.call("onPurchaseCancelled", new Object[] {} );
- //showError("User cancelled purchase");
}
}
@@ -309,15 +265,9 @@ public void onCancel() {
public static void requestReceipts() {
mOuyaFacade.requestReceipts(new ReceiptListener());
}
-
- /**
- * Display an error to the user. We're using a toast for simplicity.
- */
private static void showError(final String errorMessage) {
- Log.w("IAP", "requestReceipts error "+errorMessage);
mCallback.call("onReceiptsFailed", new Object[] { errorMessage } );
- //Toast.makeText(IapSampleActivity.this, errorMessage, Toast.LENGTH_LONG).show();
}
/**
@@ -388,7 +338,6 @@ public int compare(Receipt lhs, Receipt rhs) {
@Override
public void onFailure(int errorCode, String errorMessage, Bundle optionalData) {
- Log.w("IAP", "Request Receipts error (code " + errorCode + ": " + errorMessage + ")");
showError("Could not fetch receipts (error " + errorCode + ": " + errorMessage + ")");
}
@@ -398,12 +347,12 @@ public void onFailure(int errorCode, String errorMessage, Bundle optionalData) {
@Override
public void onCancel()
{
- //showError("User cancelled getting receipts");
mCallback.call("onReceiptsCancelled", new Object[] {} );
}
}
public static String getReceiptProductIDs() {
+ // will return list of purchased products identifiers, delimited by space character
String product_ids = "";
for ( int i = 0; i < mReceiptList.size(); i++ )
product_ids += ( i > 0 ? " ": "" ) + mReceiptList.get( i ).getIdentifier();