package org.webinos.android.impl.payment.demowallet; import java.util.LinkedList; import java.util.Queue; import org.meshpoint.anode.bridge.Env; import org.webinos.api.payment.PaymentError; import org.webinos.api.payment.PaymentErrorCB; import org.webinos.api.payment.PaymentErrors; import org.webinos.api.payment.PaymentSuccessCB; import org.webinos.api.payment.ShoppingItem; import android.content.Context; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; public class PaymentTransaction { private final static String TAG = PaymentTransaction.class.getName(); private Context context; private Env env; private WalletEngine walletEngine; @SuppressWarnings("unused") private String customerID; private String sellerID; final private PaymentSuccessCB successCallback; final private PaymentErrorCB errorCallback; private Looper answerLooper; private Handler answerHandler; private Handler.Callback answerCallback; private boolean exit; private Queue<AsyncCall> callQueue = new LinkedList<AsyncCall>(); private abstract class AsyncCall implements Runnable { private void call() { answerHandler.postDelayed(this, 400L); } } private class OpenShop extends AsyncCall { @Override public void run() { walletEngine = new WalletEngine(context, answerCallback); /* we need to wait for a short time to allow the * library to connect to the webinos wallet service; there is * no synchronisation for this */ try { Thread.sleep(500L); } catch(InterruptedException ie) {} Store store = new Store(sellerID, "Webinos Application"); walletEngine.openShop(store); } } private class AddItems extends AsyncCall { private ShoppingItem bill; AddItems(ShoppingItem bill) { this.bill = bill; } @Override public void run() { String productID = bill.productID; String productName = bill.productID; /* FIXME: do we need an explicit product name */ String productDescription = bill.description; String currency = bill.currency; /* FIXME: change this multiplier based on currency? */ long price = (long)(bill.itemsPrice * 100); Log.d(TAG, "Received item " + productDescription + " with price " + bill.itemsPrice + " and sending " + price + " to the payment"); BillableItem billItem = new BillableItem(productID, productName, productDescription, currency, price, 1); walletEngine.addItem(billItem); } } private class Checkout extends AsyncCall { @Override public void run() { walletEngine.checkout(); } } private PaymentError handleAnswer(Message msg) { /* if we've been told to exit, then this answer * is the answer we were waiting for .. so exit on return */ if(exit) answerLooper.quit(); if(msg.what == WalletEngine.RESPONSE_CODE_OK || msg.what == WalletEngine.RESPONSE_CODE_CHECKOUT_OK) { return null; } PaymentError error = new PaymentError(); error.message = "The payment failed"; switch(msg.what) { case WalletEngine.RESPONSE_CODE_CHECKOUT_FAIL: //error.code = PaymentErrors.PAYMENT_AUTHENTICATION_FAILED; //error.code = PaymentErrors.PAYMENT_CHARGEABLE_EXCEEDED; error.code = PaymentErrors.PAYMENT_CHARGE_FAILED; break; case WalletEngine.RESPONSE_CODE_UNKNOWN: error.code = PaymentErrors.INVALID_OPTION; break; case WalletEngine.RESPONSE_CODE_FAIL: error.code = PaymentErrors.INVALID_OPTION; break; } return error; } public PaymentTransaction(Context ctx, String customerID, String sellerID, PaymentSuccessCB successCallback, PaymentErrorCB errorCallback) { this.context = ctx; this.env = Env.getCurrent(); this.customerID = customerID; this.sellerID = sellerID; this.successCallback = successCallback; this.errorCallback = errorCallback; } public void perform(final ShoppingItem[] itemList, final ShoppingItem bill) { callQueue.add(new OpenShop()); callQueue.add(new AddItems(bill)); callQueue.add(new Checkout()); /* Callbacks for async indications from the shop engine */ answerCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { /* get the answer for the current operation */ PaymentError error = handleAnswer(msg); if(error != null) { /* if there was an error, end here */ errorCallback.onError(error); finish(); return true; } /* if there's no next call, we're done */ if(callQueue.isEmpty()) { if(finish()) { String proofOfPurchase = "Receipt received"; /* we don't get one from the engine */ successCallback.onSuccess(proofOfPurchase); } return true; } /* if there's a next item, call it */ callQueue.remove().call(); return true; } }; /* this thread is the handler that handles answer messages * from the shop engine; therefore we have to open the shop * in this thread */ synchronized(this) { (new Thread() { @Override public void run() { Env.setEnv(env); Looper.prepare(); answerHandler = new Handler(); synchronized(PaymentTransaction.this) {PaymentTransaction.this.notify();} answerLooper = Looper.myLooper(); Looper.loop(); } }).start(); try { wait(); } catch(InterruptedException ie) {} } /* call the first item in the queue */ callQueue.remove().call(); } private boolean finish() { /* ensure we don't reenter this */ if(exit) return false; if(walletEngine != null) { /* we need to release the engine, and don't * exit the looper until we've had the answer to that */ exit = true; walletEngine.release(); walletEngine = null; return true; } /* otherwise there's nothing to do, so we can * quit immediately */ answerLooper.quit(); return true; } }