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
30 commits
Select commit Hold shift + click to select a range
652e5d1
Draft implementation of platform interface
mvanbeusekom Apr 2, 2021
bcb9d38
Added finishPurchase and restorePurchase definitions
mvanbeusekom Apr 7, 2021
23230c9
Fix formatting and analysis warnings
mvanbeusekom Apr 7, 2021
8f17a42
Added missing license header
mvanbeusekom Apr 7, 2021
164700d
Fix analysis warnings
mvanbeusekom Apr 7, 2021
af8a388
Fix typo
mvanbeusekom Apr 7, 2021
a7693bd
Remove NoopInAppPurchase implementation
mvanbeusekom Apr 7, 2021
b80a3c6
Apply feedback from PR
mvanbeusekom Apr 8, 2021
12943f4
Removed obsolete PurchaseException
mvanbeusekom Apr 9, 2021
7ec5de6
Updated documentation per feedback
mvanbeusekom Apr 14, 2021
3ceef85
Fixed formatting
mvanbeusekom Apr 14, 2021
efc6120
Allow nullable instance
mvanbeusekom Apr 14, 2021
e7f6216
Updated readme to reflect setInstance method
mvanbeusekom Apr 14, 2021
24d305b
Removed platform specific comments
mvanbeusekom Apr 21, 2021
d86ac55
Add interfaces to support InAppPurchaseAddition
mvanbeusekom Apr 21, 2021
9eb358f
Document the addition functionality in README
mvanbeusekom Apr 22, 2021
33d1118
Added example code and documentation
mvanbeusekom Apr 22, 2021
a7cea45
Reference the platform_interface
mvanbeusekom Apr 22, 2021
06dd755
Initial iOS specific implementation
mvanbeusekom Apr 26, 2021
21eb28d
Added StoreKit wrapper tests
mvanbeusekom Apr 26, 2021
0cfc84d
Added unit-tests
mvanbeusekom Apr 28, 2021
fa22504
Merged with master
mvanbeusekom Apr 28, 2021
2f115b9
Clean up code
mvanbeusekom Apr 28, 2021
d4e9165
Added initial version of README.md
mvanbeusekom Apr 28, 2021
61dc0be
Fixed typo
mvanbeusekom Apr 28, 2021
a33f312
Added support for iOS specific features
mvanbeusekom Apr 28, 2021
415ac5e
Add missing license headers
mvanbeusekom Apr 28, 2021
70e89cd
Added feedback from PR
mvanbeusekom Apr 29, 2021
99fde90
Merge remote-tracking branch 'upstream/master' into iap_federated_ios…
mvanbeusekom Apr 29, 2021
0474545
Unblock tree by solving lint error
mvanbeusekom Apr 30, 2021
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
7 changes: 7 additions & 0 deletions packages/in_app_purchase/in_app_purchase/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@ description: A Flutter plugin for in-app purchases. Exposes APIs for making in-a
homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
version: 0.5.2

# TODO(mvanbeusekom): Remove when in_app_purchase_platform_interface is published
publish_to: 'none'

dependencies:
flutter:
sdk: flutter
json_annotation: ^4.0.0
meta: ^1.3.0
collection: ^1.15.0

# TODO(mvanbeusekom): Replace with pub.dev version when in_app_purchase_platform_interface is published
in_app_purchase_platform_interface:
path: ../in_app_purchase_platform_interface

dev_dependencies:
build_runner: ^1.11.1
json_serializable: ^4.0.0
Expand Down
3 changes: 3 additions & 0 deletions packages/in_app_purchase/in_app_purchase_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.1.0

* Initial open-source release.
25 changes: 25 additions & 0 deletions packages/in_app_purchase/in_app_purchase_ios/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Copyright 2013 The Flutter Authors. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 changes: 34 additions & 0 deletions packages/in_app_purchase/in_app_purchase_ios/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# in_app_purchase_ios

The iOS implementation of [`in_app_purchase`][1].

## Usage

### Import the package

This package has been endorsed, meaning that you only need to add `in_app_purchase`
as a dependency in your `pubspec.yaml`. It will be automatically included in your app
when you depend on `package:in_app_purchase`.

This is what the above means to your `pubspec.yaml`:

```yaml
...
dependencies:
...
in_app_purchase: ^0.6.0
...
```

