j3k0 / cordova-plugin-purchase

In-App Purchase for Cordova on iOS, Android and Windows
https://purchase.cordova.fovea.cc
1.3k stars 537 forks source link

[IOS][ANDROID] Is it possible to determine if a purchase was made on android FROM iOS and vice versa? #1281

Open stevebrowndotco opened 2 years ago

stevebrowndotco commented 2 years ago

General question and I can't find this in the documentation anywhere.

If a user purchases a subscription on Android, but then views the app on an iOS device, I would like to know if their "subscription" is active, regardless of the device.

I am using Fovea.Billing receipt validator if that helps...

Thank you!

stevebrowndotco commented 2 years ago

For what it's worth, I found a solution by handling transaction receipts with the applicationUsername corresponding to my user's UID in my own database. Then I have the webhook service from the Fovea Billing service post to my Firebase Cloud API function.

Within this function, I am iterating through all purchases to find expired: false. If at least one is found, an active subscription is determined to be open regardless of whether or not the user is on iOS, android, or web (my app runs on all three places)

However I am having issues with the webhook service from Fovea where when a subscription has expired, it won't trigger the webhook, unless I manually refresh the receipt. I have contacted support about this...

arindoneATK commented 2 years ago

I'm having to do a custom implementation because fovea billing isn't able to really be trust 100% of the time.

stevebrowndotco commented 2 years ago

Not sure if this is the right place to say it here, but is there any open source implementation that you are using? Or totally custom?

arindoneATK commented 2 years ago

It has to be custom server validation, because fovea would be used instead. I do feel that this plugin was built with mainly forcing the users of the plugin to use Fovea...it makes sense because they can make money that way.

alanmilinovic commented 2 years ago

It make sense to force people using Fovea but still they are providing flexibility to build your own validator to have full control, for free. Documentation is also really nice, I was able to build my own validator on the rest api side.

louisameline commented 2 years ago

@alanmilinovic Would you open source your validator by any means?

I have no issues paying Fovea, but I fear that the service might just be stopped without notice at some point, as support for this project is somehow lacking.

alanmilinovic commented 2 years ago

Sure, I can show you part of my code, as an example how I did it on my end, just to give you an idea.

$rootScope.subscription_state_status = "Loading...";

// Show errors for 10 seconds.
store.error(function (error) {
    $rootScope.subscription_state_error = "ERROR " + error.code + " - " + " " + error.message;

    setTimeout(function () {
        $rootScope.subscription_state_error = "";
    }, 10000);

    $ionicLoading.hide();
});

// Register all products
store.register([{
    id: '1_year_subscription',
    type: store.PAID_SUBSCRIPTION,
}, {
    id: '1_month_subscription',
    type: store.PAID_SUBSCRIPTION,
}]);

// Contact API webservice to validate
store.validator = function (product, callback) {
    var token;

    if (product.transaction.type == "android-playstore") {
        token = product.transaction.purchaseToken;
    }

    $rootScope.subscription_state_status = "Processing, please wait...";

    $appService.verifyPurchase(product.id, token, $localStorage.get('USER_ID')).then(function (data) {
        if ($appHelper.ResultMessage(data)) {
            if (data.ResultSets[0][0]) {
                $rootScope.premium_expiry_date = moment.utc(data.ResultSets[0][0].PremiumExpirityDate).local().format('DD MMM YYYY HH:mm:ss');
                $rootScope.premium = moment.utc(data.ResultSets[0][0].PremiumExpirityDate).toDate() > moment() ? true : false;
                $rootScope.premium_verified = true;

                $localStorage.set('SESSION_PREMIUM_EXPIRITY_DATE', $rootScope.premium_expiry_date);
                $localStorage.set('SESSION_PREMIUM', $rootScope.premium);

                callback(true, { transaction: "Success!" });
            } else {
                $rootScope.premium = false;

                $rootScope.subscription_state_error = "";
                callback(false, "Something went wrong");
            }
        }
        else {
            $rootScope.premium = false;

            $rootScope.subscription_state_error = "";
            callback(false, "Something went wrong");
        }
    }, function (errorResponse) {
        // Check if true error occurs, not a manual abort().
        if (errorResponse && errorResponse.statusText != 'abort') {
            $rootScope.premium = false;

            //$rootScope.subscription_state_error = "";
            callback(false, "Something went wrong");
        }
    });
};

// Call when order (purchase) requested by the user
store.when('product').requested(function (p) {
    $ionicLoading.show({
        template: '<ion-spinner></ion-spinner><br/>Processing, please wait...',
        delay: 250
    });
});

// Call when a product order is cancelled by the user
store.when('product').cancelled(function (p) {
    //store.update();
    $rootScope.subscription_state_status = "";
    $ionicLoading.hide();
});

// Call when payment approved
store.when('product').approved(function (p) {
    p.verify();
});

// Call when payment verified
store.when('product').verified(function (p) {
    p.finish();
});

// Call when payment finished
store.when('product').finished(function (p) {
    store.update();
    $rootScope.subscription_state_status = "";
    $ionicLoading.hide();
});

// Called when any subscription product is updated
store.when('subscription').updated(function () {
    const product1 = store.get('1_year_subscription') || {};
    const product2 = store.get('1_month_subscription') || {};

    if (product1.owned) {
        //status = 'Subscribed to premium';

        //$rootScope.premium = true;
    } else if (product2.owned) {
        //status = 'Subscribed to premium';

        //$rootScope.premium = true;
    } else if (product1.state === 'approved' || product2.state === 'approved') {
        $rootScope.subscription_state_status = "Processing, please wait...";
    } else {
        $rootScope.subscription_state_status = "";
    }

    $rootScope.subscription_state_product1 = product1;
    $rootScope.subscription_state_product2 = product2;
});

// Load informations about products and purchases
store.refresh();