how to get checkout details from paddle in mac app? - objective-c

I have integrated my mac app with paddle this what i followed from documentation and integrated but what i want when user purchases from here i want all the order details.
// Your Paddle SDK Config from the Vendor Dashboard:
NSString* myPaddleProductID = #"12345";
NSString *myPaddleVendorID = #"56791";
NSString* myPaddleAPIKey = #"abc123def345hij678";
// Populate a local object in case we're unable to retrieve data
// from the Vendor Dashboard:
PADProductConfiguration *defaultProductConfig = [[PADProductConfiguration alloc] init];
defaultProductConfig.productName = #"My v4 Product";
defaultProductConfig.vendorName = #"My Company";
// Initialize the SDK Instance with Seller details:
Paddle *paddle = [Paddle sharedInstanceWithVendorID:myPaddleVendorID
apiKey:myPaddleAPIKey
productID:myPaddleProductID
configuration:defaultProductConfig];
// Initialize the Product you'd like to work with:
PADProduct *paddleProduct = [[PADProduct alloc] initWithProductID:myPaddleProductID productType:PADProductTypeSDKProduct configuration:nil];
// Ask the Product to get it's latest state and info from the Paddle Platform:
[paddleProduct refresh:^(NSDictionary * _Nullable productDelta, NSError * _Nullable error) {
// Launch the "Product Info" gatekeeper UI with buy, activate, etc:
[paddle showProductAccessDialogWithProduct:paddleProduct];
however in documentation for custom implementation by using below block we can get but i want by using showProductAccessDialogWithProduct
[paddle showCheckoutForProduct:paddleProduct options:nil checkoutStatusCompletion:^(PADCheckoutState state, NSDictionary * _Nullable checkoutData) {
// Examine checkout state to determine the checkout result
}];
Any Suggestions ?
Thanks In Advance !!

So i got the answer till V4.0.9 we cannot get the details it is included after v4.0.10.
All you need is PADProductDelegate
-(void)productPurchased:(PADCheckoutData *)checkoutData
using this delegate method you can get checkoutdata and if you need order details
get checkout_id from checkoutdata and pass it on below api.
https://checkout.paddle.com/api/1.0/order?checkout_id=xxxxxxxxx

Related

Dialogflow V2 (beta1) SDK for Obj-C

I would like to use dialogflow service in the app written using obj-c. Have been using api.ai library for a while but could not seem to find a library for obj-c for dialogflow v2(beta1) apis. My agent is upgraded to v2 already, but the api.ai internally is using /v1/ endpoints and I need to use v2beta1 specific features like access to knowledge bases. (https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2beta1#queryparameters - knowledge_base_names).
The dialogflow api is a standard REST API, so all I need to have is OAuth2.0 & REST client, but coding this sounds like re-inventing the wheel.
Please advice. Thank you
I don't think there's a library written specifically for Dialogflow v2; however, the library google-api-objectivec-client-for-rest is a generic library provided by Google, that simplifies the code to consume their Rest APIs.
This library is updated to be used with Dialogflow V2. In order to use it, you'll need to match the Rest API, with the "Queries" (API methods) and "Objects" (API types) in the library, which is not that difficult because the names are basically the same.
For example, the detectIntent method full name is:
projects.agent.sessions.detectIntent
In the library, it is the equivalent to the Query:
GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent
Here's an example of a detectIntent request:
// Create the service
GTLRDialogflowService *service = [[GTLRDialogflowService alloc] init];
// Create the request object (The JSON payload)
GTLRDialogflow_GoogleCloudDialogflowV2DetectIntentRequest *request =
[GTLRDialogflow_GoogleCloudDialogflowV2DetectIntentRequest object];
// Set the information in the request object
request.inputAudio = myInputAudio;
request.outputAudioConfig = myOutputAudioConfig;
request.queryInput = myQueryInput;
request.queryParams = myQueryParams;
// Create a query with session (Path parameter) and the request object
GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent *query =
[GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent queryWithObject:request
session:#"session"];
// Create a ticket with a callback to fetch the result
GTLRServiceTicket *ticket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRDialogflow_GoogleCloudDialogflowV2DetectIntentResponse *detectIntentResponse,
NSError *callbackError) {
// This callback block is run when the fetch completes.
if (callbackError != nil) {
NSLog(#"Fetch failed: %#", callbackError);
} else {
// The response from the agent
NSLog(#"%#", detectIntentResponse.queryResult.fulfillmentText);
}
}];
You can find more information and samples, in the library wiki. Finally, the library also has a sample code using Google Cloud Storage which ilustrates its use with GCP services.
I think that without a specific library for Dialogflow V2, this might be the next thing to try before implementing it from scratch.
EDIT
Oops, I was missing the fact that the generated service for Dialogflow does not contain v2beta1.
In this case, it is needed an additional first step, which is to use the Dialogflow v2beta1 DiscoveryDocument and the ServiceGenerator, to create the service interface for v2beta1. Then you can continue working the same as I mentioned before.
Following #Tlaquetzal example, I ended up doing something like below
In pod file
pod 'GoogleAPIClientForREST'
pod 'JWT'
Using ServiceGenerator and Discovery Document as mentioned above, generated set of DialogFlow v2beta1 classes. Command line
./ServiceGenerator --outputDir . --verbose --gtlrFrameworkName GTLR --addServiceNameDir yes --guessFormattedNames https://dialogflow.googleapis.com/\$discovery/rest?version=v2beta1
And added them to the project.
#import "DialogflowV2Beta1/GTLRDialogflow.h"
Next step is to generate JWT Token. I have used this library JSON Web Token implementation in Objective-C. I want to connect using a service account.
NSInteger unixtime = [[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] integerValue];
NSInteger expires = unixtime + 3600; //expire in one hour
NSString *iat = [NSString stringWithFormat:#"%ld", unixtime];
NSString *exp = [NSString stringWithFormat:#"%ld", expires];
NSDictionary *payload = #{
#"iss": #"<YOUR-SERVICE-ACCOUNT-EMAIL>",
#"sub": #"<YOUR-SERVICE-ACCOUNT-EMAIL>",
#"aud": #"https://dialogflow.googleapis.com/",
#"iat": iat,
#"exp": exp
};
NSDictionary *headers = #{
#"alg" : #"RS256",
#"typ" : #"JWT",
#"kid" : #"<KID FROM YOUR SERVICE ACCOUNT FILE>"
};
NSString *algorithmName = #"RS256";
NSData *privateKeySecretData = [[[NSDataAsset alloc] initWithName:#"<IOS-ASSET-NAME-JSON-SERVICE-ACCOUNT-FILE>"] data];
NSString *passphraseForPrivateKey = #"<PASSWORD-FOR-PRIVATE-KEY-IN-CERT-JSON>";
JWTBuilder *builder = [JWTBuilder encodePayload:payload].headers(headers).secretData(privateKeySecretData).privateKeyCertificatePassphrase(passphraseForPrivateKey).algorithmName(algorithmName);
NSString *token = builder.encode;
// check error
if (builder.jwtError == nil) {
JwtToken *jwtToken = [[JwtToken alloc] initWithToken:token expires:expires];
success(jwtToken);
}
else {
// error occurred.
MSLog(#"ERROR. jwtError = %#", builder.jwtError);
failure(builder.jwtError);
}
When token is generated, it can be used for an hour (or time you specify above).
To make a call to dialogflow you need to define your project path. To create a project path for the call, append to the code below your unique session identifier. Session is like a conversation for dialogflow, so different users should use different session ids
#define PROJECTPATH #"projects/<YOUR-PROJECT-NAME>/agent/sessions/"
Making dialogflow call
// Create the service
GTLRDialogflowService *service = [[GTLRDialogflowService alloc] init];
//authorise with token
service.additionalHTTPHeaders = #{
#"Authorization" : [NSString stringWithFormat:#"Bearer %#", self.getToken.token]
};
// Create the request object (The JSON payload)
GTLRDialogflow_GoogleCloudDialogflowV2beta1DetectIntentRequest *request = [GTLRDialogflow_GoogleCloudDialogflowV2beta1DetectIntentRequest object];
//create query
GTLRDialogflow_GoogleCloudDialogflowV2beta1QueryInput *queryInput = [GTLRDialogflow_GoogleCloudDialogflowV2beta1QueryInput object];
//text query
GTLRDialogflow_GoogleCloudDialogflowV2beta1TextInput *userText = [GTLRDialogflow_GoogleCloudDialogflowV2beta1TextInput object];
userText.text = question;
userText.languageCode = LANGUAGE;
queryInput.text = #"YOUR QUESTION TO dialogflow agent"; //userText;
// Set the information in the request object
//request.inputAudio = myInputAudio;
//request.outputAudioConfig = myOutputAudioConfig;
request.queryInput = queryInput;
//request.queryParams = myQueryParams;
//Create API project path with session
NSString *pathAndSession = [NSString stringWithFormat:#"%#%#", PROJECTPATH, [self getSession]];
// Create a query with session (Path parameter) and the request object
GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent *query = [GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent queryWithObject:request session:pathAndSession];
// Create a ticket with a callback to fetch the result
// GTLRServiceTicket *ticket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket, GTLRDialogflow_GoogleCloudDialogflowV2beta1DetectIntentResponse *detectIntentResponse, NSError *callbackError) {
// This callback block is run when the fetch completes.
if (callbackError != nil) {
NSLog(#"error");
NSLog(#"Fetch failed: %#", callbackError);
//TODO: Register failure with analytics
failure( callbackError );
}
else {
// NSLog(#"Success");
// The response from the agent
// NSLog(#"%#", detectIntentResponse.queryResult.fulfillmentText);
NSString *response = detectIntentResponse.queryResult.fulfillmentText;
success( response );
}
}];
This is a basic implementation, but works and good for demo.
Good luck

Access workout data even when apple watch screen turn off

I success to get heart rate data in live without workout session on apple watch os 2. But when apple watch screen turn off, my completion block is not anymore called. I would like to continue to manage these data in live and to make my phone ring when heart rate is too low.
Maybe i can let the app on the iphone perma open and maybe it can access to the healthkit data during this workout ?
Do you think this can work ? or do you have another idea ?
Regards
Hey i found a solution :
i keep iphone app in foreground with :
[UIApplication sharedApplication].idleTimerDisabled = YES
And with the same query than apple watch (HKAnchoredObjectQuery) i can access the latest health kit data. I well get live heart rate data even when my apple watch is turn off (with a workout session)
my query
HKQuantityType *type = [HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierHeartRate];
HKAnchoredObjectQuery *heartRateQuery = [[HKAnchoredObjectQuery alloc]
initWithType:type
predicate:nil
anchor:self.anchor
limit:HKObjectQueryNoLimit
resultsHandler:^(HKAnchoredObjectQuery * _Nonnull query, NSArray<__kindof HKSample *> * _Nullable sampleObjects, NSArray<HKDeletedObject *> * _Nullable deletedObjects, HKQueryAnchor * _Nullable newAnchor, NSError * _Nullable error) {
if (error) {
// Perform proper error handling here...
NSLog(#"*** An error occured while performing the anchored object query. %# ***",
error.localizedDescription);
}
self.anchor = newAnchor;
HKQuantitySample *sample = (HKQuantitySample *)[sampleObjects firstObject];
if (sample) {
double value = [sample.quantity doubleValueForUnit:[HKUnit unitFromString:#"count/min"]];
dispatch_async(dispatch_get_main_queue(), ^(void){
self.heartrateLabel.text = [NSString stringWithFormat:#"%0.0f",value];
});
NSLog([NSString stringWithFormat:#"%0.0f",value]);
[self.hkStore stopQuery:heartRateQuery];
}
}];
[self.hkStore executeQuery:heartRateQuery];
By design, watchOS 2 apps are not allowed to run while the watch screen is off. You cannot change this behavior.

Trouble getting the original app version that the user installed (receipt validation)?

I have an app that I recently updated to work with in app purchases. The previous version (paid but with no in app purchases) was 1.0 and the current version is 1.1.
As the in app purchase essentially unlocks all features (which were included in the paid version 1.0), I wanted a way for users that had originally downloaded version 1.0 to be upgraded if they pressed my restore purchases button.
To do this, I first try to restore purchases and if the response to:
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
If this provides me with a queue with a transactions count of 0, I check the receipt to see if the original version installed was 1.0.
The code to get the receipt is as per Apple's documentation
- (void)tryRestoreFromOriginalPurchase
{
// Load the receipt from the app bundle
NSError *error;
NSData *receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
if (receipt == nil) {
[self restoreFromOriginalVersionWithReceipt:nil];
return;
}
// Create the JSON object that describes the request
NSDictionary *requestContents = #{#"receipt-data": [receipt base64EncodedStringWithOptions:0]};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
if (!requestData) {
[self restoreFromOriginalVersionWithReceipt:nil];
return;
}
// Create a POST request with the receipt data
NSURL *storeURL = [NSURL URLWithString:#"https://buy.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:#"POST"];
[storeRequest setHTTPBody:requestData];
// Make a connection to the iTunes Store on a background queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (!connectionError) {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (jsonResponse) [self restoreFromOriginalVersionWithReceipt:jsonResponse];
else [self restoreFromOriginalVersionWithReceipt:nil];
} else {
[self restoreFromOriginalVersionWithReceipt:nil];
}
}];
}
This then calls the following method:
- (void)restoreFromOriginalVersionWithReceipt:(NSDictionary *)receipt
{
if (receipt == nil) {
// CALL METHOD TO HANDLE FAILED RESTORE
} else {
NSInteger status = [[receipt valueForKey:#"status"] integerValue];
if (status == 0) {
NSString *originalApplicationVersion = [[receipt valueForKey:#"receipt"] valueForKey:#"original_application_version"];
if (originalApplicationVersion != nil && [originalApplicationVersion isEqualToString:#"1.0"]) {
// CALL METHOD TO HANDLE SUCCESSFUL RESTORE
} else {
// CALL METHOD TO HANDLE FAILED RESTORE
}
} else {
// CALL METHOD TO HANDLE FAILED RESTORE
}
}
}
Now this doesn't work. When someone installs version 1.1 and taps restore purchases, it restores successfully when it shouldn't.
I've just realized that in my Info.plist, my CFBundleShortVersionString is 1.1 but my CFBundleVersion is 1.0.
This may be a really daft question, but is the receipt providing an original_application_version of 1.0 (due to the wrong CFBundleVersion) even though the update is versioned 1.1?
So if I release a new update with this corrected to say version 1.2 (for both the CFBundleShortVersionString and CFBundleVersion), will the problem be resolved?
-- UPDATE --
So I just uploaded a new version to the app store with both the CGBundleVersion and CFBundleShortVersionString equal to 1.2. However, I'm still facing the same problem - users downloading version 1.2 for the first time and tapping on restore purchases are being upgraded for free (due to the receipt check outlined above). It seems the original_application_version is always coming to 1.0.
Note: I am downloading the app under a new iTunes account that has not previously downloaded the app.
Here is the receipt I have if I install from the app store and then try to get the receipt through Xcode
2014-08-27 08:46:42.858 AppName[4138:1803] {
environment = Production;
receipt = {
"adam_id" = AppID;
"application_version" = "1.0";
"bundle_id" = "com.CompanyName.AppName";
"download_id" = 94004873536255;
"in_app" = (
);
"original_application_version" = "1.0";
"original_purchase_date" = "2014-08-26 22:30:49 Etc/GMT";
"original_purchase_date_ms" = 1409092249000;
"original_purchase_date_pst" = "2014-08-26 15:30:49 America/Los_Angeles";
"receipt_type" = Production;
"request_date" = "2014-08-26 22:46:42 Etc/GMT";
"request_date_ms" = 1409093202544;
"request_date_pst" = "2014-08-26 15:46:42 America/Los_Angeles";
};
status = 0;
}
Any thoughts?
I stumbled upon the same issue - I converted my app from paid to freemium and tried to use original_application_version in the app receipt to decide who to unlock the new freemium features for. I, too, was unsuccessful.
However, what I found out was that I was using original_application_version incorrectly. The name misled me into thinking that this string corresponds to the version number of the app. On iOS, it does not. original_application_version is actually the build number of the app.
Original Application Version
This corresponds to the value of CFBundleVersion (in iOS) or
CFBundleShortVersionString (in macOS) in the Info.plist file when the
purchase was originally made. In the sandbox environment, the value of this field is always “1.0”.
Source: https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
I think this could be the reason why you are getting a number you are not expecting.
Using original_purchase_date, as you did in the end, is a reliable alternative though.
It's been a while since this question was asked, but it raises some very important points:
The original app version field in the receipt corresponds to CFBundleVersion, not CFBundleShortVersionString. In a sandbox (developer build) environment, the value string is always "1.0".
Note that when converting from a paid app to freemium, if an original (paid version) user uninstalls the app, then reinstalls from iTunes, there will be no local receipt. Calling SKPaymentQueue restoreCompletedTransactions will not cause a new receipt to be downloaded if that user has never made an in-app purchase. In this situation, you need ask for a receipt refresh using SKReceiptRefreshRequest and not rely on restore functionality.

Parse ios8 User Push Notification Actions

I've registered the Notification Action properly and confirmed with a call to [[UIApplication sharedApplication] currentUserNotificationSettings].
I'm using Parse to assemble and send the push like this:
NSString *pushMessage = [NSString stringWithFormat:#"%# just directly asked: %#", [[PFUser currentUser] username], [self.question objectForKey:#"text"]];
NSDictionary *data = [NSDictionary dictionaryWithObjectsAndKeys:
pushMessage, #"alert",
#"questionNotification", #"category",
nil];
PFPush *push = [[PFPush alloc] init];
[push setQuery:pushQuery];
[push setData:data];
[push sendPushInBackground];
The notification does get delivered to the device but it does not have any of the custom actions available that have been registered for the category.
At this point I feel like Parse cannot properly convey the category in the payload? Has anyone gotten them to work with Parse?
Edit:
This is the response I get from currentUserNotificationSettings that make me thing the action has been registered successfully:
<UIUserNotificationSettings: 0x14e3eaa0; types: (UIUserNotificationTypeAlert UIUserNotificationTypeBadge UIUserNotificationTypeSound);categories: {(
<UIUserNotificationCategory: 0x14e54c40; identifier: questionNotification, actions: {
1 = (
"<UIMutableUserNotificationAction: 0x14e4b860; identifier: yupAction, title: YUP activationMode: UIUserNotificationActivationModeBackground, isAuthenticationRequired:NO, isDestructive:NO>"
);}>)};>
Update: More research, in case anyone else is having this problem, when I output the userInfo received from the push notification I get:
{aps = {alert = "m just directly asked: Abaxinj";}; category = "QUESTION_CATEGORY";}
So it appears that Parse is putting the category field outside of the aps container, which is probably the problem.
Opened a bug with Parse, they confirmed they're not equipped to handle this yet. They said to check the Parse.com blog for updates when they add ios8's new functionality.

iOS7 xCode5 how to resolve "invalid product ID" for in app purchases in 2014?

I'm trying to implement in-app purchases in one of my apps, and have an issue where I get no products returned when I send a product request for sandbox testing. I see a lot of very old posts, like this one (invalid product id from 2010). What am I doing wrong with my in app purchase setup? Is there any recent tutorials on how to configure xCode5 to use in app purchasing?
- (void)requestProUpgradeProductData
{
NSSet *productIdentifiers = [NSSet setWithObject:self.productID ];
productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
productsRequest.delegate = self;
[productsRequest start];
// we will release the request object in the delegate callback
}
Here's the callback for the product request:
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray *products = response.products;
for(id object in products)
{
//handle valid products
}
for (NSString *invalidProductId in response.invalidProductIdentifiers)
{
NSLog(#"Invalid product id: %#" , invalidProductId);
}
[hud hide:YES];
}
Using this option created a duplicate app id with the same number, but different name on hte developer portal. I cannot delete that app ID.
This is the first consumable in app purchase, no need to host content. The app is in "ready to upload binary stage". It has been over 24 hours since I created the in app purchase via itunesconnect
Found a solution here:
http://www.raywenderlich.com/21081/introduction-to-in-app-purchases-in-ios-6-tutorial
Turns out when I created the product in iTunesConnect, I called it "product", when it should've been "com.mysite.product"