If you wish to use the iOS package only, you can add `in_app_purchase_ios` as a
dependency:

```yaml
...
dependencies:
...
in_app_purchase_ios: ^1.0.0
...
```

[1]: ../in_app_purchase/in_app_purchase
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: ../../../analysis_options_legacy.yaml
7 changes: 7 additions & 0 deletions packages/in_app_purchase/in_app_purchase_ios/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
targets:
$default:
builders:
json_serializable:
options:
any_map: true
create_to_json: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2013 The Flutter 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>
#import <StoreKit/StoreKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface FIAObjectTranslator : NSObject

+ (NSDictionary *)getMapFromSKProduct:(SKProduct *)product;

+ (NSDictionary *)getMapFromSKProductSubscriptionPeriod:(SKProductSubscriptionPeriod *)period
API_AVAILABLE(ios(11.2));

+ (NSDictionary *)getMapFromSKProductDiscount:(SKProductDiscount *)discount
API_AVAILABLE(ios(11.2));

+ (NSDictionary *)getMapFromSKProductsResponse:(SKProductsResponse *)productResponse;

+ (NSDictionary *)getMapFromSKPayment:(SKPayment *)payment;

+ (NSDictionary *)getMapFromNSLocale:(NSLocale *)locale;

+ (SKMutablePayment *)getSKMutablePaymentFromMap:(NSDictionary *)map;

+ (NSDictionary *)getMapFromSKPaymentTransaction:(SKPaymentTransaction *)transaction;

+ (NSDictionary *)getMapFromNSError:(NSError *)error;

@end
;

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright 2013 The Flutter 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 "FIAObjectTranslator.h"

#pragma mark - SKProduct Coders

@implementation FIAObjectTranslator

+ (NSDictionary *)getMapFromSKProduct:(SKProduct *)product {
if (!product) {
return nil;
}
NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{
@"localizedDescription" : product.localizedDescription ?: [NSNull null],
@"localizedTitle" : product.localizedTitle ?: [NSNull null],
@"productIdentifier" : product.productIdentifier ?: [NSNull null],
@"price" : product.price.description ?: [NSNull null]

}];
// TODO(cyanglaz): NSLocale is a complex object, want to see the actual need of getting this
// expanded to a map. Matching android to only get the currencySymbol for now.
// https://github.com/flutter/flutter/issues/26610
[map setObject:[FIAObjectTranslator getMapFromNSLocale:product.priceLocale] ?: [NSNull null]
forKey:@"priceLocale"];
if (@available(iOS 11.2, *)) {
[map setObject:[FIAObjectTranslator
getMapFromSKProductSubscriptionPeriod:product.subscriptionPeriod]
?: [NSNull null]
forKey:@"subscriptionPeriod"];
}
if (@available(iOS 11.2, *)) {
[map setObject:[FIAObjectTranslator getMapFromSKProductDiscount:product.introductoryPrice]
?: [NSNull null]
forKey:@"introductoryPrice"];
}
if (@available(iOS 12.0, *)) {
[map setObject:product.subscriptionGroupIdentifier ?: [NSNull null]
forKey:@"subscriptionGroupIdentifier"];
}
return map;
}

+ (NSDictionary *)getMapFromSKProductSubscriptionPeriod:(SKProductSubscriptionPeriod *)period {
if (!period) {
return nil;
}
return @{@"numberOfUnits" : @(period.numberOfUnits), @"unit" : @(period.unit)};
}

+ (NSDictionary *)getMapFromSKProductDiscount:(SKProductDiscount *)discount {
if (!discount) {
return nil;
}
NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{
@"price" : discount.price.description ?: [NSNull null],
@"numberOfPeriods" : @(discount.numberOfPeriods),
@"subscriptionPeriod" :
[FIAObjectTranslator getMapFromSKProductSubscriptionPeriod:discount.subscriptionPeriod]
?: [NSNull null],
@"paymentMode" : @(discount.paymentMode)
}];

// TODO(cyanglaz): NSLocale is a complex object, want to see the actual need of getting this
// expanded to a map. Matching android to only get the currencySymbol for now.
// https://github.com/flutter/flutter/issues/26610
[map setObject:[FIAObjectTranslator getMapFromNSLocale:discount.priceLocale] ?: [NSNull null]
forKey:@"priceLocale"];
return map;
}

