|
@@ -0,0 +1,179 @@
|
|
|
+// Copyright (c) 2017 Amaplex Software, Inc.
|
|
|
+// Use of this source code is governed by the MIT license that can be
|
|
|
+// found in the LICENSE file.
|
|
|
+
|
|
|
+#include "atom/browser/mac/in_app_purchase_product.h"
|
|
|
+
|
|
|
+#include "base/bind.h"
|
|
|
+#include "base/strings/sys_string_conversions.h"
|
|
|
+#include "content/public/browser/browser_thread.h"
|
|
|
+
|
|
|
+#import <StoreKit/StoreKit.h>
|
|
|
+
|
|
|
+// ============================================================================
|
|
|
+// InAppPurchaseProduct
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
+// --------------------------------- Interface --------------------------------
|
|
|
+
|
|
|
+@interface InAppPurchaseProduct : NSObject<SKProductsRequestDelegate> {
|
|
|
+ @private
|
|
|
+ in_app_purchase::InAppPurchaseProductsCallback callback_;
|
|
|
+}
|
|
|
+
|
|
|
+- (id)initWithCallback:
|
|
|
+ (const in_app_purchase::InAppPurchaseProductsCallback&)callback;
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+// ------------------------------- Implementation -----------------------------
|
|
|
+
|
|
|
+@implementation InAppPurchaseProduct
|
|
|
+
|
|
|
+/**
|
|
|
+ * Init with a callback.
|
|
|
+ *
|
|
|
+ * @param callback - The callback that will be called to return the products.
|
|
|
+ */
|
|
|
+- (id)initWithCallback:
|
|
|
+ (const in_app_purchase::InAppPurchaseProductsCallback&)callback {
|
|
|
+ if ((self = [super init])) {
|
|
|
+ callback_ = callback;
|
|
|
+ }
|
|
|
+
|
|
|
+ return self;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Return products.
|
|
|
+ *
|
|
|
+ * @param productIDs - The products' id to fetch.
|
|
|
+ */
|
|
|
+- (void)getProducts:(NSSet*)productIDs {
|
|
|
+ SKProductsRequest* productsRequest;
|
|
|
+ productsRequest =
|
|
|
+ [[SKProductsRequest alloc] initWithProductIdentifiers:productIDs];
|
|
|
+
|
|
|
+ productsRequest.delegate = self;
|
|
|
+ [productsRequest start];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * @see SKProductsRequestDelegate
|
|
|
+ */
|
|
|
+- (void)productsRequest:(SKProductsRequest*)request
|
|
|
+ didReceiveResponse:(SKProductsResponse*)response {
|
|
|
+ // Release request object.
|
|
|
+ [request release];
|
|
|
+
|
|
|
+ // Get the products.
|
|
|
+ NSArray* products = response.products;
|
|
|
+
|
|
|
+ // Convert the products.
|
|
|
+ std::vector<in_app_purchase::Product> converted;
|
|
|
+ converted.reserve([products count]);
|
|
|
+
|
|
|
+ for (SKProduct* product in products) {
|
|
|
+ converted.push_back([self skProductToStruct:product]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Send the callback to the browser thread.
|
|
|
+ content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
|
|
|
+ base::Bind(callback_, converted));
|
|
|
+
|
|
|
+ [self release];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Format local price.
|
|
|
+ *
|
|
|
+ * @param price - The price to format.
|
|
|
+ * @param priceLocal - The local format.
|
|
|
+ */
|
|
|
+- (NSString*)formatPrice:(NSDecimalNumber*)price
|
|
|
+ withLocal:(NSLocale*)priceLocal {
|
|
|
+ NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
|
|
|
+
|
|
|
+ [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
|
|
|
+ [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
|
|
|
+ [numberFormatter setLocale:priceLocal];
|
|
|
+
|
|
|
+ return [numberFormatter stringFromNumber:price];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Convert a skProduct object to Product structure.
|
|
|
+ *
|
|
|
+ * @param product - The SKProduct object to convert.
|
|
|
+ */
|
|
|
+- (in_app_purchase::Product)skProductToStruct:(SKProduct*)product {
|
|
|
+ in_app_purchase::Product productStruct;
|
|
|
+
|
|
|
+ // Product Identifier
|
|
|
+ if (product.productIdentifier != nil) {
|
|
|
+ productStruct.productIdentifier = [product.productIdentifier UTF8String];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Product Attributes
|
|
|
+ if (product.localizedDescription != nil) {
|
|
|
+ productStruct.localizedDescription =
|
|
|
+ [product.localizedDescription UTF8String];
|
|
|
+ }
|
|
|
+ if (product.localizedTitle != nil) {
|
|
|
+ productStruct.localizedTitle = [product.localizedTitle UTF8String];
|
|
|
+ }
|
|
|
+ if (product.contentVersion != nil) {
|
|
|
+ productStruct.contentVersion = [product.contentVersion UTF8String];
|
|
|
+ }
|
|
|
+ if (product.contentLengths != nil) {
|
|
|
+ productStruct.contentLengths.reserve([product.contentLengths count]);
|
|
|
+
|
|
|
+ for (NSNumber* contentLength in product.contentLengths) {
|
|
|
+ productStruct.contentLengths.push_back([contentLength longLongValue]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Pricing Information
|
|
|
+ if (product.price != nil) {
|
|
|
+ productStruct.price = [product.price doubleValue];
|
|
|
+
|
|
|
+ if (product.priceLocale != nil) {
|
|
|
+ productStruct.formattedPrice =
|
|
|
+ [[self formatPrice:product.price withLocal:product.priceLocale]
|
|
|
+ UTF8String];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Downloadable Content Information
|
|
|
+ if (product.downloadable == true) {
|
|
|
+ productStruct.downloadable = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return productStruct;
|
|
|
+}
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+// ============================================================================
|
|
|
+// C++ in_app_purchase
|
|
|
+// ============================================================================
|
|
|
+
|
|
|
+namespace in_app_purchase {
|
|
|
+
|
|
|
+void GetProducts(const std::vector<std::string>& productIDs,
|
|
|
+ const InAppPurchaseProductsCallback& callback) {
|
|
|
+ auto* iapProduct = [[InAppPurchaseProduct alloc] initWithCallback:callback];
|
|
|
+
|
|
|
+ // Convert the products' id to NSSet.
|
|
|
+ NSMutableSet* productsIDSet =
|
|
|
+ [NSMutableSet setWithCapacity:productIDs.size()];
|
|
|
+
|
|
|
+ for (auto& productID : productIDs) {
|
|
|
+ [productsIDSet addObject:base::SysUTF8ToNSString(productID)];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fetch the products.
|
|
|
+ [iapProduct getProducts:productsIDSet];
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace in_app_purchase
|