iOS UIWebView 应用内购买不工作

iOS UIWebView in-app purchase not working

我正在尝试从 UIWebView 实现应用内购买:

index.html

<html><body>

<script>
    function purchase() {
        var iframe = document.createElement("IFRAME");
        iframe.setAttribute("src", "purchase");
        document.documentElement.appendChild(iframe);
    };
</script>

<br><br><br><button onclick="purchase()">Purchase!</button>

</body></html>

ViewController.m

#import "ViewController.h"
#import "PurchaseBackend.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
    _webview.delegate = self;
    [_webview loadHTMLString:htmlString baseURL:nil];
}

- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{

    if ([request.URL.absoluteString rangeOfString:@"purchase"].location == NSNotFound) {
        return YES;
    } else {
        PurchaseBackend *pb = [[PurchaseBackend alloc] init];
        [pb purchaseClick];

        return NO;
    }

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

PurchaseBackend.h

#import <StoreKit/StoreKit.h>

@interface PurchaseBackend : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>

-(void)purchaseClick;

@end

PurchaseBackend.m

#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import "PurchaseBackend.h"

@implementation PurchaseBackend

#define productID @"com.organization.MonthSubscription"

- (void)purchaseClick{
    NSLog(@"User clicked purchase button");

    if([SKPaymentQueue canMakePayments]){
        NSLog(@"User can make payments");

        //If you have more than one in-app purchase, and would like
        //to have the user purchase a different product, simply define
        //another function and replace kRemoveAdsProductIdentifier with
        //the identifier for the other product

        SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productID]];
        productsRequest.delegate = self;
        [productsRequest start];

    }
    else{
        NSLog(@"User cannot make payments due to parental controls");
        //this is called the user cannot make payments, most likely due to parental controls
    }
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    SKProduct *validProduct = nil;
    int count = [response.products count];
    if(count > 0){
        validProduct = [response.products objectAtIndex:0];
        NSLog(@"Products Available!");
        [self purchase:validProduct];
    }
    else if(!validProduct){
        NSLog(@"No products available");
        //this is called if your product id is not valid, this shouldn't be called unless that happens.
    }
}

- (void)purchase:(SKProduct *)product{
    SKPayment *payment = [SKPayment paymentWithProduct:product];

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    for(SKPaymentTransaction *transaction in transactions){
        switch(transaction.transactionState){
            case SKPaymentTransactionStatePurchasing: NSLog(@"Transaction state -> Purchasing");
            //called when the user is in the process of purchasing, do not add any of your own code here.
            break;
            case SKPaymentTransactionStatePurchased:
            //this is called when the user has successfully purchased the package (Cha-Ching!)
            [self paid]; //you can add your code for what you want to happen when the user buys the purchase here
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            NSLog(@"Transaction state -> Purchased");
            break;
            case SKPaymentTransactionStateRestored:
            NSLog(@"Transaction state -> Restored");
            //add the same code as you did from SKPaymentTransactionStatePurchased here
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
            case SKPaymentTransactionStateFailed:
            //called when the transaction does not finish
            if(transaction.error.code == SKErrorPaymentCancelled){
                NSLog(@"Transaction state -> Cancelled");
                //the user cancelled the payment ;(
            }
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
        }
    }
}

- (void)paid{
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"PAID"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    NSLog(@"User Paid, state saved");
}

@end

结果

xcode 控制台

2015-12-31 13:13:26.120 AppName[392:102559] User clicked purchase button
2015-12-31 13:13:26.144 AppName[392:102559] User can make payments
(lldb)

求助!

更新:

已更新ViewController.m

#import "ViewController.h"
#import "PurchaseBackend.h"

@interface ViewController ()

@end

@implementation ViewController

PurchaseBackend *purchaseBackend;

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *htmlFile = [[NSBundle mainBundle] pathForResource:@"index" ofType:@"html"];
    NSString* htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
    _webview.delegate = self;
    [_webview loadHTMLString:htmlString baseURL:nil];
}

- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{

    if ([request.URL.absoluteString rangeOfString:@"purchase"].location == NSNotFound) {
        return YES;
    } else {
        purchaseBackend = [[PurchaseBackend alloc] init];
        [purchaseBackend purchaseClick];

        return NO;
    }

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

感谢有用的评论,我发现了内存崩溃的原因 - PurchaseBackend 委托对象正在被释放,而 storekit 框架仍在使用它。我已经如上所示更新了 ViewController.m,但现在没有崩溃,而是在打印 "User can make payments" 之后没有任何反应。我在didReceiveResponse下了一个断点,它没有执行。

我想你可能有内存问题,"purchaseBackend" 对象似乎在它有机会捕获 didReceiveResponse 回调之前被释放。

尝试为它添加一个强 属性 并确保您的 "viewController" 没有自行释放。

为了避免此类问题,将 purchaseBackend 转换为单例可能是个好主意

您可以查看有关 ARC 的教程,以便更好地理解 iOS 中的内存管理方式:Beginning ARC in iOS 5 Tutorial