google renewable subscriptions abuse - in-app-purchase

we have an application that utilizes auto renewable subscriptions in android.
The users are going subscribing through the app normally and the receipt is sent for our backend to be validated using the IAP Google API.
until this point everything is fine, but we recently discovred a loophole in our system, some users are subscribe and cancel their subscriptions and re-enable the auto subscription, this way google will issues a new receipt that is sent to our backend and we are giving them another year, this is easily solved by granting the only the duration given in the receipt
but then users started using the same google account with different accounts for our apps and they give everyone free subscriptions by the generated receipt
I read the API in here and I can't find any field that tells us that this receipt is only a re-activation not a new subscription
https://developers.google.com/android-publisher/api-ref/purchases/subscriptions
when we send a request for google api to get the info this is what we are getting:
status_from_google_play
{
"autoRenewing": true,
"cancelReason": null,
"countryCode": "SA",
"developerPayload": "",
"expiryTimeMillis": "1534073485784",
"kind": "androidpublisher#subscriptionPurchase",
"linkedPurchaseToken": "sometoken",
"orderId": "GPA.xxxx-xxxx-xxxx-xxxxx",
"paymentState": 1,
"priceAmountMicros": "290000",
"priceCurrencyCode": "SAR",
"purchaseType": 0,
"startTimeMillis": "1534071687580",
"userCancellationTimeMillis": null
}
There is no field that gives me this piece of information
am I missing something? is there another way to validate this?

Turns out the linkedPurchaseToken field can be used for this purpose
if the linked token is not empty it means it's a re-activation only or an upgrade/downgrade for the subscription.
It's not a direct usage as you can see but it's how you know.

Related

PayPal parallel adaptive payment verification is not remembering the buyer

I have the following requirements:
The PayPal verification screen remembers the buyer between transactions.
The layout of PayPal verification screen is suitable for mobile devices (responsive layout).
Parallel payments (2 and more receivers).
Apple iOS Safari browser.
Current implementation:
PayPal API: Adaptive Payments.Reference: https://developer.paypal.com/docs/archive/adaptive-payments/integration-guide/APIntro/
Adaptive Payments kind of payment: parallel.
The payment approval type: Explicit approval.
We create the payment on the backend with the call:
POST https://svcs.paypal.com/AdaptivePayments/Pay
Body:
{
'receiverList': {
'receiver': [
{
'primary': false,
'email': 'redacted#example.com',
'amount': '.51'
}
]
},
'memo': 'redacted',
'returnUrl': 'http://oursite.example.com',
'cancelUrl': 'http://oursite.example.com',
'feesPayer': 'EACHRECEIVER',
'actionType': 'PAY',
'currencyCode': 'USD',
'requestEnvelope': {},
'ipnNotificationUrl': 'http://oursite.example.com'
}
After getting the response, we redirect the buyer in Apple Safari (iOS 13.6.1) to:
https://www.paypal.com/webapps/adaptivepayment/flow/pay?expType=mini&paykey=AP-1234567
We get the following pages (one before login, the second after login):
PayPal OneTouch is enabled for the buyer account. Reference: https://www.paypal.com/us/webapps/mpp/one-touch-checkout
The buyer makes the payment in the US with a US-based PayPal account.
The problems:
The layout is not for mobile. It is hard to read and navigate.
PayPal doesn’t remember my previous authentication.
How to fix it?
Adaptive Payments is very old, was deprecated since December of 2017, and is now obsolete.
AP is not mobile optimized, and is not designed to work with One Touch.
You can still test it in Sandbox mode, and legacy live integrations may still work, but no new integrations are supported. You cannot obtain a new Adaptive Payments APP ID. They don't exist anymore.
Basically, you should not use Adaptive Payments for anything.
As for alternate solutions that might work for a "parallel" scenario, perhaps you could implement a PayPal Checkout to a single receiver that has Payouts, and then send Payouts to other receivers. You'll need to contact PayPal to find out if Payouts is permitted for your particular use case and will be activated for your account in live. Here is the information on requesting that approval: https://developer.paypal.com/docs/payouts/integrate/prerequisites/#get-access-to-paypal-payouts

"The receipt could not be authenticated" - should it be checked again?

Out of Apple's status codes for an in-app purchase (in Table 2-1 here), some should obviously be tried again, and some not and should be considered to say that the receipt in invalid.
But what about "21003 - The receipt could not be authenticated."? Is it similar to 21010 or to 21005? I.e. should this be sent again later or considered an invalid purchase?
From my experience, the "21003 - The receipt could not be authenticated." status is related to the App-Specific Shared Secret.
When you validate receipts with the AppStore, the App-Specific Shared Secret is used to set the value of the password field in the JSON request that you sent to the AppStore validation endpoint. (See Validating Receipts with the App Store documentation)
Keep in mind that according to Apple's documentation, this apps-specific shared secret is only necessary when validating receipt for apps that use auto-renewable subscriptions. However, in my experience, it might be worth setting it for any app receipt validation, just to avoid the 21003 error.

Jira ServiceDesk API - response suddenly missing the field emailAddress

