This commit is contained in:
Gautier Pelloux-Prayer 2015-04-21 16:22:06 +02:00
parent 93e79ce231
commit c13e063f3a
12 changed files with 178 additions and 76 deletions

View file

@ -4,7 +4,10 @@ minimum_perc = 1
[linphone-ios.localizablestrings]
file_filter = Resources/<lang>.lproj/Localizable.strings
source_file = Resources/en.lproj/Localizable.strings
source_lang = en
[linphone-ios.localizablestrings]
file_filter = Resources/InAppSettings.bundle/<lang>.lproj/Root.strings
source_lang = en
[linphone-ios.aboutviewcontrollerstrings]

View file

@ -148,7 +148,6 @@
</state>
<connections>
<action selector="onPurchaseAccountClick:" destination="-1" eventType="touchUpInside" id="dQ7-Ig-hvt"/>
<action selector="onRemoteProvisioningClick:" destination="-1" eventType="touchUpInside" id="7oT-2f-Rrb"/>
</connections>
</button>
</subviews>

View file

@ -33,6 +33,7 @@
[_pdescription setText: [prod localizedDescription]];
[_pprice setText: formattedPrice];
[_ppurchased setOn: [[[LinphoneManager instance] iapManager] isPurchasedWithID:prod.productIdentifier]];
_productID = prod.productIdentifier;
}
- (id)initWithIdentifier:(NSString*)identifier maximized:(bool)maximized {
if ((self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]) != nil) {

View file

@ -20,8 +20,13 @@
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import <XMLRPCConnectionDelegate.h>
extern NSString *const kLinphoneIAPurchaseNotification;
@interface InAppProductsXMLRPCDelegate : NSObject <XMLRPCConnectionDelegate>
@end
@interface InAppProductsManager : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
#define IAPAvailableSucceeded @"IAPAvailableSucceeded"
@ -35,6 +40,9 @@ extern NSString *const kLinphoneIAPurchaseNotification;
typedef NSString* IAPPurchaseNotificationStatus;
// needed because request:didFailWithError method is already used by SKProductsRequestDelegate...
@property (nonatomic, retain) InAppProductsXMLRPCDelegate *xmlrpc;
@property (nonatomic, retain) IAPPurchaseNotificationStatus status;
@property (nonatomic, copy) NSString *errlast;
@ -45,5 +53,8 @@ typedef NSString* IAPPurchaseNotificationStatus;
- (BOOL)isPurchasedWithID:(NSString*)productId;
- (void)purchaseWithID:(NSString*)productId;
- (void)restore;
- (void)retrievePurchases;
- (void)XMLRPCRequest:(XMLRPCRequest *)request didReceiveResponse:(XMLRPCResponse *)response;
- (void)XMLRPCRequest:(XMLRPCRequest *)request didFailWithError:(NSError *)error;
@end

View file

@ -18,27 +18,64 @@
*/
#import "InAppProductsManager.h"
#import <XMLRPCConnection.h>
#import <XMLRPCConnectionManager.h>
#import <XMLRPCResponse.h>
#import <XMLRPCRequest.h>
#import "Utils.h"
#import "LinphoneManager.h"
NSString *const kLinphoneIAPurchaseNotification = @"LinphoneIAProductsNotification";
@implementation InAppProductsXMLRPCDelegate {
InAppProductsManager *iapm;
}
#pragma mark - XMLRPCConnectionDelegate Functions
- (void)request:(XMLRPCRequest *)request didReceiveResponse:(XMLRPCResponse *)response {
[[[LinphoneManager instance] iapManager] XMLRPCRequest:request didReceiveResponse:response];
}
- (void)request:(XMLRPCRequest *)request didFailWithError:(NSError *)error {
[[[LinphoneManager instance] iapManager] XMLRPCRequest:request didFailWithError:error];
}
- (BOOL)request:(XMLRPCRequest *)request canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return FALSE;
}
- (void)request:(XMLRPCRequest *)request didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
}
- (void)request:(XMLRPCRequest *)request didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
}
@end
@implementation InAppProductsManager {
}
- (instancetype)init {
if ((self = [super init]) != nil) {
_xmlrpc = [[InAppProductsXMLRPCDelegate alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[self loadProducts];
}
return self;
}
#define INAPP_AVAIL() ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) && ([SKPaymentQueue canMakePayments])
- (void)loadProducts {
if (! [SKPaymentQueue canMakePayments]) {
return;
}
NSArray * list = [[[[LinphoneManager instance] lpConfigStringForKey:@"inapp_products_list"] stringByReplacingOccurrencesOfString:@" " withString:@""] componentsSeparatedByString:@","];
if (!INAPP_AVAIL()) return;
NSArray * list = [[[[LinphoneManager instance] lpConfigStringForKey:@"products_list" forSection:@"in_app_purchase"] stringByReplacingOccurrencesOfString:@" " withString:@""] componentsSeparatedByString:@","];
_productsIDPurchased = [[NSMutableArray alloc] initWithCapacity:0];
@ -75,9 +112,10 @@ NSString *const kLinphoneIAPurchaseNotification = @"LinphoneIAProductsNotificati
return false;
}
- (void)purchaseWithID:(NSString *)productId {
- (void)purchaseWithID:(NSString *)productID {
LOGI(@"Trying to purchase %@", productID);
for (SKProduct *product in _productsAvailable) {
if ([product.productIdentifier compare:productId options:NSLiteralSearch] == NSOrderedSame) {
if ([product.productIdentifier compare:productID options:NSLiteralSearch] == NSOrderedSame) {
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
return;
@ -88,53 +126,49 @@ NSString *const kLinphoneIAPurchaseNotification = @"LinphoneIAProductsNotificati
-(void)restore {
LOGI(@"Restoring user purchases...");
if (! [SKPaymentQueue canMakePayments]) {
LOGF(@"Not allowed to do in app purchase!!!");
}
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
static SKRequest* req = nil;
- (void)requestDidFinish:(SKRequest *)request {
if (req == request) {
LOGI(@"Got receipt");
[self checkReceipt:nil];
}
}
- (void)request:(SKRequest *)skrequest didFailWithError:(NSError *)error {
LOGE(@"Did not get receipt: %@", error);
[self setStatus:IAPReceiptFailed];
}
- (void)checkReceipt: (SKPaymentTransaction*)transaction {
NSData *receiptData = nil;
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
// Test whether the receipt is present at the above URL
if(![[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
receiptData = [NSData dataWithContentsOfURL:receiptURL];
receiptData = [NSData dataWithContentsOfURL:receiptURL];
LOGI(@"Found appstore receipt containing: %@", receiptData);
} else if (req == nil) {
// We are probably in sandbox environment, trying to retrieve it...
req = [[SKReceiptRefreshRequest alloc] init];
LOGI(@"Receipt not found yet, trying to retrieve it...");
req.delegate = self;
[req start];
} else {
LOGI(@"Could not find any receipt in application, using the transaction one!");
receiptData = transaction.transactionReceipt;
LOGF(@"No receipt found");
}
// We must validate the receipt on our server
NSURL *storeURL = [NSURL URLWithString:[[LinphoneManager instance] lpConfigStringForKey:@"inapp_receipt_validation_url"]];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:receiptData];
NSURL *URL = [NSURL URLWithString:[[LinphoneManager instance] lpConfigStringForKey:@"receipt_validation_url" forSection:@"in_app_purchase"]];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest
queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (data == nil) {
LOGE(@"Server replied nothing, aborting now.");
} else if (connectionError) {
LOGE(@"Could not verify the receipt, aborting now: (%d) %@.", connectionError.code, connectionError);
} else {
NSError *jsonError;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
if (jsonError == nil) {
// Hourray, we can finally unlock purchase stuff in app!
LOGI(@"In apps purchased are %@", jsonResponse);
LOGE(@"To do: [_productsIDPurchased addObject:transaction.payment.productIdentifier];");
[self postNotificationforStatus:IAPReceiptSucceeded];
return;
} else {
LOGE(@"Impossible to parse receipt JSON response, aborting now because of %@: %@.", jsonError, [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
}
}
// Something failed when checking the receipt
[self postNotificationforStatus:IAPReceiptFailed];
}];
XMLRPCRequest *request = [[XMLRPCRequest alloc] initWithURL: URL];
[request setMethod: @"in_app_receipt_validation" withParameters:[NSArray arrayWithObjects:@"ios", receiptData, nil]];
XMLRPCConnectionManager *manager = [XMLRPCConnectionManager sharedManager];
[manager spawnConnectionWithXMLRPCRequest: request delegate: self.xmlrpc];
[request release];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
@ -147,23 +181,12 @@ NSString *const kLinphoneIAPurchaseNotification = @"LinphoneIAProductsNotificati
[self checkReceipt: transaction];
[self completeTransaction:transaction forStatus:IAPPurchaseSucceeded];
break;
// case SKPaymentTransactionStatePurchasing:
// break;
// case SKPaymentTransactionStateDeferred:
// [self transactionDeferred:transaction];
// break;
// case SKPaymentTransactionStatePurchased:
// [self completeTransaction:transaction];
// break;
// case SKPaymentTransactionStateRestored:
// [self restoreTransaction:transaction];
// break;
// case SKPaymentTransactionStateFailed:
// [self failedTransaction:transaction];
// break;
default:
case SKPaymentTransactionStateDeferred:
//waiting for parent approval
break;
case SKPaymentTransactionStateFailed:
_errlast = [NSString stringWithFormat:@"Purchase of %@ failed.",transaction.payment.productIdentifier];
LOGE(@"%@", _errlast);
[self completeTransaction:transaction forStatus:IAPPurchaseFailed];
break;
}
@ -204,4 +227,57 @@ NSString *const kLinphoneIAPurchaseNotification = @"LinphoneIAProductsNotificati
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
- (void)retrievePurchases {
[self checkReceipt:nil];
}
- (void)XMLRPCRequest:(XMLRPCRequest *)request didReceiveResponse:(XMLRPCResponse *)response {
LOGI(@"XMLRPC %@: %@", [request method], [response body]);
// [waitView setHidden:true];
if ([response isFault]) {
LOGE(@"Communication issue (%@)", [response faultString]);
// NSString *errorString = [NSString stringWithFormat:NSLocalizedString(@"Communication issue (%@)", nil), [response faultString]];
// UIAlertView* errorView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Communication issue",nil)
// message:errorString
// delegate:nil
// cancelButtonTitle:NSLocalizedString(@"Continue",nil)
// otherButtonTitles:nil,nil];
// [errorView show];
// [errorView release];
} else if([response object] != nil) {
//Don't handle if not object: HTTP/Communication Error
if([[request method] isEqualToString:@"check_account"]) {
if([response object] == [NSNumber numberWithInt:0]) {
// UIAlertView* errorView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Check issue",nil)
// message:NSLocalizedString(@"Username already exists", nil)
// delegate:nil
// cancelButtonTitle:NSLocalizedString(@"Continue",nil)
// otherButtonTitles:nil,nil];
// [errorView show];
// [errorView release];
[self.productsIDPurchased addObject:@"test.one_time"];
[self postNotificationforStatus:IAPReceiptSucceeded];
return;
}
}
}
[self postNotificationforStatus:IAPReceiptFailed];
}
- (void)XMLRPCRequest:(XMLRPCRequest *)request didFailWithError:(NSError *)error {
LOGE(@"Communication issue (%@)", [error localizedDescription]);
[self.productsIDPurchased addObject:@"test.one_time"];
// NSString *errorString = [NSString stringWithFormat:NSLocalizedString(@"Communication issue (%@)", nil), [error localizedDescription]];
// UIAlertView* errorView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Communication issue", nil)
// message:errorString
// delegate:nil
// cancelButtonTitle:NSLocalizedString(@"Continue", nil)
// otherButtonTitles:nil,nil];
// [errorView show];
// [errorView release];
// [waitView setHidden:true];
[self postNotificationforStatus:IAPReceiptFailed];
}
@end

View file

@ -92,7 +92,6 @@
[alert release];
} else {
//try to purchase item, and if successfull change the switch
LOGI(@"Trying to purchase %@", cell.productID);
[[[LinphoneManager instance] iapManager] purchaseWithID: cell.productID];
}
}

View file

@ -1370,6 +1370,8 @@ static LinphoneCoreVTable linphonec_vtable = {
}
linphone_core_enable_video(theLinphoneCore, FALSE, FALSE);
}
// Retrieve InApp purchases
[_iapManager retrievePurchases];
LOGW(@"Linphone [%s] started on [%s]", linphone_core_get_version(), [[UIDevice currentDevice].model cStringUsingEncoding:[NSString defaultCStringEncoding]]);
@ -2257,5 +2259,7 @@ static void audioRouteChangeListenerCallback (
}
}
#pragma InApp Purchase
@end

View file

@ -733,20 +733,23 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)inAppPurchaseNotification: (NSNotification*)notification {
InAppProductsManager *iapm = [[LinphoneManager instance] iapManager];
if ([iapm isPurchasedWithID:[[LinphoneManager instance] lpConfigStringForKey:@"inapp_paid_account_id" forSection:@"wizard"]]) {
nextView = createAccountView;
[self loadWizardConfig:@"wizard_linphone_create.rc"];
if ([iapm isPurchasedWithID:[[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]]) {
[self onPurchaseAccountClick:self];
}
}
- (IBAction)onPurchaseAccountClick:(id)sender {
InAppProductsManager *iapm = [[LinphoneManager instance] iapManager];
//if has already purchased, continue
if (false) {
nextView = createAccountView;
[self loadWizardConfig:@"wizard_linphone_create.rc"];
if ([iapm isPurchasedWithID:[[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]]) {
NSString *username = [WizardViewController findTextField:ViewElement_Username view:contentView].text;
NSString *password = [WizardViewController findTextField:ViewElement_Password view:contentView].text;
NSString *domain = [WizardViewController findTextField:ViewElement_Domain view:contentView].text;
NSString *transport = [self.transportChooser titleForSegmentAtIndex:self.transportChooser.selectedSegmentIndex];
[self verificationSignInWithUsername:username password:password domain:domain withTransport:transport];
} else {
[iapm purchaseWithID: [[LinphoneManager instance] lpConfigStringForKey:@"inapp_paid_account_id" forSection:@"wizard"]];
[iapm purchaseWithID: [[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]];
}
}

View file

@ -47,7 +47,6 @@ transport=tls
sharing_server=https://www.linphone.org:444/upload.php
ice=1
stun=stun.linphone.org
inapp_paid_account_id = test.auto_renew_7days
[video]
display_filter_auto_rotate=0
@ -55,5 +54,9 @@ display_filter_auto_rotate=0
[app]
#contact_display_username_only=1
#contact_filter_on_default_domain=1
inapp_receipt_validation_url = https://linphone.org/inapp/veriyReceipt
inapp_products_list = test.auto_renew_7days, test.non_renew, test.one_time, test.auto_renew_1month_withfree
[in_app_purchase]
enabled = 1
paid_account_id = test.auto_renew_7days
receipt_validation_url = https://www.linphone.org/inapp.php
products_list = test.auto_renew_7days, test.non_renew, test.one_time, test.auto_renew_1month_withfree

View file

@ -47,11 +47,12 @@ transport=tls
sharing_server=https://www.linphone.org:444/upload.php
ice=1
stun=stun.linphone.org
inapp_paid_account_id = test.auto_renew_7days
[video]
display_filter_auto_rotate=0
[app]
inapp_receipt_validation_url = https://linphone.org/inapp/veriyReceipt
inapp_products_list = test.auto_renew_7days, test.non_renew, test.one_time, test.auto_renew_1month_withfree
[in_app_purchase]
enabled = 1
paid_account_id = test.auto_renew_7days
receipt_validation_url = https://www.linphone.org/inapp.php
products_list = test.auto_renew_7days, test.non_renew, test.one_time, test.auto_renew_1month_withfree

View file

@ -53,7 +53,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>2</string>
<string>4</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationExitsOnSuspend</key>

View file

@ -936,7 +936,6 @@
1D3623250D0F684500981E51 /* LinphoneAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LinphoneAppDelegate.m; sourceTree = "<group>"; };
1D6058910D05DD3D006BFB54 /* linphone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = linphone.app; sourceTree = BUILT_PRODUCTS_DIR; };
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
1FE76362DA6217E7341ED1DF /* libPods-KifTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-KifTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
220FAD2910765B400068D98F /* libgsm.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgsm.a; path = "liblinphone-sdk/apple-darwin/lib/libgsm.a"; sourceTree = "<group>"; };
220FAD2C10765B400068D98F /* libortp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libortp.a; path = "liblinphone-sdk/apple-darwin/lib/libortp.a"; sourceTree = "<group>"; };
220FAD2F10765B400068D98F /* libspeex.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libspeex.a; path = "liblinphone-sdk/apple-darwin/lib/libspeex.a"; sourceTree = "<group>"; };
@ -2310,7 +2309,6 @@
22509041196BD902007863F6 /* libopenh264.a */,
22AF73C11754C0D000BE8398 /* libopus.a */,
220FAD2C10765B400068D98F /* libortp.a */,
1FE76362DA6217E7341ED1DF /* libPods-KifTests.a */,
57B0E35F173C010400A476B8 /* libpolarssl.a */,
F0BB8C34193624C800974404 /* libresolv.9.dylib */,
22D1B68012A3E0BE001AE361 /* libresolv.dylib */,
@ -5336,6 +5334,7 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/liblinphone-sdk/apple-darwin/include",
"$(SRCROOT)/Classes/Utils/XMLRPC/",
);
INFOPLIST_FILE = KifTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@ -5380,6 +5379,7 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/liblinphone-sdk/apple-darwin/include",
"$(SRCROOT)/Classes/Utils/XMLRPC/",
);
INFOPLIST_FILE = KifTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@ -5424,6 +5424,7 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/liblinphone-sdk/apple-darwin/include",
"$(SRCROOT)/Classes/Utils/XMLRPC/",
);
INFOPLIST_FILE = KifTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@ -5468,6 +5469,7 @@
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/liblinphone-sdk/apple-darwin/include",
"$(SRCROOT)/Classes/Utils/XMLRPC/",
);
INFOPLIST_FILE = KifTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";