mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-01-25 07:08:11 +00:00
340 lines
13 KiB
Objective-C
340 lines
13 KiB
Objective-C
/* InAppProductsManager.h
|
|
*
|
|
* Copyright (C) 2012 Belledonne Comunications, Grenoble, France
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#import "InAppProductsManager.h"
|
|
|
|
NSString *const kLinphoneIAPurchaseNotification = @"LinphoneIAProductsNotification";
|
|
|
|
// In app purchase are not supported by the Simulator
|
|
#import <XMLRPCConnection.h>
|
|
#import <XMLRPCConnectionManager.h>
|
|
#import <XMLRPCResponse.h>
|
|
#import <XMLRPCRequest.h>
|
|
|
|
#import "Utils.h"
|
|
#import "LinphoneManager.h"
|
|
|
|
#import "PhoneMainView.h"
|
|
#import "InAppProductsViewController.h"
|
|
|
|
|
|
|
|
@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 {
|
|
NSString *accountCreationSipURI;
|
|
NSString *accountCreationPassword;
|
|
}
|
|
|
|
#if !TARGET_IPHONE_SIMULATOR
|
|
- (instancetype)init {
|
|
if ((self = [super init]) != nil) {
|
|
_xmlrpc = [[InAppProductsXMLRPCDelegate alloc] init];
|
|
_status = IAPNotReadyYet;
|
|
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
|
|
[self loadProducts];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
#define INAPP_AVAIL() ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) && ([SKPaymentQueue canMakePayments])
|
|
|
|
- (void)loadProducts {
|
|
if (!INAPP_AVAIL()) return;
|
|
|
|
NSArray * list = [[[[LinphoneManager instance] lpConfigStringForKey:@"products_list" forSection:@"in_app_purchase"] stringByReplacingOccurrencesOfString:@" " withString:@""] componentsSeparatedByString:@","];
|
|
|
|
_productsIDPurchased = [[NSMutableArray alloc] initWithCapacity:0];
|
|
|
|
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:list]];
|
|
productsRequest.delegate = self;
|
|
[productsRequest start];
|
|
}
|
|
|
|
- (void)productsRequest:(SKProductsRequest *)request
|
|
didReceiveResponse:(SKProductsResponse *)response {
|
|
_productsAvailable = [[NSMutableArray arrayWithArray: response.products] retain];
|
|
|
|
LOGI(@"Found %lu products available", (unsigned long)_productsAvailable.count);
|
|
|
|
if (response.invalidProductIdentifiers.count > 0) {
|
|
for (NSString *invalidIdentifier in response.invalidProductIdentifiers) {
|
|
LOGW(@"Found product Identifier with invalid ID '%@'", invalidIdentifier);
|
|
}
|
|
[self postNotificationforStatus:IAPAvailableFailed];
|
|
} else {
|
|
[self postNotificationforStatus:IAPAvailableSucceeded];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isPurchasedWithID:(NSString *)productID {
|
|
for (NSString *prod in _productsIDPurchased) {
|
|
if ([prod isEqual: productID]) {
|
|
bool isBought = true;
|
|
LOGE(@"%@ is %s bought.", prod, isBought?"":"NOT");
|
|
return isBought;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
- (SKProduct*) productIDAvailable:(NSString*)productID {
|
|
for (SKProduct *product in _productsAvailable) {
|
|
if ([product.productIdentifier compare:productID options:NSLiteralSearch] == NSOrderedSame) {
|
|
return product;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)purchaseAccount:(NSString *)sipURI withPassword:(NSString *)password {
|
|
NSString* productID = [[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"];
|
|
SKProduct *prod = [self productIDAvailable:productID];
|
|
if (prod) {
|
|
accountCreationSipURI = [sipURI retain];
|
|
accountCreationPassword = [password retain];
|
|
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:prod];
|
|
[[SKPaymentQueue defaultQueue] addPayment:payment];
|
|
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
|
|
} else {
|
|
[self postNotificationforStatus:IAPPurchaseFailed];
|
|
}
|
|
}
|
|
|
|
-(void)restore {
|
|
LOGI(@"Restoring user purchases...");
|
|
//force new query of the server
|
|
latestReceiptMD5 = nil;
|
|
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
|
|
}
|
|
|
|
- (void)requestDidFinish:(SKRequest *)request {
|
|
if([request isKindOfClass:[SKReceiptRefreshRequest class]]) {
|
|
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptUrl path]]) {
|
|
LOGI(@"App Receipt exists");
|
|
[self checkReceipt:nil];
|
|
} else {
|
|
LOGE(@"Receipt request done but there is no receipt");
|
|
|
|
// This can happen if the user cancels the login screen for the store.
|
|
// If we get here it means there is no receipt and an attempt to get it failed because the user cancelled the login.
|
|
//[self trackFailedAttempt];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)request:(SKRequest *)skrequest didFailWithError:(NSError *)error {
|
|
LOGE(@"Did not get receipt: %@", error.localizedDescription);
|
|
[self setStatus:IAPReceiptFailed];
|
|
}
|
|
|
|
- (void)checkReceipt: (SKPaymentTransaction*)transaction {
|
|
NSString *receiptBase64 = nil;
|
|
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
|
|
|
|
// Test whether the receipt is present at the above URL
|
|
if(![[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
|
|
// We are probably in sandbox environment, trying to retrieve it...
|
|
SKRequest* req = [[SKReceiptRefreshRequest alloc] init];
|
|
LOGI(@"Receipt not found yet, trying to retrieve it...");
|
|
req.delegate = self;
|
|
[req start];
|
|
return;
|
|
}
|
|
|
|
LOGI(@"Found appstore receipt");
|
|
receiptBase64 = [[NSData dataWithContentsOfURL:receiptURL] base64EncodedStringWithOptions:0];
|
|
//only check the receipt if it has changed
|
|
if (latestReceiptMD5 == nil || ! [latestReceiptMD5 isEqualToString:[receiptBase64 md5]]) {
|
|
// We must validate the receipt on our server
|
|
NSURL *URL = [NSURL URLWithString:[[LinphoneManager instance] lpConfigStringForKey:@"receipt_validation_url" forSection:@"in_app_purchase"]];
|
|
|
|
XMLRPCRequest *request = [[XMLRPCRequest alloc] initWithURL: URL];
|
|
|
|
// Happen when restoring user purchases at application start or if user click the "restore" button
|
|
if (transaction == nil) {
|
|
LOGE(@"Todo!");
|
|
return;
|
|
} else if ([transaction.payment.productIdentifier isEqualToString:[[LinphoneManager instance] lpConfigStringForKey:@"paid_account_id" forSection:@"in_app_purchase"]]) {
|
|
[request setMethod: @"create_account_from_in_app_purchase" withParameters:[NSArray arrayWithObjects:
|
|
@"",
|
|
accountCreationSipURI,
|
|
accountCreationPassword,
|
|
receiptBase64,
|
|
@"",
|
|
@"apple",
|
|
nil]];
|
|
} else {
|
|
LOGE(@"Hum, not handling product with ID %@", transaction.payment.productIdentifier);
|
|
return;
|
|
}
|
|
|
|
latestReceiptMD5 = [[receiptBase64 md5] retain];
|
|
|
|
XMLRPCConnectionManager *manager = [XMLRPCConnectionManager sharedManager];
|
|
[manager spawnConnectionWithXMLRPCRequest: request delegate: self.xmlrpc];
|
|
LOGI(@"XMLRPC query %@: %@", [request method], [request body]);
|
|
[request release];
|
|
} else {
|
|
LOGW(@"Skipping receipt check, it has already been done!");
|
|
}
|
|
}
|
|
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
|
|
for(SKPaymentTransaction * transaction in transactions) {
|
|
switch (transaction.transactionState) {
|
|
case SKPaymentTransactionStatePurchasing:
|
|
break;
|
|
case SKPaymentTransactionStatePurchased:
|
|
case SKPaymentTransactionStateRestored:
|
|
[self checkReceipt: transaction];
|
|
[self completeTransaction:transaction forStatus:IAPPurchaseSucceeded];
|
|
break;
|
|
case SKPaymentTransactionStateDeferred:
|
|
//waiting for parent approval
|
|
break;
|
|
case SKPaymentTransactionStateFailed:
|
|
_errlast = [NSString stringWithFormat:@"Purchase of %@ failed: %@.",transaction.payment.productIdentifier,transaction.error.localizedDescription];
|
|
LOGE(@"%@", _errlast);
|
|
[self completeTransaction:transaction forStatus:IAPPurchaseFailed];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions {
|
|
for(SKPaymentTransaction * transaction in transactions) {
|
|
LOGI(@"%@ was removed from the payment queue.", transaction.payment.productIdentifier);
|
|
}
|
|
}
|
|
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
|
|
if (error.code != SKErrorPaymentCancelled) {
|
|
_errlast = [error localizedDescription];
|
|
[self postNotificationforStatus:IAPRestoreFailed];
|
|
}
|
|
}
|
|
|
|
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
|
|
LOGI(@"All restorable transactions have been processed by the payment queue.");
|
|
}
|
|
|
|
-(void)postNotificationforStatus:(IAPPurchaseNotificationStatus)status {
|
|
_status = status;
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kLinphoneIAPurchaseNotification object:self];
|
|
LOGI(@"Triggering notification for status %@", status);
|
|
}
|
|
|
|
-(void)completeTransaction:(SKPaymentTransaction *)transaction forStatus:(IAPPurchaseNotificationStatus)status {
|
|
if (transaction.error.code != SKErrorPaymentCancelled) {
|
|
[self postNotificationforStatus:status];
|
|
} else {
|
|
_status = status;
|
|
}
|
|
|
|
// Remove the transaction from the queue for purchased and restored statuses
|
|
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
|
}
|
|
|
|
- (void)retrievePurchases {
|
|
[self checkReceipt:nil];
|
|
}
|
|
|
|
- (void)XMLRPCRequest:(XMLRPCRequest *)request didReceiveResponse:(XMLRPCResponse *)response {
|
|
LOGI(@"XMLRPC response %@: %@", [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) {
|
|
if([[request method] isEqualToString:@"get_expiration_date"]) {
|
|
if([response object] > [NSNumber numberWithInt:1]) {
|
|
LOGE(@"Todo: parse the response");
|
|
[self postNotificationforStatus:IAPReceiptSucceeded];
|
|
return;
|
|
} else {
|
|
LOGI(@"Account has expired");
|
|
[[PhoneMainView instance] changeCurrentView:[InAppProductsViewController compositeViewDescription]];
|
|
}
|
|
} else if([[request method] isEqualToString:@"create_account_from_in_app_purchase"]) {
|
|
LOGI(@"Account created?");
|
|
}
|
|
}
|
|
latestReceiptMD5 = nil;
|
|
[self postNotificationforStatus:IAPReceiptFailed];
|
|
}
|
|
|
|
- (void)XMLRPCRequest:(XMLRPCRequest *)request didFailWithError:(NSError *)error {
|
|
LOGE(@"Communication issue (%@)", [error localizedDescription]);
|
|
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];
|
|
latestReceiptMD5 = nil;
|
|
[self postNotificationforStatus:IAPReceiptFailed];
|
|
}
|
|
#else
|
|
- (void)purchaseAccount:(NSString *)sipURI withPassword:(NSString *)password { LOGE(@"Not supported"); }
|
|
- (void)purchaseWithID:(NSString *)productId { LOGE(@"Not supported"); }
|
|
- (void)restore { LOGE(@"Not supported"); }
|
|
- (void)XMLRPCRequest:(XMLRPCRequest *)request didFailWithError:(NSError *)error { LOGE(@"Not supported"); }
|
|
- (void)XMLRPCRequest:(XMLRPCRequest *)request didReceiveResponse:(XMLRPCResponse *)response { LOGE(@"Not supported"); }
|
|
- (BOOL)isPurchasedWithID:(NSString *)productId { LOGE(@"Not supported"); return FALSE; }
|
|
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { LOGE(@"Not supported"); }
|
|
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { LOGE(@"Not supported"); }
|
|
- (void)retrievePurchases { LOGE(@"Not supported"); }
|
|
#endif
|
|
@end
|