I am getting a list of users in an organisation from the Jira ServiceDesk API, as documented on this page.
So far this is working as expected but today all off a sudden the code is broken. That is to say; the results im getting from the API have changed. As the docs state i SHOULD be getting values that include the field emailAddress. However since today i am getting the right users, but there is no longer an emailAddress field. The following is the result (structure) i'm recieving:
{
"size": 1,
"start": 0,
"limit": 50,
"isLastPage": true,
"_links": {
"self": "https://someprefix.atlassian.net/rest/servicedeskapi/organization/1/user?start=0&limit=50",
"base": "BaseURL",
"context": ""
},
"values": [
{
"accountId": "123456:SomeGUID",
"name": "SomeUserName",
"key": "someusername",
"displayName": "Some Username",
"active": true,
"timeZone": "Europe/Amsterdam",
"_links": {
"jiraRest": "LinkToTheThisUser",
"avatarUrls": {
"48x48": "LinkTo48X48Url",
"24x24": "LinkTo24X24Url",
"16x16": "LinkTo16X16Url",
"32x32": "LinkTo32X32Url"
},
"self": "LinkToThisResource"
}
}
]
}
I asked to check the setting User email visibility and it is currently set to logged in users only.
My question:
Why is this field suddenly missing from the API? Is there or are there settings i'm unaware off that influence this field being present or not?
Ok, it seems this is by design to keep in line with GDPR guidelines. Long story short; emailAddress will no longer be reliably part of the API's since a user now has to consent to it. If not all users emailAddresses are reliably part of the response this makes for a poor field of comparison in synchronization applications.
Which profile information will apps be able to access?
Any personal information users set as Anyone will be available for apps to use. For
example, apps will be able to access this information, store it, and
show it to other users.
Users' local times and locations will always be available to apps,
regardless of their visibility settings. This lets apps customize
their experiences.
Users' email addresses will be available to some apps that have been
approved by Atlassian. Note that if a user chooses to set their email
address to Anyone, then any installed apps will be able to use it.
When users install apps, the apps will request that they consent to
share their profile information with the apps. If they consent, the
apps will have access to all of their profile information. This is not
applicable to admins.
However. There will be a new API giving access to user emailAddresses regardless of user settings. Access to this API is restricted to approved applications. More about this can be found on this page.
In order to apply for access to this API the app must meet all current
requirements for being listed on Atlassian Marketplace (even if the
app is not listed on Atlassian Marketplace).
This means:
The app developer has provided a privacy policy
The app developer has provided a customer terms of use agreement
The app developer must signal whether or not the app collects and stores personal data.
If the app is storing personal data the app must report the accountIDs that have been collected and stored every 15 days.

Google Analytics tracking Ecommerce In thankyou page

I’ve got a simple e-commerce site realized in php without using any e-commerce framework.
I’m trying to track purchases amount and their source (organic, social, paid search) with Google analytics using e-commerce tracking. Purchases are done from paypal. I set the thankyou page correctly and users are redirected to this page after the payment. I use Instant Payment Notification for updating the db with the new purchase and send a confirmation email to the customer.
How could I get the client session data to get correctly these data with:
ga('ecommerce:addTransaction', {
'id': '1234', // Transaction ID. Required.
'affiliation': 'Acme Clothing', // Affiliation or store name.
'revenue': '11.99', // Grand Total.
'shipping': '5', // Shipping.
'tax': '1.29' // Tax.
});
And send them with:
ga('ecommerce:send');
The problem is that if I insert the google analytics tracking code in the thankyou page, the page is not “linked” to the session of the customer because paypal redirect there as a new one, I suppose, and I cannot get the purchase and source data.
I also tried to use measurement protocol in IPN page, and that worked fine, but all the traffic in Google Analytics appear as direct, because these informations are sent from the php page on the server and not from the client browser.
I’m looking for the correct approach to these situation.
Thank you
Probably what you are looking for is this.
Google Analytics uses this for its cross-domain tracking, basically it passes the client id from a domain to another through a ?ga=XXXYYYZZZ parameters that links that session to that specific user to bypass the "cross-cookie" situation.
Unfortunately, I do not have experience with IPN but this is probably what you are looking for.
Example:
ga('create', 'UA-XXXXX-Y', 'auto');
ga(function(tracker) {
// Logs the client ID for the current user.
console.log(tracker.get('clientId'));
});
And then you send this clientID in your request and it will be linked by Analytics with the proper information.

Is there anyway to check if an Apple Inapp purchase product identifier is valid from the content server?

I am creating the backend for an iOSapp that has inapp purchase products in them.
The backend does the content serving stuff and validations of receipts and what not.
What bothers me is that there is no way to be sure that the product identifier entered in the backend is a valid one.
I know there is an API that can be called from the iOS to get a list of product identifiers for a specific app(was it company?), is there anyway to obtain this information from the backend so true validation of the entered identifier can be done?
Thanks in advance.
Once a user makes a valid purchase, you receive an encoded purchase receipt. To check if the purchase is valid, you need to send this receipt to Apple's validation servers. You'll receive a response indicating if the purchase was valid, and if it's valid - details about the purchase (purchase time, product ID, transaction ID, etc...).
I used the example code here and it was pretty easy and straight forward. Use the sandbox url as an endpoint while testing (including with TestFlight), and the production URL once the app is in the app store.
BTW, I would recommend generating a shared secret for your IAP (you can do it in iTunes Connect), and including it in your validation requests under a field called password:
private function encodeRequest() {
return json_encode(array(
'receipt-data' => $this->getReceipt(),
'password' => $sharedSecret));
}

Resources