+ (NSDictionary *)getMapFromSKProductsResponse:(SKProductsResponse *)productResponse {
if (!productResponse) {
return nil;
}
NSMutableArray *productsMapArray = [NSMutableArray new];
for (SKProduct *product in productResponse.products) {
[productsMapArray addObject:[FIAObjectTranslator getMapFromSKProduct:product]];
}
return @{
@"products" : productsMapArray,
@"invalidProductIdentifiers" : productResponse.invalidProductIdentifiers ?: @[]
};
}

+ (NSDictionary *)getMapFromSKPayment:(SKPayment *)payment {
if (!payment) {
return nil;
}
NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{
@"productIdentifier" : payment.productIdentifier ?: [NSNull null],
@"requestData" : payment.requestData ? [[NSString alloc] initWithData:payment.requestData
encoding:NSUTF8StringEncoding]
: [NSNull null],
@"quantity" : @(payment.quantity),
@"applicationUsername" : payment.applicationUsername ?: [NSNull null]
}];
if (@available(iOS 8.3, *)) {
[map setObject:@(payment.simulatesAskToBuyInSandbox) forKey:@"simulatesAskToBuyInSandbox"];
}
return map;
}

+ (NSDictionary *)getMapFromNSLocale:(NSLocale *)locale {
if (!locale) {
return nil;
}
NSMutableDictionary *map = [[NSMutableDictionary alloc] init];
[map setObject:[locale objectForKey:NSLocaleCurrencySymbol] ?: [NSNull null]
forKey:@"currencySymbol"];
[map setObject:[locale objectForKey:NSLocaleCurrencyCode] ?: [NSNull null]
forKey:@"currencyCode"];
return map;
}

+ (SKMutablePayment *)getSKMutablePaymentFromMap:(NSDictionary *)map {
if (!map) {
return nil;
}
SKMutablePayment *payment = [[SKMutablePayment alloc] init];
payment.productIdentifier = map[@"productIdentifier"];
NSString *utf8String = map[@"requestData"];
payment.requestData = [utf8String dataUsingEncoding:NSUTF8StringEncoding];
payment.quantity = [map[@"quantity"] integerValue];
payment.applicationUsername = map[@"applicationUsername"];
if (@available(iOS 8.3, *)) {
payment.simulatesAskToBuyInSandbox = [map[@"simulatesAskToBuyInSandbox"] boolValue];
}
return payment;
}

+ (NSDictionary *)getMapFromSKPaymentTransaction:(SKPaymentTransaction *)transaction {
if (!transaction) {
return nil;
}
NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{
@"error" : [FIAObjectTranslator getMapFromNSError:transaction.error] ?: [NSNull null],
@"payment" : transaction.payment ? [FIAObjectTranslator getMapFromSKPayment:transaction.payment]
: [NSNull null],
@"originalTransaction" : transaction.originalTransaction
? [FIAObjectTranslator getMapFromSKPaymentTransaction:transaction.originalTransaction]
: [NSNull null],
@"transactionTimeStamp" : transaction.transactionDate
? @(transaction.transactionDate.timeIntervalSince1970)
: [NSNull null],
@"transactionIdentifier" : transaction.transactionIdentifier ?: [NSNull null],
@"transactionState" : @(transaction.transactionState)
}];

return map;
}

+ (NSDictionary *)getMapFromNSError:(NSError *)error {
if (!error) {
return nil;
}
NSMutableDictionary *userInfo = [NSMutableDictionary new];
for (NSErrorUserInfoKey key in error.userInfo) {
id value = error.userInfo[key];
if ([value isKindOfClass:[NSError class]]) {
userInfo[key] = [FIAObjectTranslator getMapFromNSError:value];
} else if ([value isKindOfClass:[NSURL class]]) {
userInfo[key] = [value absoluteString];
} else {
userInfo[key] = value;
}
}
return @{@"code" : @(error.code), @"domain" : error.domain ?: @"", @"userInfo" : userInfo};
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2013 The Flutter 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

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

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2013 The Flutter 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 "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
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2013 The Flutter 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>
#import <StoreKit/StoreKit.h>

NS_ASSUME_NONNULL_BEGIN

typedef void (^ProductRequestCompletion)(SKProductsResponse *_Nullable response,
NSError *_Nullable errror);

@interface FIAPRequestHandler : NSObject

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

@end

NS_ASSUME_NONNULL_END
Loading