Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
3d21006
draft
Feb 15, 2019
73caea7
second draft
Feb 15, 2019
9420db0
finish transaction
Feb 15, 2019
62a9974
adding more comments
Feb 15, 2019
2ec9c58
more comments
Feb 15, 2019
e75411a
let dart intercept the payment from store
Feb 15, 2019
824eee8
create payment method
Feb 15, 2019
98f8d03
rearrange method order
Feb 15, 2019
e1176e5
formatting
Feb 15, 2019
6878875
draft
Feb 19, 2019
8feddbf
draft 2
Feb 19, 2019
2ccfe7e
Merge branch 'master' of github.com:flutter/plugins into iap_make_pay…
Feb 19, 2019
0e8de06
Merge branch 'iap_make_payment_objc' into iap_payment_queue_dart_ios
Feb 19, 2019
ec177e3
add payment
Feb 19, 2019
ab75985
typo fix
Feb 19, 2019
5edec3f
Merge branch 'iap_make_payment_objc' into iap_payment_queue_dart_ios
Feb 19, 2019
ed11706
more comments
Feb 19, 2019
564d13b
remove unused code
Feb 19, 2019
330dc3d
Merge branch 'iap_make_payment_objc' into iap_payment_queue_dart_ios
Feb 19, 2019
b11e3c5
update to weakself
Feb 19, 2019
7f9e5a9
review fixes
Feb 19, 2019
0187f9a
formatting
Feb 20, 2019
ec1f0bb
merge the objective-c branch
Feb 20, 2019
6b2d7ea
rename callback methods
Feb 20, 2019
e7da33a
nit fix
Feb 20, 2019
b756e70
rearrange payment object handling code
Feb 20, 2019
4dd5b14
fix unit test, remove nil check for callback blocks
Feb 20, 2019
f3cdb83
formatting
Feb 20, 2019
7ddbc6c
merge and resolve conflicts
Feb 20, 2019
18f1e82
observer update
Feb 20, 2019
a37123e
assert transaction observer is set
Feb 20, 2019
7d57282
finish transaction method
Feb 20, 2019
da19308
merge master
Feb 21, 2019
ad2f3ee
Merge branch 'master' into iap_payment_queue_dart_ios
Feb 21, 2019
c0eabdd
reslove conflicts
Feb 21, 2019
f11949e
formatting
Feb 21, 2019
bd746bf
fixes
Feb 21, 2019
06f8495
local test
Feb 21, 2019
541c573
add assert message
Feb 21, 2019
a3fe929
fix price precision
Feb 21, 2019
61c66d1
more local test code
Feb 21, 2019
8762cfc
review fixes
Feb 21, 2019
736838e
revert sku_details_wrapper.g
Feb 21, 2019
114b5ce
update price to string to avoid precision lost
Feb 21, 2019
343a976
remove unnecessary print statement
Feb 21, 2019
372fad7
Merge branch 'get_product_list_fix' into local_test
Feb 21, 2019
f845b61
fixes
Feb 21, 2019
36d9d25
test case fixes
Feb 21, 2019
055a88f
Merge branch 'get_product_list_fix' into iap_payment_queue_dart_ios
Feb 21, 2019
087d954
Merge branch 'local_test' into iap_payment_queue_dart_ios
Feb 21, 2019
324d7a9
revert main.dart
Feb 21, 2019
17af41a
Merge branch 'master' into iap_payment_queue_dart_ios
Feb 22, 2019
80fcac1
remove unnecessary method
Feb 22, 2019
670bc84
Merge branch 'master' into iap_payment_queue_dart_ios
Feb 22, 2019
5c8b3c5
remove unnessary addpayment logic
Feb 22, 2019
d7df3be
restore transactions
Feb 22, 2019
8132c22
formatting and renaming
Feb 22, 2019
de3a84b
store notifications if observer is not set
Feb 22, 2019
4c7624b
merge payment queue branch
Feb 22, 2019
ffaf35c
fix transform map to transactions and downloads
Feb 22, 2019
6989936
refresh serializer
Feb 22, 2019
693ed90
formatting
Feb 22, 2019
7a8f005
Merge branch 'iap_payment_queue_dart_ios' into iap_restore_purchase
Feb 27, 2019
43acaf4
merge master
Feb 28, 2019
69cd34c
formatting
Mar 1, 2019
31ef630
update doc
Mar 1, 2019
c0e6eeb
add retrive receipt method in objc
Mar 2, 2019
2729eee
retrive receipt data on dart
Mar 2, 2019
d8b788d
retrive receipt method can be static
Mar 2, 2019
4b328c8
merge master
Mar 2, 2019
c0c45c1
receipt manager
Mar 2, 2019
ced1bce
only get receipt data
Mar 4, 2019
5621c04
formating
Mar 4, 2019
0afd8a9
remove testing code
Mar 4, 2019
b3e61d5
adding refresh receipt method
Mar 5, 2019
88a49c6
formatting
Mar 5, 2019
b2d4394
typo fix
Mar 5, 2019
47d5886
comments
Mar 5, 2019
df7e6f2
license header
Mar 5, 2019
94022cb
export receipt manager
Mar 5, 2019
37334e1
remove unnecessary parameter
Mar 5, 2019
a92652d
dartdoc typo fix
Mar 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ @interface InAppPurchasePluginTest : XCTestCase
@implementation InAppPurchasePluginTest

- (void)setUp {
self.plugin = [[InAppPurchasePluginStub alloc] init];
self.plugin =
[[InAppPurchasePluginStub alloc] initWithReceiptManager:[FIAPReceiptManagerStub new]];
}

- (void)tearDown {
Expand Down Expand Up @@ -171,4 +172,35 @@ - (void)testRestoreTransactions {
XCTAssertTrue(callbackInvoked);
}

- (void)testRetrieveReceiptData {
XCTestExpectation* expectation = [self expectationWithDescription:@"receipt data retrieved"];
FlutterMethodCall* call = [FlutterMethodCall
methodCallWithMethodName:@"-[InAppPurchasePlugin retrieveReceiptData:result:]"
arguments:nil];
__block NSDictionary* result;
[self.plugin handleMethodCall:call
result:^(id r) {
result = r;
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5];
NSLog(@"%@", result);
XCTAssertNotNil(result);
}

- (void)testRefreshReceiptRequest {
XCTestExpectation* expectation = [self expectationWithDescription:@"expect success"];
FlutterMethodCall* call =
[FlutterMethodCall methodCallWithMethodName:@"-[InAppPurchasePlugin refreshReceipt:result:]"
arguments:nil];
__block BOOL result = NO;
[self.plugin handleMethodCall:call
result:^(id r) {
result = YES;
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertTrue(result);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,37 @@ - (void)testRequestHandlerWithProductRequestFailure {
XCTAssertNil(response);
}

- (void)testRequestHandlerWithRefreshReceiptSuccess {
SKReceiptRefreshRequestStub *request =
[[SKReceiptRefreshRequestStub alloc] initWithReceiptProperties:nil];
FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request];
XCTestExpectation *expectation = [self expectationWithDescription:@"expect no error"];
__block NSError *e;
[handler
startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable r, NSError *error) {
e = error;
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertNil(e);
}

- (void)testRequestHandlerWithRefreshReceiptFailure {
SKReceiptRefreshRequestStub *request = [[SKReceiptRefreshRequestStub alloc]
initWithFailureError:[NSError errorWithDomain:@"test" code:123 userInfo:@{}]];
FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request];
XCTestExpectation *expectation = [self expectationWithDescription:@"expect error"];
__block NSError *error;
__block SKProductsResponse *response;
[handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable r, NSError *e) {
error = e;
response = r;
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5];
XCTAssertNotNil(error);
XCTAssertEqual(error.domain, @"test");
XCTAssertNil(response);
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import "FIAPReceiptManager.h"
#import "FIAPRequestHandler.h"
#import "InAppPurchasePlugin.h"

Expand Down Expand Up @@ -53,4 +54,11 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithMap:(NSDictionary *)map;
@end

@interface FIAPReceiptManagerStub : FIAPReceiptManager
@end

@interface SKReceiptRefreshRequestStub : SKReceiptRefreshRequest
- (instancetype)initWithFailureError:(NSError *)error;
@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ - (void)start {
} else {
[self.delegate productsRequest:self didReceiveResponse:response];
}
[self.delegate requestDidFinish:self];
}

@end
Expand Down Expand Up @@ -134,6 +133,10 @@ - (SKProduct *)getProduct:(NSString *)productID {
return [SKProduct new];
}

- (SKReceiptRefreshRequestStub *)getRefreshReceiptRequest:(NSDictionary *)properties {
return [[SKReceiptRefreshRequestStub alloc] initWithReceiptProperties:properties];
}

@end

@interface SKPaymentQueueStub ()
Expand Down Expand Up @@ -235,3 +238,37 @@ - (instancetype)initWithMap:(NSDictionary *)map {
}

@end

@implementation FIAPReceiptManagerStub : FIAPReceiptManager

- (NSData *)getReceiptData:(NSURL *)url {
NSString *originalString = [NSString stringWithFormat:@"test"];
return [[NSData alloc] initWithBase64EncodedString:originalString options:kNilOptions];
}

@end

@implementation SKReceiptRefreshRequestStub {
NSError *_error;
}

- (instancetype)initWithReceiptProperties:(NSDictionary<NSString *, id> *)properties {
self = [super initWithReceiptProperties:properties];
return self;
}

- (instancetype)initWithFailureError:(NSError *)error {
self = [super init];
_error = error;
return self;
}

- (void)start {
if (_error) {
[self.delegate request:self didFailWithError:_error];
} else {
[self.delegate requestDidFinish:self];
}
}

@end
17 changes: 17 additions & 0 deletions packages/in_app_purchase/ios/Classes/FIAPReceiptManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@class FlutterError;

@interface FIAPReceiptManager : NSObject

- (NSString *)retrieveReceiptWithError:(FlutterError **)error;

@end

NS_ASSUME_NONNULL_END
29 changes: 29 additions & 0 deletions packages/in_app_purchase/ios/Classes/FIAPReceiptManager.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// FIAPReceiptManager.m
// in_app_purchase
//
// Created by Chris Yang on 3/2/19.
//

#import "FIAPReceiptManager.h"
#import <Flutter/Flutter.h>

@implementation FIAPReceiptManager

- (NSString *)retrieveReceiptWithError:(FlutterError **)error {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [self getReceiptData:receiptURL];
if (!receipt) {
*error = [FlutterError errorWithCode:@"storekit_no_receipt"
message:@"Cannot find receipt for the current main bundle."
details:nil];
return nil;
}
return [receipt base64EncodedStringWithOptions:kNilOptions];
}

- (NSData *)getReceiptData:(NSURL *)url {
return [NSData dataWithContentsOfURL:url];
}

@end
2 changes: 1 addition & 1 deletion packages/in_app_purchase/ios/Classes/FIAPRequestHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ typedef void (^ProductRequestCompletion)(SKProductsResponse *_Nullable response,

@interface FIAPRequestHandler : NSObject

- (instancetype)initWithRequest:(SKProductsRequest *)request;
- (instancetype)initWithRequest:(SKRequest *)request;
- (void)startProductRequestWithCompletionHandler:(ProductRequestCompletion)completion;

@end
Expand Down
11 changes: 8 additions & 3 deletions packages/in_app_purchase/ios/Classes/FIAPRequestHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
@interface FIAPRequestHandler () <SKProductsRequestDelegate>

@property(copy, nonatomic) ProductRequestCompletion completion;
@property(strong, nonatomic) SKProductsRequest *request;
@property(strong, nonatomic) SKRequest *request;

@end

@implementation FIAPRequestHandler

- (instancetype)initWithRequest:(SKProductsRequest *)request {
- (instancetype)initWithRequest:(SKRequest *)request {
self = [super init];
if (self) {
self.request = request;
Expand All @@ -34,11 +34,16 @@ - (void)productsRequest:(SKProductsRequest *)request
didReceiveResponse:(SKProductsResponse *)response {
if (self.completion) {
self.completion(response, nil);
// set the completion to nil here so self.completion won't be triggered again in
// requestDidFinish for SKProductRequest.
self.completion = nil;
}
}

// Reserved for other SKRequests.
- (void)requestDidFinish:(SKRequest *)request {
if (self.completion) {
self.completion(nil, nil);
}
}

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
Expand Down
3 changes: 3 additions & 0 deletions packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@

#import <Flutter/Flutter.h>
@class FIAPaymentQueueHandler;
@class FIAPReceiptManager;

@interface InAppPurchasePlugin : NSObject <FlutterPlugin>

@property(strong, nonatomic) FIAPaymentQueueHandler *paymentQueueHandler;

- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager;

@end
64 changes: 63 additions & 1 deletion packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#import "InAppPurchasePlugin.h"
#import <StoreKit/StoreKit.h>
#import "FIAObjectTranslator.h"
#import "FIAPReceiptManager.h"
#import "FIAPRequestHandler.h"
#import "FIAPaymentQueueHandler.h"

Expand All @@ -24,6 +25,8 @@ @interface InAppPurchasePlugin ()
@property(strong, nonatomic) NSObject<FlutterBinaryMessenger> *messenger;
@property(strong, nonatomic) NSObject<FlutterPluginRegistrar> *registrar;

@property(strong, nonatomic) FIAPReceiptManager *receiptManager;

@end

@implementation InAppPurchasePlugin
Expand All @@ -36,11 +39,18 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
[registrar addMethodCallDelegate:instance channel:channel];
}

- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager {
self = [self init];
self.receiptManager = receiptManager;
return self;
}

- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
self = [self initWithReceiptManager:[FIAPReceiptManager new]];
self.registrar = registrar;
self.registry = [registrar textures];
self.messenger = [registrar messenger];

__weak typeof(self) weakSelf = self;
self.paymentQueueHandler =
[[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueue defaultQueue]
Expand Down Expand Up @@ -79,6 +89,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
[self finishTransaction:call result:result];
} else if ([@"-[InAppPurchasePlugin restoreTransactions:result:]" isEqualToString:call.method]) {
[self restoreTransactions:call result:result];
} else if ([@"-[InAppPurchasePlugin retrieveReceiptData:result:]" isEqualToString:call.method]) {
[self retrieveReceiptData:call result:result];
} else if ([@"-[InAppPurchasePlugin refreshReceipt:result:]" isEqualToString:call.method]) {
[self refreshReceipt:call result:result];
} else {
result(FlutterMethodNotImplemented);
}
Expand Down Expand Up @@ -206,6 +220,50 @@ - (void)restoreTransactions:(FlutterMethodCall *)call result:(FlutterResult)resu
[self.paymentQueueHandler restoreTransactions:call.arguments];
}

- (void)retrieveReceiptData:(FlutterMethodCall *)call result:(FlutterResult)result {
FlutterError *error = nil;
NSString *receiptData = [self.receiptManager retrieveReceiptWithError:&error];
if (error) {
result(error);
return;
}
result(receiptData);
}

- (void)refreshReceipt:(FlutterMethodCall *)call result:(FlutterResult)result {
NSDictionary *arguments = call.arguments;
SKReceiptRefreshRequest *request;
if (arguments) {
if (![arguments isKindOfClass:[NSDictionary class]]) {
result([FlutterError errorWithCode:@"storekit_invalid_argument"
message:@"Argument type of startRequest is not array"
details:call.arguments]);
return;
}
NSMutableDictionary *properties = [NSMutableDictionary new];
properties[SKReceiptPropertyIsExpired] = arguments[@"isExpired"];
properties[SKReceiptPropertyIsRevoked] = arguments[@"isRevoked"];
properties[SKReceiptPropertyIsVolumePurchase] = arguments[@"isVolumePurchase"];
request = [self getRefreshReceiptRequest:properties];
} else {
request = [self getRefreshReceiptRequest:nil];
}
FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request];
[self.requestHandlers addObject:handler];
__weak typeof(self) weakSelf = self;
[handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
result([FlutterError errorWithCode:@"storekit_refreshreceiptrequest_platform_error"
message:error.description
details:error.userInfo]);
return;
}
result(nil);
[weakSelf.requestHandlers removeObject:handler];
}];
}

#pragma mark - delegates

- (void)handleTransactionsUpdated:(NSArray<SKPaymentTransaction *> *)transactions {
Expand Down Expand Up @@ -267,6 +325,10 @@ - (SKProduct *)getProduct:(NSString *)productID {
return [self.productsCache objectForKey:productID];
}

- (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties {
return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties];
}

#pragma mark - getter

- (NSSet *)requestHandlers {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'package:in_app_purchase/src/channel.dart';

///This class contains static methods to manage StoreKit receipts.
class SKReceiptManager {
/// Retrieve the receipt data from your application's main bundle.
///
/// The receipt data will be based64 encoded. The structure of the payload is defined using ASN.1.
/// You can use the receipt data retrieved by this method to validate users' purchases.
/// There are 2 ways to do so. Either validate locally or validate with App Store.
/// For more details on how to validate the receipt data, you can refer to Apple's document about [`About Receipt Validation`](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1).
/// If the receipt is invalid or missing, you can use [SKRequestMaker.startRefreshReceiptRequest] to request a new receipt.
Future<String> retrieveReceiptData() {
return channel
.invokeMethod('-[InAppPurchasePlugin retrieveReceiptData:result:]');
}
}
Loading