Improve in-app purchase for MacOS (#12464)

* Add methods to finish transactions

* Add a method to get the product descriptions from the App Store

* Improve the documentation of a transaction structure

* Add a tutorial for In App Purchase

* Fix typo in In-App Purchase tutorial

* Fix style of In-App Purchase files

* Fix In-App-Purchase product structure conversion in amr64

* Fix code style in In-App Purchase tutorial documentation

* Fix typos in In-App Purchase documentation

* Fix typo in In-App Purchase spec

* Slight style fixes
This commit is contained in:
Adrien Fery 2018-04-05 08:33:13 +02:00 committed by Cheng Zhao
parent 52b1065b3b
commit 5486a65702
13 changed files with 481 additions and 22 deletions

View file

@ -19,6 +19,10 @@ typedef base::Callback<void(bool isProductValid)> InAppPurchaseCallback;
bool CanMakePayments(void);
void FinishAllTransactions(void);
void FinishTransactionByDate(const std::string& date);
std::string GetReceiptURL(void);
void PurchaseProduct(const std::string& productID,

View file

@ -136,6 +136,34 @@ bool CanMakePayments() {
return [SKPaymentQueue canMakePayments];
}
void FinishAllTransactions() {
for (SKPaymentTransaction* transaction in SKPaymentQueue.defaultQueue
.transactions) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
void FinishTransactionByDate(const std::string& date) {
// Create the date formatter.
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
NSLocale* enUSPOSIXLocale =
[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:enUSPOSIXLocale];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ssZZZZZ"];
// Remove the transaction.
NSString* transactionDate = base::SysUTF8ToNSString(date);
for (SKPaymentTransaction* transaction in SKPaymentQueue.defaultQueue
.transactions) {
if ([transactionDate
isEqualToString:[dateFormatter
stringFromDate:transaction.transactionDate]]) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
}
std::string GetReceiptURL() {
NSURL* receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
if (receiptURL != nil) {

View file

@ -17,9 +17,8 @@
namespace {
using InAppTransactionCallback =
base::RepeatingCallback<
void(const std::vector<in_app_purchase::Transaction>&)>;
using InAppTransactionCallback = base::RepeatingCallback<void(
const std::vector<in_app_purchase::Transaction>&)>;
} // namespace
@ -72,8 +71,8 @@ using InAppTransactionCallback =
}
// Send the callback to the browser thread.
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE, base::Bind(callback_, converted));
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(callback_, converted));
}
/**
@ -141,9 +140,9 @@ using InAppTransactionCallback =
}
if (transaction.transactionState < 5) {
transactionStruct.transactionState = [[@[
@"purchasing", @"purchased", @"failed", @"restored", @"deferred"
] objectAtIndex:transaction.transactionState] UTF8String];
transactionStruct.transactionState =
[[@[ @"purchasing", @"purchased", @"failed", @"restored", @"deferred" ]
objectAtIndex:transaction.transactionState] UTF8String];
}
if (transaction.payment != nil) {
@ -177,8 +176,8 @@ namespace in_app_purchase {
TransactionObserver::TransactionObserver() : weak_ptr_factory_(this) {
obeserver_ = [[InAppTransactionObserver alloc]
initWithCallback:base::Bind(&TransactionObserver::OnTransactionsUpdated,
weak_ptr_factory_.GetWeakPtr())];
initWithCallback:base::Bind(&TransactionObserver::OnTransactionsUpdated,
weak_ptr_factory_.GetWeakPtr())];
}
TransactionObserver::~TransactionObserver() {

View file

@ -0,0 +1,47 @@
// Copyright (c) 2018 Amaplex Software, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_MAC_IN_APP_PURCHASE_PRODUCT_H_
#define ATOM_BROWSER_MAC_IN_APP_PURCHASE_PRODUCT_H_
#include <string>
#include <vector>
#include "base/callback.h"
namespace in_app_purchase {
// --------------------------- Structures ---------------------------
struct Product {
// Product Identifier
std::string productIdentifier;
// Product Attributes
std::string localizedDescription;
std::string localizedTitle;
std::string contentVersion;
std::vector<uint32_t> contentLengths;
// Pricing Information
double price = 0.0;
std::string formattedPrice;
// Downloadable Content Information
bool downloadable = false;
};
// --------------------------- Typedefs ---------------------------
typedef base::Callback<void(const std::vector<in_app_purchase::Product>&)>
InAppPurchaseProductsCallback;
// --------------------------- Functions ---------------------------
void GetProducts(const std::vector<std::string>& productIDs,
const InAppPurchaseProductsCallback& callback);
} // namespace in_app_purchase
#endif // ATOM_BROWSER_MAC_IN_APP_PURCHASE_PRODUCT_H_

View file

@